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 com.pyranid.JsonParameter.BindingPreference; 020import org.jspecify.annotations.NonNull; 021import org.jspecify.annotations.Nullable; 022 023import javax.annotation.concurrent.ThreadSafe; 024import java.lang.reflect.Array; 025import java.lang.reflect.Type; 026import java.math.BigDecimal; 027import java.util.Collection; 028import java.util.List; 029import java.util.Map; 030import java.util.Optional; 031import java.util.Set; 032 033import static java.util.Objects.requireNonNull; 034 035/** 036 * Fluent interface for acquiring instances of specialized parameter types. 037 * 038 * @author <a href="https://www.revetkn.com">Mark Allen</a> 039 * @since 3.0.0 040 */ 041@ThreadSafe 042public final class Parameters { 043 private Parameters() { 044 // Prevents instantiation 045 } 046 047 /** 048 * Acquires a secure parameter with the default {@code <redacted>} diagnostics mask. 049 * <p> 050 * This is display-only: Pyranid binds the wrapped value exactly as if it had not been secured, but renders the mask 051 * instead of the value in its own diagnostics. 052 * 053 * @param value the value to bind 054 * @return a secure parameter for the given value 055 * @since 4.4.0 056 */ 057 @NonNull 058 public static SecureParameter secure(@Nullable Object value) { 059 return secure(value, SecureParameterSupport.DEFAULT_MASK); 060 } 061 062 /** 063 * Acquires a secure parameter with a custom diagnostics mask. 064 * <p> 065 * This is display-only: Pyranid binds the wrapped value exactly as if it had not been secured, but renders the mask 066 * instead of the value in its own diagnostics. 067 * 068 * @param value the value to bind 069 * @param mask the value to render in diagnostics 070 * @return a secure parameter for the given value 071 * @since 4.4.0 072 */ 073 @NonNull 074 public static SecureParameter secure(@Nullable Object value, 075 @NonNull String mask) { 076 requireNonNull(mask); 077 return new DefaultSecureParameter(value, mask); 078 } 079 080 /** 081 * Acquires a SQL ARRAY parameter for a {@link List} given an appropriate <a href="https://docs.oracle.com/en/java/javase/26/docs/api/java.sql/java/sql/Array.html#getBaseTypeName()" target="_blank">{@code java.sql.Array#getBaseTypeName()}</a>. 082 * <p> 083 * You may determine available {@code baseTypeName} values for your database by examining metadata exposed via {@link Database#readDatabaseMetaData(DatabaseMetaDataReader)}. 084 * <p> 085 * SQL ARRAY binding requires JDBC driver support for {@link java.sql.Connection#createArrayOf(String, Object[])}. 086 * Unsupported dialects, including MySQL, MariaDB, SQLite, SQL Server, and Oracle, fail with a clear {@link DatabaseException}. 087 * Elements may themselves be {@link SqlArrayParameter}s, in which case Pyranid materializes the nested arrays 088 * recursively. Multidimensional support remains driver-specific and is integration-verified on DuckDB. Each 089 * containing array's {@code baseTypeName} must name its element type, for example {@code "VARCHAR[]"} for an 090 * outer array whose elements are {@code VARCHAR} arrays. 091 * <p> 092 * For DuckDB elements that represent an instant, use an explicit {@code TIMESTAMP} or {@code TIMESTAMPTZ} 093 * base type so Pyranid can apply the intended timestamp and configured-time-zone semantics. A named DuckDB 094 * type alias does not expose its underlying timestamp semantics through this binding path, so Pyranid fails 095 * fast for these values rather than guessing. 096 * <p> 097 * For non-SQL array binding where you need to preserve the Java element type for custom binders, 098 * use {@link #arrayOf(Class, Object)} instead. 099 * 100 * @param baseTypeName the SQL ARRAY element type, e.g. {@code "text"}, {@code "uuid"}, {@code "float4"}, {@code "float8"} ... 101 * @param list the list whose elements will be used to populate the SQL ARRAY 102 * @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}. 103 * @return a SQL ARRAY parameter for the given list 104 */ 105 @NonNull 106 public static <E> SqlArrayParameter<E> sqlArrayOf(@NonNull String baseTypeName, 107 @Nullable List<E> list) { 108 requireNonNull(baseTypeName); 109 return new DefaultSqlArrayParameter(baseTypeName, list == null ? null : list.toArray()); 110 } 111 112 /** 113 * Acquires a SQL ARRAY parameter for a native Java array given an appropriate <a href="https://docs.oracle.com/en/java/javase/26/docs/api/java.sql/java/sql/Array.html#getBaseTypeName()" target="_blank">{@code java.sql.Array#getBaseTypeName()}</a>. 114 * <p> 115 * You may determine available {@code baseTypeName} values for your database by examining metadata exposed via {@link Database#readDatabaseMetaData(DatabaseMetaDataReader)}. 116 * <p> 117 * SQL ARRAY binding requires JDBC driver support for {@link java.sql.Connection#createArrayOf(String, Object[])}. 118 * Unsupported dialects, including MySQL, MariaDB, SQLite, SQL Server, and Oracle, fail with a clear {@link DatabaseException}. 119 * Elements may themselves be {@link SqlArrayParameter}s, in which case Pyranid materializes the nested arrays 120 * recursively. Multidimensional support remains driver-specific and is integration-verified on DuckDB. Each 121 * containing array's {@code baseTypeName} must name its element type, for example {@code "VARCHAR[]"} for an 122 * outer array whose elements are {@code VARCHAR} arrays. 123 * <p> 124 * For DuckDB elements that represent an instant, use an explicit {@code TIMESTAMP} or {@code TIMESTAMPTZ} 125 * base type so Pyranid can apply the intended timestamp and configured-time-zone semantics. A named DuckDB 126 * type alias does not expose its underlying timestamp semantics through this binding path, so Pyranid fails 127 * fast for these values rather than guessing. 128 * <p> 129 * For non-SQL array binding where you need to preserve the Java element type for custom binders, 130 * use {@link #arrayOf(Class, Object)} instead. 131 * 132 * @param baseTypeName the SQL ARRAY element type, e.g. {@code "text"}, {@code "uuid"}, {@code "float4"}, {@code "float8"} ... 133 * @param array the native Java array whose elements will be used to populate the SQL ARRAY 134 * @param <E> the element type of the Java array ({@code T[]}); each element must be bindable to {@code baseTypeName} by the active {@link PreparedStatementBinder}. 135 * @return a SQL ARRAY parameter for the given Java array 136 */ 137 @NonNull 138 public static <E> SqlArrayParameter<E> sqlArrayOf(@NonNull String baseTypeName, 139 E @Nullable [] array) { 140 requireNonNull(baseTypeName); 141 return new DefaultSqlArrayParameter(baseTypeName, array); 142 } 143 144 /** 145 * Acquires a SQL STRUCT parameter for a {@link List} of positional attributes. 146 * <p> 147 * The {@code typeName} is database-specific and is passed to 148 * {@link java.sql.Connection#createStruct(String, Object[])}. SQL STRUCT binding is currently 149 * supported for DuckDB. Attributes may contain {@code null}, nested {@link SqlStructParameter}s, 150 * or {@link SqlArrayParameter}s. 151 * <p> 152 * For DuckDB attributes that represent an instant ({@link java.time.Instant}, 153 * {@link java.time.OffsetDateTime}, {@link java.time.ZonedDateTime}, {@link java.sql.Timestamp}, or 154 * {@link java.util.Date}), use a complete inline STRUCT declaration so Pyranid can distinguish 155 * {@code TIMESTAMP} from {@code TIMESTAMPTZ} and apply the configured 156 * {@link Database.Builder#timeZone(java.time.ZoneId)}. A named DuckDB {@code CREATE TYPE} alias does not 157 * expose its attribute types through this binding path, so Pyranid fails fast for these values rather 158 * than guessing their timestamp semantics. 159 * 160 * @param typeName the database-specific SQL STRUCT type name 161 * @param attributes the positional attributes, or {@code null} to bind a SQL {@code NULL} STRUCT 162 * @return a SQL STRUCT parameter for the given attributes 163 * @since 4.7.0 164 */ 165 @NonNull 166 public static SqlStructParameter sqlStructOf(@NonNull String typeName, 167 @Nullable List<?> attributes) { 168 requireNonNull(typeName); 169 return new DefaultSqlStructParameter(typeName, attributes == null ? null : attributes.toArray()); 170 } 171 172 /** 173 * Acquires a SQL STRUCT parameter for an array of positional attributes. 174 * <p> 175 * The {@code typeName} is database-specific and is passed to 176 * {@link java.sql.Connection#createStruct(String, Object[])}. SQL STRUCT binding is currently 177 * supported for DuckDB. Attributes may contain {@code null}, nested {@link SqlStructParameter}s, 178 * or {@link SqlArrayParameter}s. 179 * <p> 180 * For DuckDB attributes that represent an instant ({@link java.time.Instant}, 181 * {@link java.time.OffsetDateTime}, {@link java.time.ZonedDateTime}, {@link java.sql.Timestamp}, or 182 * {@link java.util.Date}), use a complete inline STRUCT declaration so Pyranid can distinguish 183 * {@code TIMESTAMP} from {@code TIMESTAMPTZ} and apply the configured 184 * {@link Database.Builder#timeZone(java.time.ZoneId)}. A named DuckDB {@code CREATE TYPE} alias does not 185 * expose its attribute types through this binding path, so Pyranid fails fast for these values rather 186 * than guessing their timestamp semantics. 187 * 188 * @param typeName the database-specific SQL STRUCT type name 189 * @param attributes the positional attributes, or {@code null} to bind a SQL {@code NULL} STRUCT 190 * @return a SQL STRUCT parameter for the given attributes 191 * @since 4.7.0 192 */ 193 @NonNull 194 public static SqlStructParameter sqlStructOf(@NonNull String typeName, 195 Object @Nullable [] attributes) { 196 requireNonNull(typeName); 197 return new DefaultSqlStructParameter(typeName, attributes); 198 } 199 200 /** 201 * Default package-private implementation of {@link SqlArrayParameter}. 202 * 203 * @author <a href="https://www.revetkn.com">Mark Allen</a> 204 * @since 3.0.0 205 */ 206 @ThreadSafe 207 static class DefaultSqlArrayParameter implements SqlArrayParameter { 208 @NonNull 209 private final String baseTypeName; // e.g. "text", "uuid", "integer", ... 210 @Nullable 211 private final Object @Nullable [] elements; 212 213 DefaultSqlArrayParameter(@NonNull String baseTypeName, 214 Object @Nullable [] elements) { 215 requireNonNull(baseTypeName); 216 217 this.baseTypeName = baseTypeName; 218 this.elements = elements == null ? null : elements.clone(); // Always perform a defensive copy 219 } 220 221 /** 222 * Gets the element type of this SQL ARRAY, which corresponds to the value of <a href="https://docs.oracle.com/en/java/javase/26/docs/api/java.sql/java/sql/Array.html#getBaseTypeName()" target="_blank">{@code java.sql.Array#getBaseTypeName()}</a> 223 * and is database-specific. 224 * 225 * @return the element type of this SQL ARRAY 226 */ 227 @NonNull 228 @Override 229 public String getBaseTypeName() { 230 return this.baseTypeName; 231 } 232 233 /** 234 * Gets the elements of this SQL ARRAY. 235 * 236 * @return the elements of this SQL ARRAY 237 */ 238 @NonNull 239 @Override 240 public Optional<Object[]> getElements() { 241 // Defensive copy 242 return this.elements == null ? Optional.empty() : Optional.of(this.elements.clone()); 243 } 244 } 245 246 /** 247 * Default package-private implementation of {@link SqlStructParameter}. 248 * 249 * @author <a href="https://www.revetkn.com">Mark Allen</a> 250 * @since 4.7.0 251 */ 252 @ThreadSafe 253 static class DefaultSqlStructParameter implements SqlStructParameter { 254 @NonNull 255 private final String typeName; 256 @Nullable 257 private final Object @Nullable [] attributes; 258 259 DefaultSqlStructParameter(@NonNull String typeName, 260 Object @Nullable [] attributes) { 261 this.typeName = requireNonNull(typeName); 262 this.attributes = attributes == null ? null : attributes.clone(); 263 } 264 265 @NonNull 266 @Override 267 public String getTypeName() { 268 return this.typeName; 269 } 270 271 @NonNull 272 @Override 273 public Optional<Object[]> getAttributes() { 274 return this.attributes == null ? Optional.empty() : Optional.of(this.attributes.clone()); 275 } 276 } 277 278 /** 279 * Acquires a parameter for SQL {@code IN} list expansion using a {@link Collection}. 280 * Elements must be non-empty and must not contain {@code null} or empty {@link Optional} values. 281 * <p> 282 * SQL {@code IN} does not match {@code NULL}; use an explicit {@code IS NULL} predicate when null matching 283 * is required. 284 * 285 * @param elements the elements to expand into SQL {@code IN} list placeholders 286 * @param <E> the element type 287 * @return an IN-list parameter for the given elements 288 */ 289 @NonNull 290 public static <E> InListParameter inList(@NonNull Collection<@NonNull E> elements) { 291 requireNonNull(elements); 292 return new DefaultInListParameter(elements.toArray()); 293 } 294 295 /** 296 * Acquires a parameter for SQL {@code IN} list expansion using a Java array. 297 * Elements must be non-empty and must not contain {@code null} or empty {@link Optional} values. 298 * <p> 299 * SQL {@code IN} does not match {@code NULL}; use an explicit {@code IS NULL} predicate when null matching 300 * is required. 301 * 302 * @param elements the elements to expand into SQL {@code IN} list placeholders 303 * @param <E> the element type 304 * @return an IN-list parameter for the given elements 305 */ 306 @NonNull 307 public static <E> InListParameter inList(@NonNull E @NonNull [] elements) { 308 requireNonNull(elements); 309 return new DefaultInListParameter(elements); 310 } 311 312 /** 313 * Acquires a parameter for SQL {@code IN} list expansion using a {@code byte[]} array. 314 * Elements must be non-empty. 315 * 316 * @param elements the elements to expand into SQL {@code IN} list placeholders 317 * @return an IN-list parameter for the given elements 318 */ 319 @NonNull 320 public static InListParameter inList(byte @NonNull [] elements) { 321 requireNonNull(elements); 322 Object[] boxed = new Object[elements.length]; 323 for (int i = 0; i < elements.length; i++) 324 boxed[i] = elements[i]; 325 return new DefaultInListParameter(boxed); 326 } 327 328 /** 329 * Acquires a parameter for SQL {@code IN} list expansion using a {@code short[]} array. 330 * Elements must be non-empty. 331 * 332 * @param elements the elements to expand into SQL {@code IN} list placeholders 333 * @return an IN-list parameter for the given elements 334 */ 335 @NonNull 336 public static InListParameter inList(short @NonNull [] elements) { 337 requireNonNull(elements); 338 Object[] boxed = new Object[elements.length]; 339 for (int i = 0; i < elements.length; i++) 340 boxed[i] = elements[i]; 341 return new DefaultInListParameter(boxed); 342 } 343 344 /** 345 * Acquires a parameter for SQL {@code IN} list expansion using an {@code int[]} array. 346 * Elements must be non-empty. 347 * 348 * @param elements the elements to expand into SQL {@code IN} list placeholders 349 * @return an IN-list parameter for the given elements 350 */ 351 @NonNull 352 public static InListParameter inList(int @NonNull [] elements) { 353 requireNonNull(elements); 354 Object[] boxed = new Object[elements.length]; 355 for (int i = 0; i < elements.length; i++) 356 boxed[i] = elements[i]; 357 return new DefaultInListParameter(boxed); 358 } 359 360 /** 361 * Acquires a parameter for SQL {@code IN} list expansion using a {@code long[]} array. 362 * Elements must be non-empty. 363 * 364 * @param elements the elements to expand into SQL {@code IN} list placeholders 365 * @return an IN-list parameter for the given elements 366 */ 367 @NonNull 368 public static InListParameter inList(long @NonNull [] elements) { 369 requireNonNull(elements); 370 Object[] boxed = new Object[elements.length]; 371 for (int i = 0; i < elements.length; i++) 372 boxed[i] = elements[i]; 373 return new DefaultInListParameter(boxed); 374 } 375 376 /** 377 * Acquires a parameter for SQL {@code IN} list expansion using a {@code float[]} array. 378 * Elements must be non-empty. 379 * 380 * @param elements the elements to expand into SQL {@code IN} list placeholders 381 * @return an IN-list parameter for the given elements 382 */ 383 @NonNull 384 public static InListParameter inList(float @NonNull [] elements) { 385 requireNonNull(elements); 386 Object[] boxed = new Object[elements.length]; 387 for (int i = 0; i < elements.length; i++) 388 boxed[i] = elements[i]; 389 return new DefaultInListParameter(boxed); 390 } 391 392 /** 393 * Acquires a parameter for SQL {@code IN} list expansion using a {@code double[]} array. 394 * Elements must be non-empty. 395 * 396 * @param elements the elements to expand into SQL {@code IN} list placeholders 397 * @return an IN-list parameter for the given elements 398 */ 399 @NonNull 400 public static InListParameter inList(double @NonNull [] elements) { 401 requireNonNull(elements); 402 Object[] boxed = new Object[elements.length]; 403 for (int i = 0; i < elements.length; i++) 404 boxed[i] = elements[i]; 405 return new DefaultInListParameter(boxed); 406 } 407 408 /** 409 * Acquires a parameter for SQL {@code IN} list expansion using a {@code boolean[]} array. 410 * Elements must be non-empty. 411 * 412 * @param elements the elements to expand into SQL {@code IN} list placeholders 413 * @return an IN-list parameter for the given elements 414 */ 415 @NonNull 416 public static InListParameter inList(boolean @NonNull [] elements) { 417 requireNonNull(elements); 418 Object[] boxed = new Object[elements.length]; 419 for (int i = 0; i < elements.length; i++) 420 boxed[i] = elements[i]; 421 return new DefaultInListParameter(boxed); 422 } 423 424 /** 425 * Acquires a parameter for SQL {@code IN} list expansion using a {@code char[]} array. 426 * Elements must be non-empty. 427 * 428 * @param elements the elements to expand into SQL {@code IN} list placeholders 429 * @return an IN-list parameter for the given elements 430 */ 431 @NonNull 432 public static InListParameter inList(char @NonNull [] elements) { 433 requireNonNull(elements); 434 Object[] boxed = new Object[elements.length]; 435 for (int i = 0; i < elements.length; i++) 436 boxed[i] = elements[i]; 437 return new DefaultInListParameter(boxed); 438 } 439 440 /** 441 * Default package-private implementation of {@link InListParameter}. 442 * 443 * @author <a href="https://www.revetkn.com">Mark Allen</a> 444 * @since 4.0.0 445 */ 446 @ThreadSafe 447 static class DefaultInListParameter implements InListParameter { 448 private final @NonNull Object @NonNull [] elements; 449 450 DefaultInListParameter(@NonNull Object @NonNull [] elements) { 451 requireNonNull(elements); 452 this.elements = elements.clone(); // Always perform a defensive copy 453 } 454 455 @Override 456 public @NonNull Object @NonNull [] getElements() { 457 // Defensive copy 458 return this.elements.clone(); 459 } 460 } 461 462 /** 463 * Acquires a parameter of array type, preserving element type information so it is accessible at runtime. 464 * <p> 465 * This is useful when you want to bind an array via a {@link CustomParameterBinder} and need the 466 * component type to be preserved for {@link TargetType} matching. 467 * <p> 468 * For SQL {@code ARRAY} binding, use {@link #sqlArrayOf(String, Object[])} instead. 469 * If you need a formal SQL {@code ARRAY} parameter for JDBC array binding, do not use this method. 470 * <p> 471 * <strong>Note:</strong> this kind of parameter requires a corresponding {@link CustomParameterBinder} 472 * to be registered, even when the wrapped value is {@code null}; implement {@link CustomParameterBinder#bindNull} 473 * if you want typed nulls to bind successfully. 474 * 475 * @param elementType the element type of the array 476 * @param array the array value to wrap; may be {@code null} 477 * @param <E> the element type of the array 478 * @return a {@link TypedParameter} representing a {@code E[]} suitable for use with custom binders 479 */ 480 @NonNull 481 public static <E> TypedParameter arrayOf(@NonNull Class<E> elementType, 482 E @Nullable [] array) { 483 requireNonNull(elementType); 484 return arrayOf((Class<?>) elementType, array); 485 } 486 487 /** 488 * Acquires a parameter of array type, preserving element type information so it is accessible at runtime. 489 * <p> 490 * This overload supports primitive arrays by passing the primitive component class 491 * (for example, {@code int.class}) and the corresponding primitive array. 492 * <p> 493 * For SQL {@code ARRAY} binding, use {@link #sqlArrayOf(String, Object[])} instead. 494 * If you need a formal SQL {@code ARRAY} parameter for JDBC array binding, do not use this method. 495 * <p> 496 * <strong>Note:</strong> this kind of parameter requires a corresponding {@link CustomParameterBinder} 497 * to be registered, even when the wrapped value is {@code null}; implement {@link CustomParameterBinder#bindNull} 498 * if you want typed nulls to bind successfully. 499 * 500 * @param elementType the element type of the array (may be primitive) 501 * @param array the array value to wrap; may be {@code null} 502 * @return a {@link TypedParameter} representing an array suitable for use with custom binders 503 */ 504 @NonNull 505 public static TypedParameter arrayOf(@NonNull Class<?> elementType, 506 @Nullable Object array) { 507 requireNonNull(elementType); 508 509 if (array != null) { 510 Class<?> arrayClass = array.getClass(); 511 512 if (!arrayClass.isArray()) 513 throw new IllegalArgumentException("Array parameter is not an array"); 514 515 Class<?> componentType = arrayClass.getComponentType(); 516 517 if (elementType.isPrimitive()) { 518 if (!componentType.equals(elementType)) 519 throw new IllegalArgumentException("Array parameter component type does not match elementType"); 520 } else if (!elementType.isAssignableFrom(componentType)) { 521 throw new IllegalArgumentException("Array parameter component type is not assignable to elementType"); 522 } 523 } 524 525 Class<?> arrayType = Array.newInstance(elementType, 0).getClass(); 526 return new DefaultTypedParameter(arrayType, array); 527 } 528 529 /** 530 * Acquires a vector parameter for an array of {@code double}. 531 * 532 * @param elements the elements of the vector parameter 533 * @return the vector parameter 534 */ 535 @NonNull 536 public static VectorParameter vectorOfDoubles(double @Nullable [] elements) { 537 return new DefaultVectorParameter(elements); 538 } 539 540 /** 541 * Acquires a vector parameter for a {@link List} of {@link Double}. 542 * 543 * @param elements the elements of the vector parameter 544 * @return the vector parameter 545 */ 546 @NonNull 547 public static VectorParameter vectorOfDoubles(@Nullable List<@NonNull Double> elements) { 548 if (elements == null) 549 return new DefaultVectorParameter(null); 550 551 double[] doubles = new double[elements.size()]; 552 for (int i = 0; i < doubles.length; i++) doubles[i] = requireNonNull(elements.get(i)); 553 return new DefaultVectorParameter(doubles); 554 } 555 556 /** 557 * Acquires a vector parameter for an array of {@code float}. 558 * 559 * @param elements the elements of the vector parameter 560 * @return the vector parameter 561 */ 562 @NonNull 563 public static VectorParameter vectorOfFloats(float @Nullable [] elements) { 564 if (elements == null) 565 return new DefaultVectorParameter(null); 566 567 double[] doubles = new double[elements.length]; 568 for (int i = 0; i < elements.length; i++) doubles[i] = elements[i]; 569 return new DefaultVectorParameter(doubles); 570 } 571 572 /** 573 * Acquires a vector parameter for a {@link List} of {@link Float}. 574 * 575 * @param elements the elements of the vector parameter 576 * @return the vector parameter 577 */ 578 @NonNull 579 public static VectorParameter vectorOfFloats(@Nullable List<@NonNull Float> elements) { 580 if (elements == null) 581 return new DefaultVectorParameter(null); 582 583 double[] doubles = new double[elements.size()]; 584 for (int i = 0; i < doubles.length; i++) doubles[i] = requireNonNull(elements.get(i)); 585 return new DefaultVectorParameter(doubles); 586 } 587 588 /** 589 * Acquires a vector parameter for a {@link List} of {@link BigDecimal}. 590 * 591 * @param elements the elements of the vector parameter 592 * @return the vector parameter 593 */ 594 @NonNull 595 public static VectorParameter vectorOfBigDecimals(@Nullable List<@NonNull BigDecimal> elements) { 596 if (elements == null) 597 return new DefaultVectorParameter(null); 598 599 double[] d = new double[elements.size()]; 600 for (int i = 0; i < d.length; i++) d[i] = requireNonNull(elements.get(i)).doubleValue(); 601 return new DefaultVectorParameter(d); 602 } 603 604 /** 605 * Default package-private implementation of {@link VectorParameter}. 606 * 607 * @author <a href="https://www.revetkn.com">Mark Allen</a> 608 * @since 3.0.0 609 */ 610 @ThreadSafe 611 static class DefaultVectorParameter implements VectorParameter { 612 @Nullable 613 private final double[] elements; 614 615 private DefaultVectorParameter(double @Nullable [] elements) { 616 if (elements == null) { 617 this.elements = null; 618 return; 619 } 620 621 if (elements.length == 0) 622 throw new IllegalArgumentException("Vector parameters must have at least 1 element"); 623 624 for (double d : elements) 625 if (!Double.isFinite(d)) 626 throw new IllegalArgumentException("Vector parameter elements must be finite (no NaN/Infinity)"); 627 628 // Always defensive copy 629 this.elements = elements.clone(); 630 } 631 632 /** 633 * Gets the elements of this vector. 634 * 635 * @return the elements of this vector 636 */ 637 @NonNull 638 @Override 639 public Optional<double[]> getElements() { 640 // Defensive copy 641 return this.elements == null ? Optional.empty() : Optional.of(this.elements.clone()); 642 } 643 } 644 645 /** 646 * Acquires a JSON parameter for "stringified" JSON, using {@link BindingPreference#BINARY}. 647 * 648 * @param json the stringified JSON for this parameter 649 * @return the JSON parameter 650 */ 651 @NonNull 652 public static JsonParameter json(@Nullable String json) { 653 return new DefaultJsonParameter(json, BindingPreference.BINARY); 654 } 655 656 /** 657 * Acquires a JSON parameter for "stringified" JSON. 658 * 659 * @param json the stringified JSON for this parameter 660 * @param bindingPreference how the JSON parameter should be bound to a {@link java.sql.PreparedStatement} 661 * @return the JSON parameter 662 */ 663 @NonNull 664 public static JsonParameter json(@Nullable String json, 665 @NonNull BindingPreference bindingPreference) { 666 requireNonNull(bindingPreference); 667 668 return new DefaultJsonParameter(json, bindingPreference); 669 } 670 671 /** 672 * Default package-private implementation of {@link JsonParameter}. 673 * 674 * @author <a href="https://www.revetkn.com">Mark Allen</a> 675 * @since 3.0.0 676 */ 677 @ThreadSafe 678 static class DefaultJsonParameter implements JsonParameter { 679 @Nullable 680 private final String json; 681 @NonNull 682 private final BindingPreference bindingPreference; 683 684 private DefaultJsonParameter(@Nullable String json, 685 @NonNull BindingPreference bindingPreference) { 686 requireNonNull(bindingPreference); 687 688 this.json = json; 689 this.bindingPreference = bindingPreference; 690 } 691 692 @NonNull 693 @Override 694 public Optional<String> getJson() { 695 return Optional.ofNullable(this.json); 696 } 697 698 @NonNull 699 @Override 700 public BindingPreference getBindingPreference() { 701 return this.bindingPreference; 702 } 703 } 704 705 /** 706 * Acquires a parameter of type {@link List}, preserving type information so it is accessible at runtime. 707 * <p> 708 * This is useful when you want to bind a parameterized collection such as {@code List<UUID>} or 709 * {@code List<String>} and need the generic type argument (e.g. {@code UUID.class}) to be preserved. 710 * <p> 711 * The resulting {@link TypedParameter} carries both the runtime value and its generic type so that 712 * {@link CustomParameterBinder#appliesTo(TargetType)} can match against the element type. 713 * <p> 714 * <strong>Note:</strong> this kind of parameter requires a corresponding {@link CustomParameterBinder} 715 * to be registered, even when the wrapped value is {@code null}; implement {@link CustomParameterBinder#bindNull} 716 * if you want typed nulls to bind successfully. 717 * 718 * @param elementType the {@link Class} representing the type of elements contained in the list; 719 * used to preserve generic type information 720 * @param list the list value to wrap; may be {@code null} 721 * @param <E> the element type of the list 722 * @return a {@link TypedParameter} representing a {@code List<E>} suitable for use with custom binders 723 */ 724 @NonNull 725 public static <E> TypedParameter listOf(@NonNull Class<E> elementType, 726 @Nullable List<E> list) { 727 requireNonNull(elementType); 728 729 Type listOfE = new DefaultParameterizedType(List.class, new Type[]{elementType}, null); 730 return new DefaultTypedParameter(listOfE, list); 731 } 732 733 /** 734 * Acquires a parameter of type {@link Set}, preserving type information so it is accessible at runtime. 735 * <p> 736 * This is useful when you want to bind a parameterized collection such as {@code Set<UUID>} or 737 * {@code Set<String>} and need the generic type argument (e.g. {@code UUID.class}) to be preserved. 738 * <p> 739 * The resulting {@link TypedParameter} carries both the runtime value and its generic type so that 740 * {@link CustomParameterBinder#appliesTo(TargetType)} can match against the element type. 741 * <p> 742 * <strong>Note:</strong> this kind of parameter requires a corresponding {@link CustomParameterBinder} 743 * to be registered, even when the wrapped value is {@code null}; implement {@link CustomParameterBinder#bindNull} 744 * if you want typed nulls to bind successfully. 745 * 746 * @param elementType the {@link Class} representing the type of elements contained in the set; 747 * used to preserve generic type information 748 * @param set the set value to wrap; may be {@code null} 749 * @param <E> the element type of the set 750 * @return a {@link TypedParameter} representing a {@code Set<E>} suitable for use with custom binders 751 */ 752 @NonNull 753 public static <E> TypedParameter setOf(@NonNull Class<E> elementType, 754 @Nullable Set<E> set) { 755 requireNonNull(elementType); 756 757 Type setOfE = new DefaultParameterizedType(Set.class, new Type[]{elementType}, null); 758 return new DefaultTypedParameter(setOfE, set); 759 } 760 761 /** 762 * Acquires a parameter of type {@link Map}, preserving key and value type information 763 * so they are accessible at runtime. 764 * <p> 765 * This is useful when you want to bind a parameterized collection such as {@code Map<UUID, Integer>} 766 * and need the generic type arguments (e.g. {@code UUID.class} and {@code Integer.class}) to be preserved. 767 * <p> 768 * The resulting {@link TypedParameter} carries both the runtime value and its generic type so that 769 * {@link CustomParameterBinder#appliesTo(TargetType)} can match against the element type. 770 * <p> 771 * <strong>Note:</strong> this kind of parameter requires a corresponding {@link CustomParameterBinder} 772 * to be registered, even when the wrapped value is {@code null}; implement {@link CustomParameterBinder#bindNull} 773 * if you want typed nulls to bind successfully. 774 * 775 * @param keyType the type of the map keys 776 * @param valueType the type of the map values 777 * @param map the map value; may be {@code null} 778 * @param <K> the key type 779 * @param <V> the value type 780 * @return a {@link TypedParameter} representing {@code Map<K,V>} 781 */ 782 @NonNull 783 public static <K, V> TypedParameter mapOf(@NonNull Class<K> keyType, 784 @NonNull Class<V> valueType, 785 @Nullable Map<K, V> map) { 786 requireNonNull(keyType); 787 requireNonNull(valueType); 788 789 Type mapOfKV = new DefaultParameterizedType(Map.class, new Type[]{keyType, valueType}, null); 790 return new DefaultTypedParameter(mapOfKV, map); 791 } 792}