This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch 2_1_X
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/2_1_X by this push:
     new ffbe1fc  [SYNCOPE-1609] Changes jsonb queries in order to improve 
performances (#233)
ffbe1fc is described below

commit ffbe1fc8c3b607f2c21f059aadbaf59be470d3f9
Author: Fabio Martelli <[email protected]>
AuthorDate: Tue Jan 12 09:03:45 2021 +0100

    [SYNCOPE-1609] Changes jsonb queries in order to improve performances (#233)
---
 .../persistence/jpa/dao/PGJPAJSONAnySearchDAO.java | 1103 ++++++++++++++++++--
 .../jpa/dao/PGJPAJSONPlainSchemaDAO.java           |    2 +-
 .../src/main/resources/pgjsonb/views.xml           |    7 -
 .../resources/docker-compose/docker-compose-ha.yml |    2 +-
 .../docker-compose/docker-compose-pgjsonb.yml      |    2 +-
 .../docker-compose/docker-compose-postgresql.yml   |    2 +-
 pom.xml                                            |    2 +-
 7 files changed, 996 insertions(+), 124 deletions(-)

diff --git 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java
 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java
index 421a519..2de1f54 100644
--- 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java
+++ 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java
@@ -19,75 +19,53 @@
 package org.apache.syncope.core.persistence.jpa.dao;
 
 import java.text.ParseException;
-import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
+import javax.persistence.Query;
+import javax.persistence.TemporalType;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
+import org.apache.syncope.core.persistence.api.dao.search.AnyTypeCond;
+import org.apache.syncope.core.persistence.api.dao.search.AssignableCond;
 import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
+import org.apache.syncope.core.persistence.api.dao.search.DynRealmCond;
+import org.apache.syncope.core.persistence.api.dao.search.MemberCond;
+import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.dao.search.PrivilegeCond;
+import org.apache.syncope.core.persistence.api.dao.search.RelationshipCond;
+import org.apache.syncope.core.persistence.api.dao.search.RelationshipTypeCond;
+import org.apache.syncope.core.persistence.api.dao.search.ResourceCond;
+import org.apache.syncope.core.persistence.api.dao.search.RoleCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
-import org.apache.syncope.core.persistence.api.entity.PlainAttr;
-import org.apache.syncope.core.persistence.api.entity.PlainAttrUniqueValue;
+import org.apache.syncope.core.persistence.api.entity.DynRealm;
+import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
-import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
-import org.apache.syncope.core.persistence.api.entity.JSONPlainAttr;
+import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.provisioning.api.utils.FormatUtils;
+import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
 
 public class PGJPAJSONAnySearchDAO extends AbstractJPAJSONAnySearchDAO {
 
-    @Override
-    protected void processOBS(
-            final SearchSupport svs,
-            final Set<String> involvedPlainAttrs,
-            final OrderBySupport obs,
-            final StringBuilder where) {
-
-        Set<String> attrs = obs.items.stream().
-                map(item -> item.orderBy.substring(0, item.orderBy.indexOf(" 
"))).collect(Collectors.toSet());
-
-        obs.views.forEach(searchView -> {
-            if (searchView.name.equals(svs.field().name)) {
-                StringBuilder attrWhere = new StringBuilder();
-                StringBuilder nullAttrWhere = new StringBuilder();
-
-                where.append(", (SELECT * FROM ").append(searchView.name);
-
-                if (svs.nonMandatorySchemas || obs.nonMandatorySchemas) {
-                    attrs.forEach(field -> {
-                        if (attrWhere.length() == 0) {
-                            attrWhere.append(" WHERE ");
-                        } else {
-                            attrWhere.append(" OR ");
-                        }
-                        attrWhere.append("plainAttrs @> 
'[{\"schema\":\"").append(field).append("\"}]'::jsonb");
-
-                        nullAttrWhere.append(" UNION SELECT DISTINCT 
any_id,").append(svs.table().alias).append(".*, ").
-                                append("'{\"schema\": \"").
-                                append(field).
-                                append("\"}'::jsonb as attrs, '{}'::jsonb as 
attrValues").
-                                append(" FROM 
").append(svs.table().name).append(" ").append(svs.table().alias).
-                                append(", ").append(svs.field().name).
-                                append(" WHERE ").
-                                append("any_id NOT IN ").
-                                append("(SELECT distinct any_id FROM ").
-                                append(svs.field().name).
-                                append(" WHERE 
").append(svs.table().alias).append(".id=any_id AND ").
-                                append("plainAttrs @> 
'[{\"schema\":\"").append(field).append("\"}]'::jsonb)");
-                    });
-                    where.append(attrWhere).append(nullAttrWhere);
-                }
-
-                where.append(')');
-            } else {
-                where.append(',').append(searchView.name);
-            }
-            where.append(' ').append(searchView.alias);
-        });
-    }
+    protected static final String ALWAYS_FALSE_ASSERTION = "1=2";
 
     @Override
     protected void parseOrderByForPlainSchema(
@@ -101,14 +79,14 @@ public class PGJPAJSONAnySearchDAO extends 
AbstractJPAJSONAnySearchDAO {
         // keep track of involvement of non-mandatory schemas in the order by 
clauses
         obs.nonMandatorySchemas = 
!"true".equals(schema.getMandatoryCondition());
 
-        obs.views.add(svs.field());
+        obs.views.add(svs.table());
 
-        item.select = svs.field().alias + ".attrValues ->> '" + 
key(schema.getType()) + "' AS " + fieldName;
-        item.where = "attrs ->> 'schema' = '" + fieldName + "'";
+        item.select = fieldName + " -> 0 AS " + fieldName;
+        item.where = StringUtils.EMPTY;
         item.orderBy = fieldName + " " + clause.getDirection().name();
     }
 
-    private void fillAttrQuery(
+    protected void fillAttrQuery(
             final AnyUtils anyUtils,
             final StringBuilder query,
             final PlainAttrValue attrValue,
@@ -119,53 +97,118 @@ public class PGJPAJSONAnySearchDAO extends 
AbstractJPAJSONAnySearchDAO {
             final SearchSupport svs) {
 
         // This first branch is required for handling with not conditions 
given on multivalue fields (SYNCOPE-1419)
-        if (not && schema.isMultivalue()
-                && !(cond instanceof AnyCond)
-                && cond.getType() != AttrCond.Type.ISNULL && cond.getType() != 
AttrCond.Type.ISNOTNULL) {
-
-            query.append("id NOT IN (SELECT DISTINCT any_id FROM ");
-            query.append(svs.field().name).append(" WHERE ");
+        if (not && !(cond instanceof AnyCond)) {
+            query.append("NOT (");
             fillAttrQuery(anyUtils, query, attrValue, schema, cond, false, 
parameters, svs);
             query.append(")");
+        } else if (not && cond.getType() == AttrCond.Type.ISNULL) {
+            cond.setType(AttrCond.Type.ISNOTNULL);
+            fillAttrQuery(anyUtils, query, attrValue, schema, cond, true, 
parameters, svs);
         } else {
-            if (!not && cond.getType() == AttrCond.Type.EQ) {
-                PlainAttr<?> container = anyUtils.newPlainAttr();
-                container.setSchema(schema);
-                if (attrValue instanceof PlainAttrUniqueValue) {
-                    container.setUniqueValue((PlainAttrUniqueValue) attrValue);
-                } else {
-                    ((JSONPlainAttr) container).add(attrValue);
+            String key = key(schema.getType());
+
+            String value = cond.getExpression();
+            if (schema.getType() == AttrSchemaType.Date) {
+                try {
+                    value = 
String.valueOf(FormatUtils.parseDate(value).getTime());
+                } catch (ParseException e) {
+                    LOG.error("Could not parse {} as date", value, e);
                 }
+            }
 
-                query.append("plainAttrs @> '").
-                        
append(POJOHelper.serialize(Arrays.asList(container)).replace("'", "''")).
-                        append("'::jsonb");
+            boolean isStr = true;
+            boolean lower;
+            if (schema.getType() == AttrSchemaType.String || schema.getType() 
== AttrSchemaType.Enum) {
+                lower = (cond.getType() == AttrCond.Type.IEQ || cond.getType() 
== AttrCond.Type.ILIKE);
             } else {
-                String key = key(schema.getType());
-                boolean lower = (schema.getType() == AttrSchemaType.String || 
schema.getType() == AttrSchemaType.Enum)
-                        && (cond.getType() == AttrCond.Type.IEQ || 
cond.getType() == AttrCond.Type.ILIKE);
-
-                query.append("attrs ->> 'schema' = 
?").append(setParameter(parameters, cond.getSchema())).
-                        append(" AND ").
-                        append(lower ? "LOWER(" : "").
-                        append(schema.isUniqueConstraint()
-                                ? "attrs -> 'uniqueValue'" : "attrValues").
-                        append(" ->> '").append(key).append("'").
-                        append(lower ? ")" : "");
-
-                appendOp(query, cond.getType(), not);
-
-                String value = cond.getExpression();
-                if (schema.getType() == AttrSchemaType.Date) {
-                    try {
-                        value = 
String.valueOf(FormatUtils.parseDate(value).getTime());
-                    } catch (ParseException e) {
-                        LOG.error("Could not parse {} as date", value, e);
+                lower = false;
+                try {
+                    switch (schema.getType()) {
+                        case Date:
+                        case Long:
+                            Long.parseLong(value);
+                            break;
+                        case Double:
+                            Double.parseDouble(value);
+                            break;
+                        case Boolean:
+                            if (!("true".equalsIgnoreCase(value)
+                                    || "false".equalsIgnoreCase(value))) {
+                                throw new IllegalArgumentException();
+                            }
+                            break;
+                        default:
                     }
+
+                    isStr = false;
+                } catch (Exception nfe) {
+                    // ignore}
                 }
-                query.append(lower ? "LOWER(" : "").
-                        append("?").append(setParameter(parameters, value)).
-                        append(lower ? ")" : "");
+            }
+
+            switch (cond.getType()) {
+
+                case ISNULL:
+                    // shouldn't occour: processed before
+                    break;
+
+                case ISNOTNULL:
+                    
query.append("jsonb_path_exists(").append(schema.getKey()).append(", '$[*]')");
+                    break;
+
+                case ILIKE:
+                case LIKE:
+                    // jsonb_path_exists(Nome, '$[*] ? (@.stringValue 
like_regex "EL.*" flag "i")')
+                    if (schema.getType() == AttrSchemaType.String || 
schema.getType() == AttrSchemaType.Enum) {
+                        
query.append("jsonb_path_exists(").append(schema.getKey()).append(", '$[*] ? ").
+                                append("(@.").append(key).append(" like_regex 
\"").
+                                append(value.replaceAll("%", ".*")).
+                                append("\"").
+                                append(lower ? " flag \"i\"" : 
"").append(")')");
+                    } else {
+                        query.append(" 1=2");
+                        LOG.error("LIKE is only compatible with string or enum 
schemas");
+                    }
+                    break;
+
+                case IEQ:
+                case EQ:
+                    
query.append("jsonb_path_exists(").append(schema.getKey()).append(", '$[*] ? ").
+                            append("(@.").append(key);
+                    if (isStr) {
+                        query.append(" like_regex 
\"").append(value).append("\"");
+                    } else {
+                        query.append(" == ").append(value);
+                    }
+
+                    query.append(lower ? " flag \"i\"" : "").append(")')");
+                    break;
+
+                case GE:
+                    
query.append("jsonb_path_exists(").append(schema.getKey()).append(", '$[*] ? ").
+                            append("(@.").append(key).append(" >= ").
+                            append(value).append(")')");
+                    break;
+
+                case GT:
+                    
query.append("jsonb_path_exists(").append(schema.getKey()).append(", '$[*] ? ").
+                            append("(@.").append(key).append(" > ").
+                            append(value).append(")')");
+                    break;
+
+                case LE:
+                    
query.append("jsonb_path_exists(").append(schema.getKey()).append(", '$[*] ? ").
+                            append("(@.").append(key).append(" <= ").
+                            append(value).append(")')");
+                    break;
+
+                case LT:
+                    
query.append("jsonb_path_exists(").append(schema.getKey()).append(", '$[*] ? ").
+                            append("(@.").append(key).append(" < ").
+                            append(value).append(")')");
+                    break;
+
+                default:
             }
         }
     }
@@ -181,7 +224,7 @@ public class PGJPAJSONAnySearchDAO extends 
AbstractJPAJSONAnySearchDAO {
         try {
             checked = check(cond, svs.anyTypeKind);
         } catch (IllegalArgumentException e) {
-            return EMPTY_QUERY;
+            return ALWAYS_FALSE_ASSERTION;
         }
 
         // normalize NULL / NOT NULL checks
@@ -193,32 +236,868 @@ public class PGJPAJSONAnySearchDAO extends 
AbstractJPAJSONAnySearchDAO {
             }
         }
 
-        StringBuilder query =
-                new StringBuilder("SELECT DISTINCT any_id FROM 
").append(svs.field().name).append(" WHERE ");
+        StringBuilder query = new StringBuilder();
+
         switch (cond.getType()) {
             case ISNOTNULL:
-                query.append("plainAttrs @> '[{\"schema\":\"").
-                        append(checked.getLeft().getKey()).
-                        append("\"}]'::jsonb");
+                query.append(not ? " NOT " : " ").
+                        
append("jsonb_path_exists(").append(checked.getLeft().getKey()).append(",'$[*]')");
                 break;
 
             case ISNULL:
-                query.append("any_id NOT IN (").
-                        append("SELECT any_id FROM ").append(svs.field().name).
-                        append(" WHERE plainAttrs @> '[{\"schema\":\"").
-                        append(checked.getLeft().getKey()).
-                        append("\"}]'::jsonb)");
+                query.append(not ? " " : " NOT ").
+                        
append("jsonb_path_exists(").append(checked.getLeft().getKey()).append(",'$[*]')");
                 break;
 
             default:
-                if (not && !(cond instanceof AnyCond) && 
checked.getLeft().isMultivalue()) {
-                    query = new StringBuilder("SELECT DISTINCT id AS any_id 
FROM ").append(svs.table().name).
-                            append(" WHERE ");
-                }
                 fillAttrQuery(anyUtilsFactory.getInstance(svs.anyTypeKind),
                         query, checked.getRight(), checked.getLeft(), cond, 
not, parameters, svs);
         }
 
         return query.toString();
     }
+
+    @Override
+    protected String getQuery(
+            final AnyTypeCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
+
+        StringBuilder query = new StringBuilder("type_id");
+
+        if (not) {
+            query.append("<>");
+        } else {
+            query.append('=');
+        }
+
+        query.append('?').append(setParameter(parameters, 
cond.getAnyTypeKey()));
+
+        return query.toString();
+    }
+
+    @Override
+    protected String getQuery(
+            final RoleCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
+
+        StringBuilder query = new StringBuilder("(");
+
+        if (not) {
+            query.append("id NOT IN (");
+        } else {
+            query.append("id IN (");
+        }
+
+        query.append("SELECT DISTINCT any_id FROM ").
+                append(svs.role().name).append(" WHERE ").
+                append("role_id=?").append(setParameter(parameters, 
cond.getRole())).
+                append(") ");
+
+        if (not) {
+            query.append("AND id NOT IN (");
+        } else {
+            query.append("OR id IN (");
+        }
+
+        query.append("SELECT DISTINCT any_id FROM ").
+                append(svs.dynrolemembership().name).append(" WHERE ").
+                append("role_id=?").append(setParameter(parameters, 
cond.getRole())).
+                append(")");
+
+        query.append(")");
+
+        return query.toString();
+    }
+
+    @Override
+    protected String getQuery(
+            final PrivilegeCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
+
+        StringBuilder query = new StringBuilder("(");
+
+        if (not) {
+            query.append("id NOT IN (");
+        } else {
+            query.append("id IN (");
+        }
+
+        query.append("SELECT DISTINCT any_id FROM ").
+                append(svs.priv().name).append(" WHERE ").
+                append("privilege_id=?").append(setParameter(parameters, 
cond.getPrivilege())).
+                append(") ");
+
+        if (not) {
+            query.append("AND id NOT IN (");
+        } else {
+            query.append("OR id IN (");
+        }
+
+        query.append("SELECT DISTINCT any_id FROM ").
+                append(svs.dynpriv().name).append(" WHERE ").
+                append("privilege_id=?").append(setParameter(parameters, 
cond.getPrivilege())).
+                append(")");
+
+        query.append(")");
+
+        return query.toString();
+    }
+
+    @Override
+    protected String getQuery(
+            final DynRealmCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
+
+        StringBuilder query = new StringBuilder();
+
+        if (not) {
+            query.append("id NOT IN (");
+        } else {
+            query.append("id IN (");
+        }
+
+        query.append("SELECT DISTINCT any_id FROM ").
+                append(svs.dynrealmmembership().name).append(" WHERE ").
+                append("dynRealm_id=?").append(setParameter(parameters, 
cond.getDynRealm())).
+                append(")");
+
+        return query.toString();
+    }
+
+    @Override
+    protected String getQuery(
+            final ResourceCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
+
+        StringBuilder query = new StringBuilder();
+
+        if (not) {
+            query.append("id NOT IN (");
+        } else {
+            query.append("id IN (");
+        }
+
+        query.append("SELECT DISTINCT any_id FROM ").
+                append(svs.resource().name).
+                append(" WHERE resource_id=?").
+                append(setParameter(parameters, cond.getResourceKey()));
+
+        if (svs.anyTypeKind == AnyTypeKind.USER || svs.anyTypeKind == 
AnyTypeKind.ANY_OBJECT) {
+            query.append(" UNION SELECT DISTINCT any_id FROM ").
+                    append(svs.groupResource().name).
+                    append(" WHERE resource_id=?").
+                    append(setParameter(parameters, cond.getResourceKey()));
+        }
+
+        query.append(')');
+
+        return query.toString();
+    }
+
+    @Override
+    protected String getQuery(
+            final AssignableCond cond,
+            final List<Object> parameters,
+            final SearchSupport svs) {
+
+        Realm realm;
+        try {
+            realm = check(cond);
+        } catch (IllegalArgumentException e) {
+            return ALWAYS_FALSE_ASSERTION;
+        }
+
+        StringBuilder query = new StringBuilder("(");
+        if (cond.isFromGroup()) {
+            realmDAO.findDescendants(realm).forEach(current -> {
+                query.append("realm_id=?").append(setParameter(parameters, 
current.getKey())).append(" OR ");
+            });
+            query.setLength(query.length() - 4);
+        } else {
+            for (Realm current = realm; current.getParent() != null; current = 
current.getParent()) {
+                query.append("realm_id=?").append(setParameter(parameters, 
current.getKey())).append(" OR ");
+            }
+            query.append("realm_id=?").append(setParameter(parameters, 
realmDAO.getRoot().getKey()));
+        }
+
+        query.append(")");
+
+        return query.toString();
+    }
+
+    @Override
+    protected String getQuery(
+            final MemberCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
+
+        String memberKey;
+        try {
+            memberKey = check(cond);
+        } catch (IllegalArgumentException e) {
+            return ALWAYS_FALSE_ASSERTION;
+        }
+
+        StringBuilder query = new StringBuilder("(");
+
+        if (not) {
+            query.append("id NOT IN (");
+        } else {
+            query.append("id IN (");
+        }
+
+        query.append("SELECT DISTINCT group_id AS any_id FROM ").
+                append(new 
SearchSupport(AnyTypeKind.USER).membership().name).append(" WHERE ").
+                append("any_id=?").append(setParameter(parameters, memberKey)).
+                append(") ");
+
+        if (not) {
+            query.append("AND id NOT IN (");
+        } else {
+            query.append("OR id IN (");
+        }
+
+        query.append("SELECT DISTINCT group_id AS any_id FROM ").
+                append(new 
SearchSupport(AnyTypeKind.ANY_OBJECT).membership().name).append(" WHERE ").
+                append("any_id=?").append(setParameter(parameters, memberKey)).
+                append(")");
+
+        query.append(")");
+
+        return query.toString();
+    }
+
+    @Override
+    protected String getQuery(
+            final RelationshipTypeCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
+
+        StringBuilder query = new StringBuilder("(");
+
+        if (not) {
+            query.append("id NOT IN (");
+        } else {
+            query.append("id IN (");
+        }
+
+        query.append("SELECT any_id ").append("FROM ").
+                append(svs.relationship().name).
+                append(" WHERE type=?").append(setParameter(parameters, 
cond.getRelationshipTypeKey())).
+                append(" UNION SELECT right_any_id AS any_id FROM ").
+                append(svs.relationship().name).
+                append(" WHERE type=?").append(setParameter(parameters, 
cond.getRelationshipTypeKey())).
+                append(')');
+
+        query.append(")");
+
+        return query.toString();
+    }
+
+    @Override
+    protected String getQuery(
+            final RelationshipCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
+
+        String rightAnyObjectKey;
+        try {
+            rightAnyObjectKey = check(cond);
+        } catch (IllegalArgumentException e) {
+            return ALWAYS_FALSE_ASSERTION;
+        }
+
+        StringBuilder query = new StringBuilder("(");
+
+        if (not) {
+            query.append("id NOT IN (");
+        } else {
+            query.append("id IN (");
+        }
+
+        query.append("SELECT DISTINCT any_id FROM ").
+                append(svs.relationship().name).append(" WHERE ").
+                append("right_any_id=?").append(setParameter(parameters, 
rightAnyObjectKey)).
+                append(')');
+
+        query.append(")");
+
+        return query.toString();
+    }
+
+    @Override
+    protected String getQuery(
+            final MembershipCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
+
+        List<String> groupKeys;
+        try {
+            groupKeys = check(cond);
+        } catch (IllegalArgumentException e) {
+            return ALWAYS_FALSE_ASSERTION;
+        }
+
+        String where = groupKeys.stream().
+                map(key -> "group_id=?" + setParameter(parameters, key)).
+                collect(Collectors.joining(" OR "));
+
+        StringBuilder query = new StringBuilder("(");
+
+        if (not) {
+            query.append("id NOT IN (");
+        } else {
+            query.append("id IN (");
+        }
+
+        query.append("SELECT DISTINCT any_id FROM ").
+                append(svs.membership().name).append(" WHERE ").
+                append("(").append(where).append(")").
+                append(") ");
+
+        if (not) {
+            query.append("AND id NOT IN (");
+        } else {
+            query.append("OR id IN (");
+        }
+
+        query.append("SELECT DISTINCT any_id FROM ").
+                append(svs.dyngroupmembership().name).append(" WHERE ").
+                append("(").append(where).append(")").
+                append(")");
+
+        query.append(")");
+
+        return query.toString();
+    }
+
+    @Override
+    protected String getQuery(
+            final AnyCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
+
+        Triple<PlainSchema, PlainAttrValue, AnyCond> checked;
+        try {
+            checked = check(cond, svs.anyTypeKind);
+        } catch (IllegalArgumentException e) {
+            return ALWAYS_FALSE_ASSERTION;
+        }
+
+        StringBuilder query = new StringBuilder();
+
+        PlainSchema schema = schemaDAO.find(cond.getSchema());
+        if (schema == null) {
+            fillAttrQuery(query, checked.getMiddle(), checked.getLeft(), 
checked.getRight(), not, parameters, svs);
+        } else {
+            fillAttrQuery(anyUtilsFactory.getInstance(svs.anyTypeKind),
+                    query, checked.getMiddle(), checked.getLeft(), 
checked.getRight(), not, parameters, svs);
+        }
+
+        return query.toString();
+    }
+
+    @Override
+    protected String buildAdminRealmsFilter(
+            final Set<String> realmKeys,
+            final SearchSupport svs,
+            final List<Object> parameters) {
+
+        List<String> realmKeyArgs = realmKeys.stream().
+                map(realmKey -> "?" + setParameter(parameters, realmKey)).
+                collect(Collectors.toList());
+        return "realm_id IN (" + StringUtils.join(realmKeyArgs, ", ") + ")";
+    }
+
+    @Override
+    protected int doCount(final Set<String> adminRealms, final SearchCond 
cond, final AnyTypeKind kind) {
+        List<Object> parameters = new ArrayList<>();
+
+        SearchSupport svs = buildSearchSupport(kind);
+
+        Pair<String, Set<String>> filter = getAdminRealmsFilter(adminRealms, 
svs, parameters);
+
+        Pair<StringBuilder, Set<String>> queryInfo =
+                getQuery(buildEffectiveCond(cond, filter.getRight()), 
parameters, svs);
+
+        StringBuilder queryString =
+                new StringBuilder("SELECT 
count(").append(svs.table().alias).append(".id").append(")");
+
+        buildFrom(queryString, queryInfo, svs, null);
+
+        buildWhere(queryString, queryInfo, filter);
+
+        Query countQuery = 
entityManager().createNativeQuery(queryString.toString());
+        fillWithParameters(countQuery, parameters);
+
+        return ((Number) countQuery.getSingleResult()).intValue();
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    protected <T extends Any<?>> List<T> doSearch(
+            final Set<String> adminRealms,
+            final SearchCond cond,
+            final int page,
+            final int itemsPerPage,
+            final List<OrderByClause> orderBy,
+            final AnyTypeKind kind) {
+
+        try {
+            List<Object> parameters = new ArrayList<>();
+
+            SearchSupport svs = buildSearchSupport(kind);
+
+            Pair<String, Set<String>> filter = 
getAdminRealmsFilter(adminRealms, svs, parameters);
+
+            SearchCond effectiveCond = buildEffectiveCond(cond, 
filter.getRight());
+
+            // 1. get the query string from the search condition
+            Pair<StringBuilder, Set<String>> queryInfo = 
getQuery(effectiveCond, parameters, svs);
+
+            // 2. take into account realms and ordering
+            OrderBySupport obs = parseOrderBy(svs, orderBy);
+
+            StringBuilder queryString = new StringBuilder("SELECT 
").append(svs.table().alias).append(".id");
+            obs.items.forEach(item -> {
+                queryString.append(",").append(item.select);
+            });
+
+            buildFrom(queryString, queryInfo, svs, obs);
+
+            buildWhere(queryString, queryInfo, filter);
+
+            LOG.debug("Query: {}, parameters: {}", queryString, parameters);
+
+            queryString.append(buildOrderBy(obs));
+
+            LOG.debug("Query with auth and order by statements: {}, 
parameters: {}", queryString, parameters);
+
+            // 3. prepare the search query
+            Query query = 
entityManager().createNativeQuery(queryString.toString());
+
+            // 4. page starts from 1, while setFirtResult() starts from 0
+            query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));
+
+            if (itemsPerPage >= 0) {
+                query.setMaxResults(itemsPerPage);
+            }
+
+            // 5. populate the search query with parameter values
+            fillWithParameters(query, parameters);
+
+            // 6. Prepare the result (avoiding duplicates)
+            return buildResult(query.getResultList(), kind);
+        } catch (SyncopeClientException e) {
+            throw e;
+        } catch (Exception e) {
+            LOG.error("While searching for {}", kind, e);
+        }
+
+        return Collections.emptyList();
+    }
+
+    protected StringBuilder buildOrderBy(final OrderBySupport obs) {
+        StringBuilder orderBy = new StringBuilder();
+
+        obs.items.forEach(item -> {
+            orderBy.append(item.orderBy).append(',');
+        });
+        if (!obs.items.isEmpty()) {
+            orderBy.insert(0, " ORDER BY ");
+            orderBy.deleteCharAt(orderBy.length() - 1);
+        }
+
+        return orderBy;
+    }
+
+    protected Pair<StringBuilder, Set<String>> getQuery(
+            final SearchCond cond, final List<Object> parameters, final 
SearchSupport svs) {
+
+        boolean not = cond.getType() == SearchCond.Type.NOT_LEAF;
+
+        StringBuilder query = new StringBuilder();
+        Set<String> involvedPlainAttrs = new HashSet<>();
+
+        switch (cond.getType()) {
+            case LEAF:
+            case NOT_LEAF:
+                cond.getLeaf(AnyTypeCond.class).
+                        filter(leaf -> AnyTypeKind.ANY_OBJECT == 
svs.anyTypeKind).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, 
parameters, svs)));
+
+                cond.getLeaf(RelationshipTypeCond.class).
+                        filter(leaf -> AnyTypeKind.GROUP != svs.anyTypeKind).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, 
parameters, svs)));
+
+                cond.getLeaf(RelationshipCond.class).
+                        filter(leaf -> AnyTypeKind.GROUP != svs.anyTypeKind).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, 
parameters, svs)));
+
+                cond.getLeaf(MembershipCond.class).
+                        filter(leaf -> AnyTypeKind.GROUP != svs.anyTypeKind).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, 
parameters, svs)));
+
+                cond.getLeaf(MemberCond.class).
+                        filter(leaf -> AnyTypeKind.GROUP == svs.anyTypeKind).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, 
parameters, svs)));
+
+                cond.getLeaf(AssignableCond.class).
+                        ifPresent(leaf -> query.append(getQuery(leaf, 
parameters, svs)));
+
+                cond.getLeaf(RoleCond.class).
+                        filter(leaf -> AnyTypeKind.USER == svs.anyTypeKind).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, 
parameters, svs)));
+
+                cond.getLeaf(PrivilegeCond.class).
+                        filter(leaf -> AnyTypeKind.USER == svs.anyTypeKind).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, 
parameters, svs)));
+
+                cond.getLeaf(DynRealmCond.class).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, 
parameters, svs)));
+
+                cond.getLeaf(ResourceCond.class).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, 
parameters, svs)));
+
+                Optional<AnyCond> anyCond = cond.getLeaf(AnyCond.class);
+                if (anyCond.isPresent()) {
+                    query.append(getQuery(anyCond.get(), not, parameters, 
svs));
+                } else {
+                    cond.getLeaf(AttrCond.class).ifPresent(leaf -> {
+                        query.append(getQuery(leaf, not, parameters, svs));
+                        try {
+                            involvedPlainAttrs.add(check(leaf, 
svs.anyTypeKind).getLeft().getKey());
+                        } catch (IllegalArgumentException e) {
+                            // ignore
+                        }
+                    });
+                }
+
+                // allow for additional search conditions
+                getQueryForCustomConds(cond, parameters, svs, not, query);
+                break;
+
+            case AND:
+                Pair<StringBuilder, Set<String>> leftAndInfo = 
getQuery(cond.getLeft(), parameters, svs);
+                involvedPlainAttrs.addAll(leftAndInfo.getRight());
+
+                Pair<StringBuilder, Set<String>> rigthAndInfo = 
getQuery(cond.getRight(), parameters, svs);
+                involvedPlainAttrs.addAll(rigthAndInfo.getRight());
+
+                query.append("(").
+                        append(leftAndInfo.getKey()).
+                        append(" AND ").
+                        append(rigthAndInfo.getKey()).
+                        append(")");
+                break;
+
+            case OR:
+                Pair<StringBuilder, Set<String>> leftOrInfo = 
getQuery(cond.getLeft(), parameters, svs);
+                involvedPlainAttrs.addAll(leftOrInfo.getRight());
+
+                Pair<StringBuilder, Set<String>> rigthOrInfo = 
getQuery(cond.getRight(), parameters, svs);
+                involvedPlainAttrs.addAll(rigthOrInfo.getRight());
+
+                query.append("(").
+                        append(leftOrInfo.getKey()).
+                        append(" OR ").
+                        append(rigthOrInfo.getKey()).
+                        append(")");
+                break;
+
+            default:
+        }
+
+        return Pair.of(query, involvedPlainAttrs);
+    }
+
+    protected void fillAttrQuery(
+            final StringBuilder query,
+            final PlainAttrValue attrValue,
+            final PlainSchema schema,
+            final AttrCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
+
+        // This first branch is required for handling with not conditions 
given on multivalue fields (SYNCOPE-1419)
+        if (not && !(cond instanceof AnyCond)) {
+
+            query.append("NOT (");
+            fillAttrQuery(query, attrValue, schema, cond, false, parameters, 
svs);
+            query.append(")");
+        } else if (not && cond.getType() == AttrCond.Type.ISNULL) {
+            cond.setType(AttrCond.Type.ISNOTNULL);
+            fillAttrQuery(query, attrValue, schema, cond, true, parameters, 
svs);
+        } else {
+            boolean lower = (schema.getType() == AttrSchemaType.String || 
schema.getType() == AttrSchemaType.Enum)
+                    && (cond.getType() == AttrCond.Type.IEQ || cond.getType() 
== AttrCond.Type.ILIKE);
+
+            String column = cond.getSchema();
+            if ((schema.getType() == AttrSchemaType.String || schema.getType() 
== AttrSchemaType.Enum) && lower) {
+                column = "LOWER (" + column + ")";
+            }
+
+            switch (cond.getType()) {
+
+                case ISNULL:
+                    // shouldn't occour: processed before
+                    break;
+
+                case ISNOTNULL:
+                    query.append(column).append(" IS NOT NULL");
+                    break;
+
+                case ILIKE:
+                case LIKE:
+                    if (schema.getType() == AttrSchemaType.String || 
schema.getType() == AttrSchemaType.Enum) {
+                        query.append(column);
+                        query.append(" LIKE ");
+                        if (lower) {
+                            
query.append("LOWER(?").append(setParameter(parameters, 
cond.getExpression())).append(')');
+                        } else {
+                            query.append('?').append(setParameter(parameters, 
cond.getExpression()));
+                        }
+                    } else {
+                        query.append(" 1=2");
+                        LOG.error("LIKE is only compatible with string or enum 
schemas");
+                    }
+                    break;
+
+                case IEQ:
+                case EQ:
+                    query.append(column);
+                    query.append('=');
+
+                    if ((schema.getType() == AttrSchemaType.String
+                            || schema.getType() == AttrSchemaType.Enum) && 
lower) {
+                        
query.append("LOWER(?").append(setParameter(parameters, 
attrValue.getValue())).append(')');
+                    } else {
+                        query.append('?').append(setParameter(parameters, 
attrValue.getValue()));
+                    }
+                    break;
+
+                case GE:
+                    query.append(column);
+                    if (not) {
+                        query.append('<');
+                    } else {
+                        query.append(">=");
+                    }
+                    query.append('?').append(setParameter(parameters, 
attrValue.getValue()));
+                    break;
+
+                case GT:
+                    query.append(column);
+                    if (not) {
+                        query.append("<=");
+                    } else {
+                        query.append('>');
+                    }
+                    query.append('?').append(setParameter(parameters, 
attrValue.getValue()));
+                    break;
+
+                case LE:
+                    query.append(column);
+                    if (not) {
+                        query.append('>');
+                    } else {
+                        query.append("<=");
+                    }
+                    query.append('?').append(setParameter(parameters, 
attrValue.getValue()));
+                    break;
+
+                case LT:
+                    query.append(column);
+                    if (not) {
+                        query.append(">=");
+                    } else {
+                        query.append('<');
+                    }
+                    query.append('?').append(setParameter(parameters, 
attrValue.getValue()));
+                    break;
+
+                default:
+            }
+        }
+    }
+
+    protected void fillWithParameters(final Query query, final List<Object> 
parameters) {
+        for (int i = 0; i < parameters.size(); i++) {
+            if (parameters.get(i) instanceof Date) {
+                query.setParameter(i + 1, (Date) parameters.get(i), 
TemporalType.TIMESTAMP);
+            } else if (parameters.get(i) instanceof Boolean) {
+                query.setParameter(i + 1, ((Boolean) parameters.get(i))
+                        ? 1
+                        : 0);
+            } else {
+                query.setParameter(i + 1, parameters.get(i));
+            }
+        }
+    }
+
+    protected OrderBySupport parseOrderBy(
+            final SearchSupport svs,
+            final List<OrderByClause> orderBy) {
+
+        AnyUtils anyUtils = anyUtilsFactory.getInstance(svs.anyTypeKind);
+
+        OrderBySupport obs = new OrderBySupport();
+
+        Set<String> orderByUniquePlainSchemas = new HashSet<>();
+        Set<String> orderByNonUniquePlainSchemas = new HashSet<>();
+        orderBy.forEach(clause -> {
+            OrderBySupport.Item item = new OrderBySupport.Item();
+
+            parseOrderByForCustom(svs, clause, item, obs);
+
+            if (item.isEmpty()) {
+                if (anyUtils.getField(clause.getField()) == null) {
+                    PlainSchema schema = schemaDAO.find(clause.getField());
+                    if (schema != null) {
+                        if (schema.isUniqueConstraint()) {
+                            orderByUniquePlainSchemas.add(schema.getKey());
+                        } else {
+                            orderByNonUniquePlainSchemas.add(schema.getKey());
+                        }
+                        if (orderByUniquePlainSchemas.size() > 1 || 
orderByNonUniquePlainSchemas.size() > 1) {
+                            SyncopeClientException invalidSearch =
+                                    
SyncopeClientException.build(ClientExceptionType.InvalidSearchExpression);
+                            invalidSearch.getElements().add("Order by more 
than one attribute is not allowed; "
+                                    + "remove one from " + 
(orderByUniquePlainSchemas.size() > 1
+                                    ? orderByUniquePlainSchemas : 
orderByNonUniquePlainSchemas));
+                            throw invalidSearch;
+                        }
+                        parseOrderByForPlainSchema(svs, obs, item, clause, 
schema, clause.getField());
+                    }
+                } else {
+                    // Manage difference among external key attribute and 
internal JPA @Id
+                    String fieldName = "key".equals(clause.getField()) ? "id" 
: clause.getField();
+
+                    // Adjust field name to column name
+                    if (ArrayUtils.contains(RELATIONSHIP_FIELDS, fieldName)) {
+                        fieldName += "_id";
+                    }
+
+                    obs.views.add(svs.field());
+
+                    item.select = svs.table().alias + "." + fieldName;
+                    item.where = StringUtils.EMPTY;
+                    item.orderBy = svs.table().alias + "." + fieldName + " " + 
clause.getDirection().name();
+                }
+            }
+
+            if (item.isEmpty()) {
+                LOG.warn("Cannot build any valid clause from {}", clause);
+            } else {
+                obs.items.add(item);
+            }
+        });
+
+        return obs;
+    }
+
+    protected Pair<String, Set<String>> getAdminRealmsFilter(
+            final Set<String> adminRealms,
+            final SearchSupport svs,
+            final List<Object> parameters) {
+
+        Set<String> realmKeys = new HashSet<>();
+        Set<String> dynRealmKeys = new HashSet<>();
+        RealmUtils.normalize(adminRealms).forEach(realmPath -> {
+            if (realmPath.startsWith("/")) {
+                Realm realm = realmDAO.findByFullPath(realmPath);
+                if (realm == null) {
+                    SyncopeClientException noRealm = 
SyncopeClientException.build(ClientExceptionType.InvalidRealm);
+                    noRealm.getElements().add("Invalid realm specified: " + 
realmPath);
+                    throw noRealm;
+                } else {
+                    realmKeys.addAll(realmDAO.findDescendants(realm).stream().
+                            map(Entity::getKey).collect(Collectors.toSet()));
+                }
+            } else {
+                DynRealm dynRealm = dynRealmDAO.find(realmPath);
+                if (dynRealm == null) {
+                    LOG.warn("Ignoring invalid dynamic realm {}", realmPath);
+                } else {
+                    dynRealmKeys.add(dynRealm.getKey());
+                }
+            }
+        });
+        if (!dynRealmKeys.isEmpty()) {
+            realmKeys.addAll(realmDAO.findAll().stream().
+                    map(Entity::getKey).collect(Collectors.toSet()));
+        }
+
+        return Pair.of(buildAdminRealmsFilter(realmKeys, svs, parameters), 
dynRealmKeys);
+    }
+
+    protected void buildFrom(
+            final StringBuilder query,
+            final Pair<StringBuilder, Set<String>> queryInfo,
+            final SearchSupport svs,
+            final OrderBySupport obs) {
+
+        query.append(" FROM ").append(svs.table().name).append(" 
").append(svs.table().alias);
+
+        Set<String> schemas = queryInfo.getRight();
+
+        if (obs != null) {
+            Pattern pattern = Pattern.compile("(.*) -> 0 AS .*");
+            obs.items.forEach(item -> {
+                Matcher matcher = pattern.matcher(item.select);
+                if (matcher.find()) {
+                    schemas.add(matcher.group(1));
+                }
+            });
+        }
+
+        schemas.forEach(schema -> {
+            // i.e jsonb_path_query(plainattrs, '$[*] ? 
(@.schema=="Nome")."values"') AS Nome
+            PlainSchema pschema = schemaDAO.find(schema);
+            if (pschema == null) {
+                // just to be sure
+                LOG.warn("Ignoring invalid schema '{}'", schema);
+            } else {
+                query.append(",").
+                        append("jsonb_path_query_array(plainattrs, '$[*] ? 
(@.schema==\"").
+                        append(schema).append("\").").
+                        append("\"").append(pschema.isUniqueConstraint() ? 
"uniqueValue" : "values").append("\"')").
+                        append(" AS ").append(schema);
+            }
+        });
+    }
+
+    protected void buildWhere(
+            final StringBuilder query,
+            final Pair<StringBuilder, Set<String>> queryInfo,
+            final Pair<String, Set<String>> realms) {
+        if (queryInfo.getLeft().length() > 0) {
+            query.append(" WHERE ").append(queryInfo.getLeft());
+        }
+
+        if (realms.getLeft().length() > 0) {
+            if (queryInfo.getLeft().length() > 0) {
+                query.append(" AND ").append(realms.getLeft());
+            } else {
+                query.append(" WHERE ").append(realms.getLeft());
+            }
+        }
+    }
+
 }
diff --git 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONPlainSchemaDAO.java
 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONPlainSchemaDAO.java
index c4d27d6..3c0cf54 100644
--- 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONPlainSchemaDAO.java
+++ 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONPlainSchemaDAO.java
@@ -27,7 +27,7 @@ public class PGJPAJSONPlainSchemaDAO extends 
AbstractJPAJSONPlainSchemaDAO {
     @Override
     public <T extends PlainAttr<?>> boolean hasAttrs(final PlainSchema schema, 
final Class<T> reference) {
         Query query = entityManager().createNativeQuery(
-                "SELECT COUNT(id) FROM " + new 
SearchSupport(getAnyTypeKind(reference)).field().name
+                "SELECT COUNT(id) FROM " + new 
SearchSupport(getAnyTypeKind(reference)).table().name
                 + " WHERE plainAttrs @> '[{\"schema\":\"" + schema.getKey() + 
"\"}]'::jsonb");
 
         return ((Number) query.getSingleResult()).intValue() > 0;
diff --git a/core/persistence-jpa-json/src/main/resources/pgjsonb/views.xml 
b/core/persistence-jpa-json/src/main/resources/pgjsonb/views.xml
index eb450be..f07024d 100644
--- a/core/persistence-jpa-json/src/main/resources/pgjsonb/views.xml
+++ b/core/persistence-jpa-json/src/main/resources/pgjsonb/views.xml
@@ -47,13 +47,6 @@ under the License.
   </entry>
 
   <!-- user -->
-  <entry key="user_search">
-    CREATE VIEW user_search AS
-
-    SELECT u.id as any_id, u.*,attrs,attrValues
-    FROM SyncopeUser u, jsonb_array_elements(COALESCE(u.plainAttrs, 
'[{}]'::jsonb)) attrs,
-    jsonb_array_elements(COALESCE(attrs -> 'values', '[{}]'::jsonb)) attrValues
-  </entry>
   <entry key="user_search_urelationship">
     CREATE VIEW user_search_urelationship AS
 
diff --git a/docker/src/main/resources/docker-compose/docker-compose-ha.yml 
b/docker/src/main/resources/docker-compose/docker-compose-ha.yml
index 0ebde3a..dd537d6 100644
--- a/docker/src/main/resources/docker-compose/docker-compose-ha.yml
+++ b/docker/src/main/resources/docker-compose/docker-compose-ha.yml
@@ -22,7 +22,7 @@ version: '3.3'
 
 services:
    db:
-     image: postgres:11.5
+     image: postgres:12
      restart: always
      environment:
        POSTGRES_DB: syncope
diff --git 
a/docker/src/main/resources/docker-compose/docker-compose-pgjsonb.yml 
b/docker/src/main/resources/docker-compose/docker-compose-pgjsonb.yml
index 5468573..0f5bb38 100644
--- a/docker/src/main/resources/docker-compose/docker-compose-pgjsonb.yml
+++ b/docker/src/main/resources/docker-compose/docker-compose-pgjsonb.yml
@@ -21,7 +21,7 @@ version: '3.3'
 
 services:
    db:
-     image: postgres:11.5
+     image: postgres:12
      restart: always
      environment:
        POSTGRES_DB: syncope
diff --git 
a/docker/src/main/resources/docker-compose/docker-compose-postgresql.yml 
b/docker/src/main/resources/docker-compose/docker-compose-postgresql.yml
index 56de700..7a661ea 100644
--- a/docker/src/main/resources/docker-compose/docker-compose-postgresql.yml
+++ b/docker/src/main/resources/docker-compose/docker-compose-postgresql.yml
@@ -21,7 +21,7 @@ version: '3.3'
 
 services:
    db:
-     image: postgres:11
+     image: postgres:12
      restart: always
      environment:
        POSTGRES_DB: syncope
diff --git a/pom.xml b/pom.xml
index b1d1c9b..3e886af 100644
--- a/pom.xml
+++ b/pom.xml
@@ -537,7 +537,7 @@ under the License.
     <nodejs.version>v8.11.4</nodejs.version>    
     <protractor.version>5.4.0</protractor.version>    
 
-    <docker.postgresql.version>11</docker.postgresql.version>
+    <docker.postgresql.version>12</docker.postgresql.version>
     <docker.mysql.version>8.0</docker.mysql.version>
     <docker.mariadb.version>10.5</docker.mariadb.version>
 

Reply via email to