#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:
Rob Bygrave
2016-12-20 02:59:47 +13:00
parent 2eb0c12213
commit 84e1e12b66
23 changed files with 339 additions and 102 deletions
@@ -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.
*/