001/* 002 * Copyright 2015-2022 Transmogrify LLC, 2022-2025 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 com.pyranid.JsonParameter.BindingPreference; 020 021import javax.annotation.Nonnull; 022import javax.annotation.Nullable; 023import javax.annotation.concurrent.ThreadSafe; 024import java.lang.reflect.Type; 025import java.math.BigDecimal; 026import java.util.List; 027import java.util.Map; 028import java.util.Optional; 029import java.util.Set; 030 031import static java.util.Objects.requireNonNull; 032 033/** 034 * Fluent interface for acquiring instances of specialized parameter types. 035 * 036 * @author <a href="https://www.revetkn.com">Mark Allen</a> 037 * @since 3.0.0 038 */ 039@ThreadSafe 040public final class Parameters { 041 private Parameters() { 042 // Prevents instantiation 043 } 044 045 /** 046 * Acquires a SQL ARRAY parameter for a {@link List} given an appropriate <a href="https://docs.oracle.com/en/java/javase/24/docs/api/java.sql/java/sql/Array.html#getBaseTypeName()" target="_blank">{@code java.sql.Array#getBaseTypeName()}</a>. 047 * <p> 048 * You may determine available {@code baseTypeName} values for your database by examining metadata exposed via {@link Database#readDatabaseMetaData(DatabaseMetaDataReader)}. 049 * 050 * @param baseTypeName the SQL ARRAY element type, e.g. {@code "text"}, {@code "uuid"}, {@code "float4"}, {@code "float8"} ... 051 * @param list the list whose elements will be used to populate the SQL ARRAY 052 * @param <E> the element type of the Java list ({@code List<E>}); each element must be bindable to {@code baseTypeName} by the active {@link PreparedStatementBinder}. 053 * @return a SQL ARRAY parameter for the given list 054 */ 055 @Nonnull 056 public static <E> ArrayParameter<E> arrayOf(@Nonnull String baseTypeName, 057 @Nullable List<E> list) { 058 requireNonNull(baseTypeName); 059 return new DefaultArrayParameter(baseTypeName, list == null ? null : list.toArray()); 060 } 061 062 /** 063 * Acquires a SQL ARRAY parameter for a native Java array given an appropriate <a href="https://docs.oracle.com/en/java/javase/24/docs/api/java.sql/java/sql/Array.html#getBaseTypeName()" target="_blank">{@code java.sql.Array#getBaseTypeName()}</a>. 064 * <p> 065 * You may determine available {@code baseTypeName} values for your database by examining metadata exposed via {@link Database#readDatabaseMetaData(DatabaseMetaDataReader)}. 066 * 067 * @param baseTypeName the SQL ARRAY element type, e.g. {@code "text"}, {@code "uuid"}, {@code "float4"}, {@code "float8"} ... 068 * @param array the native Java array whose elements will be used to populate the SQL ARRAY 069 * @param <E> the element type of the Java array ({@code T[]}); each element must be bindable to {@code baseTypeName} by the active {@link PreparedStatementBinder}. 070 * @return a SQL ARRAY parameter for the given Java array 071 */ 072 @Nonnull 073 public static <E> ArrayParameter<E> arrayOf(@Nonnull String baseTypeName, 074 @Nullable E[] array) { 075 requireNonNull(baseTypeName); 076 return new DefaultArrayParameter(baseTypeName, array); 077 } 078 079 /** 080 * Default package-private implementation of {@link ArrayParameter}. 081 * 082 * @author <a href="https://www.revetkn.com">Mark Allen</a> 083 * @since 3.0.0 084 */ 085 @ThreadSafe 086 static class DefaultArrayParameter implements ArrayParameter { 087 @Nonnull 088 private final String baseTypeName; // e.g. "text", "uuid", "integer", ... 089 @Nullable 090 private final Object[] elements; 091 092 DefaultArrayParameter(@Nonnull String baseTypeName, 093 @Nullable Object[] elements) { 094 requireNonNull(baseTypeName); 095 096 this.baseTypeName = baseTypeName; 097 this.elements = elements == null ? null : elements.clone(); // Always perform a defensive copy 098 } 099 100 /** 101 * Gets the element type of this SQL ARRAY, which corresponds to the value of <a href="https://docs.oracle.com/en/java/javase/24/docs/api/java.sql/java/sql/Array.html#getBaseTypeName()" target="_blank">{@code java.sql.Array#getBaseTypeName()}</a> 102 * and is database-specific. 103 * 104 * @return the element type of this SQL ARRAY 105 */ 106 @Nonnull 107 @Override 108 public String getBaseTypeName() { 109 return this.baseTypeName; 110 } 111 112 /** 113 * Gets the elements of this SQL ARRAY. 114 * 115 * @return the elements of this SQL ARRAY 116 */ 117 @Nonnull 118 @Override 119 public Optional<Object[]> getElements() { 120 // Defensive copy 121 return this.elements == null ? Optional.empty() : Optional.of(this.elements.clone()); 122 } 123 } 124 125 /** 126 * Acquires a vector parameter for an array of {@code double}. 127 * 128 * @param elements the elements of the vector parameter 129 * @return the vector parameter 130 */ 131 @Nonnull 132 public static VectorParameter vectorOfDoubles(@Nullable double[] elements) { 133 return new DefaultVectorParameter(elements); 134 } 135 136 /** 137 * Acquires a vector parameter for a {@link List} of {@link Double}. 138 * 139 * @param elements the elements of the vector parameter 140 * @return the vector parameter 141 */ 142 @Nonnull 143 public static VectorParameter vectorOfDoubles(@Nullable List<Double> elements) { 144 if (elements == null) 145 return new DefaultVectorParameter(null); 146 147 double[] doubles = new double[elements.size()]; 148 for (int i = 0; i < doubles.length; i++) doubles[i] = requireNonNull(elements.get(i)); 149 return new DefaultVectorParameter(doubles); 150 } 151 152 /** 153 * Acquires a vector parameter for an array of {@code float}. 154 * 155 * @param elements the elements of the vector parameter 156 * @return the vector parameter 157 */ 158 @Nonnull 159 public static VectorParameter vectorOfFloats(@Nullable float[] elements) { 160 if (elements == null) 161 return new DefaultVectorParameter(null); 162 163 double[] doubles = new double[elements.length]; 164 for (int i = 0; i < elements.length; i++) doubles[i] = elements[i]; 165 return new DefaultVectorParameter(doubles); 166 } 167 168 /** 169 * Acquires a vector parameter for a {@link List} of {@link Float}. 170 * 171 * @param elements the elements of the vector parameter 172 * @return the vector parameter 173 */ 174 @Nonnull 175 public static VectorParameter vectorOfFloats(@Nullable List<Float> elements) { 176 if (elements == null) 177 return new DefaultVectorParameter(null); 178 179 double[] doubles = new double[elements.size()]; 180 for (int i = 0; i < doubles.length; i++) doubles[i] = requireNonNull(elements.get(i)); 181 return new DefaultVectorParameter(doubles); 182 } 183 184 /** 185 * Acquires a vector parameter for a {@link List} of {@link BigDecimal}. 186 * 187 * @param elements the elements of the vector parameter 188 * @return the vector parameter 189 */ 190 @Nonnull 191 public static VectorParameter vectorOfBigDecimals(@Nullable List<BigDecimal> elements) { 192 if (elements == null) 193 return new DefaultVectorParameter(null); 194 195 double[] d = new double[elements.size()]; 196 for (int i = 0; i < d.length; i++) d[i] = requireNonNull(elements.get(i)).doubleValue(); 197 return new DefaultVectorParameter(d); 198 } 199 200 /** 201 * Default package-private implementation of {@link VectorParameter}. 202 * 203 * @author <a href="https://www.revetkn.com">Mark Allen</a> 204 * @since 3.0.0 205 */ 206 @ThreadSafe 207 static class DefaultVectorParameter implements VectorParameter { 208 @Nullable 209 private final double[] elements; 210 211 private DefaultVectorParameter(@Nullable double[] elements) { 212 if (elements == null) { 213 this.elements = null; 214 return; 215 } 216 217 if (elements.length == 0) 218 throw new IllegalArgumentException("Vector parameters must have at least 1 element"); 219 220 for (double d : elements) 221 if (!Double.isFinite(d)) 222 throw new IllegalArgumentException("Vector parameter elements must be finite (no NaN/Infinity)"); 223 224 // Always defensive copy 225 this.elements = elements.clone(); 226 } 227 228 /** 229 * Gets the elements of this vector. 230 * 231 * @return the elements of this vector 232 */ 233 @Nonnull 234 @Override 235 public Optional<double[]> getElements() { 236 // Defensive copy 237 return this.elements == null ? Optional.empty() : Optional.of(this.elements.clone()); 238 } 239 } 240 241 /** 242 * Acquires a JSON parameter for "stringified" JSON, using {@link BindingPreference#BINARY}. 243 * 244 * @param json the stringified JSON for this parameter 245 * @return the JSON parameter 246 */ 247 @Nonnull 248 public static JsonParameter json(@Nullable String json) { 249 return new DefaultJsonParameter(json, BindingPreference.BINARY); 250 } 251 252 /** 253 * Acquires a JSON parameter for "stringified" JSON. 254 * 255 * @param json the stringified JSON for this parameter 256 * @param bindingPreference how the JSON parameter should be bound to a {@link java.sql.PreparedStatement} 257 * @return the JSON parameter 258 */ 259 @Nonnull 260 public static JsonParameter json(@Nullable String json, 261 @Nonnull BindingPreference bindingPreference) { 262 requireNonNull(bindingPreference); 263 264 return new DefaultJsonParameter(json, bindingPreference); 265 } 266 267 /** 268 * Default package-private implementation of {@link JsonParameter}. 269 * 270 * @author <a href="https://www.revetkn.com">Mark Allen</a> 271 * @since 3.0.0 272 */ 273 @ThreadSafe 274 static class DefaultJsonParameter implements JsonParameter { 275 @Nullable 276 private final String json; 277 @Nonnull 278 private final BindingPreference bindingPreference; 279 280 private DefaultJsonParameter(@Nullable String json, 281 @Nonnull BindingPreference bindingPreference) { 282 requireNonNull(bindingPreference); 283 284 this.json = json; 285 this.bindingPreference = bindingPreference; 286 } 287 288 @Nonnull 289 @Override 290 public Optional<String> getJson() { 291 return Optional.ofNullable(this.json); 292 } 293 294 @Nonnull 295 @Override 296 public BindingPreference getBindingPreference() { 297 return this.bindingPreference; 298 } 299 } 300 301 /** 302 * Acquires a parameter of type {@link List}, preserving type information so it is accessible at runtime. 303 * <p> 304 * This is useful when you want to bind a parameterized collection such as {@code List<UUID>} or 305 * {@code List<String>} and need the generic type argument (e.g. {@code UUID.class}) to be preserved. 306 * <p> 307 * The resulting {@link TypedParameter} carries both the runtime value and its generic type so that 308 * {@link CustomParameterBinder#appliesTo(TargetType)} can match against the element type. 309 * <p> 310 * <strong>Note:</strong> this kind of parameter requires a corresponding {@link CustomParameterBinder} 311 * to be registered; otherwise, binding will fail-fast. 312 * 313 * @param elementType the {@link Class} representing the type of elements contained in the list; 314 * used to preserve generic type information 315 * @param list the list value to wrap; may be {@code null} 316 * @param <E> the element type of the list 317 * @return a {@link TypedParameter} representing a {@code List<E>} suitable for use with custom binders 318 */ 319 @Nonnull 320 public static <E> TypedParameter listOf(@Nonnull Class<E> elementType, 321 @Nullable List<E> list) { 322 requireNonNull(elementType); 323 324 Type listOfE = new DefaultParameterizedType(List.class, new Type[]{elementType}, null); 325 return new DefaultTypedParameter(listOfE, list); 326 } 327 328 /** 329 * Acquires a parameter of type {@link Set}, preserving type information so it is accessible at runtime. 330 * <p> 331 * This is useful when you want to bind a parameterized collection such as {@code Set<UUID>} or 332 * {@code Set<String>} and need the generic type argument (e.g. {@code UUID.class}) to be preserved. 333 * <p> 334 * The resulting {@link TypedParameter} carries both the runtime value and its generic type so that 335 * {@link CustomParameterBinder#appliesTo(TargetType)} can match against the element type. 336 * <p> 337 * <strong>Note:</strong> this kind of parameter requires a corresponding {@link CustomParameterBinder} 338 * to be registered; otherwise, binding will fail-fast. 339 * 340 * @param elementType the {@link Class} representing the type of elements contained in the set; 341 * used to preserve generic type information 342 * @param set the set value to wrap; may be {@code null} 343 * @param <E> the element type of the set 344 * @return a {@link TypedParameter} representing a {@code Set<E>} suitable for use with custom binders 345 */ 346 @Nonnull 347 public static <E> TypedParameter setOf(@Nonnull Class<E> elementType, 348 @Nullable Set<E> set) { 349 requireNonNull(elementType); 350 351 Type setOfE = new DefaultParameterizedType(Set.class, new Type[]{elementType}, null); 352 return new DefaultTypedParameter(setOfE, set); 353 } 354 355 /** 356 * Acquires a parameter of type {@link Map}, preserving key and value type information 357 * so they are accessible at runtime. 358 * <p> 359 * This is useful when you want to bind a parameterized collection such as {@code Map<UUID, Integer>} 360 * and need the generic type arguments (e.g. {@code UUID.class} and {@code Integer.class}) to be preserved. 361 * <p> 362 * The resulting {@link TypedParameter} carries both the runtime value and its generic type so that 363 * {@link CustomParameterBinder#appliesTo(TargetType)} can match against the element type. 364 * <p> 365 * <strong>Note:</strong> this kind of parameter requires a corresponding {@link CustomParameterBinder} 366 * to be registered; otherwise, binding will fail-fast. 367 * 368 * @param keyType the type of the map keys 369 * @param valueType the type of the map values 370 * @param map the map value; may be {@code null} 371 * @param <K> the key type 372 * @param <V> the value type 373 * @return a {@link TypedParameter} representing {@code Map<K,V>} 374 */ 375 @Nonnull 376 public static <K, V> TypedParameter mapOf(@Nonnull Class<K> keyType, 377 @Nonnull Class<V> valueType, 378 @Nullable Map<K, V> map) { 379 requireNonNull(keyType); 380 requireNonNull(valueType); 381 382 Type mapOfKV = new DefaultParameterizedType(Map.class, new Type[]{keyType, valueType}, null); 383 return new DefaultTypedParameter(mapOfKV, map); 384 } 385}