diff --git a/integration_test.go b/integration_test.go index f921771..026589c 100644 --- a/integration_test.go +++ b/integration_test.go @@ -1060,7 +1060,7 @@ func TestOperationalAttributesMigration(t *testing.T) { runTestCases(t, tcs) } -func TestMemberOf(t *testing.T) { +func TestAssociation(t *testing.T) { type A []string type M map[string][]string @@ -1109,6 +1109,21 @@ func TestMemberOf(t *testing.T) { }, &AssertEntry{}, }, + Search{ + "ou=Groups," + testServer.GetSuffix(), + "cn=A1", + ldap.ScopeWholeSubtree, + A{"member"}, + &AssertEntries{ + ExpectEntry{ + "cn=A1", + "ou=Groups", + M{ + "member": A{"uid=user1,ou=Users," + testServer.GetSuffix()}, + }, + }, + }, + }, Search{ "ou=Users," + testServer.GetSuffix(), "uid=user1", @@ -1268,3 +1283,760 @@ func TestMemberOf(t *testing.T) { runTestCases(t, tcs) } + +func TestSearchByAssociation(t *testing.T) { + type A []string + type M map[string][]string + + tcs := []Command{ + Conn{}, + Bind{"cn=Manager", "secret", &AssertResponse{}}, + AddDC("example", "dc=com"), + AddOU("Groups"), + AddOU("Users"), + Add{ + "uid=user1", "ou=Users", + M{ + "objectClass": A{"inetOrgPerson"}, + "cn": A{"user1"}, + "sn": A{"user1"}, + "userPassword": A{SSHA("password1")}, + }, + &AssertEntry{}, + }, + Add{ + "uid=user2", "ou=Users", + M{ + "objectClass": A{"inetOrgPerson"}, + "cn": A{"user2"}, + "sn": A{"user2"}, + "userPassword": A{SSHA("password1")}, + }, + &AssertEntry{}, + }, + Add{ + "uid=user3", "ou=Users", + M{ + "objectClass": A{"inetOrgPerson"}, + "cn": A{"user3"}, + "sn": A{"user3"}, + "userPassword": A{SSHA("password1")}, + }, + &AssertEntry{}, + }, + Add{ + "cn=A1", "ou=Groups", + M{ + "objectClass": A{"groupOfNames"}, + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + }, + }, + &AssertEntry{}, + }, + Add{ + "cn=A2", "ou=Groups", + M{ + "objectClass": A{"groupOfNames"}, + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + &AssertEntry{}, + }, + Add{ + "cn=A3", "ou=Groups", + M{ + "objectClass": A{"groupOfNames"}, + "member": A{ + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + &AssertEntry{}, + }, + // member only + Search{ + "ou=Groups," + testServer.GetSuffix(), + "member=uid=user1,ou=Users," + testServer.GetSuffix(), + ldap.ScopeWholeSubtree, + A{"member"}, + &AssertEntries{ + ExpectEntry{ + "cn=A1", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "cn=A2", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + // member AND uid + Search{ + "ou=Groups," + testServer.GetSuffix(), + "(&(member=uid=user1,ou=Users," + testServer.GetSuffix() + ")(cn=A1))", + ldap.ScopeWholeSubtree, + A{"member"}, + &AssertEntries{ + ExpectEntry{ + "cn=A1", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + // member OR uid + Search{ + "ou=Groups," + testServer.GetSuffix(), + "(|(member=uid=user1,ou=Users," + testServer.GetSuffix() + ")(cn=A3))", + ldap.ScopeWholeSubtree, + A{"member"}, + &AssertEntries{ + ExpectEntry{ + "cn=A1", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "cn=A2", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "cn=A3", + "ou=Groups", + M{ + "member": A{ + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + // member AND member + Search{ + "ou=Groups," + testServer.GetSuffix(), + "(&(member=uid=user1,ou=Users," + testServer.GetSuffix() + ")(member=uid=user2,ou=Users," + testServer.GetSuffix() + "))", + ldap.ScopeWholeSubtree, + A{"member"}, + &AssertEntries{ + ExpectEntry{ + "cn=A2", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + // member OR member + Search{ + "ou=Groups," + testServer.GetSuffix(), + "(|(member=uid=user1,ou=Users," + testServer.GetSuffix() + ")(member=uid=user2,ou=Users," + testServer.GetSuffix() + "))", + ldap.ScopeWholeSubtree, + A{"member"}, + &AssertEntries{ + ExpectEntry{ + "cn=A1", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "cn=A2", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "cn=A3", + "ou=Groups", + M{ + "member": A{ + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + Search{ + "ou=Groups," + testServer.GetSuffix(), + "(|(member=uid=user1,ou=Users," + testServer.GetSuffix() + ")(member=uid=user1,ou=Users," + testServer.GetSuffix() + "))", + ldap.ScopeWholeSubtree, + A{"member"}, + &AssertEntries{ + ExpectEntry{ + "cn=A1", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "cn=A2", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + // memberOf only + Search{ + "ou=Users," + testServer.GetSuffix(), + "memberOf=cn=A1,ou=Groups," + testServer.GetSuffix(), + ldap.ScopeWholeSubtree, + A{"memberOf"}, + &AssertEntries{ + ExpectEntry{ + "uid=user1", + "ou=Users", + M{ + "memberOf": A{ + "cn=A1,ou=Groups," + testServer.GetSuffix(), + "cn=A2,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + // memberOf AND uid + Search{ + "ou=Users," + testServer.GetSuffix(), + "(&(memberOf=cn=A1,ou=Groups," + testServer.GetSuffix() + ")(uid=user1))", + ldap.ScopeWholeSubtree, + A{"memberOf"}, + &AssertEntries{ + ExpectEntry{ + "uid=user1", + "ou=Users", + M{ + "memberOf": A{ + "cn=A1,ou=Groups," + testServer.GetSuffix(), + "cn=A2,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + // memberOf OR uid + Search{ + "ou=Users," + testServer.GetSuffix(), + "(|(memberOf=cn=A1,ou=Groups," + testServer.GetSuffix() + ")(uid=user2))", + ldap.ScopeWholeSubtree, + A{"memberOf"}, + &AssertEntries{ + ExpectEntry{ + "uid=user1", + "ou=Users", + M{ + "memberOf": A{ + "cn=A1,ou=Groups," + testServer.GetSuffix(), + "cn=A2,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "uid=user2", + "ou=Users", + M{ + "memberOf": A{ + "cn=A2,ou=Groups," + testServer.GetSuffix(), + "cn=A3,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + // memberOf AND memberOf + Search{ + "ou=Users," + testServer.GetSuffix(), + "(&(memberOf=cn=A1,ou=Groups," + testServer.GetSuffix() + ")(memberOf=cn=A2,ou=Groups," + testServer.GetSuffix() + "))", + ldap.ScopeWholeSubtree, + A{"memberOf"}, + &AssertEntries{ + ExpectEntry{ + "uid=user1", + "ou=Users", + M{ + "memberOf": A{ + "cn=A1,ou=Groups," + testServer.GetSuffix(), + "cn=A2,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + // memberOf OR memberOf + Search{ + "ou=Users," + testServer.GetSuffix(), + "(|(memberOf=cn=A1,ou=Groups," + testServer.GetSuffix() + ")(memberOf=cn=A3,ou=Groups," + testServer.GetSuffix() + "))", + ldap.ScopeWholeSubtree, + A{"memberOf"}, + &AssertEntries{ + ExpectEntry{ + "uid=user1", + "ou=Users", + M{ + "memberOf": A{ + "cn=A1,ou=Groups," + testServer.GetSuffix(), + "cn=A2,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "uid=user2", + "ou=Users", + M{ + "memberOf": A{ + "cn=A2,ou=Groups," + testServer.GetSuffix(), + "cn=A3,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + Search{ + "ou=Users," + testServer.GetSuffix(), + "(|(memberOf=cn=A1,ou=Groups," + testServer.GetSuffix() + ")(memberOf=cn=A1,ou=Groups," + testServer.GetSuffix() + "))", + ldap.ScopeWholeSubtree, + A{"memberOf"}, + &AssertEntries{ + ExpectEntry{ + "uid=user1", + "ou=Users", + M{ + "memberOf": A{ + "cn=A1,ou=Groups," + testServer.GetSuffix(), + "cn=A2,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + // not member + Search{ + "ou=Groups," + testServer.GetSuffix(), + "(!(member=uid=user1,ou=Users," + testServer.GetSuffix() + "))", + ldap.ScopeWholeSubtree, + A{"member"}, + &AssertEntries{ + ExpectEntry{ + "ou=Groups", + "", + M{}, + }, + ExpectEntry{ + "cn=A3", + "ou=Groups", + M{ + "member": A{ + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + // not memberOf + Search{ + "ou=Users," + testServer.GetSuffix(), + "(!(memberOf=cn=A1,ou=Groups," + testServer.GetSuffix() + "))", + ldap.ScopeWholeSubtree, + A{"memberOf"}, + &AssertEntries{ + ExpectEntry{ + "ou=Users", + "", + M{}, + }, + ExpectEntry{ + "uid=user2", + "ou=Users", + M{ + "memberOf": A{ + "cn=A2,ou=Groups," + testServer.GetSuffix(), + "cn=A3,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "uid=user3", + "ou=Users", + M{ + "memberOf": A{}, + }, + }, + }, + }, + // has member + Search{ + "ou=Groups," + testServer.GetSuffix(), + "(member=*)", + ldap.ScopeWholeSubtree, + A{"member"}, + &AssertEntries{ + ExpectEntry{ + "cn=A1", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "cn=A2", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "cn=A3", + "ou=Groups", + M{ + "member": A{ + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + // has memberOf + Search{ + "ou=Users," + testServer.GetSuffix(), + "(memberOf=*)", + ldap.ScopeWholeSubtree, + A{"memberOf"}, + &AssertEntries{ + ExpectEntry{ + "uid=user1", + "ou=Users", + M{ + "memberOf": A{ + "cn=A1,ou=Groups," + testServer.GetSuffix(), + "cn=A2,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "uid=user2", + "ou=Users", + M{ + "memberOf": A{ + "cn=A2,ou=Groups," + testServer.GetSuffix(), + "cn=A3,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + // has not member + Search{ + "ou=Groups," + testServer.GetSuffix(), + "(!(member=*))", + ldap.ScopeWholeSubtree, + A{"member"}, + &AssertEntries{ + ExpectEntry{ + "ou=Groups", + "", + M{}, + }, + }, + }, + // has not memberOf + Search{ + "ou=Users," + testServer.GetSuffix(), + "(!(memberOf=*))", + ldap.ScopeWholeSubtree, + A{"memberOf"}, + &AssertEntries{ + ExpectEntry{ + "ou=Users", + "", + M{}, + }, + ExpectEntry{ + "uid=user3", + "ou=Users", + M{}, + }, + }, + }, + // complex + Search{ + "" + testServer.GetSuffix(), + "(|(member=*)(memberOf=*))", + ldap.ScopeWholeSubtree, + A{"member", "memberOf"}, + &AssertEntries{ + ExpectEntry{ + "cn=A1", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "cn=A2", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "cn=A3", + "ou=Groups", + M{ + "member": A{ + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "uid=user1", + "ou=Users", + M{ + "memberOf": A{ + "cn=A1,ou=Groups," + testServer.GetSuffix(), + "cn=A2,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "uid=user2", + "ou=Users", + M{ + "memberOf": A{ + "cn=A2,ou=Groups," + testServer.GetSuffix(), + "cn=A3,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + Search{ + "" + testServer.GetSuffix(), + "(|(member=uid=user1,ou=Users," + testServer.GetSuffix() + ")(memberOf=cn=A3,ou=Groups," + testServer.GetSuffix() + "))", + ldap.ScopeWholeSubtree, + A{"member", "memberOf"}, + &AssertEntries{ + ExpectEntry{ + "cn=A1", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "cn=A2", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "uid=user2", + "ou=Users", + M{ + "memberOf": A{ + "cn=A2,ou=Groups," + testServer.GetSuffix(), + "cn=A3,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + Search{ + "" + testServer.GetSuffix(), + "(|(member=uid=user1,ou=Users," + testServer.GetSuffix() + ")(memberOf=cn=A3,ou=Groups," + testServer.GetSuffix() + ")(member=*))", + ldap.ScopeWholeSubtree, + A{"member", "memberOf"}, + &AssertEntries{ + ExpectEntry{ + "cn=A1", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "cn=A2", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "cn=A3", + "ou=Groups", + M{ + "member": A{ + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "uid=user2", + "ou=Users", + M{ + "memberOf": A{ + "cn=A2,ou=Groups," + testServer.GetSuffix(), + "cn=A3,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + Search{ + "" + testServer.GetSuffix(), + "(|(member=uid=user1,ou=Users," + testServer.GetSuffix() + ")(memberOf=cn=A3,ou=Groups," + testServer.GetSuffix() + ")(memberOf=*))", + ldap.ScopeWholeSubtree, + A{"member", "memberOf"}, + &AssertEntries{ + ExpectEntry{ + "cn=A1", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "cn=A2", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "uid=user1", + "ou=Users", + M{ + "memberOf": A{ + "cn=A1,ou=Groups," + testServer.GetSuffix(), + "cn=A2,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "uid=user2", + "ou=Users", + M{ + "memberOf": A{ + "cn=A2,ou=Groups," + testServer.GetSuffix(), + "cn=A3,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + }, + }, + Search{ + "" + testServer.GetSuffix(), + "(|(member=uid=user1,ou=Users," + testServer.GetSuffix() + ")(memberOf=cn=A3,ou=Groups," + testServer.GetSuffix() + ")(&(!(memberOf=*))(objectClass=inetOrgPerson)))", + ldap.ScopeWholeSubtree, + A{"member", "memberOf"}, + &AssertEntries{ + ExpectEntry{ + "cn=A1", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "cn=A2", + "ou=Groups", + M{ + "member": A{ + "uid=user1,ou=Users," + testServer.GetSuffix(), + "uid=user2,ou=Users," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "uid=user2", + "ou=Users", + M{ + "memberOf": A{ + "cn=A2,ou=Groups," + testServer.GetSuffix(), + "cn=A3,ou=Groups," + testServer.GetSuffix(), + }, + }, + }, + ExpectEntry{ + "uid=user3", + "ou=Users", + M{}, + }, + }, + }, + } + + runTestCases(t, tcs) +} diff --git a/repo_hybrid.go b/repo_hybrid.go index a21ed99..9c01442 100644 --- a/repo_hybrid.go +++ b/repo_hybrid.go @@ -1730,9 +1730,8 @@ func (t *HybridDBFilterTranslator) EqualityMatch(s *AttributeType, q *HybridDBFi q.params[parentDNNormKey] = reqDN.ParentDN().DNNormStrWithoutSuffix(s.schemaDef.server.Suffix) /* - [CASE EXISTS] -- association filter by uniqueMember - INNER JOIN ( + LEFT JOIN ( SELECT DISTINCT a1.id FROM @@ -1740,9 +1739,10 @@ func (t *HybridDBFilterTranslator) EqualityMatch(s *AttributeType, q *HybridDBFi WHERE ae1.rdn_norm = 'uid=user1' AND c1.dn_norm = 'ou=people' ) t1 ON t1.id = e.id + WHERE + t1.id IS NOT NULL - [CASE NOT EXISTS] - -- association filter by uniqueMember + -- not association filter by uniqueMember LEFT JOIN ( SELECT DISTINCT a1.id @@ -1758,11 +1758,7 @@ func (t *HybridDBFilterTranslator) EqualityMatch(s *AttributeType, q *HybridDBFi q.join.WriteString(` -- association filter by `) q.join.WriteString(s.Name) q.join.WriteString(" \n ") - if isNot { - q.join.WriteString(`LEFT JOIN (`) - } else { - q.join.WriteString(`INNER JOIN (`) - } + q.join.WriteString(`LEFT JOIN (`) q.join.WriteString(`SELECT DISTINCT a`) q.join.WriteString(nameKey) q.join.WriteString(`.id FROM ldap_association a`) @@ -1796,12 +1792,13 @@ func (t *HybridDBFilterTranslator) EqualityMatch(s *AttributeType, q *HybridDBFi q.join.WriteString(` ON t`) q.join.WriteString(nameKey) q.join.WriteString(`.id = e.id`) + + q.where.WriteString(`t`) + q.where.WriteString(nameKey) if isNot { - q.where.WriteString(`t`) - q.where.WriteString(nameKey) q.where.WriteString(`.id IS NULL`) } else { - q.where.WriteString(`TRUE`) + q.where.WriteString(`.id IS NOT NULL`) } } else if s.IsReverseAssociationAttribute() { @@ -1818,9 +1815,8 @@ func (t *HybridDBFilterTranslator) EqualityMatch(s *AttributeType, q *HybridDBFi q.params[parentDNNormKey] = reqDN.ParentDN().DNNormStrWithoutSuffix(s.schemaDef.server.Suffix) /* - [CASE EXISTS] -- association filter by memberOf - INNER JOIN ( + LEFT JOIN ( SELECT DISTINCT a1.member_id FROM @@ -1828,9 +1824,10 @@ func (t *HybridDBFilterTranslator) EqualityMatch(s *AttributeType, q *HybridDBFi WHERE ae1.rdn_norm = 'cn=group1' AND c1.dn_norm = 'ou=groups' ) t1 ON t1.member_id = e.id + WHERE + t1.member_id IS NOT NULL - [CASE NOT EXISTS] - -- association filter by memberOf + -- not association filter by memberOf LEFT JOIN ( SELECT DISTINCT a1.member_id @@ -1846,11 +1843,7 @@ func (t *HybridDBFilterTranslator) EqualityMatch(s *AttributeType, q *HybridDBFi q.join.WriteString(` -- association filter by `) q.join.WriteString(s.Name) q.join.WriteString(" \n ") - if isNot { - q.join.WriteString(`LEFT JOIN (`) - } else { - q.join.WriteString(`INNER JOIN (`) - } + q.join.WriteString(`LEFT JOIN (`) q.join.WriteString(`SELECT DISTINCT a`) q.join.WriteString(rdnNormKey) q.join.WriteString(`.member_id FROM ldap_association a`) @@ -1880,12 +1873,13 @@ func (t *HybridDBFilterTranslator) EqualityMatch(s *AttributeType, q *HybridDBFi q.join.WriteString(` ON t`) q.join.WriteString(rdnNormKey) q.join.WriteString(`.member_id = e.id`) + + q.where.WriteString(`t`) + q.where.WriteString(rdnNormKey) if isNot { - q.where.WriteString(`t`) - q.where.WriteString(rdnNormKey) q.where.WriteString(`.member_id IS NULL`) } else { - q.where.WriteString(`TRUE`) + q.where.WriteString(`.member_id IS NOT NULL`) } } else { @@ -2001,7 +1995,12 @@ func (t *HybridDBFilterTranslator) PresentMatch(s *AttributeType, q *HybridDBFil q.params[nameKey] = s.Name q.where.WriteString(` - (SELECT EXISTS ( + (SELECT `) + if isNot { + q.where.WriteString(`NOT `) + } + q.where.WriteString(` + EXISTS ( SELECT 1 FROM ldap_association a WHERE a.name = :`) @@ -2011,8 +2010,13 @@ func (t *HybridDBFilterTranslator) PresentMatch(s *AttributeType, q *HybridDBFil } else if s.IsReverseAssociationAttribute() { q.where.WriteString(` - (SELECT EXISTS ( - SELECT 1 FROM ldap_association a, ldap_entry moe, ldap_container moc + (SELECT `) + if isNot { + q.where.WriteString(`NOT `) + } + q.where.WriteString(` + EXISTS ( + SELECT 1 FROM ldap_association a WHERE e.id = a.member_id ))`)