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}