001/*
002 * Copyright 2015-2022 Transmogrify LLC, 2022-2026 Revetware LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.pyranid;
018
019import org.jspecify.annotations.NonNull;
020import org.jspecify.annotations.Nullable;
021
022import javax.annotation.concurrent.NotThreadSafe;
023import java.util.List;
024import java.util.Map;
025import java.util.Optional;
026import java.util.function.Function;
027import java.util.stream.Stream;
028
029/**
030 * Fluent builder for SQL statements.
031 * <p>
032 * Obtain instances via {@link Database#query(String)}.
033 * Positional parameters via {@code ?} are not supported; use named parameters (e.g. {@code :id}) and {@link #bind(String, Object)}.
034 * <p>
035 * Example usage:
036 * <pre>{@code
037 * // Query returning one row
038 * Optional<Employee> employee = database.query("SELECT * FROM employee WHERE id = :id")
039 *   .bind("id", 42)
040 *   .fetchObject(Employee.class);
041 *
042 * // Query returning multiple rows
043 * List<Employee> employees = database.query("SELECT * FROM employee WHERE dept = :dept")
044 *   .bind("dept", "Engineering")
045 *   .fetchList(Employee.class);
046 *
047 * // DML with no result
048 * long rowsAffected = database.query("UPDATE employee SET active = :active WHERE id = :id")
049 *   .bind("id", 42)
050 *   .bind("active", false)
051 *   .execute();
052 *
053 * // DML with RETURNING clause
054 * Optional<Employee> updated = database.query("UPDATE employee SET salary = :salary WHERE id = :id RETURNING *")
055 *   .bind("id", 42)
056 *   .bind("salary", new BigDecimal("150000"))
057 *   .executeForObject(Employee.class);
058 * }</pre>
059 * <p>
060 * Implementations of this interface are intended for use by a single thread.
061 *
062 * @author <a href="https://www.revetkn.com">Mark Allen</a>
063 * @see Database#query(String)
064 * @since 4.0.0
065 */
066@NotThreadSafe
067public interface Query {
068        /**
069         * Binds a named parameter to a value.
070         *
071         * @param name  the parameter name (without the leading {@code :})
072         * @param value the value to bind (may be {@code null}). Raw {@link java.util.Collection} and array
073         *              values are not expanded; use {@link Parameters#inList(java.util.Collection)},
074         *              {@link Parameters#sqlArrayOf(String, Object[])}, or {@link Parameters#arrayOf(Class, Object)}
075         *              as appropriate.
076         * @return this builder, for chaining
077         */
078        @NonNull
079        Query bind(@NonNull String name,
080                                                 @Nullable Object value);
081
082        /**
083         * Binds all entries from the given map as named parameters.
084         *
085         * @param parameters map of parameter names to values
086         * @return this builder, for chaining
087         */
088        @NonNull
089        Query bindAll(@NonNull Map<@NonNull String, @Nullable Object> parameters);
090
091        /**
092         * Associates an identifier with this query for logging/diagnostics.
093         * <p>
094         * If not called, a default ID will be generated.
095         *
096         * @param id the identifier
097         * @return this builder, for chaining
098         */
099        @NonNull
100        Query id(@Nullable Object id);
101
102        /**
103         * Customizes the {@link java.sql.PreparedStatement} before execution.
104         * <p>
105         * If called multiple times, the most recent customizer wins.
106         *
107         * @param preparedStatementCustomizer customization callback
108         * @return this builder, for chaining
109         */
110        @NonNull
111        Query customize(@NonNull PreparedStatementCustomizer preparedStatementCustomizer);
112
113        /**
114         * Executes the query and returns a single result.
115         *
116         * @param resultType the type to marshal each row to
117         * @param <T>        the result type
118         * @return the single result, or empty if no rows
119         * @throws DatabaseException if more than one row is returned
120         */
121        @NonNull
122        <T> Optional<T> fetchObject(@NonNull Class<T> resultType);
123
124        /**
125         * Executes the query and returns all results as a list.
126         *
127         * @param resultType the type to marshal each row to
128         * @param <T>        the result type
129         * @return list of results (empty if no rows)
130         */
131        @NonNull
132        <T> List<@Nullable T> fetchList(@NonNull Class<T> resultType);
133
134        /**
135         * Executes the query and provides a {@link Stream} backed by the underlying {@link java.sql.ResultSet}.
136         * <p>
137         * This approach is useful for processing very large resultsets (e.g. millions of rows), where it's impractical to load all rows into memory at once.
138         * <p>
139         * JDBC resources are closed automatically when {@code streamFunction} returns (or throws), so the stream must be fully consumed
140         * within that callback. Do not escape the stream from the function.
141         * <p>
142         * The stream must be consumed within the scope of the transaction or connection that created it.
143         *
144         * @param resultType     the type to marshal each row to
145         * @param streamFunction function that consumes the stream and returns a result
146         * @param <T>            the result type
147         * @param <R>            the return type
148         * @return the value returned by {@code streamFunction}
149         */
150        @Nullable
151        <T, R> R fetchStream(@NonNull Class<T> resultType,
152                                                                                         @NonNull Function<Stream<@Nullable T>, R> streamFunction);
153
154
155        /**
156         * Executes a DML statement (INSERT, UPDATE, DELETE) with no resultset.
157         *
158         * @return the number of rows affected
159         */
160        @NonNull
161        Long execute();
162
163        /**
164         * Executes a DML statement in batch over groups of named parameters.
165         * <p>
166         * Any parameters already bound on this {@code Query} apply to all groups; group values override them.
167         *
168         * @param parameterGroups groups of named parameter values (without the leading {@code :})
169         * @return the number of rows affected by the SQL statement per-group
170         */
171        @NonNull
172        List<Long> executeBatch(@NonNull List<@NonNull Map<@NonNull String, @Nullable Object>> parameterGroups);
173
174        /**
175         * Executes a DML statement that returns a single row (e.g., with {@code RETURNING} clause).
176         *
177         * @param resultType the type to marshal the row to
178         * @param <T>        the result type
179         * @return the single result, or empty if no rows
180         * @throws DatabaseException if more than one row is returned
181         */
182        @NonNull
183        <T> Optional<T> executeForObject(@NonNull Class<T> resultType);
184
185        /**
186         * Executes a DML statement that returns multiple rows (e.g., with {@code RETURNING} clause).
187         *
188         * @param resultType the type to marshal each row to
189         * @param <T>        the result type
190         * @return list of results
191         */
192        @NonNull
193        <T> List<@Nullable T> executeForList(@NonNull Class<T> resultType);
194}