Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NH-3303 - Refactoring and fixes for custom SQL code #1945

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
175 changes: 106 additions & 69 deletions src/NHibernate.Test/Async/SqlTest/Query/NativeSQLQueriesFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,20 @@
//------------------------------------------------------------------------------


using System;
using System.Collections;
using System.Linq;
using NHibernate.Criterion;
using NHibernate.Transform;
using NUnit.Framework;
using NHibernate.Criterion;

namespace NHibernate.Test.SqlTest.Query
{
using System.Threading.Tasks;
using System.Threading;
[TestFixture]
public class GeneralTestAsync : TestCase
{
protected const string OrganizationFetchJoinEmploymentSQL =
"SELECT org.ORGID as {org.id}, " +
" org.NAME as {org.name}, " +
" emp.EMPLOYER as {emp.key}, " +
" emp.EMPID as {emp.element}, " +
" {emp.element.*} " +
"FROM ORGANIZATION org " +
" LEFT OUTER JOIN EMPLOYMENT emp ON org.ORGID = emp.EMPLOYER";

protected const string OrganizationJoinEmploymentSQL =
"SELECT org.ORGID as {org.id}, " +
" org.NAME as {org.name}, " +
" {emp.*} " +
"FROM ORGANIZATION org " +
" LEFT OUTER JOIN EMPLOYMENT emp ON org.ORGID = emp.EMPLOYER";

protected const string EmploymentSQL = "SELECT * FROM EMPLOYMENT";

protected string EmploymentSQLMixedScalarEntity =
Expand Down Expand Up @@ -306,24 +292,6 @@ public async Task MappedAliasStrategyAsync()
await (t.CommitAsync());
s.Close();

if (TestDialect.SupportsDuplicatedColumnAliases)
{
s = OpenSession();
t = s.BeginTransaction();
sqlQuery = s.GetNamedQuery("organizationreturnproperty");
sqlQuery.SetResultTransformer(CriteriaSpecification.AliasToEntityMap);
list = await (sqlQuery.ListAsync());
Assert.AreEqual(2, list.Count);
m = (IDictionary) list[0];
Assert.IsTrue(m.Contains("org"));
AssertClassAssignability(m["org"].GetType(), typeof(Organization));
Assert.IsTrue(m.Contains("emp"));
AssertClassAssignability(m["emp"].GetType(), typeof(Employment));
Assert.AreEqual(2, m.Count);
await (t.CommitAsync());
s.Close();
}

s = OpenSession();
t = s.BeginTransaction();
namedQuery = s.GetNamedQuery("EmploymentAndPerson");
Expand Down Expand Up @@ -469,9 +437,9 @@ public async Task AutoDetectAliasingAsync()

// TODO H3: H3.2 can guess the return column type so they can use just addScalar("employerid"),
// but NHibernate currently can't do it.
list =
await (s.CreateSQLQuery(EmploymentSQLMixedScalarEntity).AddScalar("employerid", NHibernateUtil.Int64).AddEntity(
typeof(Employment)).ListAsync());
list = await (s.CreateSQLQuery(EmploymentSQLMixedScalarEntity)
.AddScalar("employerid", NHibernateUtil.Int64)
.AddEntity(typeof(Employment)).ListAsync());
Assert.AreEqual(1, list.Count);
o = (object[]) list[0];
Assert.AreEqual(2, o.Length);
Expand All @@ -484,37 +452,6 @@ public async Task AutoDetectAliasingAsync()
list = await (queryWithCollection.ListAsync());
Assert.AreEqual(list.Count, 1);

s.Clear();

list = await (s.CreateSQLQuery(OrganizationJoinEmploymentSQL)
.AddEntity("org", typeof(Organization))
.AddJoin("emp", "org.employments")
.ListAsync());
Assert.AreEqual(2, list.Count);

s.Clear();

list = await (s.CreateSQLQuery(OrganizationFetchJoinEmploymentSQL)
.AddEntity("org", typeof(Organization))
.AddJoin("emp", "org.employments")
.ListAsync());
Assert.AreEqual(2, list.Count);

s.Clear();

if (TestDialect.SupportsDuplicatedColumnAliases)
{
// TODO : why twice?
await (s.GetNamedQuery("organizationreturnproperty").ListAsync());
list = await (s.GetNamedQuery("organizationreturnproperty").ListAsync());
Assert.AreEqual(2, list.Count);

s.Clear();

list = await (s.GetNamedQuery("organizationautodetect").ListAsync());
Assert.AreEqual(2, list.Count);
}

await (t.CommitAsync());
s.Close();

Expand Down Expand Up @@ -558,6 +495,106 @@ public async Task AutoDetectAliasingAsync()
s.Close();
}

public Task CanQueryWithGeneratedAliasesOnly_UsingWildcardAsync(CancellationToken cancellationToken = default(CancellationToken))
{
try
{
const string SQL =
"SELECT org.ORGID as {org.id}, " +
" org.NAME as {org.name}, " +
" {emp.*} " +
"FROM ORGANIZATION org " +
" LEFT OUTER JOIN EMPLOYMENT emp ON org.ORGID = emp.EMPLOYER";

return VerifyOrganisationQueryAsync(session => session.CreateSQLQuery(SQL)
.AddEntity("org", typeof(Organization))
.AddJoin("emp", "org.employments"), cancellationToken);
}
catch (Exception ex)
{
return Task.FromException<object>(ex);
}
}

[Test]
public async Task CanQueryWithGeneratedAliasesOnly_UsingCollectionElementWildcardAsync()
{
const string SQL =
"SELECT org.ORGID as {org.id}, " +
" org.NAME as {org.name}, " +
" emp.EMPLOYER as {emp.key}, " +
" emp.EMPID as {emp.element}, " +
" {emp.element.*} " +
"FROM ORGANIZATION org " +
" LEFT OUTER JOIN EMPLOYMENT emp ON org.ORGID = emp.EMPLOYER";

await (VerifyOrganisationQueryAsync(session => session.CreateSQLQuery(SQL)
.AddEntity("org", typeof(Organization))
.AddJoin("emp", "org.employments")));
}

[Test]
public async Task CanQueryWithColumnNamesOnlyAsync()
{
await (VerifyOrganisationQueryAsync(session => session.GetNamedQuery("organization-using-manual-aliases")));
}

[Test]
public async Task CanQueryWithManualAliasesOnlyAsync()
{
await (VerifyOrganisationQueryAsync(session => session.GetNamedQuery("organization-using-column-names")));
}

[Test]
public async Task CanQueryWithMixOfColumnNamesAndManualAliasesAsync()
{
await (VerifyOrganisationQueryAsync(session => session.GetNamedQuery("organization-using-column-names-and-manual-aliases")));
}

private async Task VerifyOrganisationQueryAsync(Func<ISession, IQuery> queryFactory, CancellationToken cancellationToken = default(CancellationToken))
{
using (ISession s = OpenSession())
using (ITransaction t = s.BeginTransaction())
{
var ifa = new Organization("IFA");
var jboss = new Organization("JBoss");
var gavin = new Person("Gavin");
var emp = new Employment(gavin, jboss, "AU");
await (s.SaveAsync(jboss, cancellationToken));
await (s.SaveAsync(ifa, cancellationToken));
await (s.SaveAsync(gavin, cancellationToken));
await (s.SaveAsync(emp, cancellationToken));

var list = await (queryFactory(s).ListAsync(cancellationToken));
Assert.AreEqual(2, list.Count);
}
}

[Test]
public async Task CanQueryWithManualComponentPropertyAliasesAsync()
{
using (ISession s = OpenSession())
using (ITransaction t = s.BeginTransaction())
{
SpaceShip enterprise = new SpaceShip();
enterprise.Model = "USS";
enterprise.Name = "Entreprise";
enterprise.Speed = 50d;
Dimension d = new Dimension(45, 10);
enterprise.Dimensions = d;
await (s.SaveAsync(enterprise));

await (s.FlushAsync());
s.Clear();

object[] result = (object[])await (s.GetNamedQuery("spaceship").UniqueResultAsync());
enterprise = (SpaceShip)result[0];
Assert.AreEqual(50d, enterprise.Speed, "Speed");
Assert.AreEqual(45, enterprise.Dimensions.Length, "Dimensions.Length");
Assert.AreEqual(10, enterprise.Dimensions.Width, "Dimensions.Width");
}
}

[Test]
public async Task MixAndMatchEntityScalarAsync()
{
Expand Down
103 changes: 60 additions & 43 deletions src/NHibernate.Test/SqlTest/Query/NativeSQLQueries.hbm.xml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--

This mapping demonstrates the use of Hibernate with
all-handwritten SQL!
This mapping demonstrates the use of Hibernate with
all-handwritten SQL!
-->

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="field.camelcase"
namespace="NHibernate.Test.SqlTest"
assembly="NHibernate.Test">
namespace="NHibernate.Test.SqlTest"
assembly="NHibernate.Test">

<class name="Organization" table="ORGANIZATION">
<id name="id" unsaved-value="0" column="ORGID">
Expand All @@ -27,7 +27,6 @@
<generator class="increment"/>
</id>
<property name="name" column="NAME" not-null="true"/>

</class>

<class name="Employment" table="EMPLOYMENT">
Expand Down Expand Up @@ -116,7 +115,6 @@
<return-join alias="pers" property="emp.employee"/>
</resultset>


<resultset name="org-description">
<return alias="org" class="Organization"/>
<return-join alias="emp" property="org.employments"/>
Expand Down Expand Up @@ -149,14 +147,14 @@
fld_name as name,
fld_model as model,
fld_speed as speed,
fld_length as flength,
fld_length as length,
fld_width as width,
fld_length * fld_width as surface,
fld_length * fld_width *10 as volume
from SpaceShip
</sql-query>

<sql-query name="orgNamesOnly">
<sql-query name="orgNamesOnly">
<return-scalar column="NAME" type="string"/>
SELECT org.NAME FROM ORGANIZATION org
</sql-query>
Expand Down Expand Up @@ -204,56 +202,75 @@
ORDER BY STARTDATE ASC, EMPLOYEE ASC
</sql-query>

<sql-query name="organizationreturnproperty">
<sql-query name="organization-using-manual-aliases">
<return alias="org" class="Organization">
<return-property name="id" column="ORGID"/>
<return-property name="name" column="NAME"/>
<return-property name="id" column="org_id"/>
<return-property name="name" column="org_name"/>
</return>
<return-join alias="emp" property="org.employments">
<return-property name="key" column="EMPLOYER"/>
<return-property name="element" column="EMPID"/>
<return-property name="element.employee" column="EMPLOYEE"/>
<return-property name="element.employer" column="EMPLOYER"/>
<return-property name="element.startDate" column="XSTARTDATE"/>
<return-property name="element.endDate" column="ENDDATE"/>
<return-property name="element.regionCode" column="REGIONCODE"/>
<return-property name="element.employmentId" column="EMPID"/>
<return-property name="key" column="emp_employer"/>
<return-property name="element" column="emp_id"/>
<return-property name="element.employee" column="emp_employee"/>
<return-property name="element.employer" column="emp_employer"/>
<return-property name="element.startDate" column="emp_startDate"/>
<return-property name="element.endDate" column="emp_endDate"/>
<return-property name="element.regionCode" column="emp_regionCode"/>
<return-property name="element.employmentId" column="emp_id"/>
<return-property name="element.salary">
<return-column name="AVALUE"/>
<return-column name="CURRENCY"/>
<return-column name="emp_avalue"/>
<return-column name="emp_currency"/>
</return-property>
</return-join>
SELECT org.ORGID as orgid,
org.NAME as name,
emp.EMPLOYER as employer,
emp.EMPID as empid,
emp.EMPLOYEE as employee,
emp.EMPLOYER as employer,
emp.STARTDATE as xstartDate,
emp.ENDDATE as endDate,
emp.REGIONCODE as regionCode,
emp.AVALUE as AVALUE,
emp.CURRENCY as CURRENCY
SELECT org.ORGID as org_id,
org.NAME as org_name,
emp.EMPLOYER as emp_employer,
emp.EMPID as emp_id,
emp.EMPLOYEE as emp_employee,
emp.STARTDATE as emp_startDate,
emp.ENDDATE as emp_endDate,
emp.REGIONCODE as emp_regionCode,
emp.AVALUE as emp_avalue,
emp.CURRENCY as emp_currency
FROM ORGANIZATION org
LEFT OUTER JOIN EMPLOYMENT emp ON org.ORGID = emp.EMPLOYER
</sql-query>


<sql-query name="organizationautodetect" resultset-ref="org-description">
<sql-query name="organization-using-column-names" resultset-ref="org-description">
<!-- equal to "organizationpropertyreturn" but since no {} nor return-property are used hibernate will fallback to use the columns directly from the mapping -->
SELECT org.ORGID as orgid,
org.NAME as name,
emp.EMPLOYER as employer,
emp.EMPID as empid,
emp.EMPLOYEE as employee,
emp.EMPLOYER as employer,
emp.STARTDATE as startDate,
emp.ENDDATE as endDate,
emp.REGIONCODE as regionCode,
SELECT org.ORGID as ORGID,
org.NAME as NAME,
emp.EMPLOYER as EMPLOYER,
emp.EMPID as EMPID,
emp.EMPLOYEE as EMPLOYEE,
emp.STARTDATE as STARTDATE,
emp.ENDDATE as ENDDATE,
emp.REGIONCODE as REGIONCODE,
emp.AVALUE as AVALUE,
emp.CURRENCY as CURRENCY
FROM ORGANIZATION org
LEFT OUTER JOIN EMPLOYMENT emp ON org.ORGID = emp.EMPLOYER
</sql-query>

<sql-query name="organization-using-column-names-and-manual-aliases">
<!-- equal to "organizationpropertyreturn" but will default to use columns directly from the mapping for properties without return-property element -->
<return alias="org" class="Organization">
<return-property name="id" column="ORGANISATION_ID"/>
</return>
<return-join alias="emp" property="org.employments">
<return-property name="element" column="EMPLOYMENT_ID"/>
<return-property name="element.id" column="EMPLOYMENT_ID"/>
</return-join>
SELECT org.ORGID as ORGANISATION_ID,
org.NAME as NAME,
emp.EMPID as EMPLOYMENT_ID,
emp.EMPLOYEE as EMPLOYEE,
emp.EMPLOYER as EMPLOYER,
emp.STARTDATE as STARTDATE,
emp.ENDDATE as ENDDATE,
emp.REGIONCODE as REGIONCODE,
emp.AVALUE as AVALUE,
emp.CURRENCY as CURRENCY
FROM ORGANIZATION org
LEFT OUTER JOIN EMPLOYMENT emp ON org.ORGID = emp.EMPLOYER
</sql-query>
</hibernate-mapping>