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}