mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-11-02 08:21:46 +00:00
#900 - Error - Can't fetch JsonDB field - org.postgresql.util.PSQLException: ERROR: could not identify an equality operator for type json
This commit is contained in:
@@ -674,6 +674,11 @@ public interface SpiQuery<T> extends Query<T> {
|
||||
*/
|
||||
boolean isDistinctQuery();
|
||||
|
||||
/**
|
||||
* Return true if this was internally set to sql distinct (ie. many where predicate).
|
||||
*/
|
||||
boolean isSqlDistinct();
|
||||
|
||||
/**
|
||||
* Return true if this query has been specified by a user to use DISTINCT.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.RawSql;
|
||||
import io.ebean.RawSql.ColumnMapping;
|
||||
import io.ebean.RawSql.ColumnMapping.Column;
|
||||
@@ -39,8 +40,8 @@ import java.util.List;
|
||||
*/
|
||||
class CQueryBuilder {
|
||||
|
||||
private final String tableAliasPlaceHolder;
|
||||
private final String columnAliasPrefix;
|
||||
protected final String tableAliasPlaceHolder;
|
||||
protected final String columnAliasPrefix;
|
||||
|
||||
private final SqlLimiter sqlLimiter;
|
||||
|
||||
@@ -110,7 +111,7 @@ class CQueryBuilder {
|
||||
|
||||
predicates.prepare(true);
|
||||
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query), getDraftSupport(query));
|
||||
SqlTree sqlTree = createSqlTree(request, predicates);
|
||||
|
||||
String sql;
|
||||
if (type.equals("Delete")) {
|
||||
@@ -186,7 +187,7 @@ class CQueryBuilder {
|
||||
// use RawSql or generated Sql
|
||||
predicates.prepare(true);
|
||||
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query), getDraftSupport(query));
|
||||
SqlTree sqlTree = createSqlTree(request, predicates);
|
||||
SqlLimitResponse s = buildSql(null, request, predicates, sqlTree);
|
||||
|
||||
queryPlan = new CQueryPlan(request, s.getSql(), sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql());
|
||||
@@ -206,14 +207,14 @@ class CQueryBuilder {
|
||||
/**
|
||||
* Return the history support if this query needs it (is a 'as of' type query).
|
||||
*/
|
||||
private <T> CQueryHistorySupport getHistorySupport(SpiQuery<T> query) {
|
||||
<T> CQueryHistorySupport getHistorySupport(SpiQuery<T> query) {
|
||||
return query.getTemporalMode() != SpiQuery.TemporalMode.CURRENT ? historySupport : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the draft support (or null) for a 'asDraft' query.
|
||||
*/
|
||||
private <T> CQueryDraftSupport getDraftSupport(SpiQuery<T> query) {
|
||||
<T> CQueryDraftSupport getDraftSupport(SpiQuery<T> query) {
|
||||
return query.getTemporalMode() == SpiQuery.TemporalMode.DRAFT ? draftSupport : null;
|
||||
}
|
||||
|
||||
@@ -248,7 +249,7 @@ class CQueryBuilder {
|
||||
|
||||
predicates.prepare(true);
|
||||
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query), getDraftSupport(query));
|
||||
SqlTree sqlTree = createSqlTree(request, predicates);
|
||||
if (SpiQuery.TemporalMode.CURRENT == query.getTemporalMode()) {
|
||||
sqlTree.addSoftDeletePredicate(query);
|
||||
}
|
||||
@@ -306,7 +307,7 @@ class CQueryBuilder {
|
||||
// Build the tree structure that represents the query.
|
||||
SpiQuery<T> query = request.getQuery();
|
||||
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query), getDraftSupport(query));
|
||||
SqlTree sqlTree = createSqlTree(request, predicates);
|
||||
if (query.isAsOfQuery()) {
|
||||
sqlTree.addAsOfTableAlias(query);
|
||||
} else if (SpiQuery.TemporalMode.CURRENT == query.getTemporalMode()) {
|
||||
@@ -347,7 +348,7 @@ class CQueryBuilder {
|
||||
* order by clauses that are not already included for the select clause.
|
||||
* </p>
|
||||
*/
|
||||
private SqlTree createSqlTree(OrmQueryRequest<?> request, CQueryPredicates predicates, CQueryHistorySupport historySupport, CQueryDraftSupport draftSupport) {
|
||||
private SqlTree createSqlTree(OrmQueryRequest<?> request, CQueryPredicates predicates) {
|
||||
|
||||
if (request.isNativeSql()) {
|
||||
return createNativeSqlTree(request, predicates);
|
||||
@@ -355,7 +356,7 @@ class CQueryBuilder {
|
||||
if (request.isRawSql()) {
|
||||
return createRawSqlSqlTree(request, predicates);
|
||||
}
|
||||
return new SqlTreeBuilder(tableAliasPlaceHolder, columnAliasPrefix, request, predicates, historySupport, draftSupport).build();
|
||||
return new SqlTreeBuilder(this, request, predicates).build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -517,13 +518,18 @@ class CQueryBuilder {
|
||||
sb.append("select ");
|
||||
if (query.isDistinctQuery()) {
|
||||
sb.append("distinct ");
|
||||
String distinctOn = select.getDistinctOn();
|
||||
if (distinctOn != null) {
|
||||
sb.append("on (");
|
||||
sb.append(distinctOn).append(") ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(select.getSelectSql());
|
||||
if (query.isDistinctQuery() && dbOrderBy != null && !query.isSingleAttribute()) {
|
||||
// add the orderBy columns to the select clause (due to distinct)
|
||||
sb.append(", ").append(convertDbOrderByForSelect(dbOrderBy));
|
||||
sb.append(", ").append(DbOrderByTrim.trim(dbOrderBy));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,17 +645,11 @@ class CQueryBuilder {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the dbOrderBy clause to be safe for adding to select. This is done when 'distinct' is
|
||||
* used.
|
||||
*/
|
||||
private String convertDbOrderByForSelect(String dbOrderBy) {
|
||||
// just remove the ASC and DESC keywords
|
||||
return dbOrderBy.replaceAll("(?i)\\b asc\\b|\\b desc\\b", "");
|
||||
}
|
||||
|
||||
private boolean isEmpty(String s) {
|
||||
return s == null || s.isEmpty();
|
||||
}
|
||||
|
||||
boolean isPlatformDistinctOn() {
|
||||
return dbPlatform.isPlatform(Platform.POSTGRES);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,12 +54,12 @@ class DefaultDbSqlContext implements DbSqlContext {
|
||||
/**
|
||||
* Construct for SELECT clause (with column alias settings).
|
||||
*/
|
||||
DefaultDbSqlContext(SqlTreeAlias alias, String tableAliasPlaceHolder,
|
||||
String columnAliasPrefix, boolean alwaysUseColumnAlias, CQueryHistorySupport historySupport, CQueryDraftSupport draftSupport) {
|
||||
DefaultDbSqlContext(SqlTreeAlias alias, CQueryBuilder builder,
|
||||
boolean alwaysUseColumnAlias, CQueryHistorySupport historySupport, CQueryDraftSupport draftSupport) {
|
||||
|
||||
this.alias = alias;
|
||||
this.tableAliasPlaceHolder = tableAliasPlaceHolder;
|
||||
this.columnAliasPrefix = columnAliasPrefix;
|
||||
this.tableAliasPlaceHolder = builder.tableAliasPlaceHolder;
|
||||
this.columnAliasPrefix = builder.columnAliasPrefix;
|
||||
this.useColumnAlias = alwaysUseColumnAlias;
|
||||
this.draftSupport = draftSupport;
|
||||
this.historySupport = historySupport;
|
||||
|
||||
@@ -27,6 +27,8 @@ class SqlTree {
|
||||
*/
|
||||
private final String summary;
|
||||
|
||||
private final String distinctOn;
|
||||
|
||||
private final String selectSql;
|
||||
|
||||
private final String fromSql;
|
||||
@@ -48,11 +50,12 @@ class SqlTree {
|
||||
/**
|
||||
* Create the SqlSelectClause.
|
||||
*/
|
||||
SqlTree(String summary, SqlTreeNode rootNode, String selectSql, String fromSql, String groupBy, String inheritanceWhereSql,
|
||||
SqlTree(String summary, SqlTreeNode rootNode, String distinctOn, String selectSql, String fromSql, String groupBy, String inheritanceWhereSql,
|
||||
BeanProperty[] encryptedProps, BeanPropertyAssocMany<?> manyProperty, Set<String> includes, boolean includeJoins) {
|
||||
|
||||
this.summary = summary;
|
||||
this.rootNode = rootNode;
|
||||
this.distinctOn = distinctOn;
|
||||
this.selectSql = selectSql;
|
||||
this.fromSql = fromSql;
|
||||
this.groupBy = groupBy;
|
||||
@@ -100,6 +103,10 @@ class SqlTree {
|
||||
return includes;
|
||||
}
|
||||
|
||||
String getDistinctOn() {
|
||||
return distinctOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the String for the actual SQL.
|
||||
*/
|
||||
|
||||
@@ -46,6 +46,7 @@ public class SqlTreeBuilder {
|
||||
|
||||
private final boolean subQuery;
|
||||
|
||||
private final boolean distinctOnPlatform;
|
||||
/**
|
||||
* Property if resultSet contains master and detail rows.
|
||||
*/
|
||||
@@ -85,6 +86,7 @@ public class SqlTreeBuilder {
|
||||
this.disableLazyLoad = request.getQuery().isDisableLazyLoading();
|
||||
this.query = null;
|
||||
this.subQuery = false;
|
||||
this.distinctOnPlatform = false;
|
||||
this.queryDetail = queryDetail;
|
||||
this.predicates = predicates;
|
||||
this.temporalMode = SpiQuery.TemporalMode.CURRENT;
|
||||
@@ -99,8 +101,7 @@ public class SqlTreeBuilder {
|
||||
* support the where and/or order by clause. If so these extra joins are added
|
||||
* to the root node.
|
||||
*/
|
||||
public SqlTreeBuilder(String tableAliasPlaceHolder, String columnAliasPrefix,
|
||||
OrmQueryRequest<?> request, CQueryPredicates predicates, CQueryHistorySupport historySupport, CQueryDraftSupport draftSupport) {
|
||||
public SqlTreeBuilder(CQueryBuilder builder, OrmQueryRequest<?> request, CQueryPredicates predicates) {
|
||||
|
||||
this.rawSql = false;
|
||||
this.rawNoId = false;
|
||||
@@ -115,7 +116,11 @@ public class SqlTreeBuilder {
|
||||
|
||||
this.predicates = predicates;
|
||||
this.alias = new SqlTreeAlias(request.getBaseTableAlias());
|
||||
this.ctx = new DefaultDbSqlContext(alias, tableAliasPlaceHolder, columnAliasPrefix, !subQuery, historySupport, draftSupport);
|
||||
this.distinctOnPlatform = builder.isPlatformDistinctOn();
|
||||
|
||||
CQueryHistorySupport historySupport = builder.getHistorySupport(query);
|
||||
CQueryDraftSupport draftSupport = builder.getDraftSupport(query);
|
||||
this.ctx = new DefaultDbSqlContext(alias, builder, !subQuery, historySupport, draftSupport);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,6 +134,7 @@ public class SqlTreeBuilder {
|
||||
buildRoot(desc);
|
||||
|
||||
// build the actual String
|
||||
String distinctOn = null;
|
||||
String selectSql = null;
|
||||
String fromSql = null;
|
||||
String inheritanceWhereSql = null;
|
||||
@@ -139,12 +145,13 @@ public class SqlTreeBuilder {
|
||||
fromSql = buildFromClause();
|
||||
inheritanceWhereSql = buildWhereClause();
|
||||
groupBy = buildGroupByClause();
|
||||
distinctOn = buildDistinctOn();
|
||||
encryptedProps = ctx.getEncryptedProps();
|
||||
}
|
||||
|
||||
boolean includeJoins = alias != null && alias.isIncludeJoins();
|
||||
|
||||
return new SqlTree(summary.toString(), rootNode, selectSql, fromSql, groupBy, inheritanceWhereSql, encryptedProps,
|
||||
return new SqlTree(summary.toString(), rootNode, distinctOn, selectSql, fromSql, groupBy, inheritanceWhereSql, encryptedProps,
|
||||
manyProperty, queryDetail.getFetchPaths(), includeJoins);
|
||||
}
|
||||
|
||||
@@ -164,18 +171,46 @@ public class SqlTreeBuilder {
|
||||
}
|
||||
ctx.startGroupBy();
|
||||
rootNode.appendGroupBy(ctx, subQuery);
|
||||
String groupBy = ctx.getContent();
|
||||
return trimComma(groupBy);
|
||||
return trimComma(ctx.getContent());
|
||||
}
|
||||
|
||||
private String buildDistinctOn() {
|
||||
|
||||
if (rawSql || !distinctOnPlatform || !query.isSqlDistinct() || Type.COUNT == query.getType()) {
|
||||
return null;
|
||||
}
|
||||
ctx.startGroupBy();
|
||||
rootNode.appendDistinctOn(ctx, subQuery);
|
||||
String idCols = trimComma(ctx.getContent());
|
||||
return idCols == null ? null : mergeOnDistinct(idCols, predicates.getDbOrderBy());
|
||||
}
|
||||
|
||||
static String mergeOnDistinct(String idCols, String dbOrderBy) {
|
||||
if (dbOrderBy == null) {
|
||||
return idCols;
|
||||
}
|
||||
dbOrderBy = DbOrderByTrim.trim(dbOrderBy);
|
||||
StringBuilder sb = new StringBuilder(dbOrderBy.length() + idCols.length() + 2);
|
||||
sb.append(dbOrderBy);
|
||||
String[] split = idCols.split(",");
|
||||
for (String col : split) {
|
||||
col = col.trim();
|
||||
if (!dbOrderBy.contains(col)) {
|
||||
sb.append(", ").append(col);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim the first comma.
|
||||
*/
|
||||
private String trimComma(String groupBy) {
|
||||
if (groupBy.length() >= SqlTreeNode.COMMA.length()) {
|
||||
groupBy = groupBy.substring(SqlTreeNode.COMMA.length());
|
||||
if (groupBy.length() < SqlTreeNode.COMMA.length()) {
|
||||
return null;
|
||||
} else {
|
||||
return groupBy.substring(SqlTreeNode.COMMA.length());
|
||||
}
|
||||
return groupBy;
|
||||
}
|
||||
|
||||
private String buildWhereClause() {
|
||||
|
||||
@@ -25,8 +25,12 @@ interface SqlTreeNode {
|
||||
boolean isAggregation();
|
||||
|
||||
/**
|
||||
* Append the required column information to the SELECT part of the sql
|
||||
* statement.
|
||||
* Append the distinct on clause (Id properties of root and many root only).
|
||||
*/
|
||||
void appendDistinctOn(DbSqlContext ctx, boolean subQuery);
|
||||
|
||||
/**
|
||||
* Append the required column information to the select clause.
|
||||
*/
|
||||
void appendSelect(DbSqlContext ctx, boolean subQuery);
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
|
||||
protected final BeanDescriptor<?> desc;
|
||||
|
||||
private final IdBinder idBinder;
|
||||
protected final IdBinder idBinder;
|
||||
|
||||
/**
|
||||
* The children which will be other SelectBean or SelectProxyBean.
|
||||
@@ -57,7 +57,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
/**
|
||||
* False if report bean and has no id property.
|
||||
*/
|
||||
private final boolean readId;
|
||||
protected final boolean readId;
|
||||
|
||||
private final boolean disableLazyLoad;
|
||||
|
||||
@@ -171,10 +171,10 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
property.buildRawSqlSelectChain(prefix, selectChain);
|
||||
}
|
||||
// recursively continue reading...
|
||||
for (SqlTreeNode aChildren : children) {
|
||||
for (SqlTreeNode child : children) {
|
||||
// read each child... and let them set their
|
||||
// values back to this localBean
|
||||
aChildren.buildRawSqlSelectChain(selectChain);
|
||||
child.buildRawSqlSelectChain(selectChain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,6 +401,15 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
ctx.popJoin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the property columns to the buffer.
|
||||
*/
|
||||
public void appendDistinctOn(DbSqlContext ctx, boolean subQuery) {
|
||||
for (SqlTreeNode child : children) {
|
||||
child.appendDistinctOn(ctx, subQuery);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the property columns to the buffer.
|
||||
*/
|
||||
@@ -451,8 +460,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
}
|
||||
}
|
||||
|
||||
private void appendSelectId(DbSqlContext ctx, BeanProperty prop) {
|
||||
|
||||
protected void appendSelectId(DbSqlContext ctx, BeanProperty prop) {
|
||||
if (prop != null) {
|
||||
prop.appendSelect(ctx, false);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,11 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode {
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendDistinctOn(DbSqlContext ctx, boolean subQuery) {
|
||||
// do nothing here ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAggregation() {
|
||||
return false;
|
||||
|
||||
@@ -19,6 +19,15 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
|
||||
this.manyProp = prop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the property columns to the buffer.
|
||||
*/
|
||||
public void appendDistinctOn(DbSqlContext ctx, boolean subQuery) {
|
||||
ctx.pushTableAlias(prefix);
|
||||
appendSelectId(ctx, idBinder.getBeanProperty());
|
||||
ctx.popTableAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityBean load(DbReadContext cquery, EntityBean parentBean, EntityBean contextParent) throws SQLException {
|
||||
// pass in null for parentBean because the localBean
|
||||
|
||||
@@ -60,6 +60,11 @@ class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendDistinctOn(DbSqlContext ctx, boolean subQuery) {
|
||||
// do nothing here ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendGroupBy(DbSqlContext ctx, boolean subQuery) {
|
||||
// do nothing here
|
||||
|
||||
@@ -25,6 +25,18 @@ final class SqlTreeNodeRoot extends SqlTreeNodeBean {
|
||||
this.includeJoin = includeJoin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the property columns to the buffer.
|
||||
*/
|
||||
public void appendDistinctOn(DbSqlContext ctx, boolean subQuery) {
|
||||
if (readId) {
|
||||
ctx.pushTableAlias(prefix);
|
||||
appendSelectId(ctx, idBinder.getBeanProperty());
|
||||
ctx.popTableAlias();
|
||||
super.appendDistinctOn(ctx, subQuery);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set AsOf support (at root level).
|
||||
*/
|
||||
|
||||
@@ -1351,6 +1351,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return distinct || sqlDistinct;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSqlDistinct() {
|
||||
return sqlDistinct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internally set to use SQL DISTINCT on the query but still have id property included.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user