summaryrefslogtreecommitdiff
path: root/src/main/java/com/amazon/carbonado
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/amazon/carbonado')
-rw-r--r--src/main/java/com/amazon/carbonado/Derived.java62
-rw-r--r--src/main/java/com/amazon/carbonado/OptimisticLockException.java32
-rw-r--r--src/main/java/com/amazon/carbonado/Version.java42
-rw-r--r--src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java50
-rw-r--r--src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java178
-rw-r--r--src/main/java/com/amazon/carbonado/gen/StorableGenerator.java181
-rw-r--r--src/main/java/com/amazon/carbonado/gen/StorableSerializer.java4
-rw-r--r--src/main/java/com/amazon/carbonado/info/StorableIntrospector.java527
-rw-r--r--src/main/java/com/amazon/carbonado/info/StorableProperty.java36
-rw-r--r--src/main/java/com/amazon/carbonado/layout/Layout.java2
-rw-r--r--src/main/java/com/amazon/carbonado/qe/AbstractQuery.java6
-rw-r--r--src/main/java/com/amazon/carbonado/qe/EmptyQuery.java18
-rw-r--r--src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java11
-rw-r--r--src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java2
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/DependentStorableFetcher.java174
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/DerivedIndexesTrigger.java164
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/IndexAnalysis.java132
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java2
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java9
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java25
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/ManagedIndex.java23
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java18
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java21
-rw-r--r--src/main/java/com/amazon/carbonado/spi/WrappedQuery.java17
-rw-r--r--src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java17
25 files changed, 1524 insertions, 229 deletions
diff --git a/src/main/java/com/amazon/carbonado/Derived.java b/src/main/java/com/amazon/carbonado/Derived.java
new file mode 100644
index 0000000..77ffd2a
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/Derived.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2007 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * Licensed 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 com.amazon.carbonado;
+
+import java.lang.annotation.*;
+
+/**
+ * Identifies a {@link Storable} property which is not directly persisted, but
+ * is instead derived from other property values. A derived property cannot be
+ * abstract, and a "set" method is optional.
+ *
+ * <p>Derived properties can be used just like a normal property in most
+ * cases. They can be used in query filters, indexes, alternate keys, and they
+ * can also be used to define a {@link Version} property.
+ *
+ * <p>If the derived property depends on {@link Join} properties and is also
+ * used in an index or alternate key, dependencies must be listed in order for
+ * the index to be properly updated.
+ *
+ * <p>Example:<pre>
+ * &#64;Indexes(&#64;Index("uppercaseName"))
+ * public abstract class UserInfo implements Storable&lt;UserInfo&gt; {
+ * /**
+ * * Derive an uppercase name for case-insensitive searches.
+ * *&#47;
+ * <b>&#64;Derived</b>
+ * public String getUppercaseName() {
+ * String name = getName();
+ * return name == null ? null : name.toUpperCase();
+ * }
+ *
+ * ...
+ * }
+ * </pre>
+ *
+ * @author Brian S O'Neill
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Derived {
+ /**
+ * List of properties that this property is derived from.
+ */
+ String[] from() default {};
+}
diff --git a/src/main/java/com/amazon/carbonado/OptimisticLockException.java b/src/main/java/com/amazon/carbonado/OptimisticLockException.java
index ccd894c..03004c2 100644
--- a/src/main/java/com/amazon/carbonado/OptimisticLockException.java
+++ b/src/main/java/com/amazon/carbonado/OptimisticLockException.java
@@ -89,6 +89,18 @@ public class OptimisticLockException extends PersistException {
}
/**
+ * Construct exception for when new version was expected to have increased.
+ *
+ * @param savedVersion actual persistent version number of storable
+ * @param s Storable which was acted upon
+ * @param newVersion new version which was provided
+ */
+ public OptimisticLockException(Object savedVersion, Storable s, Object newVersion) {
+ super(makeMessage(savedVersion, s, newVersion));
+ mStorable = s;
+ }
+
+ /**
* Returns the Storable which was acted upon, or null if not available.
*/
public Storable getStorable() {
@@ -114,4 +126,24 @@ public class OptimisticLockException extends PersistException {
return message;
}
+
+ private static String makeMessage(Object savedVersion, Storable s, Object newVersion) {
+ String message;
+ if (savedVersion == null && newVersion == null) {
+ message = "New version is not larger than existing version";
+ } else {
+ message = "New version of " + newVersion +
+ " is not larger than existing version of " + savedVersion;
+ }
+
+ if (s != null) {
+ if (message == null) {
+ message = s.toStringKeyOnly();
+ } else {
+ message = message + ": " + s.toStringKeyOnly();
+ }
+ }
+
+ return message;
+ }
}
diff --git a/src/main/java/com/amazon/carbonado/Version.java b/src/main/java/com/amazon/carbonado/Version.java
index 7c3c2b4..cb5dcb8 100644
--- a/src/main/java/com/amazon/carbonado/Version.java
+++ b/src/main/java/com/amazon/carbonado/Version.java
@@ -25,34 +25,22 @@ import java.lang.annotation.*;
* number for the entire Storable instance. Only one property can have this
* designation.
*
- * <p>Support for the version property falls into three categories. A
- * repository may manage the version; it may respect the version; or it may
- * merely check the version.
+ * <p>Philosophically, a version property can be considered part of the
+ * identity of the storable. Unless the version is {@link Derived}, the
+ * repository is responsibile for establishing the version on insert, and for
+ * auto-incrementing it on update. Under no circumstances should a normal
+ * version property be incremented manually; this can result in a false {@link
+ * OptimisticLockException}, or worse may allow the persistent record to become
+ * corrupted.
*
- * <p><b>Manage</b>: Each storable with a version property must have one and
- * only one repository which is responsible for managing the version property.
- * That repository takes responsibility for establishing the version on insert,
- * and for auto-incrementing it on update. Under no circumstances should the
- * version property be incremented manually; this can result in a false
- * optimistic lock exception, or worse may allow the persistent record to
- * become corrupted. Prior to incrementing, these repositories will verify
- * that the version exactly matches the version of the current record, throwing
- * an {@link OptimisticLockException} otherwise. The JDBC repository is the
- * canonical example of this sort of repository.
- *
- * <p><b>Respect</b>: Repositories which respect the version use the version to
- * guarantee that updates are idempotent -- that is, that an update is applied
- * once and only once. These repositories will check that the version property
- * is strictly greater than the version of the current record, and will
- * (silently) ignore changes which fail this check.
- *
- * <p><b>Check</b>: Philosophically, a version property can be considered part
- * of the identity of the storable. That is, if the storable has a version
- * property, it cannot be considered fully specified unless that property is
- * specified. Thus, the minimal required support for all repositories is to
- * check that the version is specified on update. All repositories -- even
- * those which neither check nor manage the version -- will throw an {@link
- * IllegalStateException} if the version property is not set before update.
+ * <p>When updating a storable which has a normal version property, a value for
+ * the version must be specified along with its primary key. Otherwise, an
+ * {@link IllegalStateException} is thrown when calling update. If the update
+ * operation detects that the specified version doesn't exactly match the
+ * version of the existing persisted storable, an {@link
+ * OptimisticLockException} is thrown. For {@link Derived} versions, an {@link
+ * OptimisticLockException} is thrown only if the update detects that the new
+ * version hasn't incremented.
*
* <p>The actual type of the version property can be anything, but some
* repositories might only support integers. For maximum portability, version
diff --git a/src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java b/src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java
index c1f26a0..1b754fc 100644
--- a/src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java
+++ b/src/main/java/com/amazon/carbonado/gen/CodeBuilderUtil.java
@@ -267,7 +267,9 @@ public class CodeBuilderUtil {
/**
* Generates code to compare two values on the stack, and branch to the
- * provided Label if they are not equal. Both values must be of the same type.
+ * provided Label if they are not equal. Both values must be of the same
+ * type. If they are floating point values, NaN is considered equal to NaN,
+ * which is inconsistent with the usual treatment for NaN.
*
* <P>The generated instruction consumes both values on the stack.
*
@@ -286,18 +288,25 @@ public class CodeBuilderUtil {
final boolean choice)
{
if (valueType.getTypeCode() != TypeDesc.OBJECT_CODE) {
- b.ifComparisonBranch(label, choice ? "==" : "!=", valueType);
+ if (valueType.getTypeCode() == TypeDesc.FLOAT_CODE) {
+ // Special treatment to handle NaN.
+ b.invokeStatic(TypeDesc.FLOAT.toObjectType(), "compare", TypeDesc.INT,
+ new TypeDesc[] {TypeDesc.FLOAT, TypeDesc.FLOAT});
+ b.ifZeroComparisonBranch(label, choice ? "==" : "!=");
+ } else if (valueType.getTypeCode() == TypeDesc.DOUBLE_CODE) {
+ // Special treatment to handle NaN.
+ b.invokeStatic(TypeDesc.DOUBLE.toObjectType(), "compare", TypeDesc.INT,
+ new TypeDesc[] {TypeDesc.DOUBLE, TypeDesc.DOUBLE});
+ b.ifZeroComparisonBranch(label, choice ? "==" : "!=");
+ } else {
+ b.ifComparisonBranch(label, choice ? "==" : "!=", valueType);
+ }
return;
}
- // Equals method returns zero for false, so if choice is true, branch
- // if not zero. Note that operator selection is opposite when invoking
- // a direct ifComparisonBranch method.
- String equalsBranchOp = choice ? "!=" : "==";
-
if (!testForNull) {
- addEqualsCallTo(b, valueType);
- b.ifZeroComparisonBranch(label, equalsBranchOp);
+ String op = addEqualsCallTo(b, valueType, choice);
+ b.ifZeroComparisonBranch(label, op);
return;
}
@@ -318,14 +327,19 @@ public class CodeBuilderUtil {
isNotNull.setLocation();
b.loadLocal(value);
b.swap();
- addEqualsCallTo(b, valueType);
- b.ifZeroComparisonBranch(label, equalsBranchOp);
+ String op = addEqualsCallTo(b, valueType, choice);
+ b.ifZeroComparisonBranch(label, op);
cont.setLocation();
}
- public static void addEqualsCallTo(CodeBuilder b, TypeDesc fieldType) {
+ /**
+ * @param fieldType must be an object type
+ * @return zero comparison branch operator
+ */
+ private static String addEqualsCallTo(CodeBuilder b, TypeDesc fieldType, boolean choice) {
if (fieldType.isArray()) {
+ // FIXME: Array comparisons don't handle desired comparison of NaN.
if (!fieldType.getComponentType().isPrimitive()) {
TypeDesc type = TypeDesc.forClass(Object[].class);
b.invokeStatic("java.util.Arrays", "deepEquals",
@@ -334,6 +348,17 @@ public class CodeBuilderUtil {
b.invokeStatic("java.util.Arrays", "equals",
TypeDesc.BOOLEAN, new TypeDesc[] {fieldType, fieldType});
}
+ return choice ? "!=" : "==";
+ } else if (fieldType.toPrimitiveType() == TypeDesc.FLOAT) {
+ // Special treatment to handle NaN.
+ b.invokeVirtual(TypeDesc.FLOAT.toObjectType(), "compareTo", TypeDesc.INT,
+ new TypeDesc[] {TypeDesc.FLOAT.toObjectType()});
+ return choice ? "==" : "!=";
+ } else if (fieldType.toPrimitiveType() == TypeDesc.DOUBLE) {
+ // Special treatment to handle NaN.
+ b.invokeVirtual(TypeDesc.DOUBLE.toObjectType(), "compareTo", TypeDesc.INT,
+ new TypeDesc[] {TypeDesc.DOUBLE.toObjectType()});
+ return choice ? "==" : "!=";
} else {
TypeDesc[] params = {TypeDesc.OBJECT};
if (fieldType.toClass() != null) {
@@ -345,6 +370,7 @@ public class CodeBuilderUtil {
} else {
b.invokeVirtual(TypeDesc.OBJECT, "equals", TypeDesc.BOOLEAN, params);
}
+ return choice ? "!=" : "==";
}
}
diff --git a/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java b/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java
index 17efb70..581a7d6 100644
--- a/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java
+++ b/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java
@@ -126,7 +126,7 @@ public final class MasterStorableGenerator<S extends Storable> {
anySequences:
if (features.contains(MasterFeature.INSERT_SEQUENCES)) {
for (StorableProperty<S> property : info.getAllProperties().values()) {
- if (property.getSequenceName() != null) {
+ if (!property.isDerived() && property.getSequenceName() != null) {
break anySequences;
}
}
@@ -266,7 +266,7 @@ public final class MasterStorableGenerator<S extends Storable> {
int ordinal = 0;
for (StorableProperty<S> property : mAllProperties.values()) {
- if (property.getSequenceName() != null) {
+ if (!property.isDerived() && property.getSequenceName() != null) {
// Check the state of this property, to see if it is
// uninitialized. Uninitialized state has value zero.
@@ -362,14 +362,16 @@ public final class MasterStorableGenerator<S extends Storable> {
Label tryStart = addEnterTransaction(b, INSERT_OP, txnVar);
if (mFeatures.contains(MasterFeature.VERSIONING)) {
- // Only set if uninitialized.
- b.loadThis();
- b.invokeVirtual(StorableGenerator.IS_VERSION_INITIALIZED_METHOD_NAME,
- TypeDesc.BOOLEAN, null);
- Label isInitialized = b.createLabel();
- b.ifZeroComparisonBranch(isInitialized, "!=");
- addAdjustVersionProperty(b, null, 1);
- isInitialized.setLocation();
+ if (!mInfo.getVersionProperty().isDerived()) {
+ // Only set if uninitialized.
+ b.loadThis();
+ b.invokeVirtual(StorableGenerator.IS_VERSION_INITIALIZED_METHOD_NAME,
+ TypeDesc.BOOLEAN, null);
+ Label isInitialized = b.createLabel();
+ b.ifZeroComparisonBranch(isInitialized, "!=");
+ addAdjustVersionProperty(b, null, 1);
+ isInitialized.setLocation();
+ }
}
if (mFeatures.contains(MasterFeature.INSERT_CHECK_REQUIRED)) {
@@ -410,7 +412,8 @@ public final class MasterStorableGenerator<S extends Storable> {
for (StorableProperty<S> property : mAllProperties.values()) {
ordinal++;
- if (property.isJoin() || property.isPrimaryKeyMember()
+ if (property.isDerived()
+ || property.isJoin() || property.isPrimaryKeyMember()
|| property.isNullable()
|| property.isAutomatic() || property.isVersion())
{
@@ -548,33 +551,109 @@ public final class MasterStorableGenerator<S extends Storable> {
b.ifZeroComparisonBranch(failed, "==");
// if (version support enabled) {
- // if (this.getVersionNumber() != saved.getVersionNumber()) {
- // throw new OptimisticLockException
- // (this.getVersionNumber(), saved.getVersionNumber(), this);
+ // if (!derived version) {
+ // if (this.getVersionNumber() != saved.getVersionNumber()) {
+ // throw new OptimisticLockException
+ // (this.getVersionNumber(), saved.getVersionNumber(), this);
+ // }
+ // } else {
+ // if (this.getVersionNumber() <= saved.getVersionNumber()) {
+ // throw new OptimisticLockException
+ // (saved.getVersionNumber(), this, this.getVersionNumber());
+ // }
// }
// }
if (mFeatures.contains(MasterFeature.VERSIONING)) {
- TypeDesc versionType = TypeDesc.forClass(mInfo.getVersionProperty().getType());
- b.loadThis();
- b.invoke(mInfo.getVersionProperty().getReadMethod());
- b.loadLocal(savedVar);
- b.invoke(mInfo.getVersionProperty().getReadMethod());
- Label sameVersion = b.createLabel();
- CodeBuilderUtil.addValuesEqualCall(b, versionType, true, sameVersion, true);
- b.newObject(optimisticLockType);
- b.dup();
- b.loadThis();
- b.invoke(mInfo.getVersionProperty().getReadMethod());
- b.convert(versionType, TypeDesc.OBJECT);
- b.loadLocal(savedVar);
- b.invoke(mInfo.getVersionProperty().getReadMethod());
- b.convert(versionType, TypeDesc.OBJECT);
- b.loadThis();
- b.invokeConstructor
- (optimisticLockType,
- new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT, storableType});
- b.throwObject();
- sameVersion.setLocation();
+ StorableProperty<S> versionProperty = mInfo.getVersionProperty();
+ TypeDesc versionType = TypeDesc.forClass(versionProperty.getType());
+
+ Label allowedVersion = b.createLabel();
+
+ if (!versionProperty.isDerived()) {
+ b.loadThis();
+ b.invoke(versionProperty.getReadMethod());
+ b.loadLocal(savedVar);
+ b.invoke(versionProperty.getReadMethod());
+ CodeBuilderUtil.addValuesEqualCall
+ (b, versionType, true, allowedVersion, true);
+
+ b.newObject(optimisticLockType);
+ b.dup();
+ b.loadThis();
+ b.invoke(versionProperty.getReadMethod());
+ b.convert(versionType, TypeDesc.OBJECT);
+ b.loadLocal(savedVar);
+ b.invoke(versionProperty.getReadMethod());
+ b.convert(versionType, TypeDesc.OBJECT);
+ b.loadThis();
+ b.invokeConstructor
+ (optimisticLockType,
+ new TypeDesc[] {TypeDesc.OBJECT, TypeDesc.OBJECT, storableType});
+ b.throwObject();
+ } else {
+ b.loadThis();
+ b.invoke(versionProperty.getReadMethod());
+ LocalVariable newVersion = b.createLocalVariable(null, versionType);
+ b.storeLocal(newVersion);
+
+ b.loadLocal(savedVar);
+ b.invoke(versionProperty.getReadMethod());
+ LocalVariable savedVersion = b.createLocalVariable(null, versionType);
+ b.storeLocal(savedVersion);
+
+ // Skip check if new or saved version is null.
+ branchIfNull(b, newVersion, allowedVersion);
+ branchIfNull(b, savedVersion, allowedVersion);
+
+ TypeDesc primVersionType = versionType.toPrimitiveType();
+ if (primVersionType != null) {
+ if (versionType != primVersionType) {
+ b.loadLocal(newVersion);
+ b.convert(versionType, primVersionType);
+ newVersion = b.createLocalVariable(null, primVersionType);
+ b.storeLocal(newVersion);
+
+ b.loadLocal(savedVersion);
+ b.convert(versionType, primVersionType);
+ savedVersion = b.createLocalVariable(null, primVersionType);
+ b.storeLocal(savedVersion);
+ }
+
+ // Skip check if new or saved version is NaN.
+ branchIfNaN(b, newVersion, allowedVersion);
+ branchIfNaN(b, savedVersion, allowedVersion);
+
+ b.loadLocal(newVersion);
+ b.loadLocal(savedVersion);
+ b.ifComparisonBranch(allowedVersion, ">", primVersionType);
+ } else if (Comparable.class.isAssignableFrom(versionProperty.getType())) {
+ b.loadLocal(newVersion);
+ b.loadLocal(savedVersion);
+ b.invokeInterface(TypeDesc.forClass(Comparable.class), "compareTo",
+ TypeDesc.INT, new TypeDesc[] {TypeDesc.OBJECT});
+ b.ifZeroComparisonBranch(allowedVersion, ">");
+ } else {
+ throw new SupportException
+ ("Derived version property must be Comparable: " +
+ versionProperty);
+ }
+
+ b.newObject(optimisticLockType);
+ b.dup();
+ b.loadLocal(savedVar);
+ b.invoke(versionProperty.getReadMethod());
+ b.convert(versionType, TypeDesc.OBJECT);
+ b.loadThis();
+ b.loadThis();
+ b.invoke(versionProperty.getReadMethod());
+ b.convert(versionType, TypeDesc.OBJECT);
+ b.invokeConstructor
+ (optimisticLockType,
+ new TypeDesc[] {TypeDesc.OBJECT, storableType, TypeDesc.OBJECT});
+ b.throwObject();
+ }
+
+ allowedVersion.setLocation();
}
// this.copyDirtyProperties(saved);
@@ -585,7 +664,9 @@ public final class MasterStorableGenerator<S extends Storable> {
b.loadLocal(savedVar);
b.invokeVirtual(COPY_DIRTY_PROPERTIES, null, new TypeDesc[] {storableType});
if (mFeatures.contains(MasterFeature.VERSIONING)) {
- addAdjustVersionProperty(b, savedVar, -1);
+ if (!mInfo.getVersionProperty().isDerived()) {
+ addAdjustVersionProperty(b, savedVar, -1);
+ }
}
// if (!saved.doTryUpdateMaster()) {
@@ -661,6 +742,31 @@ public final class MasterStorableGenerator<S extends Storable> {
}
}
+ private void branchIfNull(CodeBuilder b, LocalVariable value, Label isNull) {
+ if (!value.getType().isPrimitive()) {
+ b.loadLocal(value);
+ b.ifNullBranch(isNull, true);
+ }
+ }
+
+ private void branchIfNaN(CodeBuilder b, LocalVariable value, Label isNaN) {
+ TypeDesc type = value.getType();
+ if (type == TypeDesc.FLOAT || type == TypeDesc.DOUBLE) {
+ b.loadLocal(value);
+ if (type == TypeDesc.FLOAT) {
+ b.invokeStatic(TypeDesc.FLOAT.toObjectType(),
+ "isNaN", TypeDesc.BOOLEAN,
+ new TypeDesc[] {TypeDesc.FLOAT});
+ b.ifZeroComparisonBranch(isNaN, "!=");
+ } else {
+ b.invokeStatic(TypeDesc.DOUBLE.toObjectType(),
+ "isNaN", TypeDesc.BOOLEAN,
+ new TypeDesc[] {TypeDesc.DOUBLE});
+ b.ifZeroComparisonBranch(isNaN, "!=");
+ }
+ }
+ }
+
/**
* Generates code to enter a transaction, if required.
*
diff --git a/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java b/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java
index 4934db3..a28f7e6 100644
--- a/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java
+++ b/src/main/java/com/amazon/carbonado/gen/StorableGenerator.java
@@ -193,7 +193,7 @@ public final class StorableGenerator<S extends Storable> {
* fully thread-safe. The Storable type itself may be an interface or a
* class. If it is a class, then it must not be final, and it must have a
* public, no-arg constructor. The constructor signature for the returned
- * abstract is defined as follows:
+ * abstract class is defined as follows:
*
* <pre>
* /**
@@ -569,27 +569,29 @@ public final class StorableGenerator<S extends Storable> {
for (StorableProperty<S> property : mAllProperties.values()) {
ordinal++;
- if (property.isVersion()) {
+ if (!property.isDerived() && property.isVersion()) {
versionOrdinal = ordinal;
}
final String name = property.getName();
final TypeDesc type = TypeDesc.forClass(property.getType());
- if (property.isJoin()) {
- // If generating wrapper, property access is not guarded by
- // synchronization. Mark as volatile instead.
- mClassFile.addField(Modifiers.PRIVATE.toVolatile(mGenMode == GEN_WRAPPED),
- name, type);
- requireStateField = true;
- } else if (mGenMode == GEN_ABSTRACT) {
- // Only define regular property fields if abstract
- // class. Wrapped class doesn't reference them. Double
- // words are volatile to prevent word tearing without
- // explicit synchronization.
- mClassFile.addField(Modifiers.PROTECTED.toVolatile(type.isDoubleWord()),
- name, type);
- requireStateField = true;
+ if (!property.isDerived()) {
+ if (property.isJoin()) {
+ // If generating wrapper, property access is not guarded by
+ // synchronization. Mark as volatile instead.
+ mClassFile.addField(Modifiers.PRIVATE.toVolatile(mGenMode == GEN_WRAPPED),
+ name, type);
+ requireStateField = true;
+ } else if (mGenMode == GEN_ABSTRACT) {
+ // Only define regular property fields if abstract
+ // class. Wrapped class doesn't reference them. Double
+ // words are volatile to prevent word tearing without
+ // explicit synchronization.
+ mClassFile.addField(Modifiers.PROTECTED.toVolatile(type.isDoubleWord()),
+ name, type);
+ requireStateField = true;
+ }
}
final String stateFieldName = PROPERTY_STATE_FIELD_NAME + (ordinal >> 4);
@@ -605,7 +607,7 @@ public final class StorableGenerator<S extends Storable> {
}
// Add read method.
- buildReadMethod: {
+ buildReadMethod: if (!property.isDerived()) {
Method readMethod = property.getReadMethod();
MethodInfo mi;
@@ -849,7 +851,7 @@ public final class StorableGenerator<S extends Storable> {
}
// Add write method.
- if (!property.isQuery()) {
+ buildWriteMethod: if (!property.isDerived() && !property.isQuery()) {
Method writeMethod = property.getWriteMethod();
MethodInfo mi;
@@ -1901,7 +1903,8 @@ public final class StorableGenerator<S extends Storable> {
new HashMap<String, StorableProperty<S>>();
for (StorableProperty property : mAllProperties.values()) {
- if (!property.isPrimaryKeyMember() &&
+ if (!property.isDerived() &&
+ !property.isPrimaryKeyMember() &&
!property.isJoin() &&
!property.isNullable()) {
@@ -2111,7 +2114,7 @@ public final class StorableGenerator<S extends Storable> {
for (StorableProperty property : mAllProperties.values()) {
// Decide if property should be part of the copy.
- boolean shouldCopy = !property.isJoin() &&
+ boolean shouldCopy = !property.isDerived() && !property.isJoin() &&
(property.isPrimaryKeyMember() && pkProperties ||
property.isVersion() && versionProperty ||
!property.isPrimaryKeyMember() && dataProperties);
@@ -2309,7 +2312,7 @@ public final class StorableGenerator<S extends Storable> {
int ordinal = 0;
int mask = 0;
for (StorableProperty property : mAllProperties.values()) {
- if (property != joinProperty && !property.isJoin()) {
+ if (property != joinProperty && !property.isDerived() && !property.isJoin()) {
// Check to see if property is an internal member of joinProperty.
for (int i=joinProperty.getJoinElementCount(); --i>=0; ) {
if (property == joinProperty.getInternalJoinElement(i)) {
@@ -2343,19 +2346,21 @@ public final class StorableGenerator<S extends Storable> {
for (StorableProperty property : mAllProperties.values()) {
ordinal++;
- if (property.isJoin() || mGenMode == GEN_ABSTRACT) {
- requireStateField = true;
- }
+ if (!property.isDerived()) {
+ if (property.isJoin() || mGenMode == GEN_ABSTRACT) {
+ requireStateField = true;
+ }
- if (ordinal == maxOrdinal || ((ordinal & 0xf) == 0xf)) {
- if (requireStateField) {
- String stateFieldName = PROPERTY_STATE_FIELD_NAME + (ordinal >> 4);
+ if (ordinal == maxOrdinal || ((ordinal & 0xf) == 0xf)) {
+ if (requireStateField) {
+ String stateFieldName = PROPERTY_STATE_FIELD_NAME + (ordinal >> 4);
- b.loadThis();
- b.loadConstant(0);
- b.storeField(stateFieldName, TypeDesc.INT);
+ b.loadThis();
+ b.loadConstant(0);
+ b.storeField(stateFieldName, TypeDesc.INT);
+ }
+ requireStateField = false;
}
- requireStateField = false;
}
}
}
@@ -2382,17 +2387,19 @@ public final class StorableGenerator<S extends Storable> {
int orMask = 0;
for (StorableProperty property : mAllProperties.values()) {
- if (property.isQuery()) {
- // Don't erase cached query.
- andMask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2);
- } else if (!property.isJoin()) {
- if (name == MARK_ALL_PROPERTIES_CLEAN) {
- // Force clean state (1) always.
- orMask |= PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2);
- } else if (name == MARK_PROPERTIES_CLEAN) {
- // Mask will convert dirty (3) to clean (1). State 2, which
- // is illegal, is converted to 0.
- andMask |= PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2);
+ if (!property.isDerived()) {
+ if (property.isQuery()) {
+ // Don't erase cached query.
+ andMask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2);
+ } else if (!property.isJoin()) {
+ if (name == MARK_ALL_PROPERTIES_CLEAN) {
+ // Force clean state (1) always.
+ orMask |= PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2);
+ } else if (name == MARK_PROPERTIES_CLEAN) {
+ // Mask will convert dirty (3) to clean (1). State 2, which
+ // is illegal, is converted to 0.
+ andMask |= PROPERTY_STATE_CLEAN << ((ordinal & 0xf) * 2);
+ }
}
}
@@ -2443,14 +2450,16 @@ public final class StorableGenerator<S extends Storable> {
int orMask = 0;
for (StorableProperty property : mAllProperties.values()) {
- if (property.isJoin()) {
- // Erase cached join properties, but don't erase cached query.
- if (!property.isQuery()) {
- andMask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2);
+ if (!property.isDerived()) {
+ if (property.isJoin()) {
+ // Erase cached join properties, but don't erase cached query.
+ if (!property.isQuery()) {
+ andMask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2);
+ }
+ } else if (name == MARK_ALL_PROPERTIES_DIRTY) {
+ // Force dirty state (3).
+ orMask |= PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2);
}
- } else if (name == MARK_ALL_PROPERTIES_DIRTY) {
- // Force dirty state (3).
- orMask |= PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2);
}
ordinal++;
@@ -2514,16 +2523,18 @@ public final class StorableGenerator<S extends Storable> {
int andMask = 0xffffffff;
int orMask = 0;
for (StorableProperty property : mAllProperties.values()) {
- if (property == ordinaryProperty) {
- if (mGenMode == GEN_ABSTRACT) {
- // Only GEN_ABSTRACT mode uses these state bits.
- orMask |= PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2);
- }
- } else if (property.isJoin()) {
- // Check to see if ordinary is an internal member of join property.
- for (int i=property.getJoinElementCount(); --i>=0; ) {
- if (ordinaryProperty == property.getInternalJoinElement(i)) {
- andMask &= ~(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2));
+ if (!property.isDerived()) {
+ if (property == ordinaryProperty) {
+ if (mGenMode == GEN_ABSTRACT) {
+ // Only GEN_ABSTRACT mode uses these state bits.
+ orMask |= PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2);
+ }
+ } else if (property.isJoin()) {
+ // Check to see if ordinary is an internal member of join property.
+ for (int i=property.getJoinElementCount(); --i>=0; ) {
+ if (ordinaryProperty == property.getInternalJoinElement(i)) {
+ andMask &= ~(PROPERTY_STATE_DIRTY << ((ordinal & 0xf) * 2));
+ }
}
}
}
@@ -2556,13 +2567,15 @@ public final class StorableGenerator<S extends Storable> {
int ordinal = 0;
int andMask = 0;
for (StorableProperty property : mAllProperties.values()) {
- if (!property.isJoin() && (!property.isPrimaryKeyMember() || includePk)) {
- // Logical 'and' will convert state 1 (clean) to state 0, so
- // that it will be ignored. State 3 (dirty) is what we're
- // looking for, and it turns into 2. Essentially, we leave the
- // high order bit on, since there is no state which has the
- // high order bit on unless the low order bit is also on.
- andMask |= 2 << ((ordinal & 0xf) * 2);
+ if (!property.isDerived()) {
+ if (!property.isJoin() && (!property.isPrimaryKeyMember() || includePk)) {
+ // Logical 'and' will convert state 1 (clean) to state 0, so
+ // that it will be ignored. State 3 (dirty) is what we're
+ // looking for, and it turns into 2. Essentially, we leave the
+ // high order bit on, since there is no state which has the
+ // high order bit on unless the low order bit is also on.
+ andMask |= 2 << ((ordinal & 0xf) * 2);
+ }
}
ordinal++;
if ((ordinal & 0xf) == 0 || ordinal >= count) {
@@ -2623,8 +2636,10 @@ public final class StorableGenerator<S extends Storable> {
int ordinal = 0;
int mask = 0;
for (StorableProperty property : mAllProperties.values()) {
- if (properties.containsKey(property.getName())) {
- mask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2);
+ if (!property.isDerived()) {
+ if (properties.containsKey(property.getName())) {
+ mask |= PROPERTY_STATE_MASK << ((ordinal & 0xf) * 2);
+ }
}
ordinal++;
if (((ordinal & 0xf) == 0 || ordinal >= mAllProperties.size()) && mask != 0) {
@@ -2772,6 +2787,7 @@ public final class StorableGenerator<S extends Storable> {
// Params to invoke String.equals.
TypeDesc[] params = {TypeDesc.OBJECT};
+ Label derivedMatch = null;
Label joinMatch = null;
for (int i=0; i<caseCount; i++) {
@@ -2802,7 +2818,12 @@ public final class StorableGenerator<S extends Storable> {
b.ifZeroComparisonBranch(notEqual, "==");
}
- if (prop.isJoin()) {
+ if (prop.isDerived()) {
+ if (derivedMatch == null) {
+ derivedMatch = b.createLabel();
+ }
+ b.branch(derivedMatch);
+ } else if (prop.isJoin()) {
if (joinMatch == null) {
joinMatch = b.createLabel();
}
@@ -2841,6 +2862,18 @@ public final class StorableGenerator<S extends Storable> {
b.invokeConstructor(exceptionType, params);
b.throwObject();
+ if (derivedMatch != null) {
+ derivedMatch.setLocation();
+
+ b.newObject(exceptionType);
+ b.dup();
+ b.loadConstant("Cannot get state for derived property: ");
+ b.loadLocal(b.getParameter(0));
+ b.invokeVirtual(TypeDesc.STRING, "concat", TypeDesc.STRING, params);
+ b.invokeConstructor(exceptionType, params);
+ b.throwObject();
+ }
+
if (joinMatch != null) {
joinMatch.setLocation();
@@ -2936,7 +2969,7 @@ public final class StorableGenerator<S extends Storable> {
boolean mixIn = false;
for (StorableProperty property : mAllProperties.values()) {
- if (property.isJoin()) {
+ if (property.isDerived() || property.isJoin()) {
continue;
}
addHashCodeCall(b, property.getName(),
@@ -3105,7 +3138,7 @@ public final class StorableGenerator<S extends Storable> {
b.storeLocal(other);
for (StorableProperty property : mAllProperties.values()) {
- if (property.isJoin()) {
+ if (property.isDerived() || property.isJoin()) {
continue;
}
// If we're only comparing keys, and this isn't a key, skip it
@@ -3211,8 +3244,10 @@ public final class StorableGenerator<S extends Storable> {
// Second pass, print non-primary keys.
if (!keyOnly) {
for (StorableProperty property : mAllProperties.values()) {
- // Don't print join properties if they may throw an exception.
- if (!property.isPrimaryKeyMember() && (!property.isJoin())) {
+ // Don't print any derived or join properties since they may throw an exception.
+ if (!property.isPrimaryKeyMember() &&
+ (!property.isDerived()) && (!property.isJoin()))
+ {
Label skipPrint = b.createLabel();
// Check if independent property is supported, and skip if not.
diff --git a/src/main/java/com/amazon/carbonado/gen/StorableSerializer.java b/src/main/java/com/amazon/carbonado/gen/StorableSerializer.java
index 5115854..09eff96 100644
--- a/src/main/java/com/amazon/carbonado/gen/StorableSerializer.java
+++ b/src/main/java/com/amazon/carbonado/gen/StorableSerializer.java
@@ -116,11 +116,11 @@ public abstract class StorableSerializer<S extends Storable> {
StorableProperty<S>[] properties;
{
- // Exclude joins.
+ // Exclude derived properties and joins.
List<StorableProperty<S>> list =
new ArrayList<StorableProperty<S>>(propertyMap.size());
for (StorableProperty<S> property : propertyMap.values()) {
- if (!property.isJoin()) {
+ if (!property.isDerived() && !property.isJoin()) {
list.add(property);
}
}
diff --git a/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java b/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java
index f318c33..7451fbd 100644
--- a/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java
+++ b/src/main/java/com/amazon/carbonado/info/StorableIntrospector.java
@@ -51,6 +51,7 @@ import com.amazon.carbonado.Alias;
import com.amazon.carbonado.AlternateKeys;
import com.amazon.carbonado.Authoritative;
import com.amazon.carbonado.Automatic;
+import com.amazon.carbonado.Derived;
import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.Index;
import com.amazon.carbonado.Indexes;
@@ -230,9 +231,54 @@ public class StorableIntrospector {
// late, then there would be a stack overflow.
for (StorableProperty property : properties.values()) {
if (property instanceof JoinProperty) {
- ((JoinProperty)property).resolve(errorMessages, properties);
+ ((JoinProperty)property).resolveJoin(errorMessages, info);
}
}
+
+ // Resolve derived properties after join properties, since they may
+ // depend on them.
+ boolean anyDerived = false;
+ for (StorableProperty<S> property : properties.values()) {
+ if (property instanceof SimpleProperty && property.isDerived()) {
+ anyDerived = true;
+ ((SimpleProperty)property).resolveDerivedFrom(errorMessages, info);
+ }
+ }
+
+ if (anyDerived && errorMessages.size() == 0) {
+ // Make sure that any indexes which refer to derived properties
+ // throwing FetchException have derived-from properties
+ // listed. Why? The exception likely indicates that a join
+ // property is being fetched.
+
+ for (StorableIndex<S> index : indexes) {
+ for (StorableProperty<S> property : index.getProperties()) {
+ if (property.isDerived() && property.getReadMethod() != null &&
+ property.getDerivedFromProperties().length == 0)
+ {
+ Class exceptionType = FetchException.class;
+
+ Class<?>[] exceptions = property.getReadMethod().getExceptionTypes();
+ boolean fetches = false;
+ for (int i=exceptions.length; --i>=0; ) {
+ if (exceptions[i].isAssignableFrom(exceptionType)) {
+ fetches = true;
+ break;
+ }
+ }
+
+ if (fetches) {
+ errorMessages.add
+ ("Index refers to a derived property which declares " +
+ "throwing a FetchException, but property does not " +
+ "list any derived-from properties: \"" +
+ property.getName() + "'");
+ }
+ }
+ }
+ }
+ }
+
if (errorMessages.size() > 0) {
cCache.remove(type);
throw new MalformedTypeException(type, errorMessages);
@@ -482,14 +528,15 @@ public class StorableIntrospector {
}
// Check if abstract method is just redefining a method in
// Storable.
- // TODO: Check if abstract method is redefining return type, which
- // is allowed for copy method. The return type must be within its
- // bounds.
try {
Method m2 = Storable.class.getMethod(m.getName(), (Class[]) m.getParameterTypes());
if (m.getReturnType() == m2.getReturnType()) {
it.remove();
}
+ // Copy method can be redefined with specialized return type.
+ if (m.getName().equals("copy") && type.isAssignableFrom(m.getReturnType())) {
+ it.remove();
+ }
} catch (NoSuchMethodException e) {
// Not defined in Storable.
}
@@ -523,7 +570,20 @@ public class StorableIntrospector {
Method readMethod = property.getReadMethod();
Method writeMethod = property.getWriteMethod();
- if (readMethod == null && writeMethod == null) {
+ boolean isAbstract;
+ if (readMethod == null) {
+ if (writeMethod == null) {
+ continue;
+ } else if (!Modifier.isAbstract(writeMethod.getModifiers()) &&
+ writeMethod.getAnnotation(Derived.class) == null)
+ {
+ // Ignore concrete property methods unless they're derived.
+ continue;
+ }
+ } else if (!Modifier.isAbstract(readMethod.getModifiers()) &&
+ readMethod.getAnnotation(Derived.class) == null)
+ {
+ // Ignore concrete property methods unless they're derived.
continue;
}
@@ -540,7 +600,7 @@ public class StorableIntrospector {
if (readMethod != null) {
String sig = createSig(readMethod);
- if (methods.containsKey(sig)) {
+ if (storableProp.isDerived() || methods.containsKey(sig)) {
methods.remove(sig);
properties.put(property.getName(), storableProp);
} else {
@@ -550,7 +610,7 @@ public class StorableIntrospector {
if (writeMethod != null) {
String sig = createSig(writeMethod);
- if (methods.containsKey(sig)) {
+ if (storableProp.isDerived() || methods.containsKey(sig)) {
methods.remove(sig);
properties.put(property.getName(), storableProp);
} else {
@@ -698,6 +758,7 @@ public class StorableIntrospector {
Automatic automatic = null;
Independent independent = null;
Join join = null;
+ Derived derived = null;
Method readMethod = property.getReadMethod();
Method writeMethod = property.getWriteMethod();
@@ -717,14 +778,16 @@ public class StorableIntrospector {
automatic = readMethod.getAnnotation(Automatic.class);
independent = readMethod.getAnnotation(Independent.class);
join = readMethod.getAnnotation(Join.class);
+ derived = readMethod.getAnnotation(Derived.class);
}
if (writeMethod == null) {
if (readMethod == null || Modifier.isAbstract(readMethod.getModifiers())) {
// Set method is always required for non-join properties. More
// work is done later on join properties, and sometimes the
- // write method is required.
- if (join == null) {
+ // write method is required. Derived properties don't need a
+ // set method.
+ if (join == null && derived == null) {
errorMessages.add("Must define proper 'set' method for property: " +
property.getName());
}
@@ -758,6 +821,35 @@ public class StorableIntrospector {
errorMessages.add
("Join annotation not allowed on mutator: " + writeMethod);
}
+ if (writeMethod.getAnnotation(Derived.class) != null) {
+ errorMessages.add
+ ("Derived annotation not allowed on mutator: " + writeMethod);
+ }
+ }
+
+ if (derived != null) {
+ if (readMethod != null && Modifier.isAbstract(readMethod.getModifiers()) ||
+ writeMethod != null && Modifier.isAbstract(writeMethod.getModifiers()))
+ {
+ errorMessages.add("Derived properties cannot be abstract: " +
+ property.getName());
+ }
+ if (pk) {
+ errorMessages.add("Derived properties cannot be a member of primary key: " +
+ property.getName());
+ }
+ if (sequence != null) {
+ errorMessages.add("Derived properties cannot have a Sequence annotation: " +
+ property.getName());
+ }
+ if (automatic != null) {
+ errorMessages.add("Derived properties cannot have an Automatic annotation: " +
+ property.getName());
+ }
+ if (join != null) {
+ errorMessages.add("Derived properties cannot have a Join annotation: " +
+ property.getName());
+ }
}
if (nullable != null && property.getType().isPrimitive()) {
@@ -813,6 +905,48 @@ public class StorableIntrospector {
gatherAdapters(property, writeMethod, false, errorMessages);
}
+ // Check that declared checked exceptions are allowed.
+ if (readMethod != null) {
+ for (Class<?> ex : readMethod.getExceptionTypes()) {
+ if (RuntimeException.class.isAssignableFrom(ex)
+ || Error.class.isAssignableFrom(ex))
+ {
+ continue;
+ }
+ if (join != null || derived != null) {
+ if (FetchException.class.isAssignableFrom(ex)) {
+ continue;
+ }
+ errorMessages.add
+ ("Checked exceptions thrown by join or derived property accessors " +
+ "must be of type FetchException: \"" + readMethod.getName() +
+ "\" declares throwing \"" + ex.getName() + '"');
+ break;
+ } else {
+ errorMessages.add
+ ("Only join and derived property accessors can throw checked " +
+ "exceptions: \"" + readMethod.getName() + "\" declares throwing \"" +
+ ex.getName() + '"');
+ break;
+ }
+ }
+ }
+
+ // Check that declared checked exceptions are allowed.
+ if (writeMethod != null) {
+ for (Class<?> ex : writeMethod.getExceptionTypes()) {
+ if (RuntimeException.class.isAssignableFrom(ex)
+ || Error.class.isAssignableFrom(ex))
+ {
+ continue;
+ }
+ errorMessages.add
+ ("Mutators cannot throw checked exceptions: \"" + writeMethod.getName() +
+ "\" declares throwing \"" + ex.getName() + '"');
+ break;
+ }
+ }
+
String sequenceName = null;
if (sequence != null) {
sequenceName = sequence.value();
@@ -825,7 +959,8 @@ public class StorableIntrospector {
return new SimpleProperty<S>
(property, enclosing, nullable != null, pk, altKey,
aliases, constraints, adapters == null ? null : adapters[0],
- version != null, sequenceName, independent != null, automatic != null);
+ version != null, sequenceName,
+ independent != null, automatic != null, derived);
}
// Do additional work for join properties.
@@ -933,7 +1068,7 @@ public class StorableIntrospector {
return new JoinProperty<S>
(property, enclosing, nullable != null, aliases,
constraints, adapters == null ? null : adapters[0],
- sequenceName, independent != null, automatic != null,
+ sequenceName, independent != null, automatic != null, derived,
joinedType, internal, external);
}
@@ -1412,6 +1547,8 @@ public class StorableIntrospector {
}
private static class SimpleProperty<S extends Storable> implements StorableProperty<S> {
+ private static final ChainedProperty[] EMPTY_CHAIN_ARRAY = new ChainedProperty[0];
+
private final BeanProperty mBeanProperty;
private final Class<S> mEnclosingType;
private final boolean mNullable;
@@ -1424,13 +1561,24 @@ public class StorableIntrospector {
private final String mSequence;
private final boolean mIndependent;
private final boolean mAutomatic;
+ private final boolean mIsDerived;
+
+ // Temporary reference until derived from is resolved.
+ private Derived mDerived;
+
+ // Resolved derived from properties.
+ private ChainedProperty<S>[] mDerivedFrom;
+
+ // Resolved derived to properties.
+ private ChainedProperty<S>[] mDerivedTo;
SimpleProperty(BeanProperty property, Class<S> enclosing,
boolean nullable, boolean primaryKey, boolean alternateKey,
String[] aliases, StorablePropertyConstraint[] constraints,
StorablePropertyAdapter adapter,
boolean isVersion, String sequence,
- boolean independent, boolean automatic)
+ boolean independent, boolean automatic,
+ Derived derived)
{
mBeanProperty = property;
mEnclosingType = enclosing;
@@ -1444,6 +1592,8 @@ public class StorableIntrospector {
mSequence = sequence;
mIndependent = independent;
mAutomatic = automatic;
+ mIsDerived = derived != null;
+ mDerived = derived;
}
public final String getName() {
@@ -1536,10 +1686,45 @@ public class StorableIntrospector {
return mIsVersion;
}
+ public final boolean isDerived() {
+ return mIsDerived;
+ }
+
+ public final ChainedProperty<S>[] getDerivedFromProperties() {
+ return (!mIsDerived || mDerivedFrom == null) ?
+ EMPTY_CHAIN_ARRAY : mDerivedFrom.clone();
+ }
+
+ public final ChainedProperty<?>[] getDerivedToProperties() {
+ if (mDerivedTo == null) {
+ // Derived-to properties must be determined on demand because
+ // introspection might have been initiated by a dependency. If
+ // that dependency is asked for derived properties, it will not
+ // yet have resolved derived-from properties.
+
+ Set<ChainedProperty<?>> derivedToSet = new LinkedHashSet<ChainedProperty<?>>();
+ Set<Class<?>> examinedSet = new HashSet<Class<?>>();
+
+ addToDerivedToSet(derivedToSet, examinedSet, examine(getEnclosingType()));
+
+ if (derivedToSet.size() > 0) {
+ mDerivedTo = derivedToSet.toArray(new ChainedProperty[derivedToSet.size()]);
+ } else {
+ mDerivedTo = EMPTY_CHAIN_ARRAY;
+ }
+ }
+
+ return mDerivedTo.clone();
+ }
+
public boolean isJoin() {
return false;
}
+ public boolean isOneToOneJoin() {
+ return false;
+ }
+
public Class<? extends Storable> getJoinedType() {
return null;
}
@@ -1635,6 +1820,216 @@ public class StorableIntrospector {
app.append(getEnclosingType().getName());
app.append('}');
}
+
+ void resolveDerivedFrom(List<String> errorMessages, StorableInfo<S> info) {
+ Derived derived = mDerived;
+ // Don't need this anymore.
+ mDerived = null;
+
+ if (!mIsDerived || derived == null) {
+ return;
+ }
+ String[] fromNames = derived.from();
+ if (fromNames == null || fromNames.length == 0) {
+ return;
+ }
+
+ Set<ChainedProperty<S>> derivedFromSet = new LinkedHashSet<ChainedProperty<S>>();
+
+ for (String fromName : fromNames) {
+ ChainedProperty<S> from;
+ try {
+ from = ChainedProperty.parse(info, fromName);
+ } catch (IllegalArgumentException e) {
+ errorMessages.add
+ ("Cannot find derived-from property: \"" +
+ getName() + "\" reports being derived from \"" +
+ fromName + '"');
+ continue;
+ }
+ addToDerivedFromSet(errorMessages, derivedFromSet, from);
+ }
+
+ if (derivedFromSet.size() > 0) {
+ if (derivedFromSet.contains(ChainedProperty.get(this))) {
+ errorMessages.add
+ ("Derived-from dependency cycle detected: \"" + getName() + '"');
+ }
+
+ mDerivedFrom = derivedFromSet
+ .toArray(new ChainedProperty[derivedFromSet.size()]);
+ } else {
+ mDerivedFrom = null;
+ }
+ }
+
+ private boolean addToDerivedFromSet(List<String> errorMessages,
+ Set<ChainedProperty<S>> derivedFromSet,
+ ChainedProperty<S> from)
+ {
+ if (derivedFromSet.contains(from)) {
+ return false;
+ }
+
+ derivedFromSet.add(from);
+
+ ChainedProperty<S> trimmed = from.getChainCount() == 0 ? null : from.trim();
+
+ if (trimmed != null) {
+ // Include all join properties as dependencies.
+ addToDerivedFromSet(errorMessages, derivedFromSet, trimmed);
+ }
+
+ StorableProperty<?> lastInChain = from.getLastProperty();
+
+ if (lastInChain.isDerived()) {
+ // Expand derived dependencies.
+ ((SimpleProperty) lastInChain)
+ .resolveDerivedFrom(errorMessages, examine(lastInChain.getEnclosingType()));
+ for (ChainedProperty<?> lastFrom : lastInChain.getDerivedFromProperties()) {
+ ChainedProperty<S> dep;
+ if (trimmed == null) {
+ dep = (ChainedProperty<S>) lastFrom;
+ } else {
+ dep = trimmed.append(lastFrom);
+ }
+ addToDerivedFromSet(errorMessages, derivedFromSet, dep);
+ }
+ }
+
+ if (lastInChain.isJoin() && errorMessages.size() == 0) {
+ // Make sure that join is doubly specified. Why? Consider the
+ // case where the derived property is a member of an index or
+ // key. If the joined Storable class gets loaded first, it will
+ // not know that an index exists that it should keep
+ // up-to-date. With the double join, it can check to see if
+ // there are any foreign indexes. This check could probably be
+ // skipped if the derived property doesn't belong to an index
+ // or key, but consistent error checking behavior is desirable.
+
+ Class<? extends Storable> joined = lastInChain.getJoinedType();
+
+ doubly: {
+ for (StorableProperty<?> prop : examine(joined).getAllProperties().values()) {
+ if (prop.isJoin() &&
+ prop.getJoinedType() == lastInChain.getEnclosingType())
+ {
+ break doubly;
+ }
+ }
+
+ StringBuilder suggest = new StringBuilder();
+
+ suggest.append("@Join");
+
+ int count = lastInChain.getJoinElementCount();
+ boolean naturalJoin = true;
+ for (int i=0; i<count; i++) {
+ if (!lastInChain.getInternalJoinElement(i).getName().equals
+ (lastInChain.getExternalJoinElement(i).getName()))
+ {
+ naturalJoin = false;
+ break;
+ }
+ }
+
+ if (!naturalJoin) {
+ suggest.append("(internal=");
+ if (count > 1) {
+ suggest.append('{');
+ }
+ for (int i=0; i<count; i++) {
+ if (i > 0) {
+ suggest.append(", ");
+ }
+ suggest.append('"');
+ // This property's external is other's internal.
+ suggest.append(lastInChain.getExternalJoinElement(i).getName());
+ suggest.append('"');
+ }
+ if (count > 1) {
+ suggest.append('}');
+ }
+
+ suggest.append(", external=");
+ if (count > 1) {
+ suggest.append('{');
+ }
+ for (int i=0; i<count; i++) {
+ if (i > 0) {
+ suggest.append(", ");
+ }
+ suggest.append('"');
+ // This property's internal is other's external.
+ suggest.append(lastInChain.getInternalJoinElement(i).getName());
+ suggest.append('"');
+ }
+ if (count > 1) {
+ suggest.append('}');
+ }
+
+ suggest.append(")");
+ }
+
+ suggest.append(' ');
+
+ if (!joined.isInterface()) {
+ suggest.append("public abstract ");
+ }
+
+ if (lastInChain.isOneToOneJoin() || lastInChain.isQuery()) {
+ suggest.append(lastInChain.getEnclosingType().getName());
+ } else {
+ suggest.append("Query<");
+ suggest.append(lastInChain.getEnclosingType().getName());
+ suggest.append('>');
+ }
+
+ suggest.append(" getXxx() throws FetchException");
+
+ errorMessages.add
+ ("Derived-from property is a join, but it is not doubly joined: \"" +
+ getName() + "\" is derived from \"" + from +
+ "\". Consider defining a join property in " + joined + " as: " + suggest);
+ }
+ }
+
+ return true;
+ }
+
+ private boolean addToDerivedToSet(Set<ChainedProperty<?>> derivedToSet,
+ Set<Class<?>> examinedSet,
+ StorableInfo<?> info)
+ {
+ if (examinedSet.contains(info.getStorableType())) {
+ return false;
+ }
+
+ // Prevent infinite loop while following join paths.
+ examinedSet.add(info.getStorableType());
+
+ final int originalSize = derivedToSet.size();
+
+ for (StorableProperty<?> property : info.getAllProperties().values()) {
+ if (property.isDerived()) {
+ for (ChainedProperty<?> from : property.getDerivedFromProperties()) {
+ if (from.getLastProperty().equals(this)) {
+ ChainedProperty<?> path = ChainedProperty.get(property);
+ if (from.getChainCount() > 0) {
+ path = path.append(from.trim());
+ }
+ derivedToSet.add(path);
+ }
+ }
+ }
+ if (property.isJoin()) {
+ addToDerivedToSet(derivedToSet, examinedSet,
+ examine(property.getJoinedType()));
+ }
+ }
+
+ return derivedToSet.size() > originalSize;
+ }
}
private static final class JoinProperty<S extends Storable> extends SimpleProperty<S> {
@@ -1649,16 +2044,19 @@ public class StorableIntrospector {
private StorableProperty<S>[] mInternal;
private StorableProperty<?>[] mExternal;
+ private boolean mOneToOne;
+
JoinProperty(BeanProperty property, Class<S> enclosing,
boolean nullable,
String[] aliases, StorablePropertyConstraint[] constraints,
StorablePropertyAdapter adapter,
String sequence, boolean independent, boolean automatic,
+ Derived derived,
Class<? extends Storable> joinedType,
String[] internal, String[] external)
{
super(property, enclosing, nullable, false, false,
- aliases, constraints, adapter, false, sequence, independent, automatic);
+ aliases, constraints, adapter, false, sequence, independent, automatic, derived);
mJoinedType = joinedType;
int length = internal.length;
@@ -1674,6 +2072,10 @@ public class StorableIntrospector {
return true;
}
+ public boolean isOneToOneJoin() {
+ return mOneToOne;
+ }
+
public Class<? extends Storable> getJoinedType() {
return mJoinedType;
}
@@ -1706,32 +2108,39 @@ public class StorableIntrospector {
* Finishes the definition of this join property. Can only be called once.
*/
@SuppressWarnings("unchecked")
- void resolve(List<String> errorMessages, Map<String, StorableProperty<S>> properties) {
- StorableInfo<?> joinedInfo = examine(getJoinedType());
+ void resolveJoin(List<String> errorMessages, StorableInfo<S> info) {
+ StorableInfo<?> joinedInfo;
+ try {
+ joinedInfo = examine(getJoinedType());
- if (mInternalNames.length == 0) {
- // Since no join elements specified, perform a natural join.
- // If the joined type is a list, then the join elements are
- // defined by this enclosing type's primary keys. Otherwise,
- // they are defined by the joined type's primary keys.
+ if (mInternalNames.length == 0) {
+ // Since no join elements specified, perform a natural join.
+ // If the joined type is a list, then the join elements are
+ // defined by this enclosing type's primary keys. Otherwise,
+ // they are defined by the joined type's primary keys.
- Map<String, ? extends StorableProperty<?>> primaryKeys;
+ Map<String, ? extends StorableProperty<?>> primaryKeys;
- if (isQuery()) {
- primaryKeys = examine(getEnclosingType()).getPrimaryKeyProperties();
- } else {
- primaryKeys = joinedInfo.getPrimaryKeyProperties();
- }
+ if (isQuery()) {
+ primaryKeys = examine(getEnclosingType()).getPrimaryKeyProperties();
+ } else {
+ primaryKeys = joinedInfo.getPrimaryKeyProperties();
+ }
- mInternalNames = new String[primaryKeys.size()];
- mExternalNames = new String[primaryKeys.size()];
+ mInternalNames = new String[primaryKeys.size()];
+ mExternalNames = new String[primaryKeys.size()];
- int i = 0;
- for (String name : primaryKeys.keySet()) {
- mInternalNames[i] = name;
- mExternalNames[i] = name;
- i++;
+ int i = 0;
+ for (String name : primaryKeys.keySet()) {
+ mInternalNames[i] = name;
+ mExternalNames[i] = name;
+ i++;
+ }
}
+ } catch (MalformedTypeException e) {
+ mInternal = new StorableProperty[0];
+ mExternal = new StorableProperty[0];
+ throw e;
}
mInternal = new StorableProperty[mInternalNames.length];
@@ -1740,7 +2149,7 @@ public class StorableIntrospector {
// Verify that internal properties exist and are not themselves joins.
for (int i=0; i<mInternalNames.length; i++) {
String internalName = mInternalNames[i];
- StorableProperty property = properties.get(internalName);
+ StorableProperty property = info.getAllProperties().get(internalName);
if (property == null) {
errorMessages.add
("Cannot find internal join element: \"" +
@@ -1958,12 +2367,12 @@ public class StorableIntrospector {
// Test which keys of joined object are specified.
// Create a copy of all the primary keys of joined object.
- Set<StorableProperty> primaryKeys =
+ Set<StorableProperty> primaryKey =
new HashSet<StorableProperty>(joinedInfo.getPrimaryKeyProperties().values());
// Remove external properties from the primary key set.
for (int i=0; i<mInternal.length; i++) {
- primaryKeys.remove(getExternalJoinElement(i));
+ primaryKey.remove(getExternalJoinElement(i));
}
// Do similar test for alternate keys.
@@ -1996,7 +2405,7 @@ public class StorableIntrospector {
if (isQuery()) {
// Key of joined object must not be completely specified.
- if (primaryKeys.size() <= 0) {
+ if (primaryKey.size() <= 0) {
errorMessages.add
("Join property \"" + getName() +
"\" completely specifies primary key of joined object; " +
@@ -2019,7 +2428,7 @@ public class StorableIntrospector {
fullKeyCheck:
{
- if (primaryKeys.size() <= 0) {
+ if (primaryKey.size() <= 0) {
break fullKeyCheck;
}
@@ -2035,6 +2444,48 @@ public class StorableIntrospector {
"declaring the property type as Query<" +
getJoinedType().getName() + '>');
}
+
+ // Determine if one-to-one join. If internal properties
+ // completely specify any key, then it is one-to-one.
+
+ boolean oneToOne = false;
+
+ oneToOneCheck: {
+ Set<StorableProperty> internalPrimaryKey =
+ new HashSet<StorableProperty>(info.getPrimaryKeyProperties().values());
+
+ for (int i=0; i<mInternal.length; i++) {
+ internalPrimaryKey.remove(getInternalJoinElement(i));
+ if (internalPrimaryKey.size() == 0) {
+ oneToOne = true;
+ break oneToOneCheck;
+ }
+ }
+
+ altKeyScan:
+ for (int i=0; i<info.getAlternateKeyCount(); i++) {
+ Set<StorableProperty> altKey = new HashSet<StorableProperty>();
+
+ for (OrderedProperty op : info.getAlternateKey(i).getProperties()) {
+ ChainedProperty chained = op.getChainedProperty();
+ if (chained.getChainCount() > 0) {
+ // Funny alt key. Pretend it does not exist.
+ continue altKeyScan;
+ }
+ altKey.add(chained.getPrimeProperty());
+ }
+
+ for (int j=0; j<mInternal.length; j++) {
+ altKey.remove(getInternalJoinElement(j));
+ if (altKey.size() == 0) {
+ oneToOne = true;
+ break oneToOneCheck;
+ }
+ }
+ }
+ }
+
+ mOneToOne = oneToOne;
}
if (mutatorAllowed && getWriteMethod() == null) {
diff --git a/src/main/java/com/amazon/carbonado/info/StorableProperty.java b/src/main/java/com/amazon/carbonado/info/StorableProperty.java
index ce374ac..b3362c6 100644
--- a/src/main/java/com/amazon/carbonado/info/StorableProperty.java
+++ b/src/main/java/com/amazon/carbonado/info/StorableProperty.java
@@ -115,13 +115,20 @@ public interface StorableProperty<S extends Storable> extends Appender {
String[] getAliases();
/**
- * Returns true if this property is joined to another Storable.
+ * Returns true if this property is joined in any way to another Storable.
*
* @see com.amazon.carbonado.Join
*/
boolean isJoin();
/**
+ * Returns true if this property is one-to-one joined to another Storable.
+ *
+ * @see com.amazon.carbonado.Join
+ */
+ boolean isOneToOneJoin();
+
+ /**
* Returns the type of property this is joined to, or null if not joined.
*/
Class<? extends Storable> getJoinedType();
@@ -211,5 +218,32 @@ public interface StorableProperty<S extends Storable> extends Appender {
*/
boolean isIndependent();
+ /**
+ * Returns true if this property is derived.
+ *
+ * @see com.amazon.carbonado.Derived
+ */
+ boolean isDerived();
+
+ /**
+ * Returns a new array with all the derived-from properties, which is empty
+ * if this is not a derived property. Otherwise, the set is the transitive
+ * closure of all dependent properties. This set may include joins and
+ * other derived properties.
+ */
+ ChainedProperty<S>[] getDerivedFromProperties();
+
+ /**
+ * Returns a new array with all the properties which are derived from this
+ * one. The set is the transitive closure of all derived properties which
+ * depend on this one.
+ *
+ * <p>Each property in the set is represented as a chain, where the prime
+ * property is the actual dependent property, and the tail is the path to
+ * reach this property's enclosing type. If a derived property resides in
+ * the same enclosing type as this one, the chain count is zero.
+ */
+ ChainedProperty<?>[] getDerivedToProperties();
+
String toString();
}
diff --git a/src/main/java/com/amazon/carbonado/layout/Layout.java b/src/main/java/com/amazon/carbonado/layout/Layout.java
index 1c6b63d..ca50172 100644
--- a/src/main/java/com/amazon/carbonado/layout/Layout.java
+++ b/src/main/java/com/amazon/carbonado/layout/Layout.java
@@ -172,7 +172,7 @@ public class Layout {
List<LayoutProperty> list = new ArrayList<LayoutProperty>(properties.size());
int ordinal = 0;
for (StorableProperty<?> property : properties) {
- if (property.isJoin()) {
+ if (property.isDerived() || property.isJoin()) {
continue;
}
StoredLayoutProperty storedLayoutProperty = mLayoutFactory.mPropertyStorage.prepare();
diff --git a/src/main/java/com/amazon/carbonado/qe/AbstractQuery.java b/src/main/java/com/amazon/carbonado/qe/AbstractQuery.java
index 436c2c2..7639348 100644
--- a/src/main/java/com/amazon/carbonado/qe/AbstractQuery.java
+++ b/src/main/java/com/amazon/carbonado/qe/AbstractQuery.java
@@ -123,4 +123,10 @@ public abstract class AbstractQuery<S extends Storable> implements Query<S>, App
}
return b.toString();
}
+
+ @Override
+ public abstract int hashCode();
+
+ @Override
+ public abstract boolean equals(Object obj);
}
diff --git a/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java b/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java
index 161ad90..d9969c5 100644
--- a/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java
+++ b/src/main/java/com/amazon/carbonado/qe/EmptyQuery.java
@@ -273,6 +273,24 @@ public final class EmptyQuery<S extends Storable> extends AbstractQuery<S> {
return false;
}
+ @Override
+ public int hashCode() {
+ return mFactory.hashCode() * 31 + mOrdering.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof EmptyQuery) {
+ EmptyQuery<?> other = (EmptyQuery<?>) obj;
+ return mFactory.equals(other.mFactory)
+ && mOrdering.equals(other.mOrdering);
+ }
+ return false;
+ }
+
private IllegalStateException error() {
return new IllegalStateException("Query doesn't have any parameters");
}
diff --git a/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java b/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java
index cba6776..c0fa0af 100644
--- a/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java
+++ b/src/main/java/com/amazon/carbonado/raw/GenericEncodingStrategy.java
@@ -19,7 +19,9 @@
package com.amazon.carbonado.raw;
import java.lang.reflect.Method;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.Map;
import org.cojen.classfile.CodeAssembler;
@@ -397,14 +399,15 @@ public class GenericEncodingStrategy<S extends Storable> {
Map<String, ? extends StorableProperty<S>> map =
StorableIntrospector.examine(mType).getDataProperties();
- StorableProperty<S>[] properties = new StorableProperty[map.size()];
+ List<StorableProperty<S>> list = new ArrayList<StorableProperty<S>>(map.size());
- int ordinal = 0;
for (StorableProperty<S> property : map.values()) {
- properties[ordinal++] = property;
+ if (!property.isDerived()) {
+ list.add(property);
+ }
}
- return properties;
+ return list.toArray(new StorableProperty[list.size()]);
}
protected StorablePropertyInfo checkSupport(StorableProperty<S> property)
diff --git a/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java b/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java
index 43677f2..2532556 100644
--- a/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java
+++ b/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java
@@ -797,7 +797,7 @@ public class GenericStorableCodec<S extends Storable> implements StorableCodec<S
StorableIntrospector.examine(altStorable).getAllProperties();
for (StorableProperty prop : currentProps.values()) {
- if (prop.isJoin()) {
+ if (prop.isDerived() || prop.isJoin()) {
continue;
}
diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/DependentStorableFetcher.java b/src/main/java/com/amazon/carbonado/repo/indexed/DependentStorableFetcher.java
new file mode 100644
index 0000000..4c24c04
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/repo/indexed/DependentStorableFetcher.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2007 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * Licensed 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 com.amazon.carbonado.repo.indexed;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.cojen.util.BeanPropertyAccessor;
+
+import com.amazon.carbonado.Cursor;
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.Query;
+import com.amazon.carbonado.RepositoryException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.SupportException;
+import com.amazon.carbonado.Transaction;
+
+import com.amazon.carbonado.filter.Filter;
+import com.amazon.carbonado.filter.RelOp;
+
+import com.amazon.carbonado.info.ChainedProperty;
+import com.amazon.carbonado.info.StorableProperty;
+
+/**
+ * Fetches Storables that have indexed derived-to properties which depend on S.
+ *
+ * @author Brian S O'Neill
+ */
+class DependentStorableFetcher<S extends Storable, D extends Storable> {
+ private final IndexedRepository mRepository;
+ private final IndexEntryAccessor<D>[] mIndexEntryAccessors;
+ private final Query<D> mQuery;
+ private final String[] mJoinProperties;
+ private final BeanPropertyAccessor mPropertyAccessor;
+
+ /**
+ * @param derivedTo special chained property from StorableProperty.getDerivedToProperties
+ */
+ DependentStorableFetcher(IndexedRepository repository,
+ Class<S> sType, ChainedProperty<D> derivedTo)
+ throws RepositoryException
+ {
+ if (derivedTo.getChainCount() == 0) {
+ throw new IllegalArgumentException();
+ }
+ if (derivedTo.getLastProperty().getType() != sType) {
+ throw new IllegalArgumentException();
+ }
+ if (!derivedTo.getLastProperty().isJoin()) {
+ throw new IllegalArgumentException();
+ }
+
+ Class<D> dType = derivedTo.getPrimeProperty().getEnclosingType();
+
+ // Find the indexes that contain the prime derivedTo property.
+ List<IndexEntryAccessor<D>> accessorList = new ArrayList<IndexEntryAccessor<D>>();
+ for (IndexEntryAccessor<D> acc : repository.getIndexEntryAccessors(dType)) {
+ for (String indexPropName : acc.getPropertyNames()) {
+ if (indexPropName.equals(derivedTo.getPrimeProperty().getName())) {
+ accessorList.add(acc);
+ break;
+ }
+ }
+ }
+
+ if (accessorList.size() == 0) {
+ throw new SupportException
+ ("Unable to find index accessors for derived-to property: " + derivedTo +
+ ", enclosing type: " + dType);
+ }
+
+ // Build a query on D joined to S.
+
+ StorableProperty<S> join = (StorableProperty<S>) derivedTo.getLastProperty();
+
+ ChainedProperty<?> base;
+ if (derivedTo.getChainCount() <= 1) {
+ base = null;
+ } else {
+ base = derivedTo.tail().trim();
+ }
+
+ int joinElementCount = join.getJoinElementCount();
+ String[] joinProperties = new String[joinElementCount];
+
+ Filter<D> dFilter = Filter.getOpenFilter(dType);
+ for (int i=0; i<joinElementCount; i++) {
+ StorableProperty<S> element = join.getInternalJoinElement(i);
+ joinProperties[i] = element.getName();
+ if (base == null) {
+ dFilter = dFilter.and(element.getName(), RelOp.EQ);
+ } else {
+ dFilter = dFilter.and(base.append(element).toString(), RelOp.EQ);
+ }
+ }
+
+ mRepository = repository;
+ mIndexEntryAccessors = accessorList.toArray(new IndexEntryAccessor[accessorList.size()]);
+ mQuery = repository.storageFor(dType).query(dFilter);
+ mJoinProperties = joinProperties;
+ mPropertyAccessor = BeanPropertyAccessor.forClass(sType);
+ }
+
+ public Transaction enterTransaction() {
+ return mRepository.enterTransaction();
+ }
+
+ public Cursor<D> fetchDependenentStorables(S storable) throws FetchException {
+ Query<D> query = mQuery;
+ for (String property : mJoinProperties) {
+ query = query.with(mPropertyAccessor.getPropertyValue(storable, property));
+ }
+ return query.fetch();
+ }
+
+ /**
+ * @return amount added to list
+ */
+ public int createIndexEntries(D master, List<Storable> indexEntries) {
+ IndexEntryAccessor[] accessors = mIndexEntryAccessors;
+ int length = accessors.length;
+ for (int i=0; i<length; i++) {
+ IndexEntryAccessor accessor = accessors[i];
+ Storable indexEntry = accessor.getIndexEntryStorage().prepare();
+ accessor.copyFromMaster(indexEntry, master);
+ indexEntries.add(indexEntry);
+ }
+ return length;
+ }
+
+ @Override
+ public int hashCode() {
+ return mQuery.getFilter().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DependentStorableFetcher) {
+ DependentStorableFetcher other = (DependentStorableFetcher) obj;
+ return mQuery.getFilter().equals(other.mQuery.getFilter())
+ && Arrays.equals(mJoinProperties, other.mJoinProperties)
+ && Arrays.equals(mIndexEntryAccessors, other.mIndexEntryAccessors);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "DependentStorableFetcher: {indexes=" + Arrays.toString(mIndexEntryAccessors) +
+ ", query=" + mQuery +
+ ", join properties=" + Arrays.toString(mJoinProperties) + '}';
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/DerivedIndexesTrigger.java b/src/main/java/com/amazon/carbonado/repo/indexed/DerivedIndexesTrigger.java
new file mode 100644
index 0000000..0a99fdd
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/repo/indexed/DerivedIndexesTrigger.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2007 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * Licensed 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 com.amazon.carbonado.repo.indexed;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.amazon.carbonado.Cursor;
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.PersistException;
+import com.amazon.carbonado.RepositoryException;
+import com.amazon.carbonado.Storable;
+import com.amazon.carbonado.Transaction;
+import com.amazon.carbonado.Trigger;
+
+import com.amazon.carbonado.info.ChainedProperty;
+
+/**
+ * Handles index updates for derived-to properties.
+ *
+ * @author Brian S O'Neill
+ */
+class DerivedIndexesTrigger<S extends Storable, D extends Storable> extends Trigger<S> {
+ private final DependentStorableFetcher<S, D> mFetcher;
+
+ /**
+ * @param derivedTo special chained property from StorableProperty.getDerivedToProperties
+ */
+ DerivedIndexesTrigger(IndexedRepository repository,
+ Class<S> sType, ChainedProperty<D> derivedTo)
+ throws RepositoryException
+ {
+ this(new DependentStorableFetcher(repository, sType, derivedTo));
+ }
+
+ DerivedIndexesTrigger(DependentStorableFetcher<S, D> fetcher) {
+ mFetcher = fetcher;
+ }
+
+ @Override
+ public Object beforeInsert(S storable) throws PersistException {
+ return createDependentIndexEntries(storable);
+ }
+
+ @Override
+ public void afterInsert(S storable, Object state) throws PersistException {
+ updateValues(storable, state);
+ }
+
+ @Override
+ public Object beforeUpdate(S storable) throws PersistException {
+ return createDependentIndexEntries(storable);
+ }
+
+ @Override
+ public void afterUpdate(S storable, Object state) throws PersistException {
+ updateValues(storable, state);
+ }
+
+ @Override
+ public Object beforeDelete(S storable) throws PersistException {
+ try {
+ if (storable.copy().tryLoad()) {
+ return createDependentIndexEntries(storable);
+ }
+ } catch (FetchException e) {
+ throw e.toPersistException();
+ }
+ return null;
+ }
+
+ @Override
+ public void afterDelete(S storable, Object state) throws PersistException {
+ updateValues(storable, state);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DerivedIndexesTrigger) {
+ DerivedIndexesTrigger other = (DerivedIndexesTrigger) obj;
+ return mFetcher.equals(other.mFetcher);
+ }
+ return false;
+ }
+
+ private List<Storable> createDependentIndexEntries(S storable) throws PersistException {
+ List<Storable> dependentIndexEntries = new ArrayList<Storable>();
+ createDependentIndexEntries(storable, dependentIndexEntries);
+ return dependentIndexEntries;
+ }
+
+ private void createDependentIndexEntries(S storable, List<Storable> dependentIndexEntries)
+ throws PersistException
+ {
+ try {
+ Transaction txn = mFetcher.enterTransaction();
+ try {
+ // Make sure write lock is acquired when reading dependencies
+ // since they might be updated later. Locks are held after this
+ // transaction exits since it is nested in the trigger's transaction.
+ txn.setForUpdate(true);
+
+ Cursor<D> dependencies = mFetcher.fetchDependenentStorables(storable);
+ try {
+ while (dependencies.hasNext()) {
+ mFetcher.createIndexEntries(dependencies.next(), dependentIndexEntries);
+ }
+ } finally {
+ dependencies.close();
+ }
+ } finally {
+ txn.exit();
+ }
+ } catch (FetchException e) {
+ throw e.toPersistException();
+ }
+ }
+
+ private void updateValues(S storable, Object state) throws PersistException {
+ if (state == null) {
+ return;
+ }
+
+ List<Storable> oldIndexEntries = (List<Storable>) state;
+ int size = oldIndexEntries.size();
+
+ List<Storable> newIndexEntries = new ArrayList<Storable>(size);
+ createDependentIndexEntries(storable, newIndexEntries);
+
+ if (size != newIndexEntries.size()) {
+ // This is not expected to happen.
+ throw new PersistException("Amount of affected dependent indexes changed: " +
+ size + " != " + newIndexEntries.size());
+ }
+
+ for (int i=0; i<size; i++) {
+ Storable oldIndexEntry = oldIndexEntries.get(i);
+ Storable newIndexEntry = newIndexEntries.get(i);
+ if (!oldIndexEntry.equalProperties(newIndexEntry)) {
+ oldIndexEntry.delete();
+ newIndexEntry.tryInsert();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexAnalysis.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexAnalysis.java
new file mode 100644
index 0000000..d0e3405
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexAnalysis.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2007 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * Licensed 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 com.amazon.carbonado.repo.indexed;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.amazon.carbonado.Storable;
+
+import com.amazon.carbonado.filter.Filter;
+import com.amazon.carbonado.filter.RelOp;
+
+import com.amazon.carbonado.info.ChainedProperty;
+import com.amazon.carbonado.info.Direction;
+import com.amazon.carbonado.info.StorableIndex;
+import com.amazon.carbonado.info.StorableInfo;
+import com.amazon.carbonado.info.StorableIntrospector;
+import com.amazon.carbonado.info.StorableProperty;
+
+import com.amazon.carbonado.qe.FilteringScore;
+
+import com.amazon.carbonado.spi.StorableIndexSet;
+
+/**
+ * Collection of static methods which perform index analysis.
+ *
+ * @author Brian S O'Neill
+ */
+class IndexAnalysis {
+ static <S extends Storable> StorableIndexSet<S> gatherDesiredIndexes(StorableInfo<S> info) {
+ StorableIndexSet<S> indexSet = new StorableIndexSet<S>();
+ indexSet.addIndexes(info);
+ indexSet.addAlternateKeys(info);
+
+ // If any join properties are used by indexed derived properties, make
+ // sure join internal properties are indexed.
+
+ for (StorableProperty<S> property : info.getAllProperties().values()) {
+ if (!isJoinAndUsedByIndexedDerivedProperty(property)) {
+ continue;
+ }
+
+ // Internal properties of join need to be indexed. Check if a
+ // suitable index exists before defining a new one.
+
+ Filter<S> filter = Filter.getOpenFilter(info.getStorableType());
+ for (int i=property.getJoinElementCount(); --i>=0; ) {
+ filter = filter.and(property.getInternalJoinElement(i).getName(), RelOp.EQ);
+ }
+
+ for (int i=info.getIndexCount(); --i>=0; ) {
+ FilteringScore<S> score = FilteringScore.evaluate(info.getIndex(i), filter);
+ if (score.getIdentityCount() == property.getJoinElementCount()) {
+ // Suitable index already exists.
+ continue;
+ }
+ }
+
+ Direction[] directions = new Direction[property.getJoinElementCount()];
+ Arrays.fill(directions, Direction.UNSPECIFIED);
+
+ StorableIndex<S> index =
+ new StorableIndex<S>(property.getInternalJoinElements(), directions);
+
+ indexSet.add(index);
+ }
+
+ return indexSet;
+ }
+
+ static boolean isUsedByIndex(StorableProperty<?> property) {
+ StorableInfo<?> info = StorableIntrospector.examine(property.getEnclosingType());
+ for (int i=info.getIndexCount(); --i>=0; ) {
+ StorableIndex<?> index = info.getIndex(i);
+ int propertyCount = index.getPropertyCount();
+ for (int j=0; j<propertyCount; j++) {
+ if (index.getProperty(j).equals(property)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ static boolean isJoinAndUsedByIndexedDerivedProperty(StorableProperty<?> property) {
+ if (property.isJoin()) {
+ for (ChainedProperty<?> derivedTo : property.getDerivedToProperties()) {
+ if (isUsedByIndex(derivedTo.getPrimeProperty())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns derived-to properties in external storables that are used by indexes.
+ *
+ * @return null if none
+ */
+ static Set<ChainedProperty<?>> gatherDerivedToDependencies(StorableInfo<?> info) {
+ Set<ChainedProperty<?>> set = null;
+ for (StorableProperty<?> property : info.getAllProperties().values()) {
+ for (ChainedProperty<?> derivedTo : property.getDerivedToProperties()) {
+ if (derivedTo.getChainCount() > 0 && isUsedByIndex(derivedTo.getPrimeProperty())) {
+ if (set == null) {
+ set = new HashSet<ChainedProperty<?>>();
+ }
+ set.add(derivedTo);
+ }
+ }
+ }
+ return set;
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java
index 0c1e6b8..5f27aa8 100644
--- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java
+++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java
@@ -59,7 +59,7 @@ public interface IndexEntryAccessor<S extends Storable> extends IndexInfo {
/**
* Returns true if the properties of the given index entry match those
* contained in the master, exluding any version property. This will always
- * return true after a call to setAllProperties.
+ * return true after a call to copyFromMaster.
*
* @param indexEntry index entry whose properties will be tested
* @param master source of property values
diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java
index 097185a..6bea049 100644
--- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java
+++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java
@@ -86,7 +86,7 @@ class IndexedRepository implements Repository,
if (Unindexed.class.isAssignableFrom(type)) {
// Verify no indexes.
- int indexCount = IndexedStorage
+ int indexCount = IndexAnalysis
.gatherDesiredIndexes(StorableIntrospector.examine(type)).size();
if (indexCount > 0) {
throw new MalformedTypeException
@@ -152,7 +152,12 @@ class IndexedRepository implements Repository,
getIndexEntryAccessors(Class<S> storableType)
throws RepositoryException
{
- return ((IndexedStorage<S>) storageFor(storableType)).getIndexEntryAccessors();
+ Storage<S> storage = storageFor(storableType);
+ if (storage instanceof IndexedStorage) {
+ return ((IndexedStorage<S>) storage).getIndexEntryAccessors();
+ } else {
+ return new IndexEntryAccessor[0];
+ }
}
public String[] getUserStorableTypeNames() throws RepositoryException {
diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java
index fe0cfe8..28a9d35 100644
--- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java
+++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java
@@ -23,6 +23,7 @@ import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -32,9 +33,11 @@ import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.IsolationLevel;
import com.amazon.carbonado.PersistException;
import com.amazon.carbonado.Query;
+import com.amazon.carbonado.Repository;
import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storable;
import com.amazon.carbonado.Storage;
+import com.amazon.carbonado.SupportException;
import com.amazon.carbonado.Transaction;
import com.amazon.carbonado.Trigger;
import com.amazon.carbonado.capability.IndexInfo;
@@ -45,6 +48,7 @@ import com.amazon.carbonado.cursor.MergeSortBuffer;
import com.amazon.carbonado.filter.Filter;
+import com.amazon.carbonado.info.ChainedProperty;
import com.amazon.carbonado.info.Direction;
import com.amazon.carbonado.info.StorableInfo;
import com.amazon.carbonado.info.StorableIntrospector;
@@ -53,6 +57,7 @@ import com.amazon.carbonado.info.StorableIndex;
import com.amazon.carbonado.cursor.SortBuffer;
import com.amazon.carbonado.qe.BoundaryType;
+import com.amazon.carbonado.qe.FilteringScore;
import com.amazon.carbonado.qe.QueryEngine;
import com.amazon.carbonado.qe.QueryExecutorFactory;
import com.amazon.carbonado.qe.StorageAccess;
@@ -69,13 +74,6 @@ import static com.amazon.carbonado.repo.indexed.ManagedIndex.*;
* @author Brian S O'Neill
*/
class IndexedStorage<S extends Storable> implements Storage<S>, StorageAccess<S> {
- static <S extends Storable> StorableIndexSet<S> gatherDesiredIndexes(StorableInfo<S> info) {
- StorableIndexSet<S> indexSet = new StorableIndexSet<S>();
- indexSet.addIndexes(info);
- indexSet.addAlternateKeys(info);
- return indexSet;
- }
-
final IndexedRepository mRepository;
final Storage<S> mMasterStorage;
@@ -102,7 +100,7 @@ class IndexedStorage<S extends Storable> implements Storage<S>, StorageAccess<S>
// The set of indexes that the Storable defines, reduced.
final StorableIndexSet<S> desiredIndexSet;
{
- desiredIndexSet = gatherDesiredIndexes(info);
+ desiredIndexSet = IndexAnalysis.gatherDesiredIndexes(info);
desiredIndexSet.reduce(Direction.ASCENDING);
}
@@ -299,6 +297,17 @@ class IndexedStorage<S extends Storable> implements Storage<S>, StorageAccess<S>
mQueryableIndexSet = queryableIndexSet;
mQueryEngine = new QueryEngine<S>(masterStorage.getStorableType(), repository);
+
+ // Install triggers to manage derived properties in external Storables.
+
+ Set<ChainedProperty<?>> derivedToDependencies =
+ IndexAnalysis.gatherDerivedToDependencies(info);
+
+ if (derivedToDependencies != null) {
+ for (ChainedProperty<?> derivedTo : derivedToDependencies) {
+ addTrigger(new DerivedIndexesTrigger(repository, getStorableType(), derivedTo));
+ }
+ }
}
public Class<S> getStorableType() {
diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/ManagedIndex.java b/src/main/java/com/amazon/carbonado/repo/indexed/ManagedIndex.java
index 2e4cea3..b1e76cc 100644
--- a/src/main/java/com/amazon/carbonado/repo/indexed/ManagedIndex.java
+++ b/src/main/java/com/amazon/carbonado/repo/indexed/ManagedIndex.java
@@ -18,6 +18,8 @@
package com.amazon.carbonado.repo.indexed;
+import java.lang.reflect.UndeclaredThrowableException;
+
import java.util.Comparator;
import org.apache.commons.logging.Log;
@@ -451,10 +453,23 @@ class ManagedIndex<S extends Storable> implements IndexEntryAccessor<S> {
}
}
- private Storable makeIndexEntry(S userStorable) {
- Storable indexEntry = mIndexEntryStorage.prepare();
- mGenerator.copyFromMaster(indexEntry, userStorable);
- return indexEntry;
+ private Storable makeIndexEntry(S userStorable) throws PersistException {
+ try {
+ Storable indexEntry = mIndexEntryStorage.prepare();
+ mGenerator.copyFromMaster(indexEntry, userStorable);
+ return indexEntry;
+ } catch (UndeclaredThrowableException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof PersistException) {
+ throw (PersistException) cause;
+ }
+ throw new PersistException(cause);
+ } catch (Exception e) {
+ if (e instanceof PersistException) {
+ throw (PersistException) e;
+ }
+ throw new PersistException(e);
+ }
}
/** Assumes caller is in a transaction */
diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java
index 2528cbf..57f7bbc 100644
--- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java
+++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java
@@ -223,7 +223,7 @@ class JDBCStorableGenerator<S extends Storable> {
// UnsupportedOperationException.
{
for (JDBCStorableProperty<S> property : mAllProperties.values()) {
- if (property.isJoin() || property.isSupported()) {
+ if (property.isDerived() || property.isJoin() || property.isSupported()) {
continue;
}
String message = "Independent property \"" + property.getName() +
@@ -444,7 +444,7 @@ class JDBCStorableGenerator<S extends Storable> {
sb.append(" ( ");
int ordinal = 0;
- for (JDBCStorableProperty<?> property : mInfo.getAllProperties().values()) {
+ for (JDBCStorableProperty<?> property : mAllProperties.values()) {
if (!property.isSelectable()) {
continue;
}
@@ -470,10 +470,12 @@ class JDBCStorableGenerator<S extends Storable> {
}
boolean useStaticInsertStatement = true;
- for (JDBCStorableProperty<?> property : mInfo.getAllProperties().values()) {
- if (property.isVersion() || property.isAutomatic()) {
- useStaticInsertStatement = false;
- break;
+ for (JDBCStorableProperty<?> property : mAllProperties.values()) {
+ if (!property.isDerived()) {
+ if (property.isVersion() || property.isAutomatic()) {
+ useStaticInsertStatement = false;
+ break;
+ }
}
}
@@ -489,7 +491,7 @@ class JDBCStorableGenerator<S extends Storable> {
insertCountVar = b.createLocalVariable(null, TypeDesc.INT);
int initialCount = 0;
- for (JDBCStorableProperty<?> property : mInfo.getAllProperties().values()) {
+ for (JDBCStorableProperty<?> property : mAllProperties.values()) {
if (!property.isSelectable()) {
continue;
}
@@ -517,7 +519,7 @@ class JDBCStorableGenerator<S extends Storable> {
CodeBuilderUtil.callStringBuilderAppendString(b);
int propNumber = -1;
- for (JDBCStorableProperty<?> property : mInfo.getAllProperties().values()) {
+ for (JDBCStorableProperty<?> property : mAllProperties.values()) {
propNumber++;
if (!property.isSelectable()) {
continue;
diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java
index aa17f11..5ff713d 100644
--- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java
+++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java
@@ -55,6 +55,7 @@ import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storable;
import com.amazon.carbonado.SupportException;
+import com.amazon.carbonado.info.ChainedProperty;
import com.amazon.carbonado.info.OrderedProperty;
import com.amazon.carbonado.info.StorableInfo;
import com.amazon.carbonado.info.StorableIntrospector;
@@ -277,7 +278,7 @@ public class JDBCStorableIntrospector extends StorableIntrospector {
ArrayList<String> errorMessages = new ArrayList<String>();
for (StorableProperty<S> mainProperty : mainProperties.values()) {
- if (mainProperty.isJoin() || tableName == null) {
+ if (mainProperty.isDerived() || mainProperty.isJoin() || tableName == null) {
jProperties.put(mainProperty.getName(), new JProperty<S>(mainProperty));
continue;
}
@@ -1271,6 +1272,10 @@ public class JDBCStorableIntrospector extends StorableIntrospector {
return mMainProperty.isJoin();
}
+ public boolean isOneToOneJoin() {
+ return mMainProperty.isOneToOneJoin();
+ }
+
public Class<? extends Storable> getJoinedType() {
return mMainProperty.getJoinedType();
}
@@ -1315,6 +1320,18 @@ public class JDBCStorableIntrospector extends StorableIntrospector {
return mMainProperty.isIndependent();
}
+ public boolean isDerived() {
+ return mMainProperty.isDerived();
+ }
+
+ public ChainedProperty<S>[] getDerivedFromProperties() {
+ return mMainProperty.getDerivedFromProperties();
+ }
+
+ public ChainedProperty<?>[] getDerivedToProperties() {
+ return mMainProperty.getDerivedToProperties();
+ }
+
public boolean isSupported() {
if (isJoin()) {
// TODO: Check if joined type is supported
@@ -1325,7 +1342,7 @@ public class JDBCStorableIntrospector extends StorableIntrospector {
}
public boolean isSelectable() {
- return mColumnName != null && !isJoin();
+ return mColumnName != null && !isJoin() && !isDerived();
}
public boolean isAutoIncrement() {
diff --git a/src/main/java/com/amazon/carbonado/spi/WrappedQuery.java b/src/main/java/com/amazon/carbonado/spi/WrappedQuery.java
index ec3ae34..ba3c013 100644
--- a/src/main/java/com/amazon/carbonado/spi/WrappedQuery.java
+++ b/src/main/java/com/amazon/carbonado/spi/WrappedQuery.java
@@ -199,6 +199,23 @@ public abstract class WrappedQuery<S extends Storable> implements Query<S> {
return mQuery.toString();
}
+ @Override
+ public int hashCode() {
+ return mQuery.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof WrappedQuery) {
+ WrappedQuery<?> other = (WrappedQuery<?>) obj;
+ return mQuery.equals(other.mQuery);
+ }
+ return false;
+ }
+
protected Query<S> getWrappedQuery() {
return mQuery;
}
diff --git a/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java b/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java
index 2c9c0dc..0832aa9 100644
--- a/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java
+++ b/src/main/java/com/amazon/carbonado/synthetic/SyntheticStorableReferenceBuilder.java
@@ -493,23 +493,22 @@ public class SyntheticStorableReferenceBuilder<S extends Storable>
continue;
}
- if (prop.getReadMethod() == null) {
- throw new SupportException
- ("Property does not have a public accessor method: " + prop);
- }
- if (prop.getWriteMethod() == null) {
- throw new SupportException
- ("Property does not have a public mutator method: " + prop);
- }
-
TypeDesc propType = TypeDesc.forClass(prop.getType());
if (toMasterPk) {
+ if (prop.getWriteMethod() == null) {
+ throw new SupportException
+ ("Property does not have a public mutator method: " + prop);
+ }
b.loadLocal(b.getParameter(0));
b.loadThis();
b.invokeVirtual(prop.getReadMethodName(), propType, null);
b.invoke(prop.getWriteMethod());
} else if (methodName.equals(mCopyFromMasterMethodName)) {
+ if (prop.getReadMethod() == null) {
+ throw new SupportException
+ ("Property does not have a public accessor method: " + prop);
+ }
b.loadThis();
b.loadLocal(b.getParameter(0));
b.invoke(prop.getReadMethod());