diff --git a/config/presubmits.py b/config/presubmits.py index d25fe8dc0..c4a9c9496 100644 --- a/config/presubmits.py +++ b/config/presubmits.py @@ -211,6 +211,7 @@ PRESUBMITS = { # CriteriaQueryBuilder is a false positive "CriteriaQueryBuilder.java", "RdapDomainSearchAction.java", + "RdapNameserverSearchAction.java", "RdapSearchActionBase.java", }, ): diff --git a/core/src/main/java/google/registry/rdap/RdapDomainSearchAction.java b/core/src/main/java/google/registry/rdap/RdapDomainSearchAction.java index 37df31b37..33a2c37e4 100644 --- a/core/src/main/java/google/registry/rdap/RdapDomainSearchAction.java +++ b/core/src/main/java/google/registry/rdap/RdapDomainSearchAction.java @@ -26,6 +26,7 @@ import static google.registry.util.DateTimeUtils.START_OF_TIME; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; @@ -489,40 +490,33 @@ public class RdapDomainSearchAction extends RdapSearchActionBase { .map(VKey::from) .collect(toImmutableSet()); } else { + // Hibernate does not allow us to query @Converted array fields directly, either + // in the CriteriaQuery or the raw text format. However, Postgres does -- so we + // use native queries to find hosts where any of the inetAddresses match. + StringBuilder queryBuilder = + new StringBuilder( + "SELECT h.repo_id FROM \"Host\" h WHERE :address = ANY(h.inet_addresses) AND " + + "h.deletion_time = CAST(:endOfTime AS timestamptz)"); + ImmutableMap.Builder parameters = + new ImmutableMap.Builder() + .put("address", InetAddresses.toAddrString(inetAddress)) + .put("endOfTime", END_OF_TIME.toString()); + if (desiredRegistrar.isPresent()) { + queryBuilder.append(" AND h.current_sponsor_registrar_id = :desiredRegistrar"); + parameters.put("desiredRegistrar", desiredRegistrar.get()); + } hostKeys = jpaTm() .transact( () -> { - // Hibernate does not allow us to query @Converted array fields directly, either - // in the CriteriaQuery or the raw text format. However, Postgres does -- so we - // use native queries to find hosts where any of the inetAddresses match. - javax.persistence.Query query; - if (desiredRegistrar.isPresent()) { - query = - jpaTm() - .getEntityManager() - .createNativeQuery( - "SELECT h.repo_id FROM \"Host\" h WHERE :address = " - + "ANY(h.inet_addresses) AND " - + "h.current_sponsor_registrar_id = :desiredRegistrar AND " - + "h.deletion_time = CAST(:endOfTime AS timestamptz)") - .setParameter("desiredRegistrar", desiredRegistrar.get()); - } else { - query = - jpaTm() - .getEntityManager() - .createNativeQuery( - "SELECT h.repo_id FROM \"Host\" h WHERE :address = " - + "ANY(h.inet_addresses) AND " - + "h.deletion_time = CAST(:endOfTime AS timestamptz)"); - } + javax.persistence.Query query = + jpaTm() + .getEntityManager() + .createNativeQuery(queryBuilder.toString()) + .setMaxResults(maxNameserversInFirstStage); + parameters.build().forEach(query::setParameter); @SuppressWarnings("unchecked") - Stream resultStream = - query - .setParameter("address", InetAddresses.toAddrString(inetAddress)) - .setParameter("endOfTime", END_OF_TIME.toString()) - .setMaxResults(maxNameserversInFirstStage) - .getResultStream(); + Stream resultStream = query.getResultStream(); return resultStream .map(repoId -> VKey.create(HostResource.class, repoId)) .collect(toImmutableSet()); diff --git a/core/src/main/java/google/registry/rdap/RdapNameserverSearchAction.java b/core/src/main/java/google/registry/rdap/RdapNameserverSearchAction.java index a60c9fe2c..58b3c8c19 100644 --- a/core/src/main/java/google/registry/rdap/RdapNameserverSearchAction.java +++ b/core/src/main/java/google/registry/rdap/RdapNameserverSearchAction.java @@ -15,9 +15,12 @@ package google.registry.rdap; import static google.registry.model.EppResourceUtils.loadByForeignKey; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; import static google.registry.request.Action.Method.GET; import static google.registry.request.Action.Method.HEAD; +import static google.registry.util.DateTimeUtils.END_OF_TIME; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.net.InetAddresses; @@ -25,6 +28,7 @@ import com.google.common.primitives.Booleans; import com.googlecode.objectify.cmd.Query; import google.registry.model.domain.DomainBase; import google.registry.model.host.HostResource; +import google.registry.persistence.transaction.CriteriaQueryBuilder; import google.registry.rdap.RdapJsonFormatter.OutputDataType; import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.SearchType; @@ -216,33 +220,90 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase { private NameserverSearchResponse searchByNameUsingPrefix(RdapSearchPattern partialStringQuery) { // Add 1 so we can detect truncation. int querySizeLimit = getStandardQuerySizeLimit(); - Query query = - queryItems( - HostResource.class, - "fullyQualifiedHostName", - partialStringQuery, - cursorString, - getDeletedItemHandling(), - querySizeLimit); - return makeSearchResults( - getMatchingResources(query, shouldIncludeDeleted(), querySizeLimit), CursorType.NAME); + if (isDatastore()) { + Query query = + queryItems( + HostResource.class, + "fullyQualifiedHostName", + partialStringQuery, + cursorString, + getDeletedItemHandling(), + querySizeLimit); + return makeSearchResults( + getMatchingResources(query, shouldIncludeDeleted(), querySizeLimit), CursorType.NAME); + } else { + return jpaTm() + .transact( + () -> { + CriteriaQueryBuilder queryBuilder = + queryItemsSql( + HostResource.class, + "fullyQualifiedHostName", + partialStringQuery, + cursorString, + getDeletedItemHandling()); + return makeSearchResults( + getMatchingResourcesSql(queryBuilder, shouldIncludeDeleted(), querySizeLimit), + CursorType.NAME); + }); + } } /** Searches for nameservers by IP address, returning a JSON array of nameserver info maps. */ private NameserverSearchResponse searchByIp(InetAddress inetAddress) { // Add 1 so we can detect truncation. int querySizeLimit = getStandardQuerySizeLimit(); - Query query = - queryItems( - HostResource.class, - "inetAddresses", - inetAddress.getHostAddress(), - Optional.empty(), - cursorString, - getDeletedItemHandling(), - querySizeLimit); - return makeSearchResults( - getMatchingResources(query, shouldIncludeDeleted(), querySizeLimit), CursorType.ADDRESS); + RdapResultSet rdapResultSet; + if (isDatastore()) { + Query query = + queryItems( + HostResource.class, + "inetAddresses", + inetAddress.getHostAddress(), + Optional.empty(), + cursorString, + getDeletedItemHandling(), + querySizeLimit); + rdapResultSet = getMatchingResources(query, shouldIncludeDeleted(), querySizeLimit); + } else { + // Hibernate does not allow us to query @Converted array fields directly, either in the + // CriteriaQuery or the raw text format. However, Postgres does -- so we use native queries to + // find hosts where any of the inetAddresses match. + StringBuilder queryBuilder = + new StringBuilder("SELECT * FROM \"Host\" WHERE :address = ANY(inet_addresses)"); + ImmutableMap.Builder parameters = + new ImmutableMap.Builder() + .put("address", InetAddresses.toAddrString(inetAddress)); + if (getDeletedItemHandling().equals(DeletedItemHandling.EXCLUDE)) { + queryBuilder.append(" AND deletion_time = CAST(:endOfTime AS timestamptz)"); + parameters.put("endOfTime", END_OF_TIME.toString()); + } + if (cursorString.isPresent()) { + // cursorString here must be the repo ID + queryBuilder.append(" AND repo_id > :repoId"); + parameters.put("repoId", cursorString.get()); + } + if (getDesiredRegistrar().isPresent()) { + queryBuilder.append(" AND current_sponsor_registrar_id = :desiredRegistrar"); + parameters.put("desiredRegistrar", getDesiredRegistrar().get()); + } + queryBuilder.append(" ORDER BY repo_id ASC"); + rdapResultSet = + jpaTm() + .transact( + () -> { + javax.persistence.Query query = + jpaTm() + .getEntityManager() + .createNativeQuery(queryBuilder.toString(), HostResource.class) + .setMaxResults(querySizeLimit); + parameters.build().forEach(query::setParameter); + @SuppressWarnings("unchecked") + List resultList = query.getResultList(); + return filterResourcesByVisibility(resultList, querySizeLimit); + }); + } + return makeSearchResults(rdapResultSet, CursorType.ADDRESS); } /** Output JSON for a lists of hosts contained in an {@link RdapResultSet}. */ diff --git a/core/src/main/java/google/registry/rdap/RdapSearchActionBase.java b/core/src/main/java/google/registry/rdap/RdapSearchActionBase.java index 37a5369c6..29a72e3cc 100644 --- a/core/src/main/java/google/registry/rdap/RdapSearchActionBase.java +++ b/core/src/main/java/google/registry/rdap/RdapSearchActionBase.java @@ -214,7 +214,7 @@ public abstract class RdapSearchActionBase extends RdapActionBase { } } - private RdapResultSet filterResourcesByVisibility( + protected RdapResultSet filterResourcesByVisibility( List queryResult, int querySizeLimit) { // If we are including deleted resources, we need to check that we're authorized for each one. List resources = new ArrayList<>(); diff --git a/core/src/test/java/google/registry/rdap/RdapNameserverSearchActionTest.java b/core/src/test/java/google/registry/rdap/RdapNameserverSearchActionTest.java index 5ac1515c4..48ec71b70 100644 --- a/core/src/test/java/google/registry/rdap/RdapNameserverSearchActionTest.java +++ b/core/src/test/java/google/registry/rdap/RdapNameserverSearchActionTest.java @@ -43,13 +43,17 @@ import google.registry.rdap.RdapMetrics.EndpointType; import google.registry.rdap.RdapMetrics.SearchType; import google.registry.rdap.RdapMetrics.WildcardType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; +import google.registry.testing.DualDatabaseTest; import google.registry.testing.FakeResponse; +import google.registry.testing.TestOfyAndSql; +import google.registry.testing.TestOfyOnly; +import google.registry.testing.TestSqlOnly; import java.net.URLDecoder; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; /** Unit tests for {@link RdapNameserverSearchAction}. */ +@DualDatabaseTest class RdapNameserverSearchActionTest extends RdapSearchActionTestCase { RdapNameserverSearchActionTest() { @@ -231,7 +235,7 @@ class RdapNameserverSearchActionTest extends RdapSearchActionTestCase