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}