From cb34e79283dc7d37e841fd97e04992427fe8b534 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 22 Oct 2009 20:02:10 +0000 Subject: [PATCH] HHH-4440 : column-level read/write fragment support git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@17821 1b8cb986-b30d-0410-93ca-fae66ebed9b2 --- .../java/org/hibernate/cfg/HbmBinder.java | 16 +- .../dialect/lock/UpdateLockingStrategy.java | 28 ++-- .../java/org/hibernate/mapping/Column.java | 32 +++- .../AbstractCollectionPersister.java | 25 ++- .../collection/BasicCollectionPersister.java | 30 ++-- .../CompositeElementPropertyMapping.java | 9 +- .../collection/OneToManyPersister.java | 10 +- .../entity/AbstractEntityPersister.java | 151 +++++++++++++----- .../entity/AbstractPropertyMapping.java | 64 ++++++-- .../entity/BasicEntityPropertyMapping.java | 8 + .../entity/JoinedSubclassEntityPersister.java | 29 +++- .../entity/SingleTableEntityPersister.java | 18 ++- .../main/java/org/hibernate/sql/Delete.java | 51 +++++- .../main/java/org/hibernate/sql/Insert.java | 15 +- .../org/hibernate/sql/SelectFragment.java | 10 ++ .../main/java/org/hibernate/sql/Update.java | 69 +++++--- .../org/hibernate/hibernate-mapping-3.0.dtd | 2 + .../docbook/en-US/content/basic_mapping.xml | 54 ++++++- .../main/docbook/en-US/content/query_sql.xml | 19 ++- .../test/component/basic/ComponentTest.java | 47 +++++- .../test/component/basic/Person.java | 7 + .../test/component/basic/User.hbm.xml | 6 + .../test/compositeelement/Child.java | 7 + .../CompositeElementTest.java | 47 ++++++ .../test/compositeelement/Parent.hbm.xml | 6 + .../test/cut/CompositeUserTypeTest.java | 39 +++++ .../org/hibernate/test/cut/MutualFund.java | 28 ++++ .../hibernate/test/cut/Transaction.hbm.xml | 13 ++ .../test/filter/DynamicFilterTest.java | 28 +++- .../org/hibernate/test/filter/Product.hbm.xml | 9 +- .../org/hibernate/test/filter/Product.java | 11 +- .../org/hibernate/test/filter/defs.hbm.xml | 6 +- .../test/hql/ASTParserLoadingTest.java | 141 +++++++++++++++- .../org/hibernate/test/hql/Animal.hbm.xml | 10 +- .../CriteriaClassicAggregationReturnTest.java | 8 +- .../test/hql/CriteriaHQLAlignmentTest.java | 16 +- .../java/org/hibernate/test/hql/Human.java | 11 +- .../java/org/hibernate/test/hql/Image.hbm.xml | 20 +++ .../java/org/hibernate/test/hql/Image.java | 49 ++++++ .../hql/SimpleEntityWithAssociation.hbm.xml | 7 +- .../test/hql/SimpleEntityWithAssociation.java | 11 +- .../instrument/buildtime/InstrumentTest.java | 15 +- .../cases/TestCustomColumnReadAndWrite.java | 66 ++++++++ .../test/instrument/domain/Document.java | 13 ++ .../test/instrument/domain/Documents.hbm.xml | 5 + ...sformingClassLoaderInstrumentTestCase.java | 4 + .../runtime/CGLIBInstrumentationTest.java | 10 +- .../runtime/JavassistInstrumentationTest.java | 10 +- .../org/hibernate/test/join/JoinTest.java | 77 +++++++++ .../org/hibernate/test/join/Person.hbm.xml | 11 ++ .../java/org/hibernate/test/join/Person.java | 13 ++ .../java/org/hibernate/test/join/User.java | 13 ++ .../test/joinedsubclass/Employee.java | 13 ++ .../joinedsubclass/JoinedSubclassTest.java | 73 +++++++++ .../test/joinedsubclass/Person.hbm.xml | 12 ++ .../hibernate/test/joinedsubclass/Person.java | 15 ++ .../org/hibernate/test/subselect/Alien.java | 7 + .../org/hibernate/test/subselect/Being.java | 7 + .../hibernate/test/subselect/Beings.hbm.xml | 21 ++- .../org/hibernate/test/subselect/Human.java | 7 + .../test/subselect/SubselectTest.java | 52 ++++++ .../test/unionsubclass2/Employee.java | 13 ++ .../test/unionsubclass2/Person.hbm.xml | 12 ++ .../hibernate/test/unionsubclass2/Person.java | 14 +- .../unionsubclass2/UnionSubclassTest.java | 74 +++++++++ 65 files changed, 1531 insertions(+), 193 deletions(-) create mode 100644 testsuite/src/test/java/org/hibernate/test/cut/MutualFund.java create mode 100644 testsuite/src/test/java/org/hibernate/test/hql/Image.hbm.xml create mode 100644 testsuite/src/test/java/org/hibernate/test/hql/Image.java create mode 100644 testsuite/src/test/java/org/hibernate/test/instrument/cases/TestCustomColumnReadAndWrite.java diff --git a/core/src/main/java/org/hibernate/cfg/HbmBinder.java b/core/src/main/java/org/hibernate/cfg/HbmBinder.java index 7af1b34b8f..db0e8ea1b5 100644 --- a/core/src/main/java/org/hibernate/cfg/HbmBinder.java +++ b/core/src/main/java/org/hibernate/cfg/HbmBinder.java @@ -25,15 +25,12 @@ package org.hibernate.cfg; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Properties; import java.util.StringTokenizer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.Element; @@ -42,10 +39,10 @@ import org.hibernate.EntityMode; import org.hibernate.FetchMode; import org.hibernate.FlushMode; import org.hibernate.MappingException; +import org.hibernate.engine.ExecuteUpdateResultCheckStyle; import org.hibernate.engine.FilterDefinition; import org.hibernate.engine.NamedQueryDefinition; import org.hibernate.engine.Versioning; -import org.hibernate.engine.ExecuteUpdateResultCheckStyle; import org.hibernate.id.PersistentIdentifierGenerator; import org.hibernate.mapping.Any; import org.hibernate.mapping.Array; @@ -100,6 +97,8 @@ import org.hibernate.type.TypeFactory; import org.hibernate.util.JoinedIterator; import org.hibernate.util.ReflectHelper; import org.hibernate.util.StringHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Walks an XML mapping document and produces the Hibernate configuration-time metamodel (the @@ -1705,7 +1704,7 @@ public final class HbmBinder { } - public static void bindColumn(Element node, Column column, boolean isNullable) { + public static void bindColumn(Element node, Column column, boolean isNullable) throws MappingException { Attribute lengthNode = node.attribute( "length" ); if ( lengthNode != null ) column.setLength( Integer.parseInt( lengthNode.getValue() ) ); Attribute scalNode = node.attribute( "scale" ); @@ -1725,6 +1724,13 @@ public final class HbmBinder { Attribute typeNode = node.attribute( "sql-type" ); if ( typeNode != null ) column.setSqlType( typeNode.getValue() ); + String customWrite = node.attributeValue( "write" ); + if(customWrite != null && !customWrite.matches("[^?]*\\?[^?]*")) { + throw new MappingException("write expression must contain exactly one value placeholder ('?') character"); + } + column.setCustomWrite( customWrite ); + column.setCustomRead( node.attributeValue( "read" ) ); + Element comment = node.element("comment"); if (comment!=null) column.setComment( comment.getTextTrim() ); diff --git a/core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java b/core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java index cd9a3af33a..da1b19a552 100644 --- a/core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java +++ b/core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java @@ -24,23 +24,23 @@ */ package org.hibernate.dialect.lock; -import org.hibernate.persister.entity.Lockable; -import org.hibernate.LockMode; -import org.hibernate.HibernateException; -import org.hibernate.StaleObjectStateException; -import org.hibernate.JDBCException; -import org.hibernate.pretty.MessageHelper; -import org.hibernate.exception.JDBCExceptionHelper; -import org.hibernate.sql.Update; -import org.hibernate.engine.SessionImplementor; -import org.hibernate.engine.SessionFactoryImplementor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.SQLException; +import org.hibernate.HibernateException; +import org.hibernate.JDBCException; +import org.hibernate.LockMode; +import org.hibernate.StaleObjectStateException; +import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.engine.SessionImplementor; +import org.hibernate.exception.JDBCExceptionHelper; +import org.hibernate.persister.entity.Lockable; +import org.hibernate.pretty.MessageHelper; +import org.hibernate.sql.Update; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * A locking strategy where the locks are obtained through update statements. *

@@ -131,7 +131,7 @@ public class UpdateLockingStrategy implements LockingStrategy { SessionFactoryImplementor factory = lockable.getFactory(); Update update = new Update( factory.getDialect() ); update.setTableName( lockable.getRootTableName() ); - update.setPrimaryKeyColumnNames( lockable.getRootTableIdentifierColumnNames() ); + update.addPrimaryKeyColumns( lockable.getRootTableIdentifierColumnNames() ); update.setVersionColumnName( lockable.getVersionColumnName() ); update.addColumn( lockable.getVersionColumnName() ); if ( factory.getSettings().isCommentsEnabled() ) { diff --git a/core/src/main/java/org/hibernate/mapping/Column.java b/core/src/main/java/org/hibernate/mapping/Column.java index 2460490612..ff98f8147b 100644 --- a/core/src/main/java/org/hibernate/mapping/Column.java +++ b/core/src/main/java/org/hibernate/mapping/Column.java @@ -31,6 +31,7 @@ import org.hibernate.MappingException; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.function.SQLFunctionRegistry; import org.hibernate.engine.Mapping; +import org.hibernate.sql.Template; import org.hibernate.util.StringHelper; /** @@ -58,6 +59,8 @@ public class Column implements Selectable, Serializable, Cloneable { private String checkConstraint; private String comment; private String defaultValue; + private String customWrite; + private String customRead; public Column() { }; @@ -260,9 +263,18 @@ public class Column implements Selectable, Serializable, Cloneable { } public String getTemplate(Dialect dialect, SQLFunctionRegistry functionRegistry) { - return getQuotedName(dialect); + String expr = getReadExpr(dialect); + return Template.renderWhereStringTemplate(expr, dialect, functionRegistry); } + public String getReadExpr(Dialect dialect) { + return ( customRead != null && customRead.length() > 0 ) ? customRead : getQuotedName(dialect); + } + + public String getWriteExpr() { + return ( customWrite != null && customWrite.length() > 0 ) ? customWrite : "?"; + } + public boolean isFormula() { return false; } @@ -304,6 +316,22 @@ public class Column implements Selectable, Serializable, Cloneable { this.defaultValue = defaultValue; } + public String getCustomWrite() { + return customWrite; + } + + public void setCustomWrite(String customWrite) { + this.customWrite = customWrite; + } + + public String getCustomRead() { + return customRead; + } + + public void setCustomRead(String customRead) { + this.customRead = customRead; + } + public String getCanonicalName() { return quoted ? name : name.toLowerCase(); } @@ -327,6 +355,8 @@ public class Column implements Selectable, Serializable, Cloneable { copy.setCheckConstraint( checkConstraint ); copy.setComment( comment ); copy.setDefaultValue( defaultValue ); + copy.setCustomRead( customRead ); + copy.setCustomWrite( customWrite ); return copy; } diff --git a/core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java b/core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java index 83094521d2..a41e17377a 100644 --- a/core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java +++ b/core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java @@ -33,16 +33,12 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.hibernate.AssertionFailure; import org.hibernate.FetchMode; import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.QueryException; import org.hibernate.TransientObjectException; -import org.hibernate.jdbc.Expectation; -import org.hibernate.jdbc.Expectations; import org.hibernate.cache.CacheException; import org.hibernate.cache.access.CollectionRegionAccessStrategy; import org.hibernate.cache.entry.CacheEntryStructure; @@ -53,15 +49,17 @@ import org.hibernate.cfg.Configuration; import org.hibernate.collection.PersistentCollection; import org.hibernate.dialect.Dialect; import org.hibernate.engine.EntityKey; +import org.hibernate.engine.ExecuteUpdateResultCheckStyle; +import org.hibernate.engine.LoadQueryInfluencers; import org.hibernate.engine.PersistenceContext; import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.engine.SessionImplementor; import org.hibernate.engine.SubselectFetch; -import org.hibernate.engine.ExecuteUpdateResultCheckStyle; -import org.hibernate.engine.LoadQueryInfluencers; import org.hibernate.exception.JDBCExceptionHelper; import org.hibernate.exception.SQLExceptionConverter; import org.hibernate.id.IdentifierGenerator; +import org.hibernate.jdbc.Expectation; +import org.hibernate.jdbc.Expectations; import org.hibernate.loader.collection.CollectionInitializer; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Column; @@ -88,6 +86,8 @@ import org.hibernate.type.Type; import org.hibernate.util.ArrayHelper; import org.hibernate.util.FilterHelper; import org.hibernate.util.StringHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -140,6 +140,9 @@ public abstract class AbstractCollectionPersister protected final String[] indexFormulas; protected final boolean[] indexColumnIsSettable; protected final String[] elementColumnNames; + protected final String[] elementColumnWriters; + protected final String[] elementColumnReaders; + protected final String[] elementColumnReaderTemplates; protected final String[] elementFormulaTemplates; protected final String[] elementFormulas; protected final boolean[] elementColumnIsSettable; @@ -320,6 +323,9 @@ public abstract class AbstractCollectionPersister int elementSpan = collection.getElement().getColumnSpan(); elementColumnAliases = new String[elementSpan]; elementColumnNames = new String[elementSpan]; + elementColumnWriters = new String[elementSpan]; + elementColumnReaders = new String[elementSpan]; + elementColumnReaderTemplates = new String[elementSpan]; elementFormulaTemplates = new String[elementSpan]; elementFormulas = new String[elementSpan]; elementColumnIsSettable = new boolean[elementSpan]; @@ -339,6 +345,9 @@ public abstract class AbstractCollectionPersister else { Column col = (Column) selectable; elementColumnNames[j] = col.getQuotedName(dialect); + elementColumnWriters[j] = col.getWriteExpr(); + elementColumnReaders[j] = col.getReadExpr(dialect); + elementColumnReaderTemplates[j] = col.getTemplate(dialect, factory.getSqlFunctionRegistry()); elementColumnIsSettable[j] = true; elementColumnIsInPrimaryKey[j] = !col.isNullable(); if ( !col.isNullable() ) { @@ -513,6 +522,8 @@ public abstract class AbstractCollectionPersister if ( elementType.isComponentType() ) { elementPropertyMapping = new CompositeElementPropertyMapping( elementColumnNames, + elementColumnReaders, + elementColumnReaderTemplates, elementFormulaTemplates, (AbstractComponentType) elementType, factory @@ -970,7 +981,7 @@ public abstract class AbstractCollectionPersister protected void appendElementColumns(SelectFragment frag, String elemAlias) { for ( int i=0; i 0; } } @@ -1879,10 +1953,11 @@ public abstract class AbstractEntityPersister // this property belongs to the table, and it is not specifically // excluded from optimistic locking by optimistic-lock="false" String[] propertyColumnNames = getPropertyColumnNames( i ); + String[] propertyColumnWriters = getPropertyColumnWriters( i ); boolean[] propertyNullness = types[i].toColumnNullness( oldFields[i], getFactory() ); for ( int k=0; k= 0; j-- ) { Delete delete = new Delete() .setTableName( getTableName( j ) ) - .setPrimaryKeyColumnNames( getKeyColumns( j ) ); + .addPrimaryKeyColumns( getKeyColumns( j ) ); if ( getFactory().getSettings().isCommentsEnabled() ) { delete.setComment( "delete " + getEntityName() + " [" + j + "]" ); } @@ -2889,12 +2964,12 @@ public abstract class AbstractEntityPersister int[] columnTableNumbers = getSubclassColumnTableNumberClosure(); String[] columnAliases = getSubclassColumnAliasClosure(); - String[] columns = getSubclassColumnClosure(); + String[] columnReaderTemplates = getSubclassColumnReaderTemplateClosure(); for ( int i = 0; i < subclassColumnNumbers.length; i++ ) { int columnNumber = subclassColumnNumbers[i]; if ( subclassColumnSelectableClosure[columnNumber] ) { final String subalias = generateTableAlias( getRootAlias(), columnTableNumbers[columnNumber] ); - selectFragment.addColumn( subalias, columns[columnNumber], columnAliases[columnNumber] ); + selectFragment.addColumnTemplate( subalias, columnReaderTemplates[columnNumber], columnAliases[columnNumber] ); } } diff --git a/core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java b/core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java index 608eeef5b1..1f9a8408e2 100644 --- a/core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java +++ b/core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java @@ -47,12 +47,22 @@ public abstract class AbstractPropertyMapping implements PropertyMapping { private final Map typesByPropertyPath = new HashMap(); private final Map columnsByPropertyPath = new HashMap(); + private final Map columnReadersByPropertyPath = new HashMap(); + private final Map columnReaderTemplatesByPropertyPath = new HashMap(); private final Map formulaTemplatesByPropertyPath = new HashMap(); public String[] getIdentifierColumnNames() { throw new UnsupportedOperationException("one-to-one is not supported here"); } + public String[] getIdentifierColumnReaderTemplates() { + throw new UnsupportedOperationException("one-to-one is not supported here"); + } + + public String[] getIdentifierColumnReaders() { + throw new UnsupportedOperationException("one-to-one is not supported here"); + } + protected abstract String getEntityName(); public Type toType(String propertyName) throws QueryException { @@ -81,14 +91,15 @@ public abstract class AbstractPropertyMapping implements PropertyMapping { if ( columns == null ) { throw propertyException( propertyName ); } - String[] templates = (String[]) formulaTemplatesByPropertyPath.get(propertyName); + String[] formulaTemplates = (String[]) formulaTemplatesByPropertyPath.get(propertyName); + String[] columnReaderTemplates = (String[]) columnReaderTemplatesByPropertyPath.get(propertyName); String[] result = new String[columns.length]; for ( int i=0; iDELETE statement @@ -34,10 +38,11 @@ import org.hibernate.util.StringHelper; public class Delete { private String tableName; - private String[] primaryKeyColumnNames; private String versionColumnName; private String where; + private Map primaryKeyColumns = new LinkedHashMap(); + private String comment; public Delete setComment(String comment) { this.comment = comment; @@ -55,12 +60,17 @@ public class Delete { buf.append( "/* " ).append(comment).append( " */ " ); } buf.append( "delete from " ).append(tableName); - if ( where != null || primaryKeyColumnNames != null || versionColumnName != null ) { + if ( where != null || !primaryKeyColumns.isEmpty() || versionColumnName != null ) { buf.append( " where " ); } boolean conditionsAppended = false; - if ( primaryKeyColumnNames != null ) { - buf.append( StringHelper.join( "=? and ", primaryKeyColumnNames ) ).append( "=?" ); + Iterator iter = primaryKeyColumns.entrySet().iterator(); + while ( iter.hasNext() ) { + Map.Entry e = (Map.Entry) iter.next(); + buf.append( e.getKey() ).append( '=' ).append( e.getValue() ); + if ( iter.hasNext() ) { + buf.append( " and " ); + } conditionsAppended = true; } if ( where!=null ) { @@ -94,8 +104,35 @@ public class Delete { return this; } - public Delete setPrimaryKeyColumnNames(String[] primaryKeyColumnNames) { - this.primaryKeyColumnNames = primaryKeyColumnNames; + public Delete setPrimaryKeyColumnNames(String[] columnNames) { + this.primaryKeyColumns.clear(); + addPrimaryKeyColumns(columnNames); + return this; + } + + public Delete addPrimaryKeyColumns(String[] columnNames) { + for ( int i=0; iUPDATE statement @@ -40,12 +39,12 @@ import org.hibernate.util.StringHelper; public class Update { private String tableName; - private String[] primaryKeyColumnNames; private String versionColumnName; private String where; private String assignments; private String comment; + private Map primaryKeyColumns = new LinkedHashMap(); private Map columns = new LinkedHashMap(); private Map whereColumns = new LinkedHashMap(); @@ -74,11 +73,38 @@ public class Update { return this; } - public Update setPrimaryKeyColumnNames(String[] primaryKeyColumnNames) { - this.primaryKeyColumnNames = primaryKeyColumnNames; + public Update setPrimaryKeyColumnNames(String[] columnNames) { + this.primaryKeyColumns.clear(); + addPrimaryKeyColumns(columnNames); + return this; + } + + public Update addPrimaryKeyColumns(String[] columnNames) { + for ( int i=0; i diff --git a/documentation/manual/src/main/docbook/en-US/content/basic_mapping.xml b/documentation/manual/src/main/docbook/en-US/content/basic_mapping.xml index 65ee3ee50d..2978943fa3 100644 --- a/documentation/manual/src/main/docbook/en-US/content/basic_mapping.xml +++ b/documentation/manual/src/main/docbook/en-US/content/basic_mapping.xml @@ -2788,7 +2788,7 @@ - + Column and formula elements Mapping elements which accept a column attribute will alternatively @@ -2807,12 +2807,22 @@ index="index_name" sql-type="sql_type_name" check="SQL expression" - default="SQL expression"/>]]> + default="SQL expression" + read="SQL expression" + write="SQL expression"/>]]> SQL expression]]> - column and formula attributes can even be combined + Most of the attributes on column provide a means of tailoring the + DDL during automatic schema generation. The read and write + attributes allow you to specify custom SQL that Hibernate will use to access the column's value. + For more on this, see the discussion of + column read and write expressions. + + + + The column and formula elements can even be combined within the same property or association mapping to express, for example, exotic join conditions. @@ -3544,6 +3554,44 @@ public class Customer implements Serializable { + + Column read and write expressions + + Hibernate allows you to customize the SQL it uses to read and write the values + of columns mapped to simple properties. + For example, if your database provides a set of data encryption functions, you can + invoke them for individual columns like this: + + +]]> + + + Hibernate applies the custom expressions automatically whenever the property is + referenced in a query. This functionality is similar to a derived-property + formula with two differences: + + + + The property is backed by one or more columns that are exported as part of automatic + schema generation. + + + + + The property is read-write, not read-only. + + + + + + The write expression, if specified, must contain exactly one '?' placeholder + for the value. + + + Auxiliary database objects diff --git a/documentation/manual/src/main/docbook/en-US/content/query_sql.xml b/documentation/manual/src/main/docbook/en-US/content/query_sql.xml index de8a9a16e8..42c3e05b8b 100644 --- a/documentation/manual/src/main/docbook/en-US/content/query_sql.xml +++ b/documentation/manual/src/main/docbook/en-US/content/query_sql.xml @@ -662,10 +662,15 @@ BEGIN Custom SQL for create, update and delete - Hibernate3 can use custom SQL statements for create, update, and - delete operations. The class and collection persisters in Hibernate - already contain a set of configuration time generated strings (insertsql, - deletesql, updatesql etc.). The mapping tags + Hibernate3 can use custom SQL for create, update, and delete operations. + The SQL can be overridden at the statement level or inidividual column level. This + section describes statement overrides. For columns, see + . + + + The class and collection persisters in Hibernate already contain a set of + configuration time generated strings (insertsql, deletesql, updatesql etc.). + The mapping tags <sql-insert>, <sql-delete>, and <sql-update> override these strings: @@ -732,7 +737,11 @@ END updatePerson;]]> Custom SQL for loading You can also declare your own SQL (or HQL) queries for entity - loading: + loading. As with inserts, updates, and deletes, this can be done at the + individual column level as described in + + or at the statement level. Here is an example of a statement level override: + diff --git a/testsuite/src/test/java/org/hibernate/test/component/basic/ComponentTest.java b/testsuite/src/test/java/org/hibernate/test/component/basic/ComponentTest.java index b605f81adf..c1116c99e0 100755 --- a/testsuite/src/test/java/org/hibernate/test/component/basic/ComponentTest.java +++ b/testsuite/src/test/java/org/hibernate/test/component/basic/ComponentTest.java @@ -7,16 +7,17 @@ import java.util.List; import junit.framework.Test; +import org.hibernate.Hibernate; import org.hibernate.Session; import org.hibernate.Transaction; -import org.hibernate.Hibernate; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import org.hibernate.cfg.Mappings; import org.hibernate.criterion.Property; +import org.hibernate.criterion.Restrictions; import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.function.SQLFunction; import org.hibernate.dialect.SybaseASE15Dialect; +import org.hibernate.dialect.function.SQLFunction; import org.hibernate.junit.functional.FunctionalTestCase; import org.hibernate.junit.functional.FunctionalTestClassTestSuite; import org.hibernate.mapping.Component; @@ -207,6 +208,48 @@ public class ComponentTest extends FunctionalTestCase { t.commit(); s.close(); } + + public void testCustomColumnReadAndWrite() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + User u = new User( "steve", "hibernater", new Person( "Steve Ebersole", new Date(), "Main St") ); + final double HEIGHT_INCHES = 73; + final double HEIGHT_CENTIMETERS = HEIGHT_INCHES * 2.54d; + u.getPerson().setHeightInches(HEIGHT_INCHES); + s.persist( u ); + s.flush(); + + // Test value conversion during insert + Double heightViaSql = (Double)s.createSQLQuery("select height_centimeters from t_user where t_user.username='steve'").uniqueResult(); + assertEquals(HEIGHT_CENTIMETERS, heightViaSql, 0.01d); + + // Test projection + Double heightViaHql = (Double)s.createQuery("select u.person.heightInches from User u where u.id = 'steve'").uniqueResult(); + assertEquals(HEIGHT_INCHES, heightViaHql, 0.01d); + + // Test restriction and entity load via criteria + u = (User)s.createCriteria(User.class) + .add(Restrictions.between("person.heightInches", HEIGHT_INCHES - 0.01d, HEIGHT_INCHES + 0.01d)) + .uniqueResult(); + assertEquals(HEIGHT_INCHES, u.getPerson().getHeightInches(), 0.01d); + + // Test predicate and entity load via HQL + u = (User)s.createQuery("from User u where u.person.heightInches between ? and ?") + .setDouble(0, HEIGHT_INCHES - 0.01d) + .setDouble(1, HEIGHT_INCHES + 0.01d) + .uniqueResult(); + assertEquals(HEIGHT_INCHES, u.getPerson().getHeightInches(), 0.01d); + + // Test update + u.getPerson().setHeightInches(1); + s.flush(); + heightViaSql = (Double)s.createSQLQuery("select height_centimeters from t_user where t_user.username='steve'").uniqueResult(); + assertEquals(2.54d, heightViaSql, 0.01d); + s.delete(u); + t.commit(); + s.close(); + } + public void testNamedQuery() { Session s = openSession(); diff --git a/testsuite/src/test/java/org/hibernate/test/component/basic/Person.java b/testsuite/src/test/java/org/hibernate/test/component/basic/Person.java index cc6661eceb..380a8aab64 100755 --- a/testsuite/src/test/java/org/hibernate/test/component/basic/Person.java +++ b/testsuite/src/test/java/org/hibernate/test/component/basic/Person.java @@ -13,6 +13,7 @@ public class Person { private String currentAddress; private String previousAddress; private int yob; + private double heightInches; Person() {} public Person(String name, Date dob, String address) { this.name = name; @@ -60,4 +61,10 @@ public class Person { public void setCurrentAddress(String currentAddress) { this.currentAddress = currentAddress; } + public double getHeightInches() { + return heightInches; + } + public void setHeightInches(double heightInches) { + this.heightInches = heightInches; + } } diff --git a/testsuite/src/test/java/org/hibernate/test/component/basic/User.hbm.xml b/testsuite/src/test/java/org/hibernate/test/component/basic/User.hbm.xml index 25d4562056..04d3caf79f 100755 --- a/testsuite/src/test/java/org/hibernate/test/component/basic/User.hbm.xml +++ b/testsuite/src/test/java/org/hibernate/test/component/basic/User.hbm.xml @@ -19,6 +19,12 @@ + + + + + + diff --git a/testsuite/src/test/java/org/hibernate/test/cut/CompositeUserTypeTest.java b/testsuite/src/test/java/org/hibernate/test/cut/CompositeUserTypeTest.java index 90fd322235..66a3cb2a0c 100755 --- a/testsuite/src/test/java/org/hibernate/test/cut/CompositeUserTypeTest.java +++ b/testsuite/src/test/java/org/hibernate/test/cut/CompositeUserTypeTest.java @@ -8,6 +8,7 @@ import java.util.List; import junit.framework.Test; import org.hibernate.Session; +import org.hibernate.criterion.Restrictions; import org.hibernate.dialect.HSQLDialect; import org.hibernate.junit.functional.FunctionalTestCase; import org.hibernate.junit.functional.FunctionalTestClassTestSuite; @@ -57,6 +58,44 @@ public class CompositeUserTypeTest extends FunctionalTestCase { t.commit(); s.close(); } + + public void testCustomColumnReadAndWrite() { + Session s = openSession(); + org.hibernate.Transaction t = s.beginTransaction(); + final BigDecimal AMOUNT = new BigDecimal(73000000d); + final BigDecimal AMOUNT_MILLIONS = AMOUNT.divide(new BigDecimal(1000000d)); + MutualFund f = new MutualFund(); + f.setHoldings( new MonetoryAmount( AMOUNT, Currency.getInstance("USD") ) ); + s.persist(f); + s.flush(); + + // Test value conversion during insert + BigDecimal amountViaSql = (BigDecimal)s.createSQLQuery("select amount_millions from MutualFund").uniqueResult(); + assertEquals(AMOUNT_MILLIONS.doubleValue(), amountViaSql.doubleValue(), 0.01d); + + // Test projection + BigDecimal amountViaHql = (BigDecimal)s.createQuery("select f.holdings.amount from MutualFund f").uniqueResult(); + assertEquals(AMOUNT.doubleValue(), amountViaHql.doubleValue(), 0.01d); + + // Test restriction and entity load via criteria + BigDecimal one = new BigDecimal(1); + f = (MutualFund)s.createCriteria(MutualFund.class) + .add(Restrictions.between("holdings.amount", AMOUNT.subtract(one), AMOUNT.add(one))) + .uniqueResult(); + assertEquals(AMOUNT.doubleValue(), f.getHoldings().getAmount().doubleValue(), 0.01d); + + // Test predicate and entity load via HQL + f = (MutualFund)s.createQuery("from MutualFund f where f.holdings.amount between ? and ?") + .setBigDecimal(0, AMOUNT.subtract(one)) + .setBigDecimal(1, AMOUNT.add(one)) + .uniqueResult(); + assertEquals(AMOUNT.doubleValue(), f.getHoldings().getAmount().doubleValue(), 0.01d); + + s.delete(f); + t.commit(); + s.close(); + + } } diff --git a/testsuite/src/test/java/org/hibernate/test/cut/MutualFund.java b/testsuite/src/test/java/org/hibernate/test/cut/MutualFund.java new file mode 100644 index 0000000000..1988e9f4cc --- /dev/null +++ b/testsuite/src/test/java/org/hibernate/test/cut/MutualFund.java @@ -0,0 +1,28 @@ +package org.hibernate.test.cut; + +/** + * @author Rob.Hasselbaum + * + */ +public class MutualFund { + + private Long id; + private MonetoryAmount holdings; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public MonetoryAmount getHoldings() { + return holdings; + } + + public void setHoldings(MonetoryAmount holdings) { + this.holdings = holdings; + } + +} diff --git a/testsuite/src/test/java/org/hibernate/test/cut/Transaction.hbm.xml b/testsuite/src/test/java/org/hibernate/test/cut/Transaction.hbm.xml index fb24e82de2..f4b80ab6bc 100755 --- a/testsuite/src/test/java/org/hibernate/test/cut/Transaction.hbm.xml +++ b/testsuite/src/test/java/org/hibernate/test/cut/Transaction.hbm.xml @@ -21,5 +21,18 @@ + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/src/test/java/org/hibernate/test/filter/DynamicFilterTest.java b/testsuite/src/test/java/org/hibernate/test/filter/DynamicFilterTest.java index b074e6c8b0..d25df135e1 100644 --- a/testsuite/src/test/java/org/hibernate/test/filter/DynamicFilterTest.java +++ b/testsuite/src/test/java/org/hibernate/test/filter/DynamicFilterTest.java @@ -11,21 +11,19 @@ import java.util.Set; import junit.framework.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.hibernate.Criteria; import org.hibernate.EntityMode; import org.hibernate.FetchMode; import org.hibernate.Hibernate; import org.hibernate.Session; import org.hibernate.Transaction; -import org.hibernate.Criteria; import org.hibernate.cache.CacheKey; import org.hibernate.cache.entry.CollectionCacheEntry; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; -import org.hibernate.criterion.Restrictions; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.criterion.Property; +import org.hibernate.criterion.Restrictions; import org.hibernate.criterion.Subqueries; import org.hibernate.engine.SessionImplementor; import org.hibernate.impl.SessionFactoryImpl; @@ -33,6 +31,8 @@ import org.hibernate.junit.functional.FunctionalTestCase; import org.hibernate.junit.functional.FunctionalTestClassTestSuite; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.transform.DistinctRootEntityResultTransformer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Implementation of DynamicFilterTest. @@ -179,6 +179,24 @@ public class DynamicFilterTest extends FunctionalTestCase { session.close(); testData.release(); } + + public void testFiltersWithCustomerReadAndWrite() { + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Custom SQL read/write with filter + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + log.info( "Starting HQL filter with custom SQL get/set tests" ); + TestData testData = new TestData(); + testData.prepare(); + + Session session = openSession(); + session.enableFilter( "heavyProducts" ).setParameter("weightKilograms", 4d); + log.info( "HQL against Product..." ); + List results = session.createQuery( "from Product").list(); + assertEquals( 1, results.size() ); + + session.close(); + testData.release(); + } public void testCriteriaQueryFilters() { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -824,6 +842,7 @@ public class DynamicFilterTest extends FunctionalTestCase { Product product1 = new Product(); product1.setName( "Acme Hair Gel" ); product1.setStockNumber( 123 ); + product1.setWeightPounds( 0.25 ); product1.setEffectiveStartDate( lastMonth.getTime() ); product1.setEffectiveEndDate( nextMonth.getTime() ); @@ -848,6 +867,7 @@ public class DynamicFilterTest extends FunctionalTestCase { Product product2 = new Product(); product2.setName( "Acme Super-Duper DTO Factory" ); product2.setStockNumber( 124 ); + product1.setWeightPounds( 10.0 ); product2.setEffectiveStartDate( sixMonthsAgo.getTime() ); product2.setEffectiveEndDate( new Date() ); diff --git a/testsuite/src/test/java/org/hibernate/test/filter/Product.hbm.xml b/testsuite/src/test/java/org/hibernate/test/filter/Product.hbm.xml index dc872baaf7..f13779dfd1 100644 --- a/testsuite/src/test/java/org/hibernate/test/filter/Product.hbm.xml +++ b/testsuite/src/test/java/org/hibernate/test/filter/Product.hbm.xml @@ -12,6 +12,12 @@ + + + @@ -30,7 +36,8 @@ + - \ No newline at end of file + diff --git a/testsuite/src/test/java/org/hibernate/test/filter/Product.java b/testsuite/src/test/java/org/hibernate/test/filter/Product.java index 2bc04d6c75..65c2280ed8 100644 --- a/testsuite/src/test/java/org/hibernate/test/filter/Product.java +++ b/testsuite/src/test/java/org/hibernate/test/filter/Product.java @@ -1,9 +1,9 @@ // $Id: Product.java 6507 2005-04-25 16:57:32Z steveebersole $ package org.hibernate.test.filter; -import java.util.Set; import java.util.Date; import java.util.HashSet; +import java.util.Set; /** * @author Steve Ebersole @@ -14,6 +14,7 @@ public class Product { private int stockNumber; // int for ease of hashCode() impl private Date effectiveStartDate; private Date effectiveEndDate; + private double weightPounds; private Set orderLineItems; private Set categories; @@ -73,6 +74,14 @@ public class Product { this.effectiveEndDate = effectiveEndDate; } + public double getWeightPounds() { + return weightPounds; + } + + public void setWeightPounds(double weightPounds) { + this.weightPounds = weightPounds; + } + public Set getCategories() { return categories; } diff --git a/testsuite/src/test/java/org/hibernate/test/filter/defs.hbm.xml b/testsuite/src/test/java/org/hibernate/test/filter/defs.hbm.xml index dca3bce0b5..92337e5e91 100644 --- a/testsuite/src/test/java/org/hibernate/test/filter/defs.hbm.xml +++ b/testsuite/src/test/java/org/hibernate/test/filter/defs.hbm.xml @@ -20,6 +20,10 @@ + + + + @@ -31,4 +35,4 @@ - \ No newline at end of file + diff --git a/testsuite/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java b/testsuite/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java index 34c4eb900f..d21e17d507 100644 --- a/testsuite/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java +++ b/testsuite/src/test/java/org/hibernate/test/hql/ASTParserLoadingTest.java @@ -13,8 +13,6 @@ import java.util.List; import java.util.Map; import junit.framework.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.hibernate.Hibernate; import org.hibernate.HibernateException; @@ -30,7 +28,6 @@ import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.Oracle8iDialect; - import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.dialect.SQLServerDialect; import org.hibernate.dialect.Sybase11Dialect; @@ -55,6 +52,8 @@ import org.hibernate.type.ComponentType; import org.hibernate.type.ManyToOneType; import org.hibernate.type.Type; import org.hibernate.util.StringHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Tests the integration of the new AST parser into the loading of query results using @@ -82,6 +81,7 @@ public class ASTParserLoadingTest extends FunctionalTestCase { "hql/FooBarCopy.hbm.xml", "hql/SimpleEntityWithAssociation.hbm.xml", "hql/CrazyIdFieldNames.hbm.xml", + "hql/Image.hbm.xml", "batchfetch/ProductLine.hbm.xml", "cid/Customer.hbm.xml", "cid/Order.hbm.xml", @@ -1085,6 +1085,101 @@ public class ASTParserLoadingTest extends FunctionalTestCase { t.commit(); s.close(); } + + public void testOrderedWithCustomColumnReadAndWrite() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + SimpleEntityWithAssociation first = new SimpleEntityWithAssociation(); + first.setNegatedNumber(1); + s.save(first); + SimpleEntityWithAssociation second = new SimpleEntityWithAssociation(); + second.setNegatedNumber(2); + s.save(second); + s.flush(); + + // Check order via SQL. Numbers are negated in the DB, so second comes first. + List listViaSql = s.createSQLQuery("select id from simple_1 order by negated_num").list(); + assertEquals(2, listViaSql.size()); + assertEquals(second.getId().longValue(), ((Number)listViaSql.get(0)).longValue()); + assertEquals(first.getId().longValue(), ((Number)listViaSql.get(1)).longValue()); + + // Check order via HQL. Now first comes first b/c the read negates the DB negation. + List listViaHql = s.createQuery("from SimpleEntityWithAssociation order by negatedNumber").list(); + assertEquals(2, listViaHql.size()); + assertEquals(first.getId(), ((SimpleEntityWithAssociation)listViaHql.get(0)).getId()); + assertEquals(second.getId(), ((SimpleEntityWithAssociation)listViaHql.get(1)).getId()); + + s.delete(first); + s.delete(second); + t.commit(); + s.close(); + + } + + public void testHavingWithCustomColumnReadAndWrite() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + SimpleEntityWithAssociation first = new SimpleEntityWithAssociation(); + first.setNegatedNumber(5); + first.setName("simple"); + s.save(first); + SimpleEntityWithAssociation second = new SimpleEntityWithAssociation(); + second.setNegatedNumber(10); + second.setName("simple"); + s.save(second); + SimpleEntityWithAssociation third = new SimpleEntityWithAssociation(); + third.setNegatedNumber(20); + third.setName("complex"); + s.save(third); + s.flush(); + + // Check order via HQL. Now first comes first b/c the read negates the DB negation. + Number r = (Number)s.createQuery("select sum(negatedNumber) from SimpleEntityWithAssociation " + + "group by name having sum(negatedNumber) < 20").uniqueResult(); + assertEquals(r.intValue(), 15); + + s.delete(first); + s.delete(second); + s.delete(third); + t.commit(); + s.close(); + + } + + public void testLoadSnapshotWithCustomColumnReadAndWrite() { + // Exercises entity snapshot load when select-before-update is true. + Session s = openSession(); + Transaction t = s.beginTransaction(); + final double SIZE_IN_KB = 1536d; + final double SIZE_IN_MB = SIZE_IN_KB / 1024d; + Image image = new Image(); + image.setName("picture.gif"); + image.setSizeKb(SIZE_IN_KB); + s.persist(image); + s.flush(); + + Double sizeViaSql = (Double)s.createSQLQuery("select size_mb from image").uniqueResult(); + assertEquals(SIZE_IN_MB, sizeViaSql, 0.01d); + t.commit(); + s.close(); + + s = openSession(); + t = s.beginTransaction(); + final double NEW_SIZE_IN_KB = 2048d; + final double NEW_SIZE_IN_MB = NEW_SIZE_IN_KB / 1024d; + image.setSizeKb(NEW_SIZE_IN_KB); + s.update(image); + s.flush(); + + sizeViaSql = (Double)s.createSQLQuery("select size_mb from image").uniqueResult(); + assertEquals(NEW_SIZE_IN_MB, sizeViaSql, 0.01d); + + s.delete(image); + t.commit(); + s.close(); + + } + private Human genSimpleHuman(String fName, String lName) { Human h = new Human(); @@ -1188,13 +1283,13 @@ public class ASTParserLoadingTest extends FunctionalTestCase { Transaction t = s.beginTransaction(); Human h = new Human(); h.setBodyWeight( (float) 74.0 ); - h.setHeight(120.5); + h.setHeightInches(120.5); h.setDescription("Me"); h.setName( new Name("Gavin", 'A', "King") ); h.setNickName("Oney"); s.persist(h); Double sum = (Double) s.createQuery("select sum(h.bodyWeight) from Human h").uniqueResult(); - Double avg = (Double) s.createQuery("select avg(h.height) from Human h").uniqueResult(); + Double avg = (Double) s.createQuery("select avg(h.heightInches) from Human h").uniqueResult(); // uses custom read and write for column assertEquals(sum.floatValue(), 74.0, 0.01); assertEquals(avg.doubleValue(), 120.5, 0.01); Long id = (Long) s.createQuery("select max(a.id) from Animal a").uniqueResult(); @@ -1208,7 +1303,7 @@ public class ASTParserLoadingTest extends FunctionalTestCase { Transaction t = s.beginTransaction(); Human h = new Human(); h.setBodyWeight( (float) 74.0 ); - h.setHeight(120.5); + h.setHeightInches(120.5); h.setDescription("Me"); h.setName( new Name("Gavin", 'A', "King") ); h.setNickName("Oney"); @@ -1395,6 +1490,40 @@ public class ASTParserLoadingTest extends FunctionalTestCase { txn.commit(); session.close(); } + + public void testFilterWithCustomColumnReadAndWrite() { + Session session = openSession(); + Transaction txn = session.beginTransaction(); + + Human human = new Human(); + human.setName( new Name( "Steve", 'L', "Ebersole" ) ); + human.setHeightInches(73d); + session.save( human ); + + Human friend = new Human(); + friend.setName( new Name( "John", 'Q', "Doe" ) ); + friend.setHeightInches(50d); + session.save( friend ); + + human.setFriends( new ArrayList() ); + friend.setFriends( new ArrayList() ); + human.getFriends().add( friend ); + friend.getFriends().add( human ); + + session.flush(); + + assertEquals( session.createFilter( human.getFriends(), "" ).list().size(), 1 ); + assertEquals( session.createFilter( human.getFriends(), "where this.heightInches < ?" ).setDouble( 0, 51d ).list().size(), 1 ); + assertEquals( session.createFilter( human.getFriends(), "where this.heightInches > ?" ).setDouble( 0, 51d ).list().size(), 0 ); + assertEquals( session.createFilter( human.getFriends(), "where this.heightInches between 49 and 51" ).list().size(), 1 ); + assertEquals( session.createFilter( human.getFriends(), "where this.heightInches not between 49 and 51" ).list().size(), 0 ); + + session.delete(human); + session.delete(friend); + + txn.commit(); + session.close(); + } public void testSelectExpressions() { createTestBaseData(); diff --git a/testsuite/src/test/java/org/hibernate/test/hql/Animal.hbm.xml b/testsuite/src/test/java/org/hibernate/test/hql/Animal.hbm.xml index 5692dce616..173368bf60 100644 --- a/testsuite/src/test/java/org/hibernate/test/hql/Animal.hbm.xml +++ b/testsuite/src/test/java/org/hibernate/test/hql/Animal.hbm.xml @@ -47,8 +47,12 @@ - - + + + @@ -147,4 +151,4 @@ - \ No newline at end of file + diff --git a/testsuite/src/test/java/org/hibernate/test/hql/CriteriaClassicAggregationReturnTest.java b/testsuite/src/test/java/org/hibernate/test/hql/CriteriaClassicAggregationReturnTest.java index 76ec1a646f..cb26d4c486 100644 --- a/testsuite/src/test/java/org/hibernate/test/hql/CriteriaClassicAggregationReturnTest.java +++ b/testsuite/src/test/java/org/hibernate/test/hql/CriteriaClassicAggregationReturnTest.java @@ -43,12 +43,12 @@ public class CriteriaClassicAggregationReturnTest extends QueryTranslatorTestCas assertEquals( "incorrect return type count", 1, translator.getReturnTypes().length ); assertEquals( "incorrect return type", Hibernate.INTEGER, translator.getReturnTypes()[0] ); - translator = createNewQueryTranslator( "select count(h.height) from Human h", sfi() ); + translator = createNewQueryTranslator( "select count(h.heightInches) from Human h", sfi() ); assertEquals( "incorrect return type count", 1, translator.getReturnTypes().length ); assertEquals( "incorrect return type", Hibernate.INTEGER, translator.getReturnTypes()[0] ); // MAX, MIN return the type of the state-field to which they are applied. - translator = createNewQueryTranslator( "select max(h.height) from Human h", sfi() ); + translator = createNewQueryTranslator( "select max(h.heightInches) from Human h", sfi() ); assertEquals( "incorrect return type count", 1, translator.getReturnTypes().length ); assertEquals( "incorrect return type", Hibernate.DOUBLE, translator.getReturnTypes()[0] ); @@ -57,7 +57,7 @@ public class CriteriaClassicAggregationReturnTest extends QueryTranslatorTestCas assertEquals( "incorrect return type", Hibernate.LONG, translator.getReturnTypes()[0] ); // AVG returns Float integrals, and otherwise the field type. - translator = createNewQueryTranslator( "select avg(h.height) from Human h", sfi() ); + translator = createNewQueryTranslator( "select avg(h.heightInches) from Human h", sfi() ); assertEquals( "incorrect return type count", 1, translator.getReturnTypes().length ); assertEquals( "incorrect return type", Hibernate.DOUBLE, translator.getReturnTypes()[0] ); @@ -78,7 +78,7 @@ public class CriteriaClassicAggregationReturnTest extends QueryTranslatorTestCas assertEquals( "incorrect return type count", 1, translator.getReturnTypes().length ); assertEquals( "incorrect return type", Hibernate.INTEGER, translator.getReturnTypes()[0] ); - translator = createNewQueryTranslator( "select sum(h.height) from Human h", sfi() ); + translator = createNewQueryTranslator( "select sum(h.heightInches) from Human h", sfi() ); assertEquals( "incorrect return type count", 1, translator.getReturnTypes().length ); assertEquals( "incorrect return type", Hibernate.DOUBLE, translator.getReturnTypes()[0] ); diff --git a/testsuite/src/test/java/org/hibernate/test/hql/CriteriaHQLAlignmentTest.java b/testsuite/src/test/java/org/hibernate/test/hql/CriteriaHQLAlignmentTest.java index 9da70fc593..d8a1b23def 100644 --- a/testsuite/src/test/java/org/hibernate/test/hql/CriteriaHQLAlignmentTest.java +++ b/testsuite/src/test/java/org/hibernate/test/hql/CriteriaHQLAlignmentTest.java @@ -47,12 +47,12 @@ public class CriteriaHQLAlignmentTest extends QueryTranslatorTestCase { assertEquals( "incorrect return type count", 1, translator.getReturnTypes().length ); assertEquals( "incorrect return type", Hibernate.LONG, translator.getReturnTypes()[0] ); - translator = createNewQueryTranslator( "select count(h.height) from Human h" ); + translator = createNewQueryTranslator( "select count(h.heightInches) from Human h" ); assertEquals( "incorrect return type count", 1, translator.getReturnTypes().length ); assertEquals( "incorrect return type", Hibernate.LONG, translator.getReturnTypes()[0] ); // MAX, MIN return the type of the state-field to which they are applied. - translator = createNewQueryTranslator( "select max(h.height) from Human h" ); + translator = createNewQueryTranslator( "select max(h.heightInches) from Human h" ); assertEquals( "incorrect return type count", 1, translator.getReturnTypes().length ); assertEquals( "incorrect return type", Hibernate.DOUBLE, translator.getReturnTypes()[0] ); @@ -61,7 +61,7 @@ public class CriteriaHQLAlignmentTest extends QueryTranslatorTestCase { assertEquals( "incorrect return type", Hibernate.LONG, translator.getReturnTypes()[0] ); // AVG returns Double. - translator = createNewQueryTranslator( "select avg(h.height) from Human h" ); + translator = createNewQueryTranslator( "select avg(h.heightInches) from Human h" ); assertEquals( "incorrect return type count", 1, translator.getReturnTypes().length ); assertEquals( "incorrect return type", Hibernate.DOUBLE, translator.getReturnTypes()[0] ); @@ -83,7 +83,7 @@ public class CriteriaHQLAlignmentTest extends QueryTranslatorTestCase { assertEquals( "incorrect return type", Hibernate.LONG, translator.getReturnTypes()[0] ); // SUM returns Double when applied to state-fields of floating point types; - translator = createNewQueryTranslator( "select sum(h.height) from Human h" ); + translator = createNewQueryTranslator( "select sum(h.heightInches) from Human h" ); assertEquals( "incorrect return type count", 1, translator.getReturnTypes().length ); assertEquals( "incorrect return type", Hibernate.DOUBLE, translator.getReturnTypes()[0] ); @@ -123,18 +123,18 @@ public class CriteriaHQLAlignmentTest extends QueryTranslatorTestCase { // EJB3: COUNT returns Long Long longValue = (Long) s.createCriteria( Human.class ).setProjection( Projections.rowCount()).uniqueResult(); assertEquals(longValue, new Long(1)); - longValue = (Long) s.createCriteria( Human.class ).setProjection( Projections.count("height")).uniqueResult(); + longValue = (Long) s.createCriteria( Human.class ).setProjection( Projections.count("heightInches")).uniqueResult(); assertEquals(longValue, new Long(1)); // MAX, MIN return the type of the state-field to which they are applied. - Double dblValue = (Double) s.createCriteria( Human.class ).setProjection( Projections.max( "height" )).uniqueResult(); + Double dblValue = (Double) s.createCriteria( Human.class ).setProjection( Projections.max( "heightInches" )).uniqueResult(); assertNotNull(dblValue); longValue = (Long) s.createCriteria( Human.class ).setProjection( Projections.max( "id" )).uniqueResult(); assertNotNull(longValue); // AVG returns Double. - dblValue = (Double) s.createCriteria( Human.class ).setProjection( Projections.avg( "height" )).uniqueResult(); + dblValue = (Double) s.createCriteria( Human.class ).setProjection( Projections.avg( "heightInches" )).uniqueResult(); assertNotNull(dblValue); dblValue = (Double) s.createCriteria( Human.class ).setProjection( Projections.avg( "id" )).uniqueResult(); @@ -151,7 +151,7 @@ public class CriteriaHQLAlignmentTest extends QueryTranslatorTestCase { assertNotNull(longValue); // SUM returns Double when applied to state-fields of floating point types; - dblValue = (Double) s.createCriteria( Human.class ).setProjection( Projections.sum( "height" )).uniqueResult(); + dblValue = (Double) s.createCriteria( Human.class ).setProjection( Projections.sum( "heightInches" )).uniqueResult(); assertNotNull(dblValue); dblValue = (Double) s.createCriteria( Human.class ).setProjection( Projections.sum( "floatValue" )).uniqueResult(); diff --git a/testsuite/src/test/java/org/hibernate/test/hql/Human.java b/testsuite/src/test/java/org/hibernate/test/hql/Human.java index 800d7fb50d..be4a7280d7 100755 --- a/testsuite/src/test/java/org/hibernate/test/hql/Human.java +++ b/testsuite/src/test/java/org/hibernate/test/hql/Human.java @@ -16,7 +16,7 @@ public class Human extends Mammal { private Collection friends; private Collection pets; private Map family; - private double height; + private double heightInches; private BigInteger bigIntegerValue; private BigDecimal bigDecimalValue; @@ -58,11 +58,12 @@ public class Human extends Mammal { this.nickName = nickName; } - public double getHeight() { - return height; + public double getHeightInches() { + return heightInches; } - public void setHeight(double height) { - this.height = height; + + public void setHeightInches(double height) { + this.heightInches = height; } public Map getFamily() { diff --git a/testsuite/src/test/java/org/hibernate/test/hql/Image.hbm.xml b/testsuite/src/test/java/org/hibernate/test/hql/Image.hbm.xml new file mode 100644 index 0000000000..6e2d1e21c7 --- /dev/null +++ b/testsuite/src/test/java/org/hibernate/test/hql/Image.hbm.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + diff --git a/testsuite/src/test/java/org/hibernate/test/hql/Image.java b/testsuite/src/test/java/org/hibernate/test/hql/Image.java new file mode 100644 index 0000000000..4da8b8ab6a --- /dev/null +++ b/testsuite/src/test/java/org/hibernate/test/hql/Image.java @@ -0,0 +1,49 @@ +package org.hibernate.test.hql; + +/** + * @author Rob.Hasselbaum + */ +public class Image { + + private Long id; + private String name; + private double sizeKb; + + /** + * @return the id + */ + public Long getId() { + return id; + } + /** + * @param id the id to set + */ + public void setId(Long id) { + this.id = id; + } + /** + * @return the name + */ + public String getName() { + return name; + } + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + /** + * @return the size in kb + */ + public double getSizeKb() { + return sizeKb; + } + /** + * @param sizeKb the size in kb to set + */ + public void setSizeKb(double sizeKb) { + this.sizeKb = sizeKb; + } + +} diff --git a/testsuite/src/test/java/org/hibernate/test/hql/SimpleEntityWithAssociation.hbm.xml b/testsuite/src/test/java/org/hibernate/test/hql/SimpleEntityWithAssociation.hbm.xml index fc05b681e2..eca296df99 100644 --- a/testsuite/src/test/java/org/hibernate/test/hql/SimpleEntityWithAssociation.hbm.xml +++ b/testsuite/src/test/java/org/hibernate/test/hql/SimpleEntityWithAssociation.hbm.xml @@ -11,6 +11,11 @@ + + + @@ -29,4 +34,4 @@ - \ No newline at end of file + diff --git a/testsuite/src/test/java/org/hibernate/test/hql/SimpleEntityWithAssociation.java b/testsuite/src/test/java/org/hibernate/test/hql/SimpleEntityWithAssociation.java index 4315704381..90b1ebe903 100644 --- a/testsuite/src/test/java/org/hibernate/test/hql/SimpleEntityWithAssociation.java +++ b/testsuite/src/test/java/org/hibernate/test/hql/SimpleEntityWithAssociation.java @@ -1,7 +1,7 @@ package org.hibernate.test.hql; -import java.util.Set; import java.util.HashSet; +import java.util.Set; /** * @author Steve Ebersole @@ -9,6 +9,7 @@ import java.util.HashSet; public class SimpleEntityWithAssociation { private Long id; private String name; + private Integer negatedNumber; private Set associatedEntities = new HashSet(); private Set manyToManyAssociatedEntities = new HashSet(); @@ -34,6 +35,14 @@ public class SimpleEntityWithAssociation { public void setName(String name) { this.name = name; } + + public Integer getNegatedNumber() { + return negatedNumber; + } + + public void setNegatedNumber(Integer negatedNumber) { + this.negatedNumber = negatedNumber; + } public Set getAssociatedEntities() { return associatedEntities; diff --git a/testsuite/src/test/java/org/hibernate/test/instrument/buildtime/InstrumentTest.java b/testsuite/src/test/java/org/hibernate/test/instrument/buildtime/InstrumentTest.java index 7ac62fc0eb..cf83b84a2b 100755 --- a/testsuite/src/test/java/org/hibernate/test/instrument/buildtime/InstrumentTest.java +++ b/testsuite/src/test/java/org/hibernate/test/instrument/buildtime/InstrumentTest.java @@ -5,18 +5,19 @@ import junit.framework.Test; import junit.framework.TestSuite; import org.hibernate.intercept.FieldInterceptionHelper; -import org.hibernate.test.instrument.domain.Document; +import org.hibernate.junit.UnitTestCase; +import org.hibernate.test.instrument.cases.Executable; +import org.hibernate.test.instrument.cases.TestCustomColumnReadAndWrite; import org.hibernate.test.instrument.cases.TestDirtyCheckExecutable; import org.hibernate.test.instrument.cases.TestFetchAllExecutable; -import org.hibernate.test.instrument.cases.TestLazyExecutable; -import org.hibernate.test.instrument.cases.TestLazyManyToOneExecutable; import org.hibernate.test.instrument.cases.TestInjectFieldInterceptorExecutable; import org.hibernate.test.instrument.cases.TestIsPropertyInitializedExecutable; +import org.hibernate.test.instrument.cases.TestLazyExecutable; +import org.hibernate.test.instrument.cases.TestLazyManyToOneExecutable; import org.hibernate.test.instrument.cases.TestLazyPropertyCustomTypeExecutable; import org.hibernate.test.instrument.cases.TestManyToOneProxyExecutable; import org.hibernate.test.instrument.cases.TestSharedPKOneToOneExecutable; -import org.hibernate.test.instrument.cases.Executable; -import org.hibernate.junit.UnitTestCase; +import org.hibernate.test.instrument.domain.Document; /** * @author Gavin King @@ -67,6 +68,10 @@ public class InstrumentTest extends UnitTestCase { execute( new TestSharedPKOneToOneExecutable() ); } + public void testCustomColumnReadAndWrite() throws Exception { + execute( new TestCustomColumnReadAndWrite() ); + } + private void execute(Executable executable) throws Exception { executable.prepare(); try { diff --git a/testsuite/src/test/java/org/hibernate/test/instrument/cases/TestCustomColumnReadAndWrite.java b/testsuite/src/test/java/org/hibernate/test/instrument/cases/TestCustomColumnReadAndWrite.java new file mode 100644 index 0000000000..d9a5be8ce6 --- /dev/null +++ b/testsuite/src/test/java/org/hibernate/test/instrument/cases/TestCustomColumnReadAndWrite.java @@ -0,0 +1,66 @@ +package org.hibernate.test.instrument.cases; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import org.hibernate.Hibernate; +import org.hibernate.Transaction; +import org.hibernate.classic.Session; +import org.hibernate.test.instrument.domain.Document; +import org.hibernate.test.instrument.domain.Folder; +import org.hibernate.test.instrument.domain.Owner; + +/** + * @author Rob.Hasselbaum + */ +public class TestCustomColumnReadAndWrite extends AbstractExecutable { + public void execute() { + Session s = getFactory().openSession(); + Transaction t = s.beginTransaction(); + final double SIZE_IN_KB = 20480; + final double SIZE_IN_MB = SIZE_IN_KB / 1024d; + Owner o = new Owner(); + Document doc = new Document(); + Folder fol = new Folder(); + o.setName("gavin"); + doc.setName("Hibernate in Action"); + doc.setSummary("blah"); + doc.updateText("blah blah"); + fol.setName("books"); + doc.setOwner(o); + doc.setFolder(fol); + doc.setSizeKb(SIZE_IN_KB); + fol.getDocuments().add(doc); + s.persist(o); + s.persist(fol); + t.commit(); + s.close(); + + s = getFactory().openSession(); + t = s.beginTransaction(); + + // Check value conversion on insert + Double sizeViaSql = (Double)s.createSQLQuery("select size_mb from documents").uniqueResult(); + assertEquals( SIZE_IN_MB, sizeViaSql, 0.01d ); + + // Test explicit fetch of all properties + doc = (Document) s.createQuery("from Document fetch all properties").uniqueResult(); + assertTrue( Hibernate.isPropertyInitialized( doc, "sizeKb" ) ); + assertEquals( SIZE_IN_KB, doc.getSizeKb() ); + t.commit(); + s.close(); + + // Test lazy fetch with custom read + s = getFactory().openSession(); + t = s.beginTransaction(); + doc = (Document) s.get( Document.class, doc.getId() ); + assertFalse( Hibernate.isPropertyInitialized( doc, "sizeKb" ) ); + assertEquals( SIZE_IN_KB, doc.getSizeKb() ); + s.delete(doc); + s.delete( doc.getOwner() ); + s.delete( doc.getFolder() ); + t.commit(); + s.close(); + } +} diff --git a/testsuite/src/test/java/org/hibernate/test/instrument/domain/Document.java b/testsuite/src/test/java/org/hibernate/test/instrument/domain/Document.java index e971102097..90af2ce7dc 100755 --- a/testsuite/src/test/java/org/hibernate/test/instrument/domain/Document.java +++ b/testsuite/src/test/java/org/hibernate/test/instrument/domain/Document.java @@ -14,6 +14,7 @@ public class Document { private String text; private Owner owner; private Folder folder; + private double sizeKb; private Date lastTextModification = new Date(); /** * @return Returns the folder. @@ -99,6 +100,18 @@ public class Document { public void setUpperCaseName(String upperCaseName) { this.upperCaseName = upperCaseName; } + /** + * @param sizeKb The size in KBs. + */ + public void setSizeKb(double sizeKb) { + this.sizeKb = sizeKb; + } + /** + * @return The size in KBs. + */ + public double getSizeKb() { + return sizeKb; + } public void updateText(String newText) { if ( !newText.equals(text) ) { diff --git a/testsuite/src/test/java/org/hibernate/test/instrument/domain/Documents.hbm.xml b/testsuite/src/test/java/org/hibernate/test/instrument/domain/Documents.hbm.xml index 3bdb33f91b..d15a7eb8e0 100755 --- a/testsuite/src/test/java/org/hibernate/test/instrument/domain/Documents.hbm.xml +++ b/testsuite/src/test/java/org/hibernate/test/instrument/domain/Documents.hbm.xml @@ -53,6 +53,11 @@ + + + diff --git a/testsuite/src/test/java/org/hibernate/test/instrument/runtime/AbstractTransformingClassLoaderInstrumentTestCase.java b/testsuite/src/test/java/org/hibernate/test/instrument/runtime/AbstractTransformingClassLoaderInstrumentTestCase.java index cf9288a532..c3922054b1 100644 --- a/testsuite/src/test/java/org/hibernate/test/instrument/runtime/AbstractTransformingClassLoaderInstrumentTestCase.java +++ b/testsuite/src/test/java/org/hibernate/test/instrument/runtime/AbstractTransformingClassLoaderInstrumentTestCase.java @@ -82,6 +82,10 @@ public abstract class AbstractTransformingClassLoaderInstrumentTestCase extends executeExecutable( "org.hibernate.test.instrument.cases.TestSharedPKOneToOneExecutable" ); } + public void testCustomColumnReadAndWrite() { + executeExecutable( "org.hibernate.test.instrument.cases.TestCustomColumnReadAndWrite" ); + } + // reflection code to ensure isolation into the created classloader ~~~~~~~ private static final Class[] SIG = new Class[] {}; diff --git a/testsuite/src/test/java/org/hibernate/test/instrument/runtime/CGLIBInstrumentationTest.java b/testsuite/src/test/java/org/hibernate/test/instrument/runtime/CGLIBInstrumentationTest.java index 5c2ea03146..289f6c2cfe 100644 --- a/testsuite/src/test/java/org/hibernate/test/instrument/runtime/CGLIBInstrumentationTest.java +++ b/testsuite/src/test/java/org/hibernate/test/instrument/runtime/CGLIBInstrumentationTest.java @@ -1,9 +1,10 @@ package org.hibernate.test.instrument.runtime; +import junit.framework.Test; +import junit.framework.TestSuite; + import org.hibernate.bytecode.BytecodeProvider; import org.hibernate.bytecode.cglib.BytecodeProviderImpl; -import junit.framework.Test; -import junit.framework.TestSuite; /** * @author Steve Ebersole @@ -52,4 +53,9 @@ public class CGLIBInstrumentationTest extends AbstractTransformingClassLoaderIns public void testSharedPKOneToOne() { super.testSharedPKOneToOne(); } + + public void testCustomColumnReadAndWrite() { + super.testCustomColumnReadAndWrite(); + } + } diff --git a/testsuite/src/test/java/org/hibernate/test/instrument/runtime/JavassistInstrumentationTest.java b/testsuite/src/test/java/org/hibernate/test/instrument/runtime/JavassistInstrumentationTest.java index 2639983b43..b84adfc7b2 100644 --- a/testsuite/src/test/java/org/hibernate/test/instrument/runtime/JavassistInstrumentationTest.java +++ b/testsuite/src/test/java/org/hibernate/test/instrument/runtime/JavassistInstrumentationTest.java @@ -1,11 +1,12 @@ //$Id: $ package org.hibernate.test.instrument.runtime; -import org.hibernate.bytecode.BytecodeProvider; -import org.hibernate.bytecode.javassist.BytecodeProviderImpl; import junit.framework.Test; import junit.framework.TestSuite; +import org.hibernate.bytecode.BytecodeProvider; +import org.hibernate.bytecode.javassist.BytecodeProviderImpl; + /** * @author Steve Ebersole */ @@ -53,4 +54,9 @@ public class JavassistInstrumentationTest extends AbstractTransformingClassLoade public void testSharedPKOneToOne() { super.testSharedPKOneToOne(); } + + public void testCustomColumnReadAndWrite() { + super.testCustomColumnReadAndWrite(); + } + } diff --git a/testsuite/src/test/java/org/hibernate/test/join/JoinTest.java b/testsuite/src/test/java/org/hibernate/test/join/JoinTest.java index e8ca46716d..acb45e4ab2 100755 --- a/testsuite/src/test/java/org/hibernate/test/join/JoinTest.java +++ b/testsuite/src/test/java/org/hibernate/test/join/JoinTest.java @@ -9,6 +9,7 @@ import junit.framework.Test; import org.hibernate.Hibernate; import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.criterion.Restrictions; import org.hibernate.junit.functional.FunctionalTestCase; import org.hibernate.junit.functional.FunctionalTestClassTestSuite; @@ -130,6 +131,82 @@ public class JoinTest extends FunctionalTestCase { t.commit(); s.close(); } + + public void testCustomColumnReadAndWrite() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + final double HEIGHT_INCHES = 73; + final double HEIGHT_CENTIMETERS = HEIGHT_INCHES * 2.54d; + Person p = new Person(); + p.setName("Emmanuel"); + p.setSex('M'); + p.setHeightInches(HEIGHT_INCHES); + s.persist(p); + final double PASSWORD_EXPIRY_WEEKS = 4; + final double PASSWORD_EXPIRY_DAYS = PASSWORD_EXPIRY_WEEKS * 7d; + User u = new User(); + u.setName("Steve"); + u.setSex('M'); + u.setPasswordExpiryDays(PASSWORD_EXPIRY_DAYS); + s.persist(u); + s.flush(); + + // Test value conversion during insert + Double heightViaSql = (Double)s.createSQLQuery("select height_centimeters from person where name='Emmanuel'").uniqueResult(); + assertEquals(HEIGHT_CENTIMETERS, heightViaSql, 0.01d); + Double expiryViaSql = (Double)s.createSQLQuery("select pwd_expiry_weeks from t_user where person_id=?") + .setLong(0, u.getId()) + .uniqueResult(); + assertEquals(PASSWORD_EXPIRY_WEEKS, expiryViaSql, 0.01d); + + // Test projection + Double heightViaHql = (Double)s.createQuery("select p.heightInches from Person p where p.name = 'Emmanuel'").uniqueResult(); + assertEquals(HEIGHT_INCHES, heightViaHql, 0.01d); + Double expiryViaHql = (Double)s.createQuery("select u.passwordExpiryDays from User u where u.name = 'Steve'").uniqueResult(); + assertEquals(PASSWORD_EXPIRY_DAYS, expiryViaHql, 0.01d); + + // Test restriction and entity load via criteria + p = (Person)s.createCriteria(Person.class) + .add(Restrictions.between("heightInches", HEIGHT_INCHES - 0.01d, HEIGHT_INCHES + 0.01d)) + .uniqueResult(); + assertEquals(HEIGHT_INCHES, p.getHeightInches(), 0.01d); + u = (User)s.createCriteria(User.class) + .add(Restrictions.between("passwordExpiryDays", PASSWORD_EXPIRY_DAYS - 0.01d, PASSWORD_EXPIRY_DAYS + 0.01d)) + .uniqueResult(); + assertEquals(PASSWORD_EXPIRY_DAYS, u.getPasswordExpiryDays(), 0.01d); + + // Test predicate and entity load via HQL + p = (Person)s.createQuery("from Person p where p.heightInches between ? and ?") + .setDouble(0, HEIGHT_INCHES - 0.01d) + .setDouble(1, HEIGHT_INCHES + 0.01d) + .uniqueResult(); + assertEquals(HEIGHT_INCHES, p.getHeightInches(), 0.01d); + u = (User)s.createQuery("from User u where u.passwordExpiryDays between ? and ?") + .setDouble(0, PASSWORD_EXPIRY_DAYS - 0.01d) + .setDouble(1, PASSWORD_EXPIRY_DAYS + 0.01d) + .uniqueResult(); + assertEquals(PASSWORD_EXPIRY_DAYS, u.getPasswordExpiryDays(), 0.01d); + + // Test update + p.setHeightInches(1); + u.setPasswordExpiryDays(7d); + s.flush(); + heightViaSql = (Double)s.createSQLQuery("select height_centimeters from person where name='Emmanuel'").uniqueResult(); + assertEquals(2.54d, heightViaSql, 0.01d); + expiryViaSql = (Double)s.createSQLQuery("select pwd_expiry_weeks from t_user where person_id=?") + .setLong(0, u.getId()) + .uniqueResult(); + assertEquals(1d, expiryViaSql, 0.01d); + + s.delete(p); + s.delete(u); + assertTrue( s.createQuery("from Person").list().isEmpty() ); + + t.commit(); + s.close(); + + } + } diff --git a/testsuite/src/test/java/org/hibernate/test/join/Person.hbm.xml b/testsuite/src/test/java/org/hibernate/test/join/Person.hbm.xml index 30962a773c..10e2245a60 100755 --- a/testsuite/src/test/java/org/hibernate/test/join/Person.hbm.xml +++ b/testsuite/src/test/java/org/hibernate/test/join/Person.hbm.xml @@ -36,6 +36,12 @@ + + + @@ -65,6 +71,11 @@ + + + diff --git a/testsuite/src/test/java/org/hibernate/test/join/Person.java b/testsuite/src/test/java/org/hibernate/test/join/Person.java index c37acc50ec..ccaed293b3 100755 --- a/testsuite/src/test/java/org/hibernate/test/join/Person.java +++ b/testsuite/src/test/java/org/hibernate/test/join/Person.java @@ -11,6 +11,7 @@ public class Person { private String address; private String zip; private String country; + private double heightInches; private char sex; /** @@ -77,6 +78,18 @@ public class Person { public void setZip(String zip) { this.zip = zip; } + /** + * @return the The height in inches. + */ + public double getHeightInches() { + return heightInches; + } + /** + * @param heightInches The height in inches. + */ + public void setHeightInches(double heightInches) { + this.heightInches = heightInches; + } /** * @param address The address to set. */ diff --git a/testsuite/src/test/java/org/hibernate/test/join/User.java b/testsuite/src/test/java/org/hibernate/test/join/User.java index 772b5b348a..5197123d46 100644 --- a/testsuite/src/test/java/org/hibernate/test/join/User.java +++ b/testsuite/src/test/java/org/hibernate/test/join/User.java @@ -7,6 +7,7 @@ package org.hibernate.test.join; public class User extends Person { private String login; private String silly; + private Double passwordExpiryDays; /** * @return Returns the login. @@ -20,4 +21,16 @@ public class User extends Person { public void setLogin(String login) { this.login = login; } + /** + * @return The password expiry policy in days. + */ + public Double getPasswordExpiryDays() { + return passwordExpiryDays; + } + /** + * @param passwordExpiryDays The password expiry policy in days. + */ + public void setPasswordExpiryDays(Double passwordExpiryDays) { + this.passwordExpiryDays = passwordExpiryDays; + } } diff --git a/testsuite/src/test/java/org/hibernate/test/joinedsubclass/Employee.java b/testsuite/src/test/java/org/hibernate/test/joinedsubclass/Employee.java index 6f8d5f7bab..32c3b53e68 100755 --- a/testsuite/src/test/java/org/hibernate/test/joinedsubclass/Employee.java +++ b/testsuite/src/test/java/org/hibernate/test/joinedsubclass/Employee.java @@ -9,6 +9,7 @@ import java.math.BigDecimal; public class Employee extends Person { private String title; private BigDecimal salary; + private double passwordExpiryDays; private Employee manager; /** * @return Returns the title. @@ -46,4 +47,16 @@ public class Employee extends Person { public void setSalary(BigDecimal salary) { this.salary = salary; } + /** + * @return The password expiry policy in days. + */ + public double getPasswordExpiryDays() { + return passwordExpiryDays; + } + /** + * @param passwordExpiryDays The password expiry policy in days. + */ + public void setPasswordExpiryDays(double passwordExpiryDays) { + this.passwordExpiryDays = passwordExpiryDays; + } } diff --git a/testsuite/src/test/java/org/hibernate/test/joinedsubclass/JoinedSubclassTest.java b/testsuite/src/test/java/org/hibernate/test/joinedsubclass/JoinedSubclassTest.java index ae58036f08..ed34d5d5f7 100755 --- a/testsuite/src/test/java/org/hibernate/test/joinedsubclass/JoinedSubclassTest.java +++ b/testsuite/src/test/java/org/hibernate/test/joinedsubclass/JoinedSubclassTest.java @@ -179,6 +179,79 @@ public class JoinedSubclassTest extends FunctionalTestCase { t.commit(); s.close(); } + + public void testCustomColumnReadAndWrite() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + final double HEIGHT_INCHES = 73; + final double HEIGHT_CENTIMETERS = HEIGHT_INCHES * 2.54d; + Person p = new Person(); + p.setName("Emmanuel"); + p.setSex('M'); + p.setHeightInches(HEIGHT_INCHES); + s.persist(p); + final double PASSWORD_EXPIRY_WEEKS = 4; + final double PASSWORD_EXPIRY_DAYS = PASSWORD_EXPIRY_WEEKS * 7d; + Employee e = new Employee(); + e.setName("Steve"); + e.setSex('M'); + e.setTitle("Mr"); + e.setPasswordExpiryDays(PASSWORD_EXPIRY_DAYS); + s.persist(e); + s.flush(); + + // Test value conversion during insert + Double heightViaSql = (Double)s.createSQLQuery("select height_centimeters from JPerson where name='Emmanuel'").uniqueResult(); + assertEquals(HEIGHT_CENTIMETERS, heightViaSql, 0.01d); + Double expiryViaSql = (Double)s.createSQLQuery("select pwd_expiry_weeks from JEmployee where person_id=?") + .setLong(0, e.getId()) + .uniqueResult(); + assertEquals(PASSWORD_EXPIRY_WEEKS, expiryViaSql, 0.01d); + + // Test projection + Double heightViaHql = (Double)s.createQuery("select p.heightInches from Person p where p.name = 'Emmanuel'").uniqueResult(); + assertEquals(HEIGHT_INCHES, heightViaHql, 0.01d); + Double expiryViaHql = (Double)s.createQuery("select e.passwordExpiryDays from Employee e where e.name = 'Steve'").uniqueResult(); + assertEquals(PASSWORD_EXPIRY_DAYS, expiryViaHql, 0.01d); + + // Test restriction and entity load via criteria + p = (Person)s.createCriteria(Person.class) + .add(Restrictions.between("heightInches", HEIGHT_INCHES - 0.01d, HEIGHT_INCHES + 0.01d)) + .uniqueResult(); + assertEquals(HEIGHT_INCHES, p.getHeightInches(), 0.01d); + e = (Employee)s.createCriteria(Employee.class) + .add(Restrictions.between("passwordExpiryDays", PASSWORD_EXPIRY_DAYS - 0.01d, PASSWORD_EXPIRY_DAYS + 0.01d)) + .uniqueResult(); + assertEquals(PASSWORD_EXPIRY_DAYS, e.getPasswordExpiryDays(), 0.01d); + + // Test predicate and entity load via HQL + p = (Person)s.createQuery("from Person p where p.heightInches between ? and ?") + .setDouble(0, HEIGHT_INCHES - 0.01d) + .setDouble(1, HEIGHT_INCHES + 0.01d) + .uniqueResult(); + assertEquals(HEIGHT_INCHES, p.getHeightInches(), 0.01d); + e = (Employee)s.createQuery("from Employee e where e.passwordExpiryDays between ? and ?") + .setDouble(0, PASSWORD_EXPIRY_DAYS - 0.01d) + .setDouble(1, PASSWORD_EXPIRY_DAYS + 0.01d) + .uniqueResult(); + assertEquals(PASSWORD_EXPIRY_DAYS, e.getPasswordExpiryDays(), 0.01d); + + // Test update + p.setHeightInches(1); + e.setPasswordExpiryDays(7); + s.flush(); + heightViaSql = (Double)s.createSQLQuery("select height_centimeters from JPerson where name='Emmanuel'").uniqueResult(); + assertEquals(2.54d, heightViaSql, 0.01d); + expiryViaSql = (Double)s.createSQLQuery("select pwd_expiry_weeks from JEmployee where person_id=?") + .setLong(0, e.getId()) + .uniqueResult(); + assertEquals(1d, expiryViaSql, 0.01d); + s.delete(p); + s.delete(e); + t.commit(); + s.close(); + + } public void testLockingJoinedSubclass() { Session s = openSession(); diff --git a/testsuite/src/test/java/org/hibernate/test/joinedsubclass/Person.hbm.xml b/testsuite/src/test/java/org/hibernate/test/joinedsubclass/Person.hbm.xml index 6603b53457..2e8d070096 100755 --- a/testsuite/src/test/java/org/hibernate/test/joinedsubclass/Person.hbm.xml +++ b/testsuite/src/test/java/org/hibernate/test/joinedsubclass/Person.hbm.xml @@ -35,6 +35,12 @@ + + + @@ -49,6 +55,12 @@ length="20"/> + + + diff --git a/testsuite/src/test/java/org/hibernate/test/joinedsubclass/Person.java b/testsuite/src/test/java/org/hibernate/test/joinedsubclass/Person.java index a3e037ab53..f14eefbc0b 100755 --- a/testsuite/src/test/java/org/hibernate/test/joinedsubclass/Person.java +++ b/testsuite/src/test/java/org/hibernate/test/joinedsubclass/Person.java @@ -10,6 +10,7 @@ public class Person { private String name; private char sex; private int version; + private double heightInches; private Address address = new Address(); /** * @return Returns the address. @@ -68,6 +69,20 @@ public class Person { this.name = identity; } + /** + * @return Returns the height in inches. + */ + public double getHeightInches() { + return heightInches; + } + + /** + * @param heightInches The height in inches to set. + */ + public void setHeightInches(double heightInches) { + this.heightInches = heightInches; + } + public int getVersion() { return version; } diff --git a/testsuite/src/test/java/org/hibernate/test/subselect/Alien.java b/testsuite/src/test/java/org/hibernate/test/subselect/Alien.java index 262f14abd8..b28df4c57b 100755 --- a/testsuite/src/test/java/org/hibernate/test/subselect/Alien.java +++ b/testsuite/src/test/java/org/hibernate/test/subselect/Alien.java @@ -9,6 +9,7 @@ public class Alien { private String identity; private String planet; private String species; + private double heightInches; public void setIdentity(String identity) { this.identity = identity; @@ -28,6 +29,12 @@ public class Alien { public String getPlanet() { return planet; } + public double getHeightInches() { + return heightInches; + } + public void setHeightInches(double heightInches) { + this.heightInches = heightInches; + } public void setId(Long id) { this.id = id; } diff --git a/testsuite/src/test/java/org/hibernate/test/subselect/Being.java b/testsuite/src/test/java/org/hibernate/test/subselect/Being.java index df52aced99..bf17cc7883 100755 --- a/testsuite/src/test/java/org/hibernate/test/subselect/Being.java +++ b/testsuite/src/test/java/org/hibernate/test/subselect/Being.java @@ -9,6 +9,7 @@ public class Being { private String identity; private String location; private String species; + private double heightInches; public void setLocation(String location) { this.location = location; @@ -28,4 +29,10 @@ public class Being { public String getIdentity() { return identity; } + public double getHeightInches() { + return heightInches; + } + public void setHeightInches(double heightInches) { + this.heightInches = heightInches; + } } diff --git a/testsuite/src/test/java/org/hibernate/test/subselect/Beings.hbm.xml b/testsuite/src/test/java/org/hibernate/test/subselect/Beings.hbm.xml index e1942cfc52..6003a6a683 100755 --- a/testsuite/src/test/java/org/hibernate/test/subselect/Beings.hbm.xml +++ b/testsuite/src/test/java/org/hibernate/test/subselect/Beings.hbm.xml @@ -36,6 +36,12 @@ not-null="true" update="false"/> + + + @@ -53,16 +59,22 @@ + + + - select bid, name as ident, address as loc, 'human' as species + select bid, name as ident, address as loc, 'human' as species, height_centimeters from humans union - select bid, ident, planet as loc, species + select bid, ident, planet as loc, species, height_centimeters from aliens @@ -77,6 +89,11 @@ + + + diff --git a/testsuite/src/test/java/org/hibernate/test/subselect/Human.java b/testsuite/src/test/java/org/hibernate/test/subselect/Human.java index b7ffa72be9..285c9ff15a 100755 --- a/testsuite/src/test/java/org/hibernate/test/subselect/Human.java +++ b/testsuite/src/test/java/org/hibernate/test/subselect/Human.java @@ -9,6 +9,7 @@ public class Human { private String name; private char sex; private String address; + private double heightInches; public void setAddress(String address) { this.address = address; @@ -34,4 +35,10 @@ public class Human { public Long getId() { return id; } + public double getHeightInches() { + return heightInches; + } + public void setHeightInches(double heightInches) { + this.heightInches = heightInches; + } } diff --git a/testsuite/src/test/java/org/hibernate/test/subselect/SubselectTest.java b/testsuite/src/test/java/org/hibernate/test/subselect/SubselectTest.java index fe21ffd971..453c798148 100755 --- a/testsuite/src/test/java/org/hibernate/test/subselect/SubselectTest.java +++ b/testsuite/src/test/java/org/hibernate/test/subselect/SubselectTest.java @@ -8,6 +8,7 @@ import junit.framework.Test; import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.criterion.Restrictions; import org.hibernate.junit.functional.FunctionalTestCase; import org.hibernate.junit.functional.FunctionalTestClassTestSuite; @@ -67,6 +68,57 @@ public class SubselectTest extends FunctionalTestCase { t.commit(); s.close(); } + + public void testCustomColumnReadAndWrite() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + final double HUMAN_INCHES = 73; + final double ALIEN_INCHES = 931; + final double HUMAN_CENTIMETERS = HUMAN_INCHES * 2.54d; + final double ALIEN_CENTIMETERS = ALIEN_INCHES * 2.54d; + Human gavin = new Human(); + gavin.setName( "gavin" ); + gavin.setSex( 'M' ); + gavin.setAddress( "Melbourne, Australia" ); + gavin.setHeightInches( HUMAN_INCHES ); + Alien x23y4 = new Alien(); + x23y4.setIdentity( "x23y4$$hu%3" ); + x23y4.setPlanet( "Mars" ); + x23y4.setSpecies( "martian" ); + x23y4.setHeightInches( ALIEN_INCHES ); + s.save(gavin); + s.save(x23y4); + s.flush(); + + // Test value conversion during insert + Double humanHeightViaSql = (Double)s.createSQLQuery("select height_centimeters from humans").uniqueResult(); + assertEquals(HUMAN_CENTIMETERS, humanHeightViaSql, 0.01d); + Double alienHeightViaSql = (Double)s.createSQLQuery("select height_centimeters from aliens").uniqueResult(); + assertEquals(ALIEN_CENTIMETERS, alienHeightViaSql, 0.01d); + s.clear(); + + // Test projection + Double heightViaHql = (Double)s.createQuery("select heightInches from Being b where b.identity = 'gavin'").uniqueResult(); + assertEquals(HUMAN_INCHES, heightViaHql, 0.01d); + + // Test restriction and entity load via criteria + Being b = (Being)s.createCriteria(Being.class) + .add(Restrictions.between("heightInches", HUMAN_INCHES - 0.01d, HUMAN_INCHES + 0.01d)) + .uniqueResult(); + assertEquals(HUMAN_INCHES, b.getHeightInches(), 0.01d); + + // Test predicate and entity load via HQL + b = (Being)s.createQuery("from Being b where b.heightInches between ? and ?") + .setDouble(0, ALIEN_INCHES - 0.01d) + .setDouble(1, ALIEN_INCHES + 0.01d) + .uniqueResult(); + assertEquals(ALIEN_INCHES, b.getHeightInches(), 0.01d); + s.delete(gavin); + s.delete(x23y4); + t.commit(); + s.close(); + + } } diff --git a/testsuite/src/test/java/org/hibernate/test/unionsubclass2/Employee.java b/testsuite/src/test/java/org/hibernate/test/unionsubclass2/Employee.java index 3ae9dd5391..1f84ff02e2 100755 --- a/testsuite/src/test/java/org/hibernate/test/unionsubclass2/Employee.java +++ b/testsuite/src/test/java/org/hibernate/test/unionsubclass2/Employee.java @@ -9,6 +9,7 @@ import java.math.BigDecimal; public class Employee extends Person { private String title; private BigDecimal salary; + private double passwordExpiryDays; private Employee manager; /** * @return Returns the title. @@ -46,4 +47,16 @@ public class Employee extends Person { public void setSalary(BigDecimal salary) { this.salary = salary; } + /** + * @return The password expiry policy in days. + */ + public double getPasswordExpiryDays() { + return passwordExpiryDays; + } + /** + * @param passwordExpiryDays The password expiry policy in days. + */ + public void setPasswordExpiryDays(double passwordExpiryDays) { + this.passwordExpiryDays = passwordExpiryDays; + } } diff --git a/testsuite/src/test/java/org/hibernate/test/unionsubclass2/Person.hbm.xml b/testsuite/src/test/java/org/hibernate/test/unionsubclass2/Person.hbm.xml index 4b364be82d..386995a428 100755 --- a/testsuite/src/test/java/org/hibernate/test/unionsubclass2/Person.hbm.xml +++ b/testsuite/src/test/java/org/hibernate/test/unionsubclass2/Person.hbm.xml @@ -34,6 +34,12 @@ + + + @@ -47,6 +53,12 @@ length="20"/> + + + diff --git a/testsuite/src/test/java/org/hibernate/test/unionsubclass2/Person.java b/testsuite/src/test/java/org/hibernate/test/unionsubclass2/Person.java index ef2f8eff65..5d029c2eb9 100755 --- a/testsuite/src/test/java/org/hibernate/test/unionsubclass2/Person.java +++ b/testsuite/src/test/java/org/hibernate/test/unionsubclass2/Person.java @@ -9,6 +9,7 @@ public class Person { private long id; private String name; private char sex; + private double heightInches; private Address address = new Address(); /** * @return Returns the address. @@ -66,5 +67,16 @@ public class Person { public void setName(String identity) { this.name = identity; } - + /** + * @return Returns the height in inches. + */ + public double getHeightInches() { + return heightInches; + } + /** + * @param heightInches The height in inches to set. + */ + public void setHeightInches(double heightInches) { + this.heightInches = heightInches; + } } diff --git a/testsuite/src/test/java/org/hibernate/test/unionsubclass2/UnionSubclassTest.java b/testsuite/src/test/java/org/hibernate/test/unionsubclass2/UnionSubclassTest.java index d579a97a5d..e947b8baca 100755 --- a/testsuite/src/test/java/org/hibernate/test/unionsubclass2/UnionSubclassTest.java +++ b/testsuite/src/test/java/org/hibernate/test/unionsubclass2/UnionSubclassTest.java @@ -149,5 +149,79 @@ public class UnionSubclassTest extends FunctionalTestCase { s.close(); } + public void testCustomColumnReadAndWrite() { + Session s = openSession(); + Transaction t = s.beginTransaction(); + final double HEIGHT_INCHES = 73; + final double HEIGHT_CENTIMETERS = HEIGHT_INCHES * 2.54d; + Person p = new Person(); + p.setName("Emmanuel"); + p.setSex('M'); + p.setHeightInches(HEIGHT_INCHES); + s.persist(p); + final double PASSWORD_EXPIRY_WEEKS = 4; + final double PASSWORD_EXPIRY_DAYS = PASSWORD_EXPIRY_WEEKS * 7d; + Employee e = new Employee(); + e.setName("Steve"); + e.setSex('M'); + e.setTitle("Mr"); + e.setPasswordExpiryDays(PASSWORD_EXPIRY_DAYS); + s.persist(e); + s.flush(); + + // Test value conversion during insert + Double heightViaSql = (Double)s.createSQLQuery("select height_centimeters from UPerson where name='Emmanuel'").uniqueResult(); + assertEquals(HEIGHT_CENTIMETERS, heightViaSql, 0.01d); + Double expiryViaSql = (Double)s.createSQLQuery("select pwd_expiry_weeks from UEmployee where person_id=?") + .setLong(0, e.getId()) + .uniqueResult(); + assertEquals(PASSWORD_EXPIRY_WEEKS, expiryViaSql, 0.01d); + + // Test projection + Double heightViaHql = (Double)s.createQuery("select p.heightInches from Person p where p.name = 'Emmanuel'").uniqueResult(); + assertEquals(HEIGHT_INCHES, heightViaHql, 0.01d); + Double expiryViaHql = (Double)s.createQuery("select e.passwordExpiryDays from Employee e where e.name = 'Steve'").uniqueResult(); + assertEquals(PASSWORD_EXPIRY_DAYS, expiryViaHql, 0.01d); + + // Test restriction and entity load via criteria + p = (Person)s.createCriteria(Person.class) + .add(Restrictions.between("heightInches", HEIGHT_INCHES - 0.01d, HEIGHT_INCHES + 0.01d)) + .uniqueResult(); + assertEquals(HEIGHT_INCHES, p.getHeightInches(), 0.01d); + e = (Employee)s.createCriteria(Employee.class) + .add(Restrictions.between("passwordExpiryDays", PASSWORD_EXPIRY_DAYS - 0.01d, PASSWORD_EXPIRY_DAYS + 0.01d)) + .uniqueResult(); + assertEquals(PASSWORD_EXPIRY_DAYS, e.getPasswordExpiryDays(), 0.01d); + + // Test predicate and entity load via HQL + p = (Person)s.createQuery("from Person p where p.heightInches between ? and ?") + .setDouble(0, HEIGHT_INCHES - 0.01d) + .setDouble(1, HEIGHT_INCHES + 0.01d) + .uniqueResult(); + assertEquals(HEIGHT_INCHES, p.getHeightInches(), 0.01d); + e = (Employee)s.createQuery("from Employee e where e.passwordExpiryDays between ? and ?") + .setDouble(0, PASSWORD_EXPIRY_DAYS - 0.01d) + .setDouble(1, PASSWORD_EXPIRY_DAYS + 0.01d) + .uniqueResult(); + assertEquals(PASSWORD_EXPIRY_DAYS, e.getPasswordExpiryDays(), 0.01d); + + // Test update + p.setHeightInches(1); + e.setPasswordExpiryDays(7); + s.flush(); + heightViaSql = (Double)s.createSQLQuery("select height_centimeters from UPerson where name='Emmanuel'").uniqueResult(); + assertEquals(2.54d, heightViaSql, 0.01d); + expiryViaSql = (Double)s.createSQLQuery("select pwd_expiry_weeks from UEmployee where person_id=?") + .setLong(0, e.getId()) + .uniqueResult(); + assertEquals(1d, expiryViaSql, 0.01d); + s.delete(p); + s.delete(e); + t.commit(); + s.close(); + + } + + }