diff --git a/src/main/java/com/avaje/ebean/annotation/ColumnHstore.java b/src/main/java/com/avaje/ebean/annotation/ColumnHstore.java new file mode 100644 index 000000000..0aa14360a --- /dev/null +++ b/src/main/java/com/avaje/ebean/annotation/ColumnHstore.java @@ -0,0 +1,15 @@ +package com.avaje.ebean.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ColumnHstore { + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/PostgresPlatform.java b/src/main/java/com/avaje/ebean/config/dbplatform/PostgresPlatform.java index 375fca5d5..5639894a9 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/PostgresPlatform.java +++ b/src/main/java/com/avaje/ebean/config/dbplatform/PostgresPlatform.java @@ -4,6 +4,7 @@ import com.avaje.ebean.BackgroundExecutor; import com.avaje.ebean.config.GlobalProperties; import javax.sql.DataSource; + import java.sql.Types; /** @@ -14,6 +15,11 @@ import java.sql.Types; */ public class PostgresPlatform extends DatabasePlatform { + /** + * Unique jdbc type id defined for hstore type. + */ + public static final int TYPE_HSTORE = 4001; + public PostgresPlatform() { super(); this.name = "postgres"; @@ -40,6 +46,8 @@ public class PostgresPlatform extends DatabasePlatform { this.openQuote = "\""; this.closeQuote = "\""; + dbTypeMap.put(TYPE_HSTORE, new DbType("hstore")); + dbTypeMap.put(Types.INTEGER, new DbType("integer", false)); dbTypeMap.put(Types.DOUBLE, new DbType("float")); dbTypeMap.put(Types.TINYINT, new DbType("smallint")); diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java index 7253ded64..66b289a95 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java @@ -122,6 +122,10 @@ public final class PersistRequestBean extends PersistRequest implements BeanP this.bean = bean; this.parentBean = parentBean; this.controller = beanDescriptor.getPersistController(); + if (Type.UPDATE == type) { + // Mark Mutable scalar properties (like Hstore) as dirty where necessary + beanDescriptor.checkMutableProperties(intercept); + } this.concurrencyMode = beanDescriptor.getConcurrencyMode(intercept); this.dirty = intercept.isDirty(); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index a69eff632..28b2b621c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -220,6 +220,12 @@ public class BeanDescriptor implements MetaBeanInfo { */ private final BeanProperty[] propertiesLocal; + /** + * Scalar mutable properties (need to dirty check on update). + */ + private final BeanProperty[] propertiesMutable; + + private final BeanPropertyAssocOne unidirectional; /** @@ -391,6 +397,7 @@ public class BeanDescriptor implements MetaBeanInfo { this.propertiesBaseCompound = listHelper.getBaseCompound(); this.propertiesEmbedded = listHelper.getEmbedded(); this.propertiesLocal = listHelper.getLocal(); + this.propertiesMutable = listHelper.getMutable(); this.unidirectional = listHelper.getUnidirectional(); this.propertiesOne = listHelper.getOnes(); this.propertiesOneExported = listHelper.getOneExported(); @@ -1918,7 +1925,27 @@ public class BeanDescriptor implements MetaBeanInfo { return false; } + /** + * Check for mutable scalar types and mark as dirty if necessary. + */ + public void checkMutableProperties(EntityBeanIntercept ebi) { + for (int i = 0; i < propertiesMutable.length; i++) { + BeanProperty beanProperty = propertiesMutable[i]; + if (ebi.isDirtyProperty(beanProperty.getPropertyIndex())) { + // already marked as dirty + } else if (ebi.isLoadedProperty(beanProperty.getPropertyIndex())) { + Object value = beanProperty.getValue(ebi.getOwner()); + if (value == null || beanProperty.isDirtyValue(value)) { + // mutable scalar value which is considered dirty so mark + // it as such so that it is included in an update + ebi.markPropertyAsChanged(beanProperty.getPropertyIndex()); + } + } + } + } + public ConcurrencyMode getConcurrencyMode(EntityBeanIntercept ebi) { + if (!hasVersionProperty(ebi)) { return ConcurrencyMode.NONE; } else { diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java index b48bb7767..21eac7132 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java @@ -477,6 +477,13 @@ public class BeanProperty implements ElPropertyValue { public boolean isDiscriminator() { return discriminator; } + + /** + * Return true if the underlying type is mutable. + */ + public boolean isMutableScalarType() { + return scalarType.isMutable(); + } public void copyProperty(EntityBean sourceBean, EntityBean destBean) { Object value = getValue(sourceBean); @@ -868,6 +875,14 @@ public class BeanProperty implements ElPropertyValue { return descriptor.getFullName() + "." + name; } + /** + * Return true if the mutable value is considered dirty. + * This is only used for 'mutable' scalar types like hstore etc. + */ + public boolean isDirtyValue(Object value) { + return scalarType.isDirty(value); + } + /** * Return the scalarType. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java index 6c21ce909..95d0e054f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java @@ -37,6 +37,8 @@ public class DeployBeanPropertyLists { private final ArrayList local = new ArrayList(); + private final ArrayList mutable = new ArrayList(); + private final ArrayList manys = new ArrayList(); private final ArrayList nonManys = new ArrayList(); @@ -135,6 +137,10 @@ public class DeployBeanPropertyLists { nonTransients.add(prop); } + if (prop.isMutableScalarType()) { + mutable.add(prop); + } + if (desc.getInheritInfo() != null && prop.isLocal()) { local.add(prop); } @@ -197,7 +203,7 @@ public class DeployBeanPropertyLists { public BeanProperty getId() { if (ids.size() > 1) { - String msg = "Ebean does not support multiple @Id properties. You need to convert to using an @EmbeddedId." + String msg = "Issue with bean "+desc+". Ebean does not support multiple @Id properties. You need to convert to using an @EmbeddedId." +" Please email the ebean google group if you need further clarification."; throw new IllegalStateException(msg); } @@ -223,6 +229,10 @@ public class DeployBeanPropertyLists { return (BeanProperty[]) local.toArray(new BeanProperty[local.size()]); } + public BeanProperty[] getMutable() { + return (BeanProperty[]) mutable.toArray(new BeanProperty[mutable.size()]); + } + public BeanPropertyAssocOne[] getEmbedded() { return (BeanPropertyAssocOne[]) embedded.toArray(new BeanPropertyAssocOne[embedded.size()]); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java index 287c33f66..ebf869e0d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java @@ -10,6 +10,7 @@ import java.util.Iterator; import javax.persistence.PersistenceException; import javax.persistence.Transient; +import com.avaje.ebean.annotation.ColumnHstore; import com.avaje.ebeaninternal.server.core.Message; import com.avaje.ebeaninternal.server.deploy.DetermineManyType; import com.avaje.ebeaninternal.server.deploy.ManyType; @@ -21,8 +22,10 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyCompound; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertySimpleCollection; import com.avaje.ebeaninternal.server.type.CtCompoundType; import com.avaje.ebeaninternal.server.type.ScalarType; +import com.avaje.ebeaninternal.server.type.ScalarTypePostgresHstore; import com.avaje.ebeaninternal.server.type.TypeManager; import com.avaje.ebeaninternal.server.type.reflect.CheckImmutableResponse; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -276,6 +279,16 @@ public class DeployCreateProperties { Class propertyType = field.getType(); Class innerType = propertyType; + String specialTypeKey = getSpecialScalarType(field); + if (specialTypeKey != null) { + ScalarType scalarType = typeManager.getScalarTypeFromKey(specialTypeKey); + if (scalarType == null) { + logger.error("Could not find ScalarType to match key ["+specialTypeKey+"]"); + } else { + return new DeployBeanProperty(desc, propertyType, scalarType, null); + } + } + // check for Collection type (list, set or map) ManyType manyType = determineManyType.getManyType(propertyType); @@ -336,6 +349,15 @@ public class DeployCreateProperties { } } + private String getSpecialScalarType(Field field) { + + if (field.getAnnotation(ColumnHstore.class) != null) { + return ScalarTypePostgresHstore.KEY; + }; + + return null; + } + private boolean isTransientField(Field field) { Transient t = field.getAnnotation(Transient.class); diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java index cdab98b37..d4a4d141e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java @@ -45,6 +45,7 @@ import com.avaje.ebeaninternal.server.type.reflect.KnownImmutable; import com.avaje.ebeaninternal.server.type.reflect.ReflectionBasedCompoundType; import com.avaje.ebeaninternal.server.type.reflect.ReflectionBasedCompoundTypeProperty; import com.avaje.ebeaninternal.server.type.reflect.ReflectionBasedTypeBuilder; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,6 +65,8 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable { private final ConcurrentHashMap> nativeMap; + private final ConcurrentHashMap> customTypeMap; + private final DefaultTypeFactory extraTypeFactory; private final ScalarType charType = new ScalarTypeChar(); @@ -136,6 +139,9 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable { this.compoundTypeMap = new ConcurrentHashMap, CtCompoundType>(); this.typeMap = new ConcurrentHashMap, ScalarType>(); this.nativeMap = new ConcurrentHashMap>(); + this.customTypeMap = new ConcurrentHashMap>(); + + this.customTypeMap.put(ScalarTypePostgresHstore.KEY, new ScalarTypePostgresHstore()); this.extraTypeFactory = new DefaultTypeFactory(config); @@ -149,6 +155,14 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable { } } + /** + * Lookup a special or custom scalar type by key. + */ + @Override + public ScalarType getScalarTypeFromKey(String specialTypeKey) { + return customTypeMap.get(specialTypeKey); + } + public boolean isKnownImmutable(Class cls) { if (cls == null) { @@ -613,7 +627,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable { * plus some other common types such as java.util.Date and java.util.Calendar. */ protected void initialiseStandard(int platformClobType, int platformBlobType, boolean binaryUUID) { - + ScalarType utilDateType = extraTypeFactory.createUtilDate(); typeMap.put(java.util.Date.class, utilDateType); diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareCollection.java b/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareCollection.java new file mode 100644 index 000000000..006ddb39e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareCollection.java @@ -0,0 +1,121 @@ +package com.avaje.ebeaninternal.server.type; + +import java.util.Collection; +import java.util.Iterator; + +/** + * Wraps a collection for the purposes of detecting modifications. + */ +public class ModifyAwareCollection implements Collection { + + protected final ModifyAwareOwner owner; + + protected final Collection c; + + /** + * Create with an Owner and the underlying collection this wraps. + *

+ * The owner is notified of the additions and removals. + *

+ */ + public ModifyAwareCollection(ModifyAwareOwner owner, Collection c) { + this.owner = owner; + this.c = c; + } + + public String toString() { + return c.toString(); + } + + public boolean add(E o) { + if (c.add(o)) { + owner.markAsModified(); + return true; + } + return false; + } + + public boolean addAll(Collection collection) { + boolean changed = false; + Iterator it = collection.iterator(); + while (it.hasNext()) { + E o = it.next(); + if (c.add(o)) { + owner.markAsModified(); + changed = true; + } + } + return changed; + } + + public void clear() { + if (!c.isEmpty()) { + owner.markAsModified(); + } + c.clear(); + } + + public boolean contains(Object o) { + return c.contains(o); + } + + public boolean containsAll(Collection collection) { + return c.containsAll(collection); + } + + public boolean isEmpty() { + return c.isEmpty(); + } + + public Iterator iterator() { + return new ModifyAwareIterator(owner, c.iterator()); + } + + public boolean remove(Object o) { + if (c.remove(o)) { + owner.markAsModified(); + return true; + } + return false; + } + + public boolean removeAll(Collection collection) { + boolean changed = false; + Iterator it = collection.iterator(); + while (it.hasNext()) { + Object o = (Object) it.next(); + if (c.remove(o)) { + owner.markAsModified(); + changed = true; + } + } + return changed; + } + + public boolean retainAll(Collection collection) { + boolean changed = false; + Iterator it = c.iterator(); + while (it.hasNext()) { + Object o = (Object) it.next(); + if (!collection.contains(o)) { + it.remove(); + owner.markAsModified(); + changed = true; + } + } + return changed; + } + + public int size() { + return c.size(); + } + + public Object[] toArray() { + return c.toArray(); + } + + public T[] toArray(T[] a) { + return c.toArray(a); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareIterator.java b/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareIterator.java new file mode 100644 index 000000000..90c7261e4 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareIterator.java @@ -0,0 +1,38 @@ +package com.avaje.ebeaninternal.server.type; + +import java.util.Iterator; + +/** + * Wraps an iterator for the purposes of detecting modifications. + */ +public class ModifyAwareIterator implements Iterator { + + private final ModifyAwareOwner owner; + + private final Iterator it; + + /** + * Create with an Owner and the underlying Iterator this wraps. + *

+ * The owner is notified of the removals. + *

+ */ + public ModifyAwareIterator(ModifyAwareOwner owner, Iterator it) { + this.owner = owner; + this.it = it; + } + + public boolean hasNext() { + return it.hasNext(); + } + + public E next() { + return it.next(); + } + + public void remove() { + owner.markAsModified(); + it.remove(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareMap.java b/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareMap.java new file mode 100644 index 000000000..efc46820c --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareMap.java @@ -0,0 +1,111 @@ +package com.avaje.ebeaninternal.server.type; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * Map that is wraps an underlying map for the purpose of detecting changes. + */ +public class ModifyAwareMap implements Map, ModifyAwareOwner { + + /** + * Dirty flag set when the map has been modified. + */ + private boolean dirty; + + /** + * The underlying map. + */ + private Map map; + + public ModifyAwareMap(Map underyling) { + this.map = underyling; + } + + public String toString() { + return map.toString(); + } + + @Override + public boolean isMarkedDirty() { + return dirty; + } + + @Override + public void markAsModified() { + dirty = true; + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public V get(Object key) { + return map.get(key); + } + + @Override + public V put(K key, V value) { + markAsModified(); + return map.put(key, value); + } + + @Override + public V remove(Object key) { + V value = map.remove(key); + if (value != null) { + markAsModified(); + } + return value; + } + + @Override + public void putAll(Map m) { + markAsModified(); + map.putAll(m); + } + + + @Override + public void clear() { + if (!map.isEmpty()) { + markAsModified(); + } + map.clear(); + } + + @Override + public Set keySet() { + return new ModifyAwareSet(this, map.keySet()); + } + + @Override + public Collection values() { + return new ModifyAwareCollection(this, map.values()); + } + + @Override + public Set> entrySet() { + return new ModifyAwareSet>(this, map.entrySet()); + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareOwner.java b/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareOwner.java new file mode 100644 index 000000000..93ca8cc31 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareOwner.java @@ -0,0 +1,17 @@ +package com.avaje.ebeaninternal.server.type; + +/** + * Owner object notified when a modification is detected. + */ +public interface ModifyAwareOwner { + + /** + * Return true if the value is considered dirty. + */ + public boolean isMarkedDirty(); + + /** + * Marks the object as modified. + */ + public void markAsModified(); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareSet.java b/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareSet.java new file mode 100644 index 000000000..138b8d822 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareSet.java @@ -0,0 +1,17 @@ +package com.avaje.ebeaninternal.server.type; + +import java.util.Set; + +/** + * Wraps a Set for the purposes of detecting modifications. + */ +public class ModifyAwareSet extends ModifyAwareCollection implements Set { + + /** + * Create with an Owner that is notified of modifications. + */ + public ModifyAwareSet(ModifyAwareOwner owner, Set s) { + super(owner, s); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareType.java b/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareType.java new file mode 100644 index 000000000..ca44984ca --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareType.java @@ -0,0 +1,6 @@ +package com.avaje.ebeaninternal.server.type; + +public interface ModifyAwareType { + + public boolean isDirty(); +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java index a1d9981a8..693a46a3d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java @@ -34,6 +34,17 @@ import com.avaje.ebeaninternal.server.text.json.WriteJsonBuffer; */ public interface ScalarType extends StringParser, StringFormatter, ScalarDataReader { + /** + * Return true if this is a mutable scalar type (like hstore). + */ + public boolean isMutable(); + + /** + * For mutable scalarType's return true if the value is dirty. + * Non-dirty properties may be excluded from updates. + */ + public boolean isDirty(Object value); + /** * Return the default DB column length for this type. *

diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBase.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBase.java index 096ea6d80..842433c1b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBase.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBase.java @@ -21,6 +21,22 @@ public abstract class ScalarTypeBase implements ScalarType { } /** + * Default implementation of mutable false. + */ + @Override + public boolean isMutable() { + return false; + } + + /** + * Default to true. + */ + @Override + public boolean isDirty(Object value) { + return true; + } + + /** * Just return 0. */ public int getLength() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java index 1e69168f0..843977369 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java @@ -26,6 +26,16 @@ public class ScalarTypeBytesEncrypted implements ScalarType { this.dataEncryptSupport = dataEncryptSupport; } + @Override + public boolean isMutable() { + return false; + } + + @Override + public boolean isDirty(Object value) { + return false; + } + public void bind(DataBind b, byte[] value) throws SQLException { value = dataEncryptSupport.encrypt(value); baseType.bind(b, value); diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java index 6ff361ae9..222e74b1b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java @@ -22,6 +22,16 @@ public class ScalarTypeEncryptedWrapper implements ScalarType { this.dataEncryptSupport = dataEncryptSupport; } + @Override + public boolean isMutable() { + return wrapped.isMutable(); + } + + @Override + public boolean isDirty(Object value) { + return wrapped.isDirty(value); + } + public Object readData(DataInput dataInput) throws IOException { return wrapped.readData(dataInput); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstore.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstore.java new file mode 100644 index 000000000..1563f9c5e --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstore.java @@ -0,0 +1,100 @@ +package com.avaje.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; +import java.util.Map; + +import com.avaje.ebean.config.dbplatform.PostgresPlatform; + +/** + * Postgres Hstore type which maps Map to a single 'HStore column' in the DB. + */ +@SuppressWarnings("rawtypes") +public class ScalarTypePostgresHstore extends ScalarTypeBase { + + public static final String KEY = "hstore"; + + public static final int HSTORE_TYPE = PostgresPlatform.TYPE_HSTORE; + + public ScalarTypePostgresHstore() { + super(Map.class, false, HSTORE_TYPE); + } + + @Override + public boolean isMutable() { + return true; + } + + @Override + public boolean isDirty(Object value) { + if (value instanceof ModifyAwareOwner) { + return ((ModifyAwareOwner)value).isMarkedDirty(); + } + return true; + } + + @SuppressWarnings("unchecked") + @Override + public Map read(DataReader dataReader) throws SQLException { + + Object value = dataReader.getObject(); + if (value == null) { + return null; + } + if (value instanceof Map == false) { + throw new RuntimeException("Expecting Hstore to return as Map but got type "+value.getClass()); + } + return new ModifyAwareMap((Map)value); + } + + @Override + public void bind(DataBind b, Map value) throws SQLException { + b.setObject(value); + } + + @Override + public Object toJdbcType(Object value) { + return value; + } + + @Override + public Map toBeanType(Object value) { + return (Map)value; + } + + @Override + public String formatValue(Map v) { + // TODO format as json + return null; + } + + @Override + public Map parse(String value) { + // TODO parse json into map + return null; + } + + @Override + public Map parseDateTime(long dateTime) { + throw new RuntimeException("Should never be called"); + } + + @Override + public boolean isDateTimeCapable() { + return false; + } + + @Override + public Object readData(DataInput dataInput) throws IOException { + return null; + } + + @Override + public void writeData(DataOutput dataOutput, Object v) throws IOException { + + } + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeWrapper.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeWrapper.java index 24bdcac59..5ccafc915 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeWrapper.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeWrapper.java @@ -37,11 +37,21 @@ public class ScalarTypeWrapper implements ScalarType { this.nullValue = converter.getNullValue(); this.wrapperType = wrapperType; } - + public String toString() { return "ScalarTypeWrapper " + wrapperType + " to " + scalarType.getType(); } + @Override + public boolean isMutable() { + return scalarType.isMutable(); + } + + @Override + public boolean isDirty(Object value) { + return scalarType.isDirty(value); + } + @SuppressWarnings("unchecked") public Object readData(DataInput dataInput) throws IOException { Object v = scalarType.readData(dataInput); diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java b/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java index 629a6cc9c..66be655ee 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java @@ -57,4 +57,9 @@ public interface TypeManager { * or String which has limitations). */ public ScalarType createEnumScalarType(Class enumType); + + /** + * Find a scalarType using a custom type key. Used for Hstore and similar special types. + */ + public ScalarType getScalarTypeFromKey(String specialTypeKey); }