StringAotQuery.java

/*
 * Copyright 2025 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.data.jpa.repository.aot;

import java.util.List;

import org.springframework.data.domain.Limit;
import org.springframework.data.jpa.repository.query.DeclaredQuery;
import org.springframework.data.jpa.repository.query.EntityQuery;
import org.springframework.data.jpa.repository.query.ParameterBinding;
import org.springframework.data.jpa.repository.query.PreprocessedQuery;
import org.springframework.data.jpa.repository.query.QueryProvider;

/**
 * An AOT query represented by a string.
 *
 * @author Mark Paluch
 * @since 4.0
 */
abstract class StringAotQuery extends AotQuery {

	private StringAotQuery(List<ParameterBinding> parameterBindings) {
		super(parameterBindings);
	}

	/**
	 * Creates a new {@code StringAotQuery} from a {@link DeclaredQuery}. Parses the query into {@link PreprocessedQuery}.
	 */
	static StringAotQuery of(DeclaredQuery query) {

		if (query instanceof PreprocessedQuery pq) {
			return new DeclaredAotQuery(pq, false);
		}

		return new DeclaredAotQuery(PreprocessedQuery.parse(query), false);
	}

	/**
	 * Creates a new {@code StringAotQuery} from a {@link EntityQuery}. Parses the query into {@link PreprocessedQuery}.
	 */
	static StringAotQuery of(EntityQuery query) {
		return new DeclaredAotQuery(query);
	}

	/**
	 * Creates a new named (via {@link org.springframework.data.repository.core.NamedQueries}) {@code StringAotQuery} from
	 * a {@link EntityQuery}. Parses the query into {@link PreprocessedQuery}.
	 */
	static StringAotQuery named(String queryName, EntityQuery query) {
		return new NamedStringAotQuery(queryName, query);
	}

	/**
	 * Creates a JPQL {@code StringAotQuery} using the given bindings and limit.
	 */
	public static StringAotQuery jpqlQuery(String queryString, List<ParameterBinding> bindings, Limit resultLimit,
			boolean delete, boolean exists) {
		return new DerivedAotQuery(queryString, bindings, resultLimit, delete, exists);
	}

	/**
	 * @return the underlying declared query.
	 */
	public abstract DeclaredQuery getQuery();

	public String getQueryString() {
		return getQuery().getQueryString();
	}

	/**
	 * @return {@literal true} if query uses an own paging mechanism through {@code {#pageable}}.
	 */
	public abstract boolean hasPagingExpression();

	public abstract StringAotQuery rewrite(QueryProvider rewritten);

	@Override
	public String toString() {
		return getQueryString();
	}

	/**
	 * @author Christoph Strobl
	 * @author Mark Paluch
	 */
	private static class DeclaredAotQuery extends StringAotQuery {

		private final PreprocessedQuery query;
		private final boolean constructorExpressionOrDefaultProjection;
		private final boolean hasPagingExpression;

		DeclaredAotQuery(EntityQuery query) {
			super(query.getParameterBindings());
			this.query = query.getQuery();
			this.hasPagingExpression = query.usesPaging();
			this.constructorExpressionOrDefaultProjection = hasConstructorExpressionOrDefaultProjection(query);
		}

		DeclaredAotQuery(PreprocessedQuery query, boolean constructorExpressionOrDefaultProjection) {
			super(query.getBindings());
			this.query = query;
			this.hasPagingExpression = query.containsPageableInSpel();
			this.constructorExpressionOrDefaultProjection = constructorExpressionOrDefaultProjection;
		}

		@Override
		public PreprocessedQuery getQuery() {
			return query;
		}

		@Override
		public String getQueryString() {
			return query.getQueryString();
		}

		@Override
		public boolean isNative() {
			return query.isNative();
		}

		@Override
		public boolean hasConstructorExpressionOrDefaultProjection() {
			return constructorExpressionOrDefaultProjection;
		}

		@Override
		public boolean hasPagingExpression() {
			return hasPagingExpression;
		}

		@Override
		public StringAotQuery rewrite(QueryProvider rewritten) {
			return new DeclaredAotQuery(query.rewrite(rewritten.getQueryString()), constructorExpressionOrDefaultProjection);
		}

	}

	static class NamedStringAotQuery extends DeclaredAotQuery {

		private final String queryName;

		NamedStringAotQuery(String queryName, EntityQuery entityQuery) {
			super(entityQuery);
			this.queryName = queryName;
		}

		NamedStringAotQuery(String queryName, PreprocessedQuery query, boolean constructorExpressionOrDefaultProjection) {
			super(query, constructorExpressionOrDefaultProjection);
			this.queryName = queryName;
		}

		public String getQueryName() {
			return queryName;
		}

	}

	/**
	 * PartTree (derived) Query with a limit associated.
	 *
	 * @author Mark Paluch
	 */
	private static class DerivedAotQuery extends StringAotQuery {

		private final String queryString;
		private final Limit limit;
		private final boolean delete;
		private final boolean exists;

		DerivedAotQuery(String queryString, List<ParameterBinding> parameterBindings, Limit limit, boolean delete,
				boolean exists) {
			super(parameterBindings);
			this.queryString = queryString;
			this.limit = limit;
			this.delete = delete;
			this.exists = exists;
		}

		@Override
		public DeclaredQuery getQuery() {
			return DeclaredQuery.jpqlQuery(queryString);
		}

		@Override
		public String getQueryString() {
			return queryString;
		}

		@Override
		public boolean isNative() {
			return false;
		}

		@Override
		public Limit getLimit() {
			return limit;
		}

		@Override
		public boolean isDelete() {
			return delete;
		}

		@Override
		public boolean isExists() {
			return exists;
		}

		@Override
		public boolean hasConstructorExpressionOrDefaultProjection() {
			return false;
		}

		@Override
		public boolean hasPagingExpression() {
			return false;
		}

		@Override
		public StringAotQuery rewrite(QueryProvider rewritten) {
			return new DerivedAotQuery(rewritten.getQueryString(), this.getParameterBindings(), getLimit(), delete, exists);
		}

	}
}