mirror of https://github.com/apache/openjpa.git
OPENJPA-1483: support count distinct compound key
git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@905540 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
784e4560db
commit
1a216e9fb7
|
@ -18,6 +18,7 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.openjpa.jdbc.kernel.exps;
|
package org.apache.openjpa.jdbc.kernel.exps;
|
||||||
|
|
||||||
|
import org.apache.openjpa.jdbc.schema.Column;
|
||||||
import org.apache.openjpa.jdbc.sql.SQLBuffer;
|
import org.apache.openjpa.jdbc.sql.SQLBuffer;
|
||||||
import org.apache.openjpa.jdbc.sql.Select;
|
import org.apache.openjpa.jdbc.sql.Select;
|
||||||
|
|
||||||
|
@ -29,16 +30,30 @@ import org.apache.openjpa.jdbc.sql.Select;
|
||||||
class Count
|
class Count
|
||||||
extends UnaryOp {
|
extends UnaryOp {
|
||||||
|
|
||||||
|
private boolean isCountMultiColumns = false;
|
||||||
|
private boolean isCountDistinct = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Provide the value to operate on.
|
* Constructor. Provide the value to operate on.
|
||||||
*/
|
*/
|
||||||
public Count(Val val) {
|
public Count(Val val) {
|
||||||
super(val);
|
super(val);
|
||||||
|
if (val instanceof Distinct)
|
||||||
|
isCountDistinct = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExpState initialize(Select sel, ExpContext ctx, int flags) {
|
public ExpState initialize(Select sel, ExpContext ctx, int flags) {
|
||||||
// join into related object if present
|
// join into related object if present
|
||||||
return initializeValue(sel, ctx, JOIN_REL);
|
ExpState expState = initializeValue(sel, ctx, JOIN_REL);
|
||||||
|
Val val = isCountDistinct ? ((Distinct)getValue()).getValue() : getValue();
|
||||||
|
if (val instanceof PCPath) {
|
||||||
|
Column[] cols = ((PCPath)val).getColumns(expState);
|
||||||
|
if (cols.length > 1) {
|
||||||
|
isCountMultiColumns = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expState;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Class getType(Class c) {
|
protected Class getType(Class c) {
|
||||||
|
@ -52,15 +67,24 @@ class Count
|
||||||
public boolean isAggregate() {
|
public boolean isAggregate() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCountDistinctMultiCols() {
|
||||||
|
return isCountDistinct && isCountMultiColumns;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overrides SQL formation by replacing COUNT(column) by COUNT(*) when specific conditions are met and
|
* Overrides SQL formation by replacing COUNT(column) by COUNT(*) when specific conditions are met and
|
||||||
* DBDictionary configuration <code>useWildCardForCount</code> is set.
|
* DBDictionary configuration <code>useWildCardForCount</code> is set.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void appendTo(Select sel, ExpContext ctx, ExpState state, SQLBuffer sql, int index) {
|
public void appendTo(Select sel, ExpContext ctx, ExpState state, SQLBuffer sql, int index) {
|
||||||
super.appendTo(sel, ctx, state, sql, index);
|
if (isCountDistinctMultiCols()) {
|
||||||
if (ctx.store.getDBDictionary().useWildCardForCount && state.joins.isEmpty()) {
|
getValue().appendTo(sel, ctx, state, sql, 0);
|
||||||
|
sql.addCastForParam(getOperator(), getValue());
|
||||||
|
} else
|
||||||
|
super.appendTo(sel, ctx, state, sql, index);
|
||||||
|
if ((ctx.store.getDBDictionary().useWildCardForCount && state.joins.isEmpty()) ||
|
||||||
|
!isCountDistinct && isCountMultiColumns){
|
||||||
String s = sql.getSQL();
|
String s = sql.getSQL();
|
||||||
if (s.startsWith("COUNT(") && s.endsWith(")")) {
|
if (s.startsWith("COUNT(") && s.endsWith(")")) {
|
||||||
sql.replaceSqlString("COUNT(".length(), s.length()-1, "*");
|
sql.replaceSqlString("COUNT(".length(), s.length()-1, "*");
|
||||||
|
|
|
@ -41,4 +41,22 @@ class Distinct
|
||||||
protected String getOperator() {
|
protected String getOperator() {
|
||||||
return "DISTINCT";
|
return "DISTINCT";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendTo(Select sel, ExpContext ctx, ExpState state,
|
||||||
|
SQLBuffer sql, int index) {
|
||||||
|
Val val = getValue();
|
||||||
|
if (val instanceof PCPath) {
|
||||||
|
boolean noParen = getNoParen();
|
||||||
|
sql.append(getOperator());
|
||||||
|
sql.append(noParen ? " " : "(");
|
||||||
|
((PCPath)val).appendTo(sel, ctx, state, sql);
|
||||||
|
sql.addCastForParam(getOperator(), val);
|
||||||
|
if (!noParen)
|
||||||
|
sql.append(")");
|
||||||
|
|
||||||
|
} else
|
||||||
|
super.appendTo(sel, ctx, state, sql, index);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -970,9 +970,23 @@ public class PCPath
|
||||||
return getColumns(state).length;
|
return getColumns(state).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void appendTo(Select sel, ExpContext ctx, ExpState state,
|
||||||
|
SQLBuffer sql) {
|
||||||
|
Column[] cols = getColumns(state);
|
||||||
|
for (int i = 0; i < cols.length; i++) {
|
||||||
|
appendTo(sel, state, sql, cols[i]);
|
||||||
|
if (i < cols.length -1)
|
||||||
|
sql.append(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void appendTo(Select sel, ExpContext ctx, ExpState state,
|
public void appendTo(Select sel, ExpContext ctx, ExpState state,
|
||||||
SQLBuffer sql, int index) {
|
SQLBuffer sql, int index) {
|
||||||
Column col = getColumns(state)[index];
|
Column col = getColumns(state)[index];
|
||||||
|
appendTo(sel, state, sql, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void appendTo(Select sel, ExpState state, SQLBuffer sql, Column col) {
|
||||||
if (sel != null)
|
if (sel != null)
|
||||||
sel.setSchemaAlias(_schemaAlias);
|
sel.setSchemaAlias(_schemaAlias);
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,8 @@ import org.apache.openjpa.kernel.exps.Expression;
|
||||||
import org.apache.openjpa.kernel.exps.QueryExpressions;
|
import org.apache.openjpa.kernel.exps.QueryExpressions;
|
||||||
import org.apache.openjpa.kernel.exps.Subquery;
|
import org.apache.openjpa.kernel.exps.Subquery;
|
||||||
import org.apache.openjpa.kernel.exps.Value;
|
import org.apache.openjpa.kernel.exps.Value;
|
||||||
|
import org.apache.openjpa.lib.util.Localizer;
|
||||||
|
import org.apache.openjpa.util.UnsupportedException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns parsed queries into selects.
|
* Turns parsed queries into selects.
|
||||||
|
@ -48,6 +50,7 @@ public class SelectConstructor
|
||||||
|
|
||||||
private boolean _extent = false;
|
private boolean _extent = false;
|
||||||
private Select _subselect = null;
|
private Select _subselect = null;
|
||||||
|
private static final Localizer _loc = Localizer.forPackage(SelectConstructor.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if we know the select to have on criteria; to be an extent.
|
* Return true if we know the select to have on criteria; to be an extent.
|
||||||
|
@ -115,6 +118,18 @@ public class SelectConstructor
|
||||||
}
|
}
|
||||||
for (int i = 0; i < exps.grouping.length; i++)
|
for (int i = 0; i < exps.grouping.length; i++)
|
||||||
((Val) exps.grouping[i]).groupBy(sel, ctx, state.grouping[i]);
|
((Val) exps.grouping[i]).groupBy(sel, ctx, state.grouping[i]);
|
||||||
|
|
||||||
|
if (exps.projections.length == 1) {
|
||||||
|
Val val = (Val) exps.projections[0];
|
||||||
|
if (val instanceof Count && ((Count)val).isCountDistinctMultiCols()) {
|
||||||
|
Select newSel = ctx.store.getSQLFactory().newSelect();
|
||||||
|
newSel.select("COUNT(*)", val);
|
||||||
|
newSel.setExpectedResultCount(1, true);
|
||||||
|
newSel.setFromSelect(sel);
|
||||||
|
sel.setExpectedResultCount(0, true);
|
||||||
|
sel = newSel;
|
||||||
|
}
|
||||||
|
}
|
||||||
return sel;
|
return sel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,6 +250,10 @@ public class SelectConstructor
|
||||||
// projections; this ensures that we have all our joins cached
|
// projections; this ensures that we have all our joins cached
|
||||||
state.projections[i] = resultVal.initialize(sel, ctx,
|
state.projections[i] = resultVal.initialize(sel, ctx,
|
||||||
Val.JOIN_REL | Val.FORCE_OUTER);
|
Val.JOIN_REL | Val.FORCE_OUTER);
|
||||||
|
if (exps.projections.length > 1 && resultVal instanceof Count) {
|
||||||
|
if (((Count)resultVal).isCountDistinctMultiCols())
|
||||||
|
throw new UnsupportedException(_loc.get("count-distinct-multi-col-only"));
|
||||||
|
}
|
||||||
joins = sel.and(joins, state.projections[i].joins);
|
joins = sel.and(joins, state.projections[i].joins);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,6 +309,8 @@ public class SelectConstructor
|
||||||
Select inner = sel.getFromSelect();
|
Select inner = sel.getFromSelect();
|
||||||
Val val;
|
Val val;
|
||||||
Joins joins = null;
|
Joins joins = null;
|
||||||
|
boolean isCountDistinctMultiCols = false;
|
||||||
|
|
||||||
if (sel.getSubselectPath() != null)
|
if (sel.getSubselectPath() != null)
|
||||||
joins = sel.newJoins().setSubselect(sel.getSubselectPath());
|
joins = sel.newJoins().setSubselect(sel.getSubselectPath());
|
||||||
|
|
||||||
|
@ -307,9 +328,18 @@ public class SelectConstructor
|
||||||
// subselect for objects; we really just need the primary key values
|
// subselect for objects; we really just need the primary key values
|
||||||
sel.select(mapping.getPrimaryKeyColumns(), joins);
|
sel.select(mapping.getPrimaryKeyColumns(), joins);
|
||||||
} else {
|
} else {
|
||||||
|
if (exps.projections.length == 1) {
|
||||||
|
val = (Val) exps.projections[0];
|
||||||
|
if (val instanceof Count && ((Count)val).isCountDistinctMultiCols()) {
|
||||||
|
isCountDistinctMultiCols = true;
|
||||||
|
if (sel.getParent() != null)
|
||||||
|
throw new UnsupportedException(_loc.get("count-distinct-multi-col-subselect-unsupported"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if we have an inner select, we need to select the candidate
|
// if we have an inner select, we need to select the candidate
|
||||||
// class' pk columns to guarantee unique instances
|
// class' pk columns to guarantee unique instances
|
||||||
if (inner != null)
|
if (inner != null && !isCountDistinctMultiCols)
|
||||||
inner.select(mapping.getPrimaryKeyColumns(), joins);
|
inner.select(mapping.getPrimaryKeyColumns(), joins);
|
||||||
|
|
||||||
// select each result value; no need to pass on the eager mode since
|
// select each result value; no need to pass on the eager mode since
|
||||||
|
@ -317,9 +347,13 @@ public class SelectConstructor
|
||||||
boolean pks = sel.getParent() != null;
|
boolean pks = sel.getParent() != null;
|
||||||
for (int i = 0; i < exps.projections.length; i++) {
|
for (int i = 0; i < exps.projections.length; i++) {
|
||||||
val = (Val) exps.projections[i];
|
val = (Val) exps.projections[i];
|
||||||
if (inner != null)
|
if (inner != null) {
|
||||||
val.selectColumns(inner, ctx, state.projections[i], pks);
|
if (!isCountDistinctMultiCols)
|
||||||
val.select(sel, ctx, state.projections[i], pks);
|
val.selectColumns(inner, ctx, state.projections[i], pks);
|
||||||
|
else
|
||||||
|
val.select(inner, ctx, state.projections[i], pks);
|
||||||
|
} else
|
||||||
|
val.select(sel, ctx, state.projections[i], pks);
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure having columns are selected since it is required by
|
// make sure having columns are selected since it is required by
|
||||||
|
|
|
@ -74,6 +74,10 @@ abstract class UnaryOp
|
||||||
public void setImplicitType(Class type) {
|
public void setImplicitType(Class type) {
|
||||||
_cast = type;
|
_cast = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getNoParen() {
|
||||||
|
return _noParen;
|
||||||
|
}
|
||||||
|
|
||||||
public ExpState initialize(Select sel, ExpContext ctx, int flags) {
|
public ExpState initialize(Select sel, ExpContext ctx, int flags) {
|
||||||
return initializeValue(sel, ctx, flags);
|
return initializeValue(sel, ctx, flags);
|
||||||
|
|
|
@ -3111,9 +3111,10 @@ public class SelectImpl
|
||||||
alias = alias + _dict.getStringVal;
|
alias = alias + _dict.getStringVal;
|
||||||
|
|
||||||
String as = null;
|
String as = null;
|
||||||
if (inner)
|
if (inner) {
|
||||||
as = ((String) alias).replace('.', '_');
|
if (alias instanceof String)
|
||||||
else if (_selectAs != null)
|
as = ((String) alias).replace('.', '_');
|
||||||
|
} else if (_selectAs != null)
|
||||||
as = (String) _selectAs.get(id);
|
as = (String) _selectAs.get(id);
|
||||||
else if (id instanceof Value)
|
else if (id instanceof Value)
|
||||||
as = ((Value) id).getAlias();
|
as = ((Value) id).getAlias();
|
||||||
|
|
|
@ -30,4 +30,8 @@ invalid-unbound-var: Invalid unbound variable "{0}" in query.
|
||||||
no-order-column: Field "{0}" does not have order column defined".
|
no-order-column: Field "{0}" does not have order column defined".
|
||||||
not-collection-parm: Invalid input parameter "{0}", a collection-valued \
|
not-collection-parm: Invalid input parameter "{0}", a collection-valued \
|
||||||
input parameter is expected.
|
input parameter is expected.
|
||||||
empty-collection-parm: Input parameter "{0}" is empty.
|
empty-collection-parm: Input parameter "{0}" is empty.
|
||||||
|
count-distinct-multi-col-only: Count distinct compound primary key is not \
|
||||||
|
supported when there are other projection items.
|
||||||
|
count-distinct-multi-col-subselect-unsupported: Count distinct multiple columns \
|
||||||
|
in the subselect is not supported.
|
|
@ -206,6 +206,55 @@ public class TestMappedById extends SingleEMFTestCase {
|
||||||
assertNotNull(newDep);
|
assertNotNull(newDep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testCountDistinctMultiCols() {
|
||||||
|
EntityManager em = emf.createEntityManager();
|
||||||
|
|
||||||
|
Employee2 emp1 = new Employee2();
|
||||||
|
EmployeeId2 empId1 = new EmployeeId2();
|
||||||
|
empId1.setFirstName("James");
|
||||||
|
empId1.setLastName("Bond");
|
||||||
|
emp1.setEmpId(empId1);
|
||||||
|
|
||||||
|
Employee2 emp2 = new Employee2();
|
||||||
|
EmployeeId2 empId2 = new EmployeeId2();
|
||||||
|
empId2.setFirstName("James");
|
||||||
|
empId2.setLastName("Obama");
|
||||||
|
emp2.setEmpId(empId2);
|
||||||
|
|
||||||
|
Dependent2 dep1 = new Dependent2();
|
||||||
|
DependentId2 depId1 = new DependentId2();
|
||||||
|
depId1.setEmpPK(empId1);
|
||||||
|
depId1.setName("Alan");
|
||||||
|
dep1.setId(depId1);
|
||||||
|
|
||||||
|
Dependent2 dep2 = new Dependent2();
|
||||||
|
DependentId2 depId2 = new DependentId2();
|
||||||
|
depId2.setEmpPK(empId2);
|
||||||
|
depId2.setName("Darren");
|
||||||
|
dep2.setId(depId2);
|
||||||
|
|
||||||
|
em.persist(emp1);
|
||||||
|
em.persist(emp2);
|
||||||
|
em.persist(dep1);
|
||||||
|
em.persist(dep2);
|
||||||
|
|
||||||
|
em.getTransaction().begin();
|
||||||
|
em.flush();
|
||||||
|
em.getTransaction().commit();
|
||||||
|
|
||||||
|
String[] jpqls = {
|
||||||
|
"SELECT COUNT (DISTINCT d2.emp) FROM Dependent2 d2",
|
||||||
|
"select count (DISTINCT d2) from Dependent2 d2",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < jpqls.length; i++) {
|
||||||
|
Query q = em.createQuery(jpqls[i]) ;
|
||||||
|
Long o = (Long)q.getSingleResult();
|
||||||
|
int count = (int)o.longValue();
|
||||||
|
assertEquals(2, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void createObj1() {
|
public void createObj1() {
|
||||||
EntityManager em = emf.createEntityManager();
|
EntityManager em = emf.createEntityManager();
|
||||||
EntityTransaction tran = em.getTransaction();
|
EntityTransaction tran = em.getTransaction();
|
||||||
|
|
Loading…
Reference in New Issue