[hibernate-commits] Hibernate SVN: r10852 - in trunk/Hibernate3: doc/reference/en/modules src/org/hibernate/hql/ast/tree src/org/hibernate/persister/entity src/org/hibernate/tuple/entity test/org/hibernate/test test/org/hibernate/test/idprops test/org/hibernate/test/legacy

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Tue Nov 21 12:39:20 EST 2006


Author: steve.ebersole at jboss.com
Date: 2006-11-21 12:39:14 -0500 (Tue, 21 Nov 2006)
New Revision: 10852

Added:
   trunk/Hibernate3/test/org/hibernate/test/idprops/
   trunk/Hibernate3/test/org/hibernate/test/idprops/IdentifierPropertyReferencesTest.java
   trunk/Hibernate3/test/org/hibernate/test/idprops/LineItem.java
   trunk/Hibernate3/test/org/hibernate/test/idprops/LineItemPK.java
   trunk/Hibernate3/test/org/hibernate/test/idprops/Mapping.hbm.xml
   trunk/Hibernate3/test/org/hibernate/test/idprops/Order.java
   trunk/Hibernate3/test/org/hibernate/test/idprops/Person.java
Modified:
   trunk/Hibernate3/doc/reference/en/modules/query_hql.xml
   trunk/Hibernate3/src/org/hibernate/hql/ast/tree/DotNode.java
   trunk/Hibernate3/src/org/hibernate/hql/ast/tree/FromElement.java
   trunk/Hibernate3/src/org/hibernate/persister/entity/AbstractEntityPersister.java
   trunk/Hibernate3/src/org/hibernate/persister/entity/AbstractPropertyMapping.java
   trunk/Hibernate3/src/org/hibernate/persister/entity/EntityPersister.java
   trunk/Hibernate3/src/org/hibernate/tuple/entity/EntityMetamodel.java
   trunk/Hibernate3/test/org/hibernate/test/legacy/CustomPersister.java
Log:
HHH-1851 : relax special 'id' property handling

Modified: trunk/Hibernate3/doc/reference/en/modules/query_hql.xml
===================================================================
--- trunk/Hibernate3/doc/reference/en/modules/query_hql.xml	2006-11-21 17:38:43 UTC (rev 10851)
+++ trunk/Hibernate3/doc/reference/en/modules/query_hql.xml	2006-11-21 17:39:14 UTC (rev 10852)
@@ -1,6 +1,6 @@
-<chapter id="queryhql">
+<chapter id="queryhql" revision="1">
     <title>HQL: The Hibernate Query Language</title>
-    
+
     <para>
         Hibernate is equipped with an extremely powerful query language that (quite intentionally)
         looks very much like SQL. But don't be fooled by the syntax; HQL is fully object-oriented,
@@ -20,12 +20,12 @@
             <literal>foo.barSet</literal> is not
             <literal>foo.BARSET</literal>.
         </para>
-        
+
         <para>
-            This manual uses lowercase HQL keywords. Some users find queries with uppercase keywords 
+            This manual uses lowercase HQL keywords. Some users find queries with uppercase keywords
             more readable, but we find this convention ugly when embedded in Java code.
         </para>
-        
+
     </sect1>
 
     <sect1 id="queryhql-from">
@@ -34,17 +34,17 @@
         <para>
             The simplest possible Hibernate query is of the form:
         </para>
-        
+
         <programlisting><![CDATA[from eg.Cat]]></programlisting>
-        
+
         <para>
             which simply returns all instances of the class <literal>eg.Cat</literal>.
             We don't usually need to qualify the class name, since <literal>auto-import</literal>
             is the default. So we almost always just write:
         </para>
-        
+
         <programlisting><![CDATA[from Cat]]></programlisting>
-        
+
         <para>
             Most of the time, you will need to assign an <emphasis>alias</emphasis>, since
             you will want to refer to the <literal>Cat</literal> in other parts of the
@@ -58,33 +58,33 @@
             instances, so we could use that alias later in the query. The <literal>as</literal>
             keyword is optional; we could also write:
         </para>
-        
+
         <programlisting><![CDATA[from Cat cat]]></programlisting>
-        
+
         <para>
             Multiple classes may appear, resulting in a cartesian product or "cross" join.
         </para>
-        
+
         <programlisting><![CDATA[from Formula, Parameter]]></programlisting>
         <programlisting><![CDATA[from Formula as form, Parameter as param]]></programlisting>
-        
+
         <para>
             It is considered good practice to name query aliases using an initial lowercase,
             consistent with Java naming standards for local variables
             (eg. <literal>domesticCat</literal>).
         </para>
-        
+
     </sect1>
 
     <sect1 id="queryhql-joins" revision="2">
         <title>Associations and joins</title>
 
         <para>
-            We may also assign aliases to associated entities, or even to elements of a 
+            We may also assign aliases to associated entities, or even to elements of a
             collection of values, using a <literal>join</literal>.
         </para>
 
-        <programlisting><![CDATA[from Cat as cat 
+        <programlisting><![CDATA[from Cat as cat
     inner join cat.mate as mate
     left outer join cat.kittens as kitten]]></programlisting>
 
@@ -118,50 +118,50 @@
                 </para>
             </listitem>
         </itemizedlist>
-    
+
         <para>
-            The <literal>inner join</literal>, <literal>left outer join</literal> and 
+            The <literal>inner join</literal>, <literal>left outer join</literal> and
             <literal>right outer join</literal> constructs may be abbreviated.
         </para>
 
-        <programlisting><![CDATA[from Cat as cat 
+        <programlisting><![CDATA[from Cat as cat
     join cat.mate as mate
     left join cat.kittens as kitten]]></programlisting>
-    
+
         <para>
             You may supply extra join conditions using the HQL <literal>with</literal>
             keyword.
         </para>
 
-        <programlisting><![CDATA[from Cat as cat 
-    left join cat.kittens as kitten 
+        <programlisting><![CDATA[from Cat as cat
+    left join cat.kittens as kitten
         with kitten.bodyWeight > 10.0]]></programlisting>
 
         <para>
-            In addition, a "fetch" join allows associations or collections of values to be 
-            initialized along with their parent objects, using a single select. This is particularly 
+            In addition, a "fetch" join allows associations or collections of values to be
+            initialized along with their parent objects, using a single select. This is particularly
             useful in the case of a collection. It effectively overrides the outer join and
             lazy declarations of the mapping file for associations and collections. See
             <xref linkend="performance-fetching"/> for more information.
         </para>
-    
-        <programlisting><![CDATA[from Cat as cat 
+
+        <programlisting><![CDATA[from Cat as cat
     inner join fetch cat.mate
     left join fetch cat.kittens]]></programlisting>
-    
+
         <para>
-            A fetch join does not usually need to assign an alias, because the associated objects 
-            should not be used in the <literal>where</literal> clause (or any other clause). Also, 
-            the associated objects are not returned directly in the query results. Instead, they may 
+            A fetch join does not usually need to assign an alias, because the associated objects
+            should not be used in the <literal>where</literal> clause (or any other clause). Also,
+            the associated objects are not returned directly in the query results. Instead, they may
             be accessed via the parent object. The only reason we might need an alias is if we are
             recursively join fetching a further collection:
         </para>
-        
-        <programlisting><![CDATA[from Cat as cat 
+
+        <programlisting><![CDATA[from Cat as cat
     inner join fetch cat.mate
     left join fetch cat.kittens child
     left join fetch child.kittens]]></programlisting>
-    
+
         <para>
             Note that the <literal>fetch</literal> construct may not be used in queries called using
             <literal>iterate()</literal> (though <literal>scroll()</literal> can be used). Nor should
@@ -171,18 +171,18 @@
             you'd expect.
             Nor may <literal>fetch</literal> be used together with an ad hoc <literal>with</literal> condition.
             It is possible to create a cartesian product by join fetching more than one collection in a
-            query, so take care in this case. Join fetching multiple collection roles also sometimes gives 
-            unexpected results for bag mappings, so be careful about how you formulate your queries in this 
-            case. Finally, note that <literal>full join fetch</literal> and <literal>right join fetch</literal> 
+            query, so take care in this case. Join fetching multiple collection roles also sometimes gives
+            unexpected results for bag mappings, so be careful about how you formulate your queries in this
+            case. Finally, note that <literal>full join fetch</literal> and <literal>right join fetch</literal>
             are not meaningful.
         </para>
-        
+
         <para>
             If you are using property-level lazy fetching (with bytecode instrumentation), it is
             possible to force Hibernate to fetch the lazy properties immediately (in the first
             query) using <literal>fetch all properties</literal>.
         </para>
-        
+
         <programlisting><![CDATA[from Document fetch all properties order by name]]></programlisting>
         <programlisting><![CDATA[from Document doc fetch all properties where lower(doc.name) like '%cats%']]></programlisting>
 
@@ -210,16 +210,52 @@
         <programlisting><![CDATA[from Cat as cat where cat.mate.name like '%s%']]></programlisting>
 	</sect1>
 
+    <sect1 id="queryhql-identifier-property">
+        <title>Refering to identifier property</title>
+
+        <para>
+            There are, generally speaking, 2 ways to refer to an entity's identifier property:
+        </para>
+        <itemizedlist spacing="compact">
+            <listitem>
+                <para>
+                    The special property (lowercase) <literal>id</literal> may be used to reference the identifier
+                    property of an entity <emphasis>provided that entity does not define a non-identifier property
+                    named id</emphasis>.
+                </para>
+            </listitem>
+            <listitem>
+                <para>
+                    If the entity defines a named identifier property, you may use that property name.
+                </para>
+            </listitem>
+        </itemizedlist>
+
+        <para>
+            References to composite identifier properties follow the same naming rules.  If the
+            entity has a non-identifier property named id, the composite identifier property can only
+            be referenced by its defined named; otherwise, the special <literal>id</literal> property
+            can be used to rerference the identifier property.
+        </para>
+
+        <para>
+            Note: this has changed significantly starting in version 3.2.2.  In previous versions,
+            <literal>id</literal> <emphasis>always</emphasis> referred to the identifier property no
+            matter what its actual name.  A ramification of that decision was that non-identifier
+            properties named <literal>id</literal> could never be referenced in Hibernate queries.
+        </para>
+    </sect1>
+
     <sect1 id="queryhql-select">
         <title>The select clause</title>
 
         <para>
-            The <literal>select</literal> clause picks which objects and properties to return in 
+            The <literal>select</literal> clause picks which objects and properties to return in
             the query result set. Consider:
         </para>
 
-        <programlisting><![CDATA[select mate 
-from Cat as cat 
+        <programlisting><![CDATA[select mate
+from Cat as cat
     inner join cat.mate as mate]]></programlisting>
 
         <para>
@@ -239,37 +275,37 @@
         <programlisting><![CDATA[select cust.name.firstName from Customer as cust]]></programlisting>
 
         <para>
-            Queries may return multiple objects and/or properties as an array of type 
+            Queries may return multiple objects and/or properties as an array of type
             <literal>Object[]</literal>,
         </para>
 
-        <programlisting><![CDATA[select mother, offspr, mate.name 
+        <programlisting><![CDATA[select mother, offspr, mate.name
 from DomesticCat as mother
     inner join mother.mate as mate
     left outer join mother.kittens as offspr]]></programlisting>
-    
+
         <para>
             or as a <literal>List</literal>,
         </para>
-        
+
         <programlisting><![CDATA[select new list(mother, offspr, mate.name)
 from DomesticCat as mother
     inner join mother.mate as mate
     left outer join mother.kittens as offspr]]></programlisting>
-    
+
         <para>
             or as an actual typesafe Java object,
         </para>
-        
+
         <programlisting><![CDATA[select new Family(mother, mate, offspr)
 from DomesticCat as mother
     join mother.mate as mate
     left join mother.kittens as offspr]]></programlisting>
-        
+
         <para>
             assuming that the class <literal>Family</literal> has an appropriate constructor.
         </para>
-        
+
         <para>
             You may assign aliases to selected expressions using <literal>as</literal>:
         </para>
@@ -280,14 +316,14 @@
         <para>
             This is most useful when used together with <literal>select new map</literal>:
         </para>
-            
+
         <programlisting><![CDATA[select new map( max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n )
 from Cat cat]]></programlisting>
 
         <para>
             This query returns a <literal>Map</literal> from aliases to selected values.
         </para>
-         
+
     </sect1>
 
     <sect1 id="queryhql-aggregation">
@@ -302,11 +338,11 @@
 
 <!-- NO LONGER SUPPORTED
         <para>
-            Collections may also appear inside aggregate functions in the <literal>select</literal> 
+            Collections may also appear inside aggregate functions in the <literal>select</literal>
             clause.
         </para>
 
-        <programlisting><![CDATA[select cat, count( elements(cat.kittens) ) 
+        <programlisting><![CDATA[select cat, count( elements(cat.kittens) )
 from Cat cat group by cat]]></programlisting>
 -->
 
@@ -331,21 +367,21 @@
                 </para>
             </listitem>
         </itemizedlist>
-    
+
         <para>
             You may use arithmetic operators, concatenation, and recognized SQL functions
             in the select clause:
         </para>
-        
-        <programlisting><![CDATA[select cat.weight + sum(kitten.weight) 
-from Cat cat 
+
+        <programlisting><![CDATA[select cat.weight + sum(kitten.weight)
+from Cat cat
     join cat.kittens kitten
 group by cat.id, cat.weight]]></programlisting>
-    
+
         <programlisting><![CDATA[select firstName||' '||initial||' '||upper(lastName) from Person]]></programlisting>
-    
+
         <para>
-            The <literal>distinct</literal> and <literal>all</literal> keywords may be used and 
+            The <literal>distinct</literal> and <literal>all</literal> keywords may be used and
             have the same semantics as in SQL.
         </para>
 
@@ -354,7 +390,7 @@
 select count(distinct cat.name), count(cat) from Cat cat]]></programlisting>
 
     </sect1>
-    
+
     <sect1 id="queryhql-polymorphism">
         <title>Polymorphic queries</title>
 
@@ -366,30 +402,30 @@
 
         <para>
             returns instances not only of <literal>Cat</literal>, but also of subclasses like
-            <literal>DomesticCat</literal>. Hibernate queries may name <emphasis>any</emphasis> Java 
-            class or interface in the <literal>from</literal> clause. The query will return instances 
-            of all persistent classes that extend that class or implement the interface. The following 
+            <literal>DomesticCat</literal>. Hibernate queries may name <emphasis>any</emphasis> Java
+            class or interface in the <literal>from</literal> clause. The query will return instances
+            of all persistent classes that extend that class or implement the interface. The following
             query would return all persistent objects:
         </para>
-        
+
         <programlisting><![CDATA[from java.lang.Object o]]></programlisting>
-        
+
         <para>
             The interface <literal>Named</literal> might be implemented by various persistent
             classes:
         </para>
-        
+
         <programlisting><![CDATA[from Named n, Named m where n.name = m.name]]></programlisting>
-        
+
         <para>
             Note that these last two queries will require more than one SQL <literal>SELECT</literal>. This
             means that the <literal>order by</literal> clause does not correctly order the whole result set.
             (It also means you can't call these queries using <literal>Query.scroll()</literal>.)
         </para>
-        
+
     </sect1>
 
-    <sect1 id="queryhql-where">
+    <sect1 id="queryhql-where" revision="1">
         <title>The where clause</title>
 
         <para>
@@ -398,18 +434,18 @@
         </para>
 
         <programlisting><![CDATA[from Cat where name='Fritz']]></programlisting>
-        
+
         <para>
         	If there is an alias, use a qualified property name:
         </para>
-        
+
         <programlisting><![CDATA[from Cat as cat where cat.name='Fritz']]></programlisting>
 
         <para>
             returns instances of <literal>Cat</literal> named 'Fritz'.
         </para>
 
-        <programlisting><![CDATA[select foo 
+        <programlisting><![CDATA[select foo
 from Foo foo, Bar bar
 where foo.startDate = bar.date]]></programlisting>
 
@@ -429,7 +465,7 @@
             something like
         </para>
 
-        <programlisting><![CDATA[from Foo foo  
+        <programlisting><![CDATA[from Foo foo
 where foo.bar.baz.customer.address.city is not null]]></programlisting>
 
         <para>
@@ -437,19 +473,20 @@
         </para>
 
         <para>
-            The <literal>=</literal> operator may be used to compare not only properties, but also 
+            The <literal>=</literal> operator may be used to compare not only properties, but also
             instances:
         </para>
 
         <programlisting><![CDATA[from Cat cat, Cat rival where cat.mate = rival.mate]]></programlisting>
 
-        <programlisting><![CDATA[select cat, mate 
+        <programlisting><![CDATA[select cat, mate
 from Cat cat, Cat mate
 where cat.mate = mate]]></programlisting>
 
         <para>
-            The special property (lowercase) <literal>id</literal> may be used to reference the 
-            unique identifier of an object. (You may also use its property name.)
+            The special property (lowercase) <literal>id</literal> may be used to reference the
+            unique identifier of an object. See <xref linkend="queryhql-identifier-property"/>
+            for more information.
         </para>
 
         <programlisting><![CDATA[from Cat as cat where cat.id = 123
@@ -461,55 +498,51 @@
         </para>
 
         <para>
-            Properties of composite identifiers may also be used. Suppose <literal>Person</literal> 
-            has a composite identifier consisting of <literal>country</literal> and 
-            <literal>medicareNumber</literal>.
+            Properties of composite identifiers may also be used. Suppose <literal>Person</literal>
+            has a composite identifier consisting of <literal>country</literal> and
+            <literal>medicareNumber</literal>.  Again, see <xref linkend="queryhql-identifier-property"/>
+            for more information regarding referencing identifier properties.
         </para>
 
         <programlisting><![CDATA[from bank.Person person
-where person.id.country = 'AU' 
+where person.id.country = 'AU'
     and person.id.medicareNumber = 123456]]></programlisting>
 
         <programlisting><![CDATA[from bank.Account account
-where account.owner.id.country = 'AU' 
+where account.owner.id.country = 'AU'
     and account.owner.id.medicareNumber = 123456]]></programlisting>
 
         <para>
             Once again, the second query requires no table join.
         </para>
-            
+
         <para>
             Likewise, the special property <literal>class</literal> accesses the discriminator value
-            of an instance in the case of polymorphic persistence. A Java class name embedded in the 
+            of an instance in the case of polymorphic persistence. A Java class name embedded in the
             where clause will be translated to its discriminator value.
         </para>
 
         <programlisting><![CDATA[from Cat cat where cat.class = DomesticCat]]></programlisting>
-        
+
         <para>
-            You may also specify properties of components or composite user types (and of components 
-            of components, etc). Never try to use a path-expression that ends in a property of component 
-            type (as opposed to a property of a component). For example, if <literal>store.owner</literal>
-            is an entity with a component <literal>address</literal>
+            You may also use components or composite user types, or properties of said
+            component types. See <xref linkend="queryhql-coomponents"/> for more details.
         </para>
 
-        <programlisting><![CDATA[store.owner.address.city    // okay
-store.owner.address         // error!]]></programlisting>
-
         <para>
             An "any" type has the special properties <literal>id</literal> and <literal>class</literal>,
             allowing us to express a join in the following way (where <literal>AuditLog.item</literal>
             is a property mapped with <literal>&lt;any&gt;</literal>).
         </para>
-       
-        <programlisting><![CDATA[from AuditLog log, Payment payment 
+
+        <programlisting><![CDATA[from AuditLog log, Payment payment
 where log.item.class = 'Payment' and log.item.id = payment.id]]></programlisting>
-    
+
         <para>
             Notice that <literal>log.item.class</literal> and <literal>payment.class</literal>
             would refer to the values of completely different database columns in the above query.
         </para>
-        
+
     </sect1>
 
     <sect1 id="queryhql-expressions">
@@ -550,14 +583,14 @@
                     <literal>is not null</literal>,
                     <literal>is empty</literal>,
                     <literal>is not empty</literal>,
-                    <literal>member of</literal> and 
+                    <literal>member of</literal> and
                     <literal>not member of</literal>
                 </para>
             </listitem>
             <listitem>
                 <para>
                 	"Simple" case, <literal>case ... when ... then ... else ... end</literal>, and
-                    "searched" case, <literal>case when ... then ... else ... end</literal> 
+                    "searched" case, <literal>case when ... then ... else ... end</literal>
                 </para>
             </listitem>
             <listitem>
@@ -573,8 +606,8 @@
             </listitem>
             <listitem>
                 <para>
-					<literal>second(...)</literal>, <literal>minute(...)</literal>, 
-					<literal>hour(...)</literal>, <literal>day(...)</literal>, 
+					<literal>second(...)</literal>, <literal>minute(...)</literal>,
+					<literal>hour(...)</literal>, <literal>day(...)</literal>,
 					<literal>month(...)</literal>, <literal>year(...)</literal>,
                 </para>
             </listitem>
@@ -592,14 +625,14 @@
             <listitem>
                 <para>
                     <literal>str()</literal> for converting numeric or temporal values to a
-                    readable string 
+                    readable string
                 </para>
             </listitem>
             <listitem>
                 <para>
                     <literal>cast(... as ...)</literal>, where the second argument is the name of
-                    a Hibernate type, and <literal>extract(... from ...)</literal> if ANSI 
-                    <literal>cast()</literal> and <literal>extract()</literal> is supported by 
+                    a Hibernate type, and <literal>extract(... from ...)</literal> if ANSI
+                    <literal>cast()</literal> and <literal>extract()</literal> is supported by
                     the underlying database
                 </para>
             </listitem>
@@ -611,15 +644,15 @@
             </listitem>
             <listitem>
                 <para>
-                    HQL functions that take collection-valued path expressions: <literal>size(), 
-                    minelement(), maxelement(), minindex(), maxindex()</literal>, along with the 
+                    HQL functions that take collection-valued path expressions: <literal>size(),
+                    minelement(), maxelement(), minindex(), maxindex()</literal>, along with the
                     special <literal>elements()</literal> and <literal>indices</literal> functions
                     which may be quantified using <literal>some, all, exists, any, in</literal>.
                 </para>
             </listitem>
             <listitem>
                 <para>
-                    Any database-supported SQL scalar function like <literal>sign()</literal>, 
+                    Any database-supported SQL scalar function like <literal>sign()</literal>,
                     <literal>trunc()</literal>, <literal>rtrim()</literal>, <literal>sin()</literal>
                 </para>
             </listitem>
@@ -663,7 +696,7 @@
         <programlisting><![CDATA[from DomesticCat cat where cat.name not in ( 'Foo', 'Bar', 'Baz' )]]></programlisting>
 
         <para>
-            Likewise, <literal>is null</literal> and <literal>is not null</literal> may be used to test 
+            Likewise, <literal>is null</literal> and <literal>is not null</literal> may be used to test
             for null values.
         </para>
 
@@ -692,20 +725,20 @@
 
         <para>
             For indexed collections, you may refer to the minimum and maximum indices using
-            <literal>minindex</literal> and <literal>maxindex</literal> functions. Similarly, 
-            you may refer to the minimum and maximum elements of a collection of basic type 
+            <literal>minindex</literal> and <literal>maxindex</literal> functions. Similarly,
+            you may refer to the minimum and maximum elements of a collection of basic type
             using the <literal>minelement</literal> and <literal>maxelement</literal>
             functions.
         </para>
-        
+
         <programlisting><![CDATA[from Calendar cal where maxelement(cal.holidays) > current_date]]></programlisting>
-        
+
         <programlisting><![CDATA[from Order order where maxindex(order.items) > 100]]></programlisting>
 
         <programlisting><![CDATA[from Order order where minelement(order.items) > 10000]]></programlisting>
-        
+
         <para>
-            The SQL functions <literal>any, some, all, exists, in</literal> are supported when passed the element 
+            The SQL functions <literal>any, some, all, exists, in</literal> are supported when passed the element
             or index set of a collection (<literal>elements</literal> and <literal>indices</literal> functions)
             or the result of a subquery (see below).
         </para>
@@ -728,12 +761,12 @@
             <literal>minelement</literal>, <literal>maxelement</literal> - may only be used in
             the where clause in Hibernate3.
         </para>
-        
+
         <para>
             Elements of indexed collections (arrays, lists, maps) may be referred to by
             index (in a where clause only):
         </para>
-        
+
         <programlisting><![CDATA[from Order order where order.items[0].id = 1234]]></programlisting>
 
         <programlisting><![CDATA[select person from Person person, Calendar calendar
@@ -749,16 +782,16 @@
         <para>
             The expression inside <literal>[]</literal> may even be an arithmetic expression.
         </para>
-        
+
         <programlisting><![CDATA[select item from Item item, Order order
 where order.items[ size(order.items) - 1 ] = item]]></programlisting>
-        
+
         <para>
-            HQL also provides the built-in <literal>index()</literal> function, for elements 
+            HQL also provides the built-in <literal>index()</literal> function, for elements
             of a one-to-many association or collection of values.
         </para>
 
-        <programlisting><![CDATA[select item, index(item) from Order order 
+        <programlisting><![CDATA[select item, index(item) from Order order
     join order.items item
 where index(item) < 5]]></programlisting>
 
@@ -769,7 +802,7 @@
         <programlisting><![CDATA[from DomesticCat cat where upper(cat.name) like 'FRI%']]></programlisting>
 
         <para>
-            If you are not yet convinced by all this, think how much longer and less readable the 
+            If you are not yet convinced by all this, think how much longer and less readable the
             following query would be in SQL:
         </para>
 
@@ -816,7 +849,7 @@
 order by cat.name asc, cat.weight desc, cat.birthdate]]></programlisting>
 
         <para>
-            The optional <literal>asc</literal> or <literal>desc</literal> indicate ascending or descending order 
+            The optional <literal>asc</literal> or <literal>desc</literal> indicate ascending or descending order
             respectively.
         </para>
     </sect1>
@@ -828,11 +861,11 @@
             A query that returns aggregate values may be grouped by any property of a returned class or components:
         </para>
 
-        <programlisting><![CDATA[select cat.color, sum(cat.weight), count(cat) 
+        <programlisting><![CDATA[select cat.color, sum(cat.weight), count(cat)
 from Cat cat
 group by cat.color]]></programlisting>
 
-        <programlisting><![CDATA[select foo.id, avg(name), max(name) 
+        <programlisting><![CDATA[select foo.id, avg(name), max(name)
 from Foo foo join foo.names name
 group by foo.id]]></programlisting>
 
@@ -840,14 +873,14 @@
             A <literal>having</literal> clause is also allowed.
         </para>
 
-        <programlisting><![CDATA[select cat.color, sum(cat.weight), count(cat) 
+        <programlisting><![CDATA[select cat.color, sum(cat.weight), count(cat)
 from Cat cat
-group by cat.color 
+group by cat.color
 having cat.color in (eg.Color.TABBY, eg.Color.BLACK)]]></programlisting>
 
         <para>
             SQL functions and aggregate functions are allowed in the <literal>having</literal>
-            and <literal>order by</literal> clauses, if supported by the underlying database 
+            and <literal>order by</literal> clauses, if supported by the underlying database
             (eg. not in MySQL).
         </para>
 
@@ -868,37 +901,37 @@
         </para>
 
     </sect1>
-    
-    <sect1 id="queryhql-subqueries" revision="2">
+
+    <sect1 id="queryhql-subqueries" revision="3">
         <title>Subqueries</title>
-        
+
         <para>
             For databases that support subselects, Hibernate supports subqueries within queries. A subquery must
             be surrounded by parentheses (often by an SQL aggregate function call). Even correlated subqueries
             (subqueries that refer to an alias in the outer query) are allowed.
         </para>
 
-        <programlisting><![CDATA[from Cat as fatcat 
-where fatcat.weight > ( 
-    select avg(cat.weight) from DomesticCat cat 
+        <programlisting><![CDATA[from Cat as fatcat
+where fatcat.weight > (
+    select avg(cat.weight) from DomesticCat cat
 )]]></programlisting>
 
-        <programlisting><![CDATA[from DomesticCat as cat 
-where cat.name = some ( 
-    select name.nickName from Name as name 
+        <programlisting><![CDATA[from DomesticCat as cat
+where cat.name = some (
+    select name.nickName from Name as name
 )]]></programlisting>
-    
-        <programlisting><![CDATA[from Cat as cat 
-where not exists ( 
-    from Cat as mate where mate.mate = cat 
+
+        <programlisting><![CDATA[from Cat as cat
+where not exists (
+    from Cat as mate where mate.mate = cat
 )]]></programlisting>
 
-        <programlisting><![CDATA[from DomesticCat as cat 
-where cat.name not in ( 
-    select name.nickName from Name as name 
+        <programlisting><![CDATA[from DomesticCat as cat
+where cat.name not in (
+    select name.nickName from Name as name
 )]]></programlisting>
 
-        <programlisting><![CDATA[select cat.id, (select max(kit.weight) from cat.kitten kit) 
+        <programlisting><![CDATA[select cat.id, (select max(kit.weight) from cat.kitten kit)
 from Cat as cat]]></programlisting>
 
         <para>
@@ -906,53 +939,30 @@
         </para>
 
         <para>
-            For subqueries with more than one expression in the select list, you can use a tuple constructor:
+            Note that subqueries can also utilize <literal>row value constructor</literal> syntax.  See
+            <xref linkend="queryhql-tuple"/> for more details.
         </para>
-        
-        <programlisting><![CDATA[from Cat as cat 
-where not ( cat.name, cat.color ) in ( 
-    select cat.name, cat.color from DomesticCat cat 
-)]]></programlisting>
 
-        <para>
-            Note that on some databases (but not Oracle or HSQL), you can use tuple constructors in other
-            contexts, for example when querying components or composite user types:
-        </para>
-
-        <programlisting><![CDATA[from Person where name = ('Gavin', 'A', 'King')]]></programlisting>
-        
-        <para>
-            Which is equivalent to the more verbose:
-        </para>
-        
-        <programlisting><![CDATA[from Person where name.first = 'Gavin' and name.initial = 'A' and name.last = 'King')]]></programlisting>
-
-        <para>
-            There are two good reasons you might not want to do this kind of thing: first, it is not 
-            completely portable between database platforms; second, the query is now dependent upon
-            the ordering of properties in the mapping document.
-        </para>
-        
     </sect1>
 
     <sect1 id="queryhql-examples">
         <title>HQL examples</title>
-        
+
         <para>
             Hibernate queries can be quite powerful and complex. In fact, the power of the query language
             is one of Hibernate's main selling points. Here are some example queries very similar to queries
             that I used on a recent project. Note that most queries you will write are much simpler than these!
         </para>
-        
+
         <para>
-            The following query returns the order id, number of items and total value of the order for all 
-            unpaid orders for a particular customer and given minimum total value, ordering the results by 
-            total value. In determining the prices, it uses the current catalog. The resulting SQL query, 
+            The following query returns the order id, number of items and total value of the order for all
+            unpaid orders for a particular customer and given minimum total value, ordering the results by
+            total value. In determining the prices, it uses the current catalog. The resulting SQL query,
             against the <literal>ORDER</literal>, <literal>ORDER_LINE</literal>, <literal>PRODUCT</literal>,
             <literal>CATALOG</literal> and <literal>PRICE</literal> tables has four inner joins and an
             (uncorrelated) subselect.
         </para>
-        
+
         <programlisting><![CDATA[select order.id, sum(price.amount), count(item)
 from Order as order
     join order.lineItems as item
@@ -964,19 +974,19 @@
     and price.product = product
     and catalog.effectiveDate < sysdate
     and catalog.effectiveDate >= all (
-        select cat.effectiveDate 
+        select cat.effectiveDate
         from Catalog as cat
         where cat.effectiveDate < sysdate
     )
 group by order
 having sum(price.amount) > :minAmount
 order by sum(price.amount) desc]]></programlisting>
-        
+
         <para>
-            What a monster! Actually, in real life, I'm not very keen on subqueries, so my query was 
+            What a monster! Actually, in real life, I'm not very keen on subqueries, so my query was
             really more like this:
         </para>
-        
+
         <programlisting><![CDATA[select order.id, sum(price.amount), count(item)
 from Order as order
     join order.lineItems as item
@@ -990,24 +1000,24 @@
 group by order
 having sum(price.amount) > :minAmount
 order by sum(price.amount) desc]]></programlisting>
-        
+
         <para>
             The next query counts the number of payments in each status, excluding all payments in the
-            <literal>AWAITING_APPROVAL</literal> status where the most recent status change was made by the 
-            current user. It translates to an SQL query with two inner joins and a correlated subselect 
-            against the <literal>PAYMENT</literal>, <literal>PAYMENT_STATUS</literal> and 
+            <literal>AWAITING_APPROVAL</literal> status where the most recent status change was made by the
+            current user. It translates to an SQL query with two inner joins and a correlated subselect
+            against the <literal>PAYMENT</literal>, <literal>PAYMENT_STATUS</literal> and
             <literal>PAYMENT_STATUS_CHANGE</literal> tables.
         </para>
 
-        <programlisting><![CDATA[select count(payment), status.name 
-from Payment as payment 
+        <programlisting><![CDATA[select count(payment), status.name
+from Payment as payment
     join payment.currentStatus as status
     join payment.statusChanges as statusChange
 where payment.status.name <> PaymentStatus.AWAITING_APPROVAL
     or (
-        statusChange.timeStamp = ( 
-            select max(change.timeStamp) 
-            from PaymentStatusChange change 
+        statusChange.timeStamp = (
+            select max(change.timeStamp)
+            from PaymentStatusChange change
             where change.payment = payment
         )
         and statusChange.user <> :currentUser
@@ -1016,11 +1026,11 @@
 order by status.sortOrder]]></programlisting>
 
         <para>
-            If I would have mapped the <literal>statusChanges</literal> collection as a list, instead of a set, 
+            If I would have mapped the <literal>statusChanges</literal> collection as a list, instead of a set,
             the query would have been much simpler to write.
         </para>
-    
-        <programlisting><![CDATA[select count(payment), status.name 
+
+        <programlisting><![CDATA[select count(payment), status.name
 from Payment as payment
     join payment.currentStatus as status
 where payment.status.name <> PaymentStatus.AWAITING_APPROVAL
@@ -1031,9 +1041,9 @@
         <para>
             The next query uses the MS SQL Server <literal>isNull()</literal> function to return all
             the accounts and unpaid payments for the organization to which the current user belongs.
-            It translates to an SQL query with three inner joins, an outer join and a subselect against 
+            It translates to an SQL query with three inner joins, an outer join and a subselect against
             the <literal>ACCOUNT</literal>, <literal>PAYMENT</literal>, <literal>PAYMENT_STATUS</literal>,
-            <literal>ACCOUNT_TYPE</literal>, <literal>ORGANIZATION</literal> and 
+            <literal>ACCOUNT_TYPE</literal>, <literal>ORGANIZATION</literal> and
             <literal>ORG_USER</literal> tables.
         </para>
 
@@ -1082,7 +1092,7 @@
         </para>
 
         <programlisting><![CDATA[select usr.id, usr.name
-from User as usr 
+from User as usr
     left join usr.messages as msg
 group by usr.id, usr.name
 order by count(msg)]]></programlisting>
@@ -1135,7 +1145,7 @@
         <para>
             Collection elements may be ordered or grouped using a query filter:
         </para>
-        
+
         <programlisting><![CDATA[Collection orderedCollection = s.filter( collection, "order by this.amount" );
 Collection counts = s.filter( collection, "select this.type, count(this) group by this.type" );]]></programlisting>
 
@@ -1147,5 +1157,78 @@
 
     </sect1>
 
+    <sect1 id="queryhql-components">
+        <title>Components</title>
+
+        <para>
+            Components might be used in just about every way that simple value types can be used in HQL
+            queries.  They can appear in the <literal>select</literal> clause:
+        </para>
+
+        <programlisting><![CDATA[select p.name from from Person p]]></programlisting>
+        <programlisting><![CDATA[select p.name.first from from Person p]]></programlisting>
+
+        <para>
+            where the Person's name property is a component.  Components can also be used
+            in the <literal>where</literal> clause:
+        </para>
+
+        <programlisting><![CDATA[from from Person p where p.name = :name]]></programlisting>
+        <programlisting><![CDATA[from from Person p where p.name.first = :firstName]]></programlisting>
+
+        <para>
+            Components can also be used in the <literal>order by</literal> clause:
+        </para>
+
+        <programlisting><![CDATA[from from Person p order by p.name]]></programlisting>
+        <programlisting><![CDATA[from from Person p order by p.name.first]]></programlisting>
+
+        <para>
+            Another common use of components is in <xref linkend="queryhql-tuple">row value constructors</xref>.
+        </para>
+    </sect1>
+
+    <sect1 id="queryhql-tuple">
+        <title>Row value constructor syntax</title>
+
+        <para>
+            HQL supports the use of ANSI SQL <literal>row value constructor</literal> syntax (sometimes
+            called <literal>tuple</literal> syntax), even though the underlying database may not support
+            that notion.  Here we are generally referring to multi-valued comparisons, typically associated
+            with components.  Consider an entity Person which defines a name component:
+        </para>
+
+        <programlisting><![CDATA[from Person p where p.name.first='John' and p.name.last='Jingleheimer-Schmidt']]></programlisting>
+
+        <para>
+            That's valid syntax, although a little verbose.  It be nice to make this a bit more concise and use
+            <literal>row value constructor</literal> syntax:
+        </para>
+
+        <programlisting><![CDATA[from Person p where p.name=('John', 'Jingleheimer-Schmidt')]]></programlisting>
+
+        <para>
+            It can also be useful to specify this in the <literal>select</literal> clause:
+        </para>
+
+        <programlisting><![CDATA[select p.name from from Person p]]></programlisting>
+
+        <para>
+            Another time using <literal>row value constructor</literal> syntax can be beneficial
+            is when using subqueries needing to compare against multiple values:
+        </para>
+
+        <programlisting><![CDATA[from Cat as cat
+where not ( cat.name, cat.color ) in (
+    select cat.name, cat.color from DomesticCat cat
+)]]></programlisting>
+
+        <para>
+            One thing to consider when deciding if you want to use this syntax is that the query will
+            be dependent upon the ordering of the component sub-properties in the metadata.
+        </para>
+
+    </sect1>
+
 </chapter>
 

Modified: trunk/Hibernate3/src/org/hibernate/hql/ast/tree/DotNode.java
===================================================================
--- trunk/Hibernate3/src/org/hibernate/hql/ast/tree/DotNode.java	2006-11-21 17:38:43 UTC (rev 10851)
+++ trunk/Hibernate3/src/org/hibernate/hql/ast/tree/DotNode.java	2006-11-21 17:39:14 UTC (rev 10852)
@@ -81,9 +81,9 @@
 	private FromElement impliedJoin;
 
 	/**
-	 * Sets the join type for the '.' node (JoinFragment.XXX).
+	 * Sets the join type for this '.' node structure.
 	 *
-	 * @param joinType
+	 * @param joinType The type of join to use.
 	 * @see JoinFragment
 	 */
 	public void setJoinType(int joinType) {
@@ -220,8 +220,7 @@
 	private Type prepareLhs() throws SemanticException {
 		FromReferenceNode lhs = getLhs();
 		lhs.prepareForDot( propertyName );
-		Type propertyType = getDataType();
-		return propertyType;
+		return getDataType();
 	}
 
 	private void dereferenceCollection(CollectionType collectionType, boolean implicitJoin, boolean indexed, String classAlias, AST parent)
@@ -298,7 +297,7 @@
 		//              select clause is part of a scalar query :/ )
 		DotNode parentAsDotNode = null;
 		String property = propertyName;
-		boolean joinIsNeeded = false;
+		final boolean joinIsNeeded;
 
 		if ( isDotNode( parent ) ) {
 			parentAsDotNode = ( DotNode ) parent;
@@ -340,13 +339,11 @@
 		String joinPath = getPath();
 
 		if ( impliedJoin && getWalker().isInFrom() ) {
-			int impliedJoinType = getWalker().getImpliedJoinType();
-			joinType = impliedJoinType;
+			joinType = getWalker().getImpliedJoinType();
 		}
 
 		FromClause currentFromClause = getWalker().getCurrentFromClause();
-		FromElement elem = null;
-		elem = currentFromClause.findJoinByPath( joinPath );
+		FromElement elem = currentFromClause.findJoinByPath( joinPath );
 
 ///////////////////////////////////////////////////////////////////////////////
 //
@@ -420,14 +417,42 @@
 		return impliedJoin;
 	}
 
-	private boolean isReferenceToPrimaryKey(String propertyName, EntityType propertyType) {
-		if ( EntityPersister.ENTITY_ID.equals( propertyName ) ) {
-			// the referenced node text is the special 'id'
-			return propertyType.isReferenceToPrimaryKey();
+	/**
+	 * Is the given property name a reference to the primary key of the associated
+	 * entity construed by the given entity type?
+	 * <p/>
+	 * For example, consider a fragment like order.customer.id
+	 * (where order is a from-element alias).  Here, we'd have:
+	 * propertyName = "id" AND
+	 * owningType = ManyToOneType(Customer)
+	 * and are being asked to determine whether "customer.id" is a reference
+	 * to customer's PK...
+	 *
+	 * @param propertyName The name of the property to check.
+	 * @param owningType The type represeting the entity "owning" the property
+	 * @return True if propertyName references the entity's (owningType->associatedEntity)
+	 * primary key; false otherwise.
+	 */
+	private boolean isReferenceToPrimaryKey(String propertyName, EntityType owningType) {
+		EntityPersister persister = getSessionFactoryHelper()
+				.getFactory()
+				.getEntityPersister( owningType.getAssociatedEntityName() );
+		if ( persister.getEntityMetamodel().hasNonIdentifierPropertyNamedId() ) {
+			// only the identifier property field name can be a reference to the associated entity's PK...
+			return propertyName.equals( persister.getIdentifierPropertyName() ) && owningType.isReferenceToPrimaryKey();
 		}
 		else {
-			String keyPropertyName = getSessionFactoryHelper().getIdentifierOrUniqueKeyPropertyName( propertyType );
-			return keyPropertyName != null && keyPropertyName.equals( propertyName ) && propertyType.isReferenceToPrimaryKey();
+			// here, we have two possibilities:
+			// 		1) the property-name matches the explicitly identifier property name
+			//		2) the property-name matches the implicit 'id' property name
+			if ( EntityPersister.ENTITY_ID.equals( propertyName ) ) {
+				// the referenced node text is the special 'id'
+				return owningType.isReferenceToPrimaryKey();
+			}
+			else {
+				String keyPropertyName = getSessionFactoryHelper().getIdentifierOrUniqueKeyPropertyName( owningType );
+				return keyPropertyName != null && keyPropertyName.equals( propertyName ) && owningType.isReferenceToPrimaryKey();
+			}
 		}
 	}
 

Modified: trunk/Hibernate3/src/org/hibernate/hql/ast/tree/FromElement.java
===================================================================
--- trunk/Hibernate3/src/org/hibernate/hql/ast/tree/FromElement.java	2006-11-21 17:38:43 UTC (rev 10851)
+++ trunk/Hibernate3/src/org/hibernate/hql/ast/tree/FromElement.java	2006-11-21 17:39:14 UTC (rev 10852)
@@ -5,7 +5,6 @@
 import java.util.List;
 
 import org.hibernate.QueryException;
-import org.hibernate.MappingException;
 import org.hibernate.engine.JoinSequence;
 import org.hibernate.hql.QueryTranslator;
 import org.hibernate.hql.CollectionProperties;
@@ -285,11 +284,19 @@
 			throw new IllegalStateException( "No table alias for node " + this );
 		}
 		String[] cols;
+		String propertyName;
+		if ( getEntityPersister() != null && getEntityPersister().getEntityMetamodel() != null
+				&& getEntityPersister().getEntityMetamodel().hasNonIdentifierPropertyNamedId() ) {
+			propertyName = getEntityPersister().getIdentifierPropertyName();
+		}
+		else {
+			propertyName = EntityPersister.ENTITY_ID;
+		}
 		if ( getWalker().getStatementType() == HqlSqlWalker.SELECT ) {
-			cols = getPropertyMapping( EntityPersister.ENTITY_ID ).toColumns( table, EntityPersister.ENTITY_ID );
+			cols = getPropertyMapping( propertyName ).toColumns( table, propertyName );
 		}
 		else {
-			cols = getPropertyMapping( EntityPersister.ENTITY_ID ).toColumns( EntityPersister.ENTITY_ID );
+			cols = getPropertyMapping( propertyName ).toColumns( propertyName );
 		}
 		String result = StringHelper.join( ", ", cols );
 		return  cols.length == 1 ? result : "(" + result + ")";
@@ -375,13 +382,6 @@
 		return origin;
 	}
 
-	/**
-	 * Returns the type of a property, given it's name (the last part) and the full path.
-	 *
-	 * @param propertyName The last part of the full path to the property.
-	 * @return The type.
-	 * @0param propertyPath The full property path.
-	 */
 	public Type getPropertyType(String propertyName, String propertyPath) {
 		return elementType.getPropertyType( propertyName, propertyPath );
 	}
@@ -422,9 +422,6 @@
 		return filter;
 	}
 
-	/**
-	 * Returns true if the from fragment should be included in the from clause.
-	 */
 	public boolean useFromFragment() {
 		checkInitialized();
 		// If it's not implied or it is implied and it's a many to many join where the target wasn't found.
@@ -472,10 +469,6 @@
 		// Do nothing, eplicit from elements are *always* in the projection list.
 	}
 
-	/**
-	 * Returns true if this element should be in the projection list.
-	 *
-	 */
 	public boolean inProjectionList() {
 		return !isImplied() && isFromOrJoinFragment();
 	}

Modified: trunk/Hibernate3/src/org/hibernate/persister/entity/AbstractEntityPersister.java
===================================================================
--- trunk/Hibernate3/src/org/hibernate/persister/entity/AbstractEntityPersister.java	2006-11-21 17:38:43 UTC (rev 10851)
+++ trunk/Hibernate3/src/org/hibernate/persister/entity/AbstractEntityPersister.java	2006-11-21 17:39:14 UTC (rev 10852)
@@ -1508,9 +1508,11 @@
 		// ALIASES
 		internalInitSubclassPropertyAliasesMap( null, model.getSubclassPropertyClosureIterator() );
 
-		// aliases for identifier ( alias.id )
-		subclassPropertyAliases.put( ENTITY_ID, getIdentifierAliases() );
-		subclassPropertyColumnNames.put( ENTITY_ID, getIdentifierColumnNames() );
+		// aliases for identifier ( alias.id ); skip if the entity defines a non-id property named 'id'
+		if ( ! entityMetamodel.hasNonIdentifierPropertyNamedId() ) {
+			subclassPropertyAliases.put( ENTITY_ID, getIdentifierAliases() );
+			subclassPropertyColumnNames.put( ENTITY_ID, getIdentifierColumnNames() );
+		}
 
 		// aliases named identifier ( alias.idname )
 		if ( hasIdentifierProperty() ) {
@@ -1527,23 +1529,26 @@
 			String[] idColumnNames = getIdentifierColumnNames();
 
 			for ( int i = 0; i < idPropertyNames.length; i++ ) {
-				subclassPropertyAliases.put(
-						ENTITY_ID + "." + idPropertyNames[i],
-						new String[] { idAliases[i] }
+				if ( entityMetamodel.hasNonIdentifierPropertyNamedId() ) {
+					subclassPropertyAliases.put(
+							ENTITY_ID + "." + idPropertyNames[i],
+							new String[] { idAliases[i] }
 					);
-				subclassPropertyColumnNames.put(
-						ENTITY_ID + "." + getIdentifierPropertyName() + "." + idPropertyNames[i],
-						new String[] { idColumnNames[i] }
+					subclassPropertyColumnNames.put(
+							ENTITY_ID + "." + getIdentifierPropertyName() + "." + idPropertyNames[i],
+							new String[] { idColumnNames[i] }
 					);
-				if (hasIdentifierProperty() && !ENTITY_ID.equals( getIdentifierPropertyName() ) ) {
+				}
+//				if (hasIdentifierProperty() && !ENTITY_ID.equals( getIdentifierPropertyName() ) ) {
+				if ( hasIdentifierProperty() ) {
 					subclassPropertyAliases.put(
 							getIdentifierPropertyName() + "." + idPropertyNames[i],
 							new String[] { idAliases[i] }
-						);
+					);
 					subclassPropertyColumnNames.put(
 							getIdentifierPropertyName() + "." + idPropertyNames[i],
 							new String[] { idColumnNames[i] }
-						);
+					);
 				}
 				else {
 					// embedded composite ids ( alias.idname1, alias.idname2 )
@@ -1554,10 +1559,8 @@
 		}
 
 		if ( entityMetamodel.isPolymorphic() ) {
-			subclassPropertyAliases.put( ENTITY_CLASS,
-					new String[]{getDiscriminatorAlias()} );
-			subclassPropertyColumnNames.put( ENTITY_CLASS,
-					new String[]{getDiscriminatorColumnName()} );
+			subclassPropertyAliases.put( ENTITY_CLASS, new String[] { getDiscriminatorAlias() } );
+			subclassPropertyColumnNames.put( ENTITY_CLASS, new String[] { getDiscriminatorColumnName() } );
 		}
 
 	}
@@ -1672,7 +1675,9 @@
 		if ( entityMetamodel.getIdentifierProperty().isEmbedded() ) {
 			propertyMapping.initPropertyPaths( null, getIdentifierType(), getIdentifierColumnNames(), null, mapping );
 		}
-		propertyMapping.initPropertyPaths( ENTITY_ID, getIdentifierType(), getIdentifierColumnNames(), null, mapping );
+		if ( ! entityMetamodel.hasNonIdentifierPropertyNamedId() ) {
+			propertyMapping.initPropertyPaths( ENTITY_ID, getIdentifierType(), getIdentifierColumnNames(), null, mapping );
+		}
 	}
 
 	private void initDiscriminatorPropertyPath(Mapping mapping) throws MappingException {
@@ -3145,7 +3150,7 @@
 		return factory;
 	}
 
-	protected EntityMetamodel getEntityMetamodel() {
+	public EntityMetamodel getEntityMetamodel() {
 		return entityMetamodel;
 	}
 

Modified: trunk/Hibernate3/src/org/hibernate/persister/entity/AbstractPropertyMapping.java
===================================================================
--- trunk/Hibernate3/src/org/hibernate/persister/entity/AbstractPropertyMapping.java	2006-11-21 17:38:43 UTC (rev 10851)
+++ trunk/Hibernate3/src/org/hibernate/persister/entity/AbstractPropertyMapping.java	2006-11-21 17:39:14 UTC (rev 10852)
@@ -34,20 +34,16 @@
 
 	public Type toType(String propertyName) throws QueryException {
 		Type type = (Type) typesByPropertyPath.get(propertyName);
-		if (type==null) throwPropertyException(propertyName);
+		if ( type == null ) {
+			throw propertyException( propertyName );
+		}
 		return type;
 	}
 
-	protected final void throwPropertyException(String propertyName) 
-	throws QueryException {
-		throw new QueryException(
-				"could not resolve property: " +
-				propertyName +
-				" of: " +
-				getEntityName()
-			);
+	protected final QueryException propertyException(String propertyName) throws QueryException {
+		return new QueryException( "could not resolve property: " + propertyName + " of: " + getEntityName() );
 	}
-	
+
 	public String[] getColumnNames(String propertyName) {
 		String[] cols = (String[]) columnsByPropertyPath.get(propertyName);
 		if (cols==null) {
@@ -56,11 +52,12 @@
 		return cols;
 	}
 
-	public String[] toColumns(String alias, String propertyName)
-	throws QueryException {
+	public String[] toColumns(String alias, String propertyName) throws QueryException {
 		//TODO: *two* hashmap lookups here is one too many...
 		String[] columns = (String[]) columnsByPropertyPath.get(propertyName);
-		if (columns==null) throwPropertyException(propertyName);
+		if ( columns == null ) {
+			throw propertyException( propertyName );
+		}
 		String[] templates = (String[]) formulaTemplatesByPropertyPath.get(propertyName);
 		String[] result = new String[columns.length];
 		for ( int i=0; i<columns.length; i++ ) {
@@ -74,10 +71,11 @@
 		return result;
 	}
 
-	public String[] toColumns(String propertyName)
-	throws QueryException {
+	public String[] toColumns(String propertyName) throws QueryException {
 		String[] columns = (String[]) columnsByPropertyPath.get(propertyName);
-		if (columns==null) throwPropertyException(propertyName);
+		if ( columns == null ) {
+			throw propertyException( propertyName );
+		}
 		String[] templates = (String[]) formulaTemplatesByPropertyPath.get(propertyName);
 		String[] result = new String[columns.length];
 		for ( int i=0; i<columns.length; i++ ) {
@@ -100,28 +98,28 @@
 	}
 
 	/*protected void initPropertyPaths(
-			final String path, 
-			final Type type, 
-			final String[] columns, 
-			final String[] formulaTemplates, 
+			final String path,
+			final Type type,
+			final String[] columns,
+			final String[] formulaTemplates,
 			final Mapping factory)
 	throws MappingException {
 		//addFormulaPropertyPath(path, type, formulaTemplates);
 		initPropertyPaths(path, type, columns, formulaTemplates, factory);
 	}*/
-	
+
 	protected void initPropertyPaths(
-			final String path, 
-			final Type type, 
-			String[] columns, 
-			final String[] formulaTemplates, 
+			final String path,
+			final Type type,
+			String[] columns,
+			final String[] formulaTemplates,
 			final Mapping factory)
 	throws MappingException {
-		
+
 		if ( columns.length!=type.getColumnSpan(factory) ) {
-			throw new MappingException( 
-					"broken column mapping for: " + path + 
-					" of: " + getEntityName() 
+			throw new MappingException(
+					"broken column mapping for: " + path +
+					" of: " + getEntityName()
 				);
 		}
 
@@ -147,12 +145,12 @@
 			AbstractComponentType actype = (AbstractComponentType) type;
 			initComponentPropertyPaths( path, actype, columns, formulaTemplates, factory );
 			if ( actype.isEmbedded() ) {
-				initComponentPropertyPaths( 
-						path==null ? null : StringHelper.qualifier(path), 
-						actype, 
-						columns, 
-						formulaTemplates, 
-						factory 
+				initComponentPropertyPaths(
+						path==null ? null : StringHelper.qualifier(path),
+						actype,
+						columns,
+						formulaTemplates,
+						factory
 					);
 			}
 		}
@@ -162,21 +160,23 @@
 	}
 
 	protected void initIdentifierPropertyPaths(
-			final String path, 
-			final EntityType etype, 
-			final String[] columns, 
-			final Mapping factory)
-	throws MappingException {
+			final String path,
+			final EntityType etype,
+			final String[] columns,
+			final Mapping factory) throws MappingException {
 
-		Type idtype = etype.getIdentifierOrUniqueKeyType(factory);
+		Type idtype = etype.getIdentifierOrUniqueKeyType( factory );
+		String idPropName = etype.getIdentifierOrUniqueKeyPropertyName(factory);
+		boolean hasNonIdentifierPropertyNamedId = hasNonIdentifierPropertyNamedId( etype, factory );
 
 		if ( etype.isReferenceToPrimaryKey() ) {
-			String idpath1 = extendPath(path, EntityPersister.ENTITY_ID);
-			addPropertyPath(idpath1, idtype, columns, null);
-			initPropertyPaths(idpath1, idtype, columns, null, factory);
+			if ( !hasNonIdentifierPropertyNamedId ) {
+				String idpath1 = extendPath(path, EntityPersister.ENTITY_ID);
+				addPropertyPath(idpath1, idtype, columns, null);
+				initPropertyPaths(idpath1, idtype, columns, null, factory);
+			}
 		}
 
-		String idPropName = etype.getIdentifierOrUniqueKeyPropertyName(factory);
 		if (idPropName!=null) {
 			String idpath2 = extendPath(path, idPropName);
 			addPropertyPath(idpath2, idtype, columns, null);
@@ -184,10 +184,22 @@
 		}
 	}
 
+	private boolean hasNonIdentifierPropertyNamedId(final EntityType entityType, final Mapping factory) {
+		// TODO : would be great to have a Mapping#hasNonIdentifierPropertyNamedId method
+		// I don't believe that Mapping#getReferencedPropertyType accounts for the identifier property; so
+		// if it returns for a property named 'id', then we should have a non-id field named id
+		try {
+			return factory.getReferencedPropertyType( entityType.getAssociatedEntityName(), EntityPersister.ENTITY_ID ) != null;
+		}
+		catch( MappingException e ) {
+			return false;
+		}
+	}
+
 	protected void initComponentPropertyPaths(
-			final String path, 
-			final AbstractComponentType type, 
-			final String[] columns, 
+			final String path,
+			final AbstractComponentType type,
+			final String[] columns,
 			String[] formulaTemplates, final Mapping factory)
 	throws MappingException {
 
@@ -199,7 +211,7 @@
 			try {
 				int length = types[i].getColumnSpan(factory);
 				String[] columnSlice = ArrayHelper.slice(columns, begin, length);
-				String[] formulaSlice = formulaTemplates==null ? 
+				String[] formulaSlice = formulaTemplates==null ?
 						null : ArrayHelper.slice(formulaTemplates, begin, length);
 				initPropertyPaths(subpath, types[i], columnSlice, formulaSlice, factory);
 				begin+=length;

Modified: trunk/Hibernate3/src/org/hibernate/persister/entity/EntityPersister.java
===================================================================
--- trunk/Hibernate3/src/org/hibernate/persister/entity/EntityPersister.java	2006-11-21 17:38:43 UTC (rev 10851)
+++ trunk/Hibernate3/src/org/hibernate/persister/entity/EntityPersister.java	2006-11-21 17:39:14 UTC (rev 10852)
@@ -8,6 +8,7 @@
 import org.hibernate.LockMode;
 import org.hibernate.MappingException;
 import org.hibernate.EntityMode;
+import org.hibernate.tuple.entity.EntityMetamodel;
 import org.hibernate.cache.CacheConcurrencyStrategy;
 import org.hibernate.cache.OptimisticCacheSource;
 import org.hibernate.cache.entry.CacheEntryStructure;
@@ -74,6 +75,13 @@
 	public String getEntityName();
 
 	/**
+	 * Retrieve the underlying entity metamodel instance...
+	 *
+	 *@return The metamodel
+	 */
+	public EntityMetamodel getEntityMetamodel();
+
+	/**
 	 * Determine whether the given name represents a subclass entity
 	 * (or this entity itself) of the entity mapped by this persister.
 	 *

Modified: trunk/Hibernate3/src/org/hibernate/tuple/entity/EntityMetamodel.java
===================================================================
--- trunk/Hibernate3/src/org/hibernate/tuple/entity/EntityMetamodel.java	2006-11-21 17:38:43 UTC (rev 10851)
+++ trunk/Hibernate3/src/org/hibernate/tuple/entity/EntityMetamodel.java	2006-11-21 17:39:14 UTC (rev 10852)
@@ -40,7 +40,7 @@
  * @author Steve Ebersole
  */
 public class EntityMetamodel implements Serializable {
-	
+
 	private static final Log log = LogFactory.getLog(EntityMetamodel.class);
 
 	private static final int NO_VERSION_INDX = -66;
@@ -73,12 +73,13 @@
 	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 	private final Map propertyIndexes = new HashMap();
 	private final boolean hasCollections;
-	private final boolean hasMutableProperties; 
+	private final boolean hasMutableProperties;
 	private final boolean hasLazyProperties;
+	private final boolean hasNonIdentifierPropertyNamedId;
 
 	private final int[] naturalIdPropertyNumbers;
 
-	private boolean lazy; //not final, proxy factory creation may fail
+	private boolean lazy; //not final because proxy factory creation can fail
 	private final boolean hasCascades;
 	private final boolean mutable;
 	private final boolean isAbstract;
@@ -95,7 +96,7 @@
 	private final Set subclassEntityNames = new HashSet();
 
 	private final EntityEntityModeToTuplizerMapping tuplizerMapping;
-	
+
 	public EntityTuplizer getTuplizer(EntityMode entityMode) {
 		return (EntityTuplizer) tuplizerMapping.getTuplizer( entityMode );
 	}
@@ -103,7 +104,7 @@
 	public EntityTuplizer getTuplizerOrNull(EntityMode entityMode) {
 		return ( EntityTuplizer ) tuplizerMapping.getTuplizerOrNull( entityMode );
 	}
-	
+
 	public EntityMode guessEntityMode(Object object) {
 		return tuplizerMapping.guessEntityMode( object );
 	}
@@ -151,7 +152,8 @@
 		boolean foundCascade = false;
 		boolean foundCollection = false;
 		boolean foundMutable = false;
-		
+		boolean foundNonIdentifierPropertyNamedId = false;
+
 		while ( iter.hasNext() ) {
 			Property prop = ( Property ) iter.next();
 
@@ -162,11 +164,15 @@
 			else {
 				properties[i] = PropertyFactory.buildStandardProperty( prop, lazyAvailable );
 			}
-			
+
 			if ( prop.isNaturalIdentifier() ) {
 				naturalIdNumbers.add( new Integer(i) );
 			}
 
+			if ( "id".equals( prop.getName() ) ) {
+				foundNonIdentifierPropertyNamedId = true;
+			}
+
 			// temporary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 			boolean lazy = prop.isLazy() && lazyAvailable;
 			if ( lazy ) hasLazy = true;
@@ -181,7 +187,7 @@
 			propertyUpdateGeneration[i] = properties[i].isUpdateGenerated();
 			propertyVersionability[i] = properties[i].isVersionable();
 			nonlazyPropertyUpdateability[i] = properties[i].isUpdateable() && !lazy;
-			propertyCheckability[i] = propertyUpdateability[i] || 
+			propertyCheckability[i] = propertyUpdateability[i] ||
 					( propertyTypes[i].isAssociationType() && ( (AssociationType) propertyTypes[i] ).isAlwaysDirtyChecked() );
 
 			cascadeStyles[i] = properties[i].getCascadeStyle();
@@ -198,7 +204,7 @@
 			if ( indicatesCollection( properties[i].getType() ) ) {
 				foundCollection = true;
 			}
-			
+
 			if ( propertyTypes[i].isMutable() && propertyCheckability[i] ) {
 				foundMutable = true;
 			}
@@ -206,7 +212,7 @@
 			mapPropertyToIndex(prop, i);
 			i++;
 		}
-		
+
 		if (naturalIdNumbers.size()==0) {
 			naturalIdPropertyNumbers = null;
 		}
@@ -215,9 +221,12 @@
 		}
 
 		hasCascades = foundCascade;
+		hasNonIdentifierPropertyNamedId = foundNonIdentifierPropertyNamedId;
 		versionPropertyIndex = tempVersionProperty;
 		hasLazyProperties = hasLazy;
-		if (hasLazyProperties) log.info("lazy property fetching available for: " + name);
+		if ( hasLazyProperties ) {
+			log.info( "lazy property fetching available for: " + name );
+		}
 
 		lazy = persistentClass.isLazy() && (
 				// TODO: this disables laziness even in non-pojo entity modes:
@@ -275,22 +284,22 @@
 			Iterator iter = ( (Component) prop.getValue() ).getPropertyIterator();
 			while ( iter.hasNext() ) {
 				Property subprop = (Property) iter.next();
-				propertyIndexes.put( 
-						prop.getName() + '.' + subprop.getName(), 
-						new Integer(i) 
+				propertyIndexes.put(
+						prop.getName() + '.' + subprop.getName(),
+						new Integer(i)
 					);
 			}
 		}
 	}
-	
+
 	public int[] getNaturalIdentifierProperties() {
 		return naturalIdPropertyNumbers;
 	}
-	
+
 	public boolean hasNaturalIdentifier() {
 		return naturalIdPropertyNumbers!=null;
 	}
-	
+
 	public Set getSubclassEntityNames() {
 		return subclassEntityNames;
 	}
@@ -358,7 +367,7 @@
 		}
 		return index.intValue();
 	}
-	
+
 	public Integer getPropertyIndexOrNull(String propertyName) {
 		return (Integer) propertyIndexes.get( propertyName );
 	}
@@ -366,11 +375,15 @@
 	public boolean hasCollections() {
 		return hasCollections;
 	}
-	
+
 	public boolean hasMutableProperties() {
 		return hasMutableProperties;
 	}
 
+	public boolean hasNonIdentifierPropertyNamedId() {
+		return hasNonIdentifierPropertyNamedId;
+	}
+
 	public boolean hasLazyProperties() {
 		return hasLazyProperties;
 	}
@@ -434,11 +447,11 @@
 	public boolean isAbstract() {
 		return isAbstract;
 	}
-	
+
 	public String toString() {
 		return "EntityMetamodel(" + name + ':' + ArrayHelper.toString(properties) + ')';
 	}
-	
+
 	// temporary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 	public String[] getPropertyNames() {
 		return propertyNames;

Added: trunk/Hibernate3/test/org/hibernate/test/idprops/IdentifierPropertyReferencesTest.java
===================================================================
--- trunk/Hibernate3/test/org/hibernate/test/idprops/IdentifierPropertyReferencesTest.java	2006-11-21 17:38:43 UTC (rev 10851)
+++ trunk/Hibernate3/test/org/hibernate/test/idprops/IdentifierPropertyReferencesTest.java	2006-11-21 17:39:14 UTC (rev 10852)
@@ -0,0 +1,187 @@
+package org.hibernate.test.idprops;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.hibernate.test.TestCase;
+import org.hibernate.Session;
+import org.hibernate.Query;
+import org.hibernate.Criteria;
+import org.hibernate.criterion.Projections;
+import org.hibernate.criterion.Restrictions;
+
+/**
+ * {@inheritDoc}
+ *
+ * @author Steve Ebersole
+ */
+public class IdentifierPropertyReferencesTest extends TestCase {
+	public IdentifierPropertyReferencesTest(String name) {
+		super( name );
+	}
+
+	protected String[] getMappings() {
+		return new String[] { "idprops/Mapping.hbm.xml" };
+	}
+
+	public static Test suite() {
+		return new TestSuite( IdentifierPropertyReferencesTest.class );
+	}
+
+	public void testHqlIdPropertyReferences() {
+		Session s = openSession();
+		s.beginTransaction();
+		Person p = new Person( new Long(1), "steve", 123 );
+		s.save( p );
+		Order o = new Order( new Long(1), p );
+		LineItem l = new LineItem( o, "my-product", 2 );
+		l.setId( "456" );
+		s.save( o );
+		s.getTransaction().commit();
+		s.close();
+
+		s = openSession();
+		s.beginTransaction();
+
+		long count = extractCount( s, "select count(*) from Person p where p.id = 123" );
+		assertEquals( "Person by id prop (non-identifier)", 1, count );
+		count = extractCount( s, "select count(*) from Person p where p.pk = 1" );
+		assertEquals( "Person by pk prop (identifier)", 1, count );
+
+		count = extractCount( s, "select count(*) from Order o where o.id = 1" );
+		assertEquals( "Order by number prop (named identifier)", 1, count );
+		count = extractCount( s, "select count(*) from Order o where o.number = 1" );
+		assertEquals( "Order by id prop (virtual identifier)", 1, count );
+
+		count = extractCount( s, "select count(*) from LineItem l where l.id = '456'" );
+		assertEquals( "LineItem by id prop (non-identifier", 1, count );
+
+		if ( getDialect().supportsRowValueConstructorSyntax() ) {
+			Query q = s.createQuery( "select count(*) from LineItem l where l.pk = (:order, :product)" )
+					.setEntity( "order", o )
+					.setString( "product", "my-product" );
+			count = extractCount( q );
+			assertEquals( "LineItem by pk prop (named composite identifier", 1, count );
+		}
+
+		count = extractCount( s, "select count(*) from Order o where o.orderee.id = 1" );
+		assertEquals( 0, count );
+		count = extractCount( s, "select count(*) from Order o where o.orderee.pk = 1" );
+		assertEquals( 1, count );
+		count = extractCount( s, "select count(*) from Order o where o.orderee.id = 123" );
+		assertEquals( 1, count );
+
+		count = extractCount( s, "select count(*) from LineItem l where l.pk.order.id = 1" );
+		assertEquals( 1, count );
+		count = extractCount( s, "select count(*) from LineItem l where l.pk.order.number = 1" );
+		assertEquals( 1, count );
+		count = extractCount( s, "select count(*) from LineItem l where l.pk.order.orderee.pk = 1" );
+		assertEquals( 1, count );
+
+		s.delete( o );
+		s.delete( p );
+		s.getTransaction().commit();
+		s.close();
+	}
+
+	public void testCriteriaIdPropertyReferences() {
+		Session s = openSession();
+		s.beginTransaction();
+		Person p = new Person( new Long(1), "steve", 123 );
+		s.save( p );
+		Order o = new Order( new Long(1), p );
+		LineItem l = new LineItem( o, "my-product", 2 );
+		l.setId( "456" );
+		s.save( o );
+		s.getTransaction().commit();
+		s.close();
+
+		s = openSession();
+		s.beginTransaction();
+
+		Criteria crit = s.createCriteria( Person.class );
+		crit.setProjection( Projections.rowCount() );
+		crit.add( Restrictions.eq( "id", new Integer(123) ) );
+		long count = extractCount( crit );
+		assertEquals( "Person by id prop (non-identifier)", 1, count );
+
+		crit = s.createCriteria( Person.class );
+		crit.setProjection( Projections.rowCount() );
+		crit.add( Restrictions.eq( "pk", new Long(1) ) );
+		count = extractCount( crit );
+		assertEquals( "Person by pk prop (identifier)", 1, count );
+
+		crit = s.createCriteria( Order.class );
+		crit.setProjection( Projections.rowCount() );
+		crit.add(  Restrictions.eq( "number", new Long(1) ) );
+		count = extractCount( crit );
+		assertEquals( "Order by number prop (named identifier)", 1, count );
+
+		crit = s.createCriteria( Order.class );
+		crit.setProjection( Projections.rowCount() );
+		crit.add(  Restrictions.eq( "id", new Long(1) ) );
+		count = extractCount( crit );
+		assertEquals( "Order by id prop (virtual identifier)", 1, count );
+
+		crit = s.createCriteria( LineItem.class );
+		crit.setProjection( Projections.rowCount() );
+		crit.add(  Restrictions.eq( "id", "456" ) );
+		count = extractCount( crit );
+		assertEquals( "LineItem by id prop (non-identifier", 1, count );
+
+		if ( getDialect().supportsRowValueConstructorSyntax() ) {
+			crit = s.createCriteria( LineItem.class );
+			crit.setProjection( Projections.rowCount() );
+			crit.add( Restrictions.eq( "pk", new LineItemPK( o, "my-product" ) ) );
+			count = extractCount( crit );
+			assertEquals( "LineItem by pk prop (named composite identifier)", 1, count );
+		}
+
+		crit = s.createCriteria( Order.class );
+		crit.setProjection( Projections.rowCount() );
+		crit.createAlias( "orderee", "p" ).add( Restrictions.eq( "p.id", new Integer(1) ) );
+		count = extractCount( crit );
+		assertEquals( 0, count );
+
+		crit = s.createCriteria( Order.class );
+		crit.setProjection( Projections.rowCount() );
+		crit.createAlias( "orderee", "p" ).add( Restrictions.eq( "p.pk", new Long(1) ) );
+		count = extractCount( crit );
+		assertEquals( 1, count );
+
+		crit = s.createCriteria( Order.class );
+		crit.setProjection( Projections.rowCount() );
+		crit.createAlias( "orderee", "p" ).add( Restrictions.eq( "p.id", new Integer(123) ) );
+		count = extractCount( crit );
+		assertEquals( 1, count );
+
+		crit = s.createCriteria( LineItem.class );
+		crit.setProjection( Projections.rowCount() );
+		crit.add( Restrictions.eq( "pk.order.id", new Long(1) ) );
+		count = extractCount( crit );
+		assertEquals( 1, count );
+
+		crit = s.createCriteria( LineItem.class );
+		crit.setProjection( Projections.rowCount() );
+		crit.add( Restrictions.eq( "pk.order.number", new Long(1) ) );
+		count = extractCount( crit );
+		assertEquals( 1, count );
+
+		s.delete( o );
+		s.delete( p );
+		s.getTransaction().commit();
+		s.close();
+	}
+
+	private long extractCount(Session s, String hql) {
+		return extractCount( s.createQuery( hql ) );
+	}
+
+	private long extractCount(Query query) {
+		return ( ( Long ) query.list().get( 0 ) ).longValue();
+	}
+
+	private long extractCount(Criteria crit) {
+		return ( ( Integer ) crit.list().get( 0 ) ).intValue();
+	}
+}

Added: trunk/Hibernate3/test/org/hibernate/test/idprops/LineItem.java
===================================================================
--- trunk/Hibernate3/test/org/hibernate/test/idprops/LineItem.java	2006-11-21 17:38:43 UTC (rev 10851)
+++ trunk/Hibernate3/test/org/hibernate/test/idprops/LineItem.java	2006-11-21 17:39:14 UTC (rev 10852)
@@ -0,0 +1,49 @@
+package org.hibernate.test.idprops;
+
+/**
+ * {@inheritDoc}
+ *
+ * @author Steve Ebersole
+ */
+public class LineItem {
+	private LineItemPK pk;
+	private int quantity;
+	private String id;
+
+	public LineItem() {
+	}
+
+	public LineItem(LineItemPK pk, int quantity) {
+		this.pk = pk;
+		this.quantity = quantity;
+		this.pk.getOrder().getLineItems().add( this );
+	}
+
+	public LineItem(Order order, String productCode, int quantity) {
+		this( new LineItemPK( order, productCode ), quantity );
+	}
+
+	public LineItemPK getPk() {
+		return pk;
+	}
+
+	public void setPk(LineItemPK pk) {
+		this.pk = pk;
+	}
+
+	public int getQuantity() {
+		return quantity;
+	}
+
+	public void setQuantity(int quantity) {
+		this.quantity = quantity;
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+}

Added: trunk/Hibernate3/test/org/hibernate/test/idprops/LineItemPK.java
===================================================================
--- trunk/Hibernate3/test/org/hibernate/test/idprops/LineItemPK.java	2006-11-21 17:38:43 UTC (rev 10851)
+++ trunk/Hibernate3/test/org/hibernate/test/idprops/LineItemPK.java	2006-11-21 17:39:14 UTC (rev 10852)
@@ -0,0 +1,64 @@
+package org.hibernate.test.idprops;
+
+import java.io.Serializable;
+
+/**
+ * {@inheritDoc}
+ *
+ * @author Steve Ebersole
+ */
+public class LineItemPK implements Serializable {
+	private Order order;
+	private String productCode;
+
+	public LineItemPK() {
+	}
+
+	public LineItemPK(Order order, String productCode) {
+		this.order = order;
+		this.productCode = productCode;
+	}
+
+	public Order getOrder() {
+		return order;
+	}
+
+	public void setOrder(Order order) {
+		this.order = order;
+	}
+
+	public String getProductCode() {
+		return productCode;
+	}
+
+	public void setProductCode(String productCode) {
+		this.productCode = productCode;
+	}
+
+	public boolean equals(Object o) {
+		if ( this == o ) {
+			return true;
+		}
+		if ( o == null || getClass() != o.getClass() ) {
+			return false;
+		}
+
+		LineItemPK that = ( LineItemPK ) o;
+
+		if ( !order.equals( that.order ) ) {
+			return false;
+		}
+		if ( !productCode.equals( that.productCode ) ) {
+			return false;
+		}
+
+		return true;
+	}
+
+	public int hashCode() {
+		int result;
+		result = order.hashCode();
+		result = 31 * result + productCode.hashCode();
+		return result;
+	}
+}

Added: trunk/Hibernate3/test/org/hibernate/test/idprops/Mapping.hbm.xml
===================================================================
--- trunk/Hibernate3/test/org/hibernate/test/idprops/Mapping.hbm.xml	2006-11-21 17:38:43 UTC (rev 10851)
+++ trunk/Hibernate3/test/org/hibernate/test/idprops/Mapping.hbm.xml	2006-11-21 17:39:14 UTC (rev 10852)
@@ -0,0 +1,48 @@
+<?xml version="1.0"?>
+
+<!DOCTYPE hibernate-mapping SYSTEM "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
+
+<hibernate-mapping package="org.hibernate.test.idprops">
+
+    <!--
+        Person has an identitifer property named something other than 'id';
+        additionally, it has a non-identitifer property named 'id'
+    -->
+    <class name="Person" table="T_ID_PERSON">
+        <id name="pk" column="PK" type="long">
+            <generator class="assigned"/>
+        </id>
+        <property name="name" column="NAME" type="string"/>
+        <property name="id" column="ID_NON_ID" type="int" />
+    </class>
+
+    <!--
+        Order has an identitifer property named something other than 'id';
+        it has no non-identitifer property named 'id'
+    -->
+    <class name="Order" table="T_ID_ORDER">
+        <id name="number" column="PK" type="long">
+            <generator class="assigned"/>
+        </id>
+        <property name="placed" column="ORDR_DT" type="timestamp"/>
+        <many-to-one name="orderee" class="Person" column="ORDEREE"/>
+        <set name="lineItems" cascade="all-delete-orphan" lazy="true" inverse="true">
+            <key column="ORDR_ID"/>
+            <one-to-many class="LineItem"/>
+        </set>
+    </class>
+
+    <!--
+        LineItem has a composite identitifer property named something other than 'id';
+        additionally, it has a non-identitifer property named 'id'
+    -->
+    <class name="LineItem" table="T_ID_LINE_ITEM">
+        <composite-id class="LineItemPK" name="pk">
+            <key-many-to-one name="order" class="Order" column="ORDR_ID" />
+            <key-property name="productCode" column="PROD_CODE" type="string"/>
+        </composite-id>
+        <property name="quantity" column="QTY" type="int"/>
+        <property name="id" column="ID_NON_ID2" type="string"/>
+    </class>
+
+</hibernate-mapping>
\ No newline at end of file

Added: trunk/Hibernate3/test/org/hibernate/test/idprops/Order.java
===================================================================
--- trunk/Hibernate3/test/org/hibernate/test/idprops/Order.java	2006-11-21 17:38:43 UTC (rev 10851)
+++ trunk/Hibernate3/test/org/hibernate/test/idprops/Order.java	2006-11-21 17:39:14 UTC (rev 10852)
@@ -0,0 +1,60 @@
+package org.hibernate.test.idprops;
+
+import java.util.Date;
+import java.util.Set;
+import java.util.HashSet;
+
+/**
+ * {@inheritDoc}
+ *
+ * @author Steve Ebersole
+ */
+public class Order {
+	private Long number;
+	private Date placed;
+	private Person orderee;
+
+	private Set lineItems = new HashSet();
+
+	public Order() {
+	}
+
+	public Order(Long number, Person orderee) {
+		this.number = number;
+		this.orderee = orderee;
+		this.placed = new Date();
+	}
+
+	public Long getNumber() {
+		return number;
+	}
+
+	public void setNumber(Long number) {
+		this.number = number;
+	}
+
+	public Date getPlaced() {
+		return placed;
+	}
+
+	public void setPlaced(Date placed) {
+		this.placed = placed;
+	}
+
+	public Person getOrderee() {
+		return orderee;
+	}
+
+	public void setOrderee(Person orderee) {
+		this.orderee = orderee;
+	}
+
+
+	public Set getLineItems() {
+		return lineItems;
+	}
+
+	public void setLineItems(Set lineItems) {
+		this.lineItems = lineItems;
+	}
+}

Added: trunk/Hibernate3/test/org/hibernate/test/idprops/Person.java
===================================================================
--- trunk/Hibernate3/test/org/hibernate/test/idprops/Person.java	2006-11-21 17:38:43 UTC (rev 10851)
+++ trunk/Hibernate3/test/org/hibernate/test/idprops/Person.java	2006-11-21 17:39:14 UTC (rev 10852)
@@ -0,0 +1,45 @@
+package org.hibernate.test.idprops;
+
+/**
+ * {@inheritDoc}
+ *
+ * @author Steve Ebersole
+ */
+public class Person {
+	private Long pk;
+	private String name;
+	private int id;
+
+	public Person() {
+	}
+
+	public Person(Long pk, String name, int id) {
+		this.pk = pk;
+		this.name = name;
+		this.id = id;
+	}
+
+	public Long getPk() {
+		return pk;
+	}
+
+	public void setPk(Long pk) {
+		this.pk = pk;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public int getId() {
+		return id;
+	}
+
+	public void setId(int id) {
+		this.id = id;
+	}
+}

Modified: trunk/Hibernate3/test/org/hibernate/test/legacy/CustomPersister.java
===================================================================
--- trunk/Hibernate3/test/org/hibernate/test/legacy/CustomPersister.java	2006-11-21 17:38:43 UTC (rev 10851)
+++ trunk/Hibernate3/test/org/hibernate/test/legacy/CustomPersister.java	2006-11-21 17:39:14 UTC (rev 10852)
@@ -11,6 +11,7 @@
 import org.hibernate.HibernateException;
 import org.hibernate.LockMode;
 import org.hibernate.MappingException;
+import org.hibernate.tuple.entity.EntityMetamodel;
 import org.hibernate.cache.CacheConcurrencyStrategy;
 import org.hibernate.cache.entry.CacheEntryStructure;
 import org.hibernate.cache.entry.UnstructuredCacheEntry;
@@ -76,7 +77,7 @@
 	public String getEntityName() {
 		return Custom.class.getName();
 	}
-	
+
 	public boolean isSubclassEntityName(String entityName) {
 		return Custom.class.getName().equals(entityName);
 	}
@@ -616,4 +617,9 @@
 	public Comparator getVersionComparator() {
 		return null;
 	}
+
+	public EntityMetamodel getEntityMetamodel() {
+		return null;
+	}
+
 }




More information about the hibernate-commits mailing list