org.jooq.impl.Tools Maven / Gradle / Ivy
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* Apache-2.0 license and offer limited warranties, support, maintenance, and
* commercial database integrations.
*
* For more information, please visit: https://www.jooq.org/legal/licensing
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.impl;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static java.lang.Character.isJavaIdentifierPart;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.nCopies;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.joining;
import static org.jooq.ContextConverter.scoped;
// ...
// ...
// ...
// ...
// ...
// ...
// ...
import static org.jooq.SQLDialect.DERBY;
import static org.jooq.SQLDialect.DUCKDB;
// ...
import static org.jooq.SQLDialect.FIREBIRD;
import static org.jooq.SQLDialect.H2;
// ...
// ...
import static org.jooq.SQLDialect.HSQLDB;
// ...
// ...
// ...
import static org.jooq.SQLDialect.MARIADB;
// ...
import static org.jooq.SQLDialect.MYSQL;
// ...
// ...
// ...
import static org.jooq.SQLDialect.POSTGRES;
// ...
// ...
// ...
// ...
import static org.jooq.SQLDialect.SQLITE;
// ...
// ...
// ...
// ...
import static org.jooq.SQLDialect.TRINO;
import static org.jooq.SQLDialect.YUGABYTEDB;
import static org.jooq.conf.BackslashEscaping.DEFAULT;
import static org.jooq.conf.BackslashEscaping.ON;
import static org.jooq.conf.ParamType.INLINED;
import static org.jooq.conf.ParamType.NAMED;
import static org.jooq.conf.ParamType.NAMED_OR_INLINED;
import static org.jooq.conf.RenderDefaultNullability.IMPLICIT_NULL;
import static org.jooq.conf.RenderQuotedNames.EXPLICIT_DEFAULT_QUOTED;
import static org.jooq.conf.SettingsTools.getBackslashEscaping;
import static org.jooq.conf.SettingsTools.updatablePrimaryKeys;
import static org.jooq.conf.ThrowExceptions.THROW_FIRST;
import static org.jooq.conf.ThrowExceptions.THROW_NONE;
import static org.jooq.exception.DataAccessException.sqlStateClass;
import static org.jooq.impl.CacheType.REFLECTION_CACHE_GET_ANNOTATED_GETTER;
import static org.jooq.impl.CacheType.REFLECTION_CACHE_GET_ANNOTATED_MEMBERS;
import static org.jooq.impl.CacheType.REFLECTION_CACHE_GET_ANNOTATED_SETTERS;
import static org.jooq.impl.CacheType.REFLECTION_CACHE_GET_MATCHING_GETTER;
import static org.jooq.impl.CacheType.REFLECTION_CACHE_GET_MATCHING_MEMBERS;
import static org.jooq.impl.CacheType.REFLECTION_CACHE_GET_MATCHING_SETTERS;
import static org.jooq.impl.CacheType.REFLECTION_CACHE_HAS_COLUMN_ANNOTATIONS;
import static org.jooq.impl.Convert.convert;
import static org.jooq.impl.DDLStatementType.ALTER_SCHEMA;
import static org.jooq.impl.DDLStatementType.ALTER_TABLE;
import static org.jooq.impl.DDLStatementType.ALTER_VIEW;
import static org.jooq.impl.DDLStatementType.CREATE_DATABASE;
import static org.jooq.impl.DDLStatementType.CREATE_DOMAIN;
import static org.jooq.impl.DDLStatementType.CREATE_INDEX;
import static org.jooq.impl.DDLStatementType.CREATE_SCHEMA;
import static org.jooq.impl.DDLStatementType.CREATE_SEQUENCE;
import static org.jooq.impl.DDLStatementType.CREATE_TABLE;
import static org.jooq.impl.DDLStatementType.CREATE_VIEW;
import static org.jooq.impl.DDLStatementType.DROP_INDEX;
import static org.jooq.impl.DDLStatementType.DROP_SCHEMA;
import static org.jooq.impl.DDLStatementType.DROP_SEQUENCE;
import static org.jooq.impl.DDLStatementType.DROP_TABLE;
import static org.jooq.impl.DDLStatementType.DROP_VIEW;
import static org.jooq.impl.DSL.all;
import static org.jooq.impl.DSL.any;
import static org.jooq.impl.DSL.asterisk;
import static org.jooq.impl.DSL.concat;
import static org.jooq.impl.DSL.escape;
import static org.jooq.impl.DSL.getDataType;
import static org.jooq.impl.DSL.keyword;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.DSL.noCondition;
import static org.jooq.impl.DSL.row;
import static org.jooq.impl.DSL.select;
import static org.jooq.impl.DSL.unquotedName;
import static org.jooq.impl.DSL.val;
import static org.jooq.impl.DefaultExecuteContext.localConnection;
import static org.jooq.impl.DefaultParseContext.SUPPORTS_HASH_COMMENT_SYNTAX;
import static org.jooq.impl.DerivedTable.NO_SUPPORT_CORRELATED_DERIVED_TABLE;
import static org.jooq.impl.Identifiers.QUOTES;
import static org.jooq.impl.Identifiers.QUOTE_END_DELIMITER;
import static org.jooq.impl.Identifiers.QUOTE_END_DELIMITER_ESCAPED;
import static org.jooq.impl.Identifiers.QUOTE_START_DELIMITER;
import static org.jooq.impl.Keywords.K_ALWAYS;
import static org.jooq.impl.Keywords.K_AS;
import static org.jooq.impl.Keywords.K_ATOMIC;
import static org.jooq.impl.Keywords.K_AUTOINCREMENT;
import static org.jooq.impl.Keywords.K_AUTO_INCREMENT;
import static org.jooq.impl.Keywords.K_BEGIN;
import static org.jooq.impl.Keywords.K_BEGIN_CATCH;
import static org.jooq.impl.Keywords.K_BEGIN_TRY;
import static org.jooq.impl.Keywords.K_BY;
import static org.jooq.impl.Keywords.K_CHARACTER_SET;
import static org.jooq.impl.Keywords.K_COLLATE;
import static org.jooq.impl.Keywords.K_DECLARE;
import static org.jooq.impl.Keywords.K_DEFAULT;
import static org.jooq.impl.Keywords.K_DO;
import static org.jooq.impl.Keywords.K_ELSE;
import static org.jooq.impl.Keywords.K_ELSIF;
import static org.jooq.impl.Keywords.K_END;
import static org.jooq.impl.Keywords.K_END_CATCH;
import static org.jooq.impl.Keywords.K_END_IF;
import static org.jooq.impl.Keywords.K_END_TRY;
import static org.jooq.impl.Keywords.K_ENUM;
import static org.jooq.impl.Keywords.K_ERROR;
import static org.jooq.impl.Keywords.K_EXCEPTION;
import static org.jooq.impl.Keywords.K_EXEC;
import static org.jooq.impl.Keywords.K_EXECUTE_BLOCK;
import static org.jooq.impl.Keywords.K_EXECUTE_IMMEDIATE;
import static org.jooq.impl.Keywords.K_EXECUTE_STATEMENT;
import static org.jooq.impl.Keywords.K_GENERATED;
import static org.jooq.impl.Keywords.K_IDENTITY;
import static org.jooq.impl.Keywords.K_IF;
import static org.jooq.impl.Keywords.K_INT;
import static org.jooq.impl.Keywords.K_LIKE;
import static org.jooq.impl.Keywords.K_NOT;
import static org.jooq.impl.Keywords.K_NOT_NULL;
import static org.jooq.impl.Keywords.K_NULL;
import static org.jooq.impl.Keywords.K_NVARCHAR;
import static org.jooq.impl.Keywords.K_PERSISTED;
import static org.jooq.impl.Keywords.K_PRIMARY_KEY;
import static org.jooq.impl.Keywords.K_RAISE;
import static org.jooq.impl.Keywords.K_RAISERROR;
import static org.jooq.impl.Keywords.K_SERIAL;
import static org.jooq.impl.Keywords.K_SERIAL4;
import static org.jooq.impl.Keywords.K_SERIAL8;
import static org.jooq.impl.Keywords.K_SQLSTATE;
import static org.jooq.impl.Keywords.K_START_WITH;
import static org.jooq.impl.Keywords.K_STORED;
import static org.jooq.impl.Keywords.K_THEN;
import static org.jooq.impl.Keywords.K_THROW;
import static org.jooq.impl.Keywords.K_VIRTUAL;
import static org.jooq.impl.Keywords.K_WHEN;
import static org.jooq.impl.QOM.GenerationOption.STORED;
import static org.jooq.impl.QOM.GenerationOption.VIRTUAL;
import static org.jooq.impl.SQLDataType.BLOB;
import static org.jooq.impl.SQLDataType.CLOB;
import static org.jooq.impl.SQLDataType.DECIMAL;
import static org.jooq.impl.SQLDataType.DOUBLE;
import static org.jooq.impl.SQLDataType.INTEGER;
import static org.jooq.impl.SQLDataType.JSON;
import static org.jooq.impl.SQLDataType.JSONB;
import static org.jooq.impl.SQLDataType.OTHER;
import static org.jooq.impl.SQLDataType.SMALLINT;
import static org.jooq.impl.SQLDataType.VARCHAR;
import static org.jooq.impl.SQLDataType.XML;
import static org.jooq.impl.SubqueryCharacteristics.DERIVED_TABLE;
import static org.jooq.impl.SubqueryCharacteristics.PREDICAND;
import static org.jooq.impl.SubqueryCharacteristics.SET_OPERATION;
import static org.jooq.impl.Tools.executeImmediate;
import static org.jooq.impl.Tools.SimpleDataKey.DATA_BLOCK_NESTING;
import static org.jooq.tools.StringUtils.defaultIfNull;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinPool.ManagedBlocker;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import java.util.stream.Stream;
// ...
// ...
// ...
import org.jooq.Asterisk;
import org.jooq.Attachable;
import org.jooq.BindContext;
import org.jooq.Catalog;
import org.jooq.Check;
import org.jooq.Clause;
import org.jooq.CommonTableExpression;
import org.jooq.Condition;
import org.jooq.Configuration;
import org.jooq.Context;
import org.jooq.ContextConverter;
import org.jooq.Converter;
import org.jooq.ConverterContext;
import org.jooq.ConverterProvider;
import org.jooq.Converters;
import org.jooq.Cursor;
import org.jooq.DSLContext;
import org.jooq.DataType;
import org.jooq.EmbeddableRecord;
import org.jooq.EnumType;
import org.jooq.ExecuteContext;
import org.jooq.ExecuteListener;
import org.jooq.Field;
import org.jooq.FieldOrRow;
import org.jooq.FieldOrRowOrSelect;
import org.jooq.Fields;
import org.jooq.ForeignKey;
import org.jooq.Function1;
import org.jooq.Function2;
import org.jooq.Function3;
import org.jooq.Generator;
import org.jooq.JSON;
import org.jooq.JSONB;
import org.jooq.JSONEntry;
import org.jooq.JoinType;
import org.jooq.Name;
import org.jooq.OrderField;
import org.jooq.Param;
// ...
import org.jooq.QualifiedAsterisk;
import org.jooq.QualifiedRecord;
import org.jooq.QuantifiedSelect;
import org.jooq.Query;
import org.jooq.QueryPart;
import org.jooq.Record;
import org.jooq.Record1;
import org.jooq.RecordQualifier;
import org.jooq.RenderContext;
import org.jooq.RenderContext.CastMode;
import org.jooq.Result;
import org.jooq.ResultOrRows;
import org.jooq.ResultQuery;
import org.jooq.Results;
import org.jooq.Row;
import org.jooq.SQLDialect;
import org.jooq.Schema;
import org.jooq.Scope;
import org.jooq.Select;
import org.jooq.SelectFieldOrAsterisk;
import org.jooq.SortField;
import org.jooq.Source;
import org.jooq.Table;
import org.jooq.TableElement;
import org.jooq.TableField;
import org.jooq.TableRecord;
import org.jooq.UDT;
import org.jooq.UDTRecord;
import org.jooq.UpdatableRecord;
import org.jooq.WindowSpecification;
import org.jooq.XML;
import org.jooq.conf.BackslashEscaping;
import org.jooq.conf.NestedCollectionEmulation;
import org.jooq.conf.ParamType;
import org.jooq.conf.ParseNameCase;
import org.jooq.conf.RenderDefaultNullability;
import org.jooq.conf.RenderMapping;
import org.jooq.conf.RenderQuotedNames;
import org.jooq.conf.Settings;
import org.jooq.conf.SettingsTools;
import org.jooq.conf.StatementType;
import org.jooq.conf.ThrowExceptions;
import org.jooq.exception.DataAccessException;
import org.jooq.exception.DataException;
import org.jooq.exception.DataTypeException;
import org.jooq.exception.DetachedException;
import org.jooq.exception.ExceptionTools;
import org.jooq.exception.IntegrityConstraintViolationException;
import org.jooq.exception.MappingException;
import org.jooq.exception.NoDataFoundException;
import org.jooq.exception.SQLStateClass;
import org.jooq.exception.TemplatingException;
import org.jooq.exception.TooManyRowsException;
import org.jooq.impl.QOM.Quantifier;
import org.jooq.impl.QOM.UEmpty;
import org.jooq.impl.ResultsImpl.ResultOrRowsImpl;
import org.jooq.tools.Ints;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.StringUtils;
import org.jooq.tools.jdbc.JDBCUtils;
import org.jooq.tools.reflect.Reflect;
import org.jooq.tools.reflect.ReflectException;
import org.jooq.types.UByte;
import org.jooq.types.UInteger;
import org.jooq.types.ULong;
import org.jooq.types.UShort;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import io.r2dbc.spi.R2dbcException;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
/**
* General internal jOOQ utilities
*
* @author Lukas Eder
*/
final class Tools {
static final JooqLogger log = JooqLogger.getLogger(Tools.class);
// ------------------------------------------------------------------------
// Empty arrays for use with Collection.toArray()
// ------------------------------------------------------------------------
static final byte[] EMPTY_BYTE = {};
static final Catalog[] EMPTY_CATALOG = {};
static final Check>[] EMPTY_CHECK = {};
static final Clause[] EMPTY_CLAUSE = {};
static final Collection>[] EMPTY_COLLECTION = {};
static final CommonTableExpression>[] EMPTY_COMMON_TABLE_EXPRESSION = {};
static final ExecuteListener[] EMPTY_EXECUTE_LISTENER = {};
static final Field>[] EMPTY_FIELD = {};
static final FieldOrRow[] EMPTY_FIELD_OR_ROW = {};
static final int[] EMPTY_INT = {};
static final JSONEntry>[] EMPTY_JSONENTRY = {};
static final Name[] EMPTY_NAME = {};
static final Object[] EMPTY_OBJECT = {};
static final Param>[] EMPTY_PARAM = {};
static final OrderField>[] EMPTY_ORDERFIELD = {};
static final Query[] EMPTY_QUERY = {};
static final QueryPart[] EMPTY_QUERYPART = {};
static final Record[] EMPTY_RECORD = {};
static final Row[] EMPTY_ROW = {};
static final Schema[] EMTPY_SCHEMA = {};
static final SortField>[] EMPTY_SORTFIELD = {};
static final Source[] EMPTY_SOURCE = {};
static final String[] EMPTY_STRING = {};
static final Table>[] EMPTY_TABLE = {};
static final TableField, ?>[] EMPTY_TABLE_FIELD = {};
static final TableRecord>[] EMPTY_TABLE_RECORD = {};
static final UpdatableRecord>[] EMPTY_UPDATABLE_RECORD = {};
// ------------------------------------------------------------------------
// Some constants for use with Context.data()
// ------------------------------------------------------------------------
/**
* A common super types for {@link BooleanDataKey}, {@link SimpleDataKey} and {@link ExtendedDataKey}
*/
sealed interface DataKey {
/**
* Whether this data key resets itself to {@link #resetValue()} when
* entering in a new scope of depth {@link #resetThreshold()}.
*/
boolean resetInSubqueryScope();
/**
* The value to reset itself to.
*/
Object resetValue();
/**
* The depth after which the key resets itself.
*/
int resetThreshold();
}
/**
* Keys for {@link Configuration#data()}, which may be referenced frequently
* and represent a {@code boolean} value and are thus stored in an
* {@link EnumSet} for speedier access.
*/
enum BooleanDataKey implements DataKey {
/**
* [#13468] The WHERE clause in a SELECT is mandatory for the current
* scope.
*/
DATA_MANDATORY_WHERE_CLAUSE(true, null, 1),
/**
* [#1520] Count the number of bind values, and potentially enforce a
* static statement.
*/
DATA_COUNT_BIND_VALUES,
/**
* [#1520] Enforce executing static statements.
*
* Some SQL dialects support only a limited amount of bind variables. This
* flag is set when static statements have too many bind variables. Known
* values are:
*
* - {@link SQLDialect#ACCESS} : 768
* - {@link SQLDialect#ASE} : 2000
* - {@link SQLDialect#INGRES} : 1024
* - {@link SQLDialect#ORACLE} : 32767
* - {@link SQLDialect#POSTGRES} : 32767
* - {@link SQLDialect#SQLITE} : 999
* - {@link SQLDialect#SQLSERVER} : 2100
* - {@link org.jooq.SQLDialect#TERADATA} : 2536
*
*/
DATA_FORCE_STATIC_STATEMENT,
/**
* [#2665] Omit the emission of clause events by {@link QueryPart}s.
*
* Some {@link QueryPart}s may contain further {@link QueryPart}s for whom
* {@link Clause} emission should be avoided. For example
* {@link Clause#FIELD_REFERENCE} may contain a
* {@link Clause#TABLE_REFERENCE}.
*/
DATA_OMIT_CLAUSE_EVENT_EMISSION,
/**
* [#2665] Wrap derived tables in parentheses.
*
* Before allowing for hooking into the SQL transformation SPI, new
* {@link RenderContext} instances could be created to "try" to render a
* given SQL subclause before inserting it into the real SQL string. This
* practice should no longer be pursued, as such "sub-renderers" will emit /
* divert {@link Clause} events.
*/
DATA_WRAP_DERIVED_TABLES_IN_PARENTHESES,
/**
* [#1905] This constant is used internally by jOOQ to indicate to
* subqueries that they're being rendered in the context of a row value
* expression predicate.
*/
DATA_ROW_VALUE_EXPRESSION_PREDICATE_SUBQUERY,
/**
* [#1629] The {@link Connection#getAutoCommit()} flag value before starting
* a new transaction.
*/
DATA_DEFAULT_TRANSACTION_PROVIDER_AUTOCOMMIT,
/**
* [#2080] When emulating OFFSET pagination in certain databases, synthetic
* aliases are generated that must be referenced also in
* ORDER BY
clauses, in lieu of their corresponding original
* aliases.
* [#8898] Oracle doesn't support aliases in RETURNING clauses.
*/
DATA_UNALIAS_ALIASED_EXPRESSIONS,
/**
* [#7139] No data must be selected in the SELECT
statement.
*/
DATA_SELECT_NO_DATA(true, null, 1),
/**
* [#3381] Omit the {@link Clause#SELECT_INTO}, as it is being emulated.
*/
DATA_OMIT_INTO_CLAUSE,
/**
* [#1658] Specify whether the trailing LIMIT clause needs to be rendered.
*/
DATA_RENDER_TRAILING_LIMIT_IF_APPLICABLE,
/**
* [#13509] In some cases, it may be desirable to enforce appending a
* LIMIT
clause, when there's an ORDER BY
* clause, e.g. to prevent the optimiser from removing the seemingly
* unnecessary sort.
*/
DATA_FORCE_LIMIT_WITH_ORDER_BY,
/**
* [#3886] Whether a list has already been indented.
*/
DATA_LIST_ALREADY_INDENTED,
/**
* [#3338] [#5086] Whether a constraint is being referenced (rather than
* declared).
*/
DATA_CONSTRAINT_REFERENCE,
/**
* [#1206] Whether to collect Semi / Anti JOIN.
*/
DATA_COLLECT_SEMI_ANTI_JOIN,
/**
* [#11486] An INSERT … SELECT
statement.
*/
DATA_INSERT_SELECT,
/**
* [#2995] An INSERT INTO t SELECT
statement. Without any
* explicit column list, the SELECT
statement must not be
* wrapped in parentheses (which would be interpreted as the column
* list's parentheses).
*/
DATA_INSERT_SELECT_WITHOUT_INSERT_COLUMN_LIST,
/**
* [#3579] [#6431] [#7222] There are nested set operations in the current
* {@link Select} scope.
*/
DATA_NESTED_SET_OPERATIONS,
/**
* [#9925] In some cases the AS
keyword is required for
* aliasing, e.g. XML.
*/
DATA_AS_REQUIRED(true, null, 0),
/**
* [#12030] MULTISET conditions need to render the MULTISET emulation
* differently to implement MULTISET semantics (ORDER agnostic)
*/
DATA_MULTISET_CONDITION,
/**
* [#12021] MULTISET content may need to be rendered differently (e.g.
* nested ROW
types).
*/
DATA_MULTISET_CONTENT,
/**
* [#15991] ROW content may need to be rendered differently (e.g.
* nested ROW
types).
*/
DATA_ROW_CONTENT,
/**
* [#12072] In some cases, it's recommended to generate an explicit
* ELSE NULL
clause in a CASE
expression.
*/
DATA_FORCE_CASE_ELSE_NULL,
/**
* [#12092] Whether the @@group_concat_max_len value has already been
* set.
*/
DATA_GROUP_CONCAT_MAX_LEN_SET,
/**
* [#11543] Whether the @@innodb_lock_wait_timeout value has already
* been set.
*/
DATA_LOCK_WAIT_TIMEOUT_SET,
/**
* [#13573] We're parsing the ON CONFLICT
clause, in which
* the VALUES()
function or EXCLUDED
pseudo
* table have a special semantics.
*/
DATA_PARSE_ON_CONFLICT,
/**
* [#228] [#13808] We're in a store assignment context.
*
* This includes e.g.
*
* INSERT
columns list.
* UPDATE … SET
clause.
* - The procedural assignment statement.
*
*/
DATA_STORE_ASSIGNMENT,
/**
* [#14985] We're in a context where implicit joins are being rendered,
* not the query itself.
*/
DATA_RENDER_IMPLICIT_JOIN,
/**
* [#17088] Tell the {@link RenderContext} that we're rendering for
* R2DBC.
*/
DATA_RENDER_FOR_R2DBC
;
private final boolean resetInSubqueryScope;
private final Object resetValue;
private final int resetThreshold;
private BooleanDataKey() {
this(false, null, 0);
}
private BooleanDataKey(boolean resetInSubqueryScope, Object resetValue, int resetThreshold) {
this.resetInSubqueryScope = resetInSubqueryScope;
this.resetValue = resetValue;
this.resetThreshold = resetThreshold;
}
@Override
public final boolean resetInSubqueryScope() {
return resetInSubqueryScope;
}
@Override
public final Object resetValue() {
return resetValue;
}
@Override
public final int resetThreshold() {
return resetThreshold;
}
}
/**
* Keys for {@link Configuration#data()}, which may be referenced frequently
* and are thus stored in an {@link EnumMap} for speedier access.
*/
enum SimpleDataKey implements DataKey {
/**
* The level of anonymous block nesting, in case we're generating a block.
*/
DATA_BLOCK_NESTING,
/**
* [#2744] Currently rendering the data change delta table syntax.
*
* In some dialects, a FINAL TABLE (INSERT …)
clause exists, which
* corresponds to the PostgreSQL INSERT … RETURNING
clause.
*/
DATA_RENDERING_DATA_CHANGE_DELTA_TABLE,
/**
* [#531] The local window definitions.
*
* The window definitions declared in the WINDOW
clause are
* needed in the SELECT
clause when emulating them by inlining
* window specifications.
*/
DATA_WINDOW_DEFINITIONS(true, null, 0),
/**
* [#1629] The {@link Connection#getAutoCommit()} flag value before starting
* a new transaction.
*/
DATA_DEFAULT_TRANSACTION_PROVIDER_SAVEPOINTS,
/**
* [#1629] The {@link DefaultConnectionProvider} instance to be used during
* the transaction.
*/
DATA_DEFAULT_TRANSACTION_PROVIDER_CONNECTION,
/**
* [#2080] When emulating OFFSET pagination in certain databases, synthetic
* aliases are generated that must be referenced also in
* ORDER BY
clauses, in lieu of their corresponding original
* aliases.
*/
DATA_OVERRIDE_ALIASES_IN_ORDER_BY,
/**
* [#3381] The table to be used for the {@link Clause#SELECT_INTO} clause.
*/
DATA_SELECT_INTO_TABLE,
/**
* [#1206] The collected Semi / Anti JOIN predicates.
*/
DATA_COLLECTED_SEMI_ANTI_JOIN,
/**
* [#5764] Sometimes, it is necessary to prepend some SQL to the
* generated SQL.
*
* This needs to be done e.g. to emulate inline table valued parameters
* in SQL Server:
*
*
* -- With TVP bind variable:
* SELECT * FROM func (?)
*
* -- Inlining TVP bind variable:
* DECLARE @t TABLE_TYPE;
* INSERT INTO @t VALUES (?),(?),...,(?);
* SELECT * FROM func (@t)
*
*/
DATA_PREPEND_SQL,
/**
* [#12092] Sometimes, it is necessary to append some SQL to the
* generated SQL.
*
* This needs to be done e.g. to make sure the
* MySQL @@group_concat_max_len setting is set to an appropriate value,
* and reset to the previous value again.
*
*
* SET @t = @@group_concat_max_len;
* SET @@group_concat_max_len = 4294967295;
* SELECT group_concat(...);
* SET @@group_concat_max_len = @t;
*
*/
DATA_APPEND_SQL,
/**
* [#6583] The target table on which a DML operation operates on.
*/
DATA_DML_TARGET_TABLE,
/**
* [#6583] [#14742] The target table on which a DML operation operates on.
*/
DATA_DML_USING_TABLES,
/**
* [#8479] There is a WHERE clause to be emulated for ON DUPLICATE KEY
*/
DATA_ON_DUPLICATE_KEY_WHERE,
/**
* [#3607] [#8522] CTEs that need to be added to the top level CTE
* section.
*/
DATA_TOP_LEVEL_CTE,
/**
* [#10540] Aliases to be applied to the current SELECT
* statement.
*/
DATA_SELECT_ALIASES,
;
private final boolean resetInSubqueryScope;
private final Object resetValue;
private final int resetThreshold;
private SimpleDataKey() {
this(false, null, 0);
}
private SimpleDataKey(boolean resetInSubqueryScope, Object resetValue, int resetThreshold) {
this.resetInSubqueryScope = resetInSubqueryScope;
this.resetValue = resetValue;
this.resetThreshold = resetThreshold;
}
@Override
public final boolean resetInSubqueryScope() {
return resetInSubqueryScope;
}
@Override
public final Object resetValue() {
return resetValue;
}
@Override
public final int resetThreshold() {
return resetThreshold;
}
}
/**
* Keys for {@link Configuration#data()}, which may be referenced very
* infrequently and are thus stored in an ordinary {@link HashMap} for a
* more optimal memory layout.
*/
enum ExtendedDataKey implements DataKey {
/**
* [#4498] [#7552] The original INSERT ON DUPLICATE KEY UPDATE query
* that produced a MERGE statement for its emulation.
*/
DATA_INSERT_ON_DUPLICATE_KEY_UPDATE,
/**
* [#9017] We've already transformed ROWNUM expressions to LIMIT.
*/
DATA_TRANSFORM_ROWNUM_TO_LIMIT,
/**
* [#1535] [#11851] The window function object that uses a
* {@link WindowSpecification}.
*/
DATA_WINDOW_FUNCTION,
/**
* [#8893] Whether {@link TableField} should be qualified with their
* tables when rendering in the current scope.
*/
DATA_RENDER_TABLE(true, null, 0),
/**
* [#15982] The base type of an empty array in the current scope.
*/
DATA_EMPTY_ARRAY_BASE_TYPE
;
private final boolean resetInSubqueryScope;
private final Object resetValue;
private final int resetThreshold;
private ExtendedDataKey() {
this(false, null, 0);
}
private ExtendedDataKey(boolean resetInSubqueryScope, Object resetValue, int resetThreshold) {
this.resetInSubqueryScope = resetInSubqueryScope;
this.resetValue = resetValue;
this.resetThreshold = resetThreshold;
}
@Override
public final boolean resetInSubqueryScope() {
return resetInSubqueryScope;
}
@Override
public final Object resetValue() {
return resetValue;
}
@Override
public final int resetThreshold() {
return resetThreshold;
}
}
static final DataKey[] DATAKEY_RESET_IN_SUBQUERY_SCOPE;
static {
DATAKEY_RESET_IN_SUBQUERY_SCOPE = Stream
.concat(
Stream.concat(Stream.of(BooleanDataKey.values()), Stream.of(SimpleDataKey.values())),
Stream.of(ExtendedDataKey.values()))
.filter(t -> t.resetInSubqueryScope())
.toArray(DataKey[]::new);
}
// ------------------------------------------------------------------------
// Other constants
// ------------------------------------------------------------------------
/**
* The default escape character for [a] LIKE [b] ESCAPE […]
* clauses.
*/
static final char ESCAPE = '!';
/**
* A lock for the initialisation of other static members
*/
private static final Object initLock = new Object();
/**
* Indicating whether JPA (jakarta.persistence
) is on the
* classpath.
*/
private static volatile JPANamespace jpaNamespace;
/**
* Indicating whether Kotlin (kotlin.*
) is on the classpath.
*/
private static volatile Boolean isKotlinAvailable;
private static volatile Reflect ktJvmClassMapping;
private static volatile Reflect ktKClasses;
private static volatile Reflect ktKClass;
private static volatile Reflect ktKTypeParameter;
/**
* [#3696] The maximum number of consumed exceptions in
* {@link #consumeExceptions(Configuration, PreparedStatement, SQLException)}
* helps prevent infinite loops and {@link OutOfMemoryError}.
*/
static int maxConsumedExceptions = 256;
static int maxConsumedResults = 65536;
/**
* A pattern for the dash line syntax
*/
private static final Pattern DASH_PATTERN = Pattern.compile("(-+)");
/**
* A pattern for the pipe line syntax
*/
private static final Pattern PIPE_PATTERN = Pattern.compile("(?<=\\|)([^|]+)(?=\\|)");
/**
* A pattern for the dash line syntax
*/
private static final Pattern PLUS_PATTERN = Pattern.compile("\\+(-+)(?=\\+)");
/**
* All characters that are matched by Java's interpretation of \s.
*
* For a more accurate set of whitespaces, refer to
* http://stackoverflow.com/a/4731164/521799. In the event of SQL
* processing, it is probably safe to ignore most of those alternative
* Unicode whitespaces.
*/
private static final char[] WHITESPACE_CHARACTERS = " \t\n\u000B\f\r".toCharArray();
/**
* Acceptable prefixes for JDBC escape syntax.
*/
private static final char[][] JDBC_ESCAPE_PREFIXES = {
"{fn ".toCharArray(),
"{d ".toCharArray(),
"{t ".toCharArray(),
"{ts ".toCharArray()
};
private static final char[] TOKEN_SINGLE_LINE_COMMENT = { '-', '-' };
private static final char[] TOKEN_SINGLE_LINE_COMMENT_C = { '/', '/' };
private static final char[] TOKEN_HASH = { '#' };
private static final char[] TOKEN_MULTI_LINE_COMMENT_OPEN = { '/', '*' };
private static final char[] TOKEN_MULTI_LINE_COMMENT_CLOSE = { '*', '/' };
private static final char[] TOKEN_APOS = { '\'' };
private static final char[] TOKEN_ESCAPED_APOS = { '\'', '\'' };
/**
* "Suffixes" that are placed behind a "?" character to form an operator,
* rather than a JDBC bind variable. This is particularly useful to prevent
* parsing PostgreSQL operators as bind variables, as can be seen here:
* https://www.postgresql.org/docs/current/static/functions-json.html,
* https://www.postgresql.org/docs/current/static/ltree.html,
* https://www.postgresql.org/docs/current/static/functions-geometry.html.
*
* [#5307] Known PostgreSQL JSON operators:
*
* - ?|
* - ?&
*
*
* [#7035] Known PostgreSQL LTREE operators:
*
* - ? (we cannot handle this one)
* - ?@>
* - ?<@
* - ?~
* - ?@
*
*
* [#7037] Known PostgreSQL Geometry operators:
*
* - ?#
* - ?-
* - ?|
*
*/
private static final char[][] NON_BIND_VARIABLE_SUFFIXES = {
{ '?' },
{ '|' },
{ '&' },
{ '@' },
{ '<' },
{ '~' },
{ '#' },
{ '-' }
};
/**
* "Suffixes" that are placed behind a "?" character to form a JDBC bind
* variable, rather than an operator.
*
* [#11442] The above NON_BIND_VARIABLE_SUFFIXES leads to false positives,
* such as "?<>"
, which is a non-equality operator, not
* an operator on its own.
*/
private static final char[][] BIND_VARIABLE_SUFFIXES = {
{ '<', '>' }
};
/**
* All hexadecimal digits accessible through array index, e.g.
* HEX_DIGITS[15] == 'f'
.
*/
private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
private static final byte[] HEX_LOOKUP = {
/* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 0x20 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 0x30 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
/* 0x40 */ 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 0x50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 0x60 */ 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
static final Set REQUIRES_BACKSLASH_ESCAPING = SQLDialect.supportedBy(MARIADB, MYSQL);
static final Set NO_SUPPORT_NULL = SQLDialect.supportedBy(DERBY, FIREBIRD, H2, HSQLDB, TRINO);
static final Set NO_SUPPORT_NOT_NULL = SQLDialect.supportedBy(TRINO);
static final Set NO_SUPPORT_BINARY_TYPE_LENGTH = SQLDialect.supportedBy(POSTGRES, TRINO, YUGABYTEDB);
static final Set NO_SUPPORT_CAST_TYPE_IN_DDL = SQLDialect.supportedBy(MARIADB, MYSQL);
static final Set SUPPORT_NON_BIND_VARIABLE_SUFFIXES = SQLDialect.supportedBy(POSTGRES, YUGABYTEDB);
static final Set SUPPORT_POSTGRES_LITERALS = SQLDialect.supportedBy(POSTGRES, YUGABYTEDB);
static final Set DEFAULT_BEFORE_NULL = SQLDialect.supportedBy(FIREBIRD, HSQLDB);
static final Set NO_SUPPORT_TIMESTAMP_PRECISION = SQLDialect.supportedBy(DERBY, FIREBIRD);
static final Set NO_SUPPORT_TIME_PRECISION = SQLDialect.supportedBy(DERBY, FIREBIRD);
static final Set DEFAULT_TIMESTAMP_NOT_NULL = SQLDialect.supportedBy(MARIADB);
static final Set REQUIRES_PARENTHESISED_DEFAULT = SQLDialect.supportedBy(SQLITE);
static final Set REQUIRES_PARENTHESISED_DEFAULT_FOR_LOBS = SQLDialect.supportedBy(MYSQL);
// ------------------------------------------------------------------------
// XXX: Record constructors and related methods
// ------------------------------------------------------------------------
/**
* Turn a {@link Result} into a list of {@link Row}
*/
static final List rows(Result> result) {
return map(result, r -> r.valuesRow());
}
/**
* Create a new record
*/
static final RecordDelegate newRecord(boolean fetched, Class type) {
return newRecord(fetched, type, null);
}
/**
* Create a new record.
*/
static final RecordDelegate newRecord(boolean fetched, Class type, AbstractRow fields) {
return newRecord(fetched, type, fields, null);
}
/**
* Create a new {@link Table} or {@link UDT} record.
*/
static final RecordDelegate newRecord(boolean fetched, RecordQualifier type) {
return newRecord(fetched, type, null);
}
/**
* Create a new {@link Table} or {@link UDT} record.
*/
static final RecordDelegate newRecord(boolean fetched, RecordQualifier type, Configuration configuration) {
return newRecord(
fetched,
recordFactory(
type,
type.getRecordType(),
(AbstractRow) type.fieldsRow()
),
configuration
);
}
/**
* Create a new record.
*/
static final RecordDelegate newRecord(boolean fetched, Class extends R> type, AbstractRow extends R> fields, Configuration configuration) {
return newRecord(fetched, recordFactory(null, type, fields), configuration);
}
/**
* Create a new record.
*/
static final RecordDelegate newRecord(boolean fetched, Supplier factory, Configuration configuration) {
return new RecordDelegate<>(configuration, factory, fetched);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
static final AbstractRow row0(FieldsImpl fields) {
switch (fields.size()) {
case 1: return new RowImpl1(fields);
case 2: return new RowImpl2(fields);
case 3: return new RowImpl3(fields);
case 4: return new RowImpl4(fields);
case 5: return new RowImpl5(fields);
case 6: return new RowImpl6(fields);
case 7: return new RowImpl7(fields);
case 8: return new RowImpl8(fields);
case 9: return new RowImpl9(fields);
case 10: return new RowImpl10(fields);
case 11: return new RowImpl11(fields);
case 12: return new RowImpl12(fields);
case 13: return new RowImpl13(fields);
case 14: return new RowImpl14(fields);
case 15: return new RowImpl15(fields);
case 16: return new RowImpl16(fields);
case 17: return new RowImpl17(fields);
case 18: return new RowImpl18(fields);
case 19: return new RowImpl19(fields);
case 20: return new RowImpl20(fields);
case 21: return new RowImpl21(fields);
case 22: return new RowImpl22(fields);
default: return (AbstractRow) new RowImplN(fields);
}
}
static final AbstractRow> row0(Collection extends Field>> fields) {
return row0(fields.toArray(EMPTY_FIELD));
}
static final AbstractRow> row0(Field>... fields) {
return row0(new FieldsImpl<>(fields));
}
static final Class extends AbstractRecord> recordType(int length) {
switch (length) {
case 1: return RecordImpl1.class;
case 2: return RecordImpl2.class;
case 3: return RecordImpl3.class;
case 4: return RecordImpl4.class;
case 5: return RecordImpl5.class;
case 6: return RecordImpl6.class;
case 7: return RecordImpl7.class;
case 8: return RecordImpl8.class;
case 9: return RecordImpl9.class;
case 10: return RecordImpl10.class;
case 11: return RecordImpl11.class;
case 12: return RecordImpl12.class;
case 13: return RecordImpl13.class;
case 14: return RecordImpl14.class;
case 15: return RecordImpl15.class;
case 16: return RecordImpl16.class;
case 17: return RecordImpl17.class;
case 18: return RecordImpl18.class;
case 19: return RecordImpl19.class;
case 20: return RecordImpl20.class;
case 21: return RecordImpl21.class;
case 22: return RecordImpl22.class;
default: return RecordImplN.class;
}
}
/**
* Create a new record factory.
*/
@SuppressWarnings({ "unchecked" })
static final Supplier recordFactory(
RecordQualifier extends R> qualifier,
Class extends R> type,
AbstractRow extends R> row
) {
// An ad-hoc type resulting from a JOIN or arbitrary SELECT
if (type == AbstractRecord.class || type == Record.class || InternalRecord.class.isAssignableFrom(type)) {
switch (row.size()) {
case 1: return () -> (R) new RecordImpl1<>(row);
case 2: return () -> (R) new RecordImpl2<>(row);
case 3: return () -> (R) new RecordImpl3<>(row);
case 4: return () -> (R) new RecordImpl4<>(row);
case 5: return () -> (R) new RecordImpl5<>(row);
case 6: return () -> (R) new RecordImpl6<>(row);
case 7: return () -> (R) new RecordImpl7<>(row);
case 8: return () -> (R) new RecordImpl8<>(row);
case 9: return () -> (R) new RecordImpl9<>(row);
case 10: return () -> (R) new RecordImpl10<>(row);
case 11: return () -> (R) new RecordImpl11<>(row);
case 12: return () -> (R) new RecordImpl12<>(row);
case 13: return () -> (R) new RecordImpl13<>(row);
case 14: return () -> (R) new RecordImpl14<>(row);
case 15: return () -> (R) new RecordImpl15<>(row);
case 16: return () -> (R) new RecordImpl16<>(row);
case 17: return () -> (R) new RecordImpl17<>(row);
case 18: return () -> (R) new RecordImpl18<>(row);
case 19: return () -> (R) new RecordImpl19<>(row);
case 20: return () -> (R) new RecordImpl20<>(row);
case 21: return () -> (R) new RecordImpl21<>(row);
case 22: return () -> (R) new RecordImpl22<>(row);
default: return () -> (R) new RecordImplN(row);
}
}
// Any generated record
else {
try {
// [#919] Allow for accessing non-public constructors
final Constructor extends R> constructor = qualifier instanceof TableImpl extends R> t
? t.getRecordConstructor()
: Reflect.accessible(type.getDeclaredConstructor());
return () -> {
try {
return constructor.newInstance();
}
catch (Exception e) {
throw new IllegalStateException("Could not construct new record", e);
}
};
}
catch (Exception e) {
throw new IllegalStateException("Could not construct new record", e);
}
}
}
private static final JooqLogger logResetTouchedOnNotNull = JooqLogger.getLogger(Tools.class, "logResetTouchedOnNotNull", 5);
/**
* [#2700] [#3582] If a POJO attribute is NULL, but the column is NOT NULL
* then we should let the database apply DEFAULT values
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
static final void resetChangedOnNotNull(Record record) {
int size = record.size();
ConverterContext c = null;
for (int i = 0; i < size; i++) {
Field field = record.field(i);
// [#17224] Reset the touched value only if T typed value is null, not U type!
// [#17272] Read only converters (such as Field.convertFrom()) can't convert back to the T type
try {
if (!field.getDataType().nullable() &&
field.getConverter().toSupported() &&
scoped(field.getConverter()).to(record.get(i), c == null ? c = converterContext(record) : c) == null
)
record.changed(i, false);
}
catch (Exception e) {
logResetTouchedOnNotNull.warn(
"Exception in Converter",
"""
An exception when calling in {converter}.to(). If this converter doesn't support the to()
method, it is recommended to override Converter.toSupported() to prevent this call.
""".replace("{converter}", field.getConverter().getClass().getName()),
e
);
}
}
}
/**
* Get an attachable's configuration or a new {@link DefaultConfiguration}
* if null
.
*/
static final Configuration configurationOrThrow(Attachable attachable) {
if (attachable.configuration() == null)
throw new DetachedException("No configuration attached: " + attachable);
else
return configuration(attachable.configuration());
}
/**
* Get an attachable's configuration or a new {@link DefaultConfiguration}
* if null
.
*/
static final Configuration configuration(Attachable attachable) {
return configuration(attachable.configuration());
}
/**
* Get a configuration or a new {@link DefaultConfiguration} if
* null
.
*/
static final Configuration configuration(Configuration configuration) {
return configuration != null ? configuration : new DefaultConfiguration();
}
/**
* Get a configuration or a new {@link DefaultConfiguration} if
* null
.
*/
static final Configuration configuration(Scope scope) {
return configuration(scope != null ? scope.configuration() : null);
}
/**
* Get a converter from a {@link ConverterProvider} or null
if
* no converter could be provided.
*/
static final ContextConverter converter(Configuration configuration, T instance, Class tType, Class uType) {
Converter result = configuration(configuration).converterProvider().provide(tType, uType);
if (result == null)
result = CONFIG.get().converterProvider().provide(tType, uType);
// [#11823] [#12208] The new ad-hoc conversion API tries to avoid the Class literal
// meaning there are perfectly reasonable API usages when using MULTISET
// where we can't decide on a converter prior to having an actual result
// type - so, let's try again if we have the result value.
if (result == null && tType == Converters.UnknownType.class)
result = converter(configuration, instance, (Class) (instance == null ? Object.class : instance.getClass()), uType);
return result == null ? null : scoped(result);
}
/**
* Get a converter from a {@link ConverterProvider} or null
if
* no converter could be provided.
*/
static final ContextConverter converterOrFail(Configuration configuration, T instance, Class tType, Class uType) {
ContextConverter result = converter(configuration, instance, tType, uType);
if (result == null)
throw new DataTypeException("No Converter found for types " + tType.getName() + " and " + uType.getName());
return result;
}
/**
* Get a converter from a {@link ConverterProvider}.
*/
static final ContextConverter converterOrFail(Attachable attachable, T instance, Class tType, Class uType) {
return converterOrFail(configuration(attachable), instance, tType, uType);
}
/**
* Get a configuration's settings or default settings if the configuration
* is null
.
*/
static final Settings settings(Attachable attachable) {
return configuration(attachable).settings();
}
/**
* Get a configuration's settings or default settings if the configuration
* is null
.
*/
static final Settings settings(Configuration configuration) {
return configuration(configuration).settings();
}
static final T attach(Attachable attachable, Configuration configuration, Supplier supplier) {
Configuration previous = attachable.configuration();
try {
attachable.attach(configuration);
return supplier.get();
}
finally {
attachable.attach(previous);
}
}
static final boolean attachRecords(Configuration configuration) {
if (configuration != null) {
Settings settings = configuration.settings();
if (settings != null)
return !FALSE.equals(settings.isAttachRecords());
}
return true;
}
static final Field>[] fieldArray(Collection extends Field>> fields) {
return fields == null ? null : fields.toArray(EMPTY_FIELD);
}
// ------------------------------------------------------------------------
// XXX: Data-type related methods
// ------------------------------------------------------------------------
static final DataType>[] dataTypes(Class>[] types) {
return map(types, t -> t != null ? getDataType(t) : getDataType(Object.class), DataType[]::new);
}
// ------------------------------------------------------------------------
// XXX: General utility methods
// ------------------------------------------------------------------------
private static final int FIELD_NAME_CACHE_SIZE = 128;
private static final String[] FIELD_NAME_STRINGS;
private static final Name[] FIELD_NAMES;
static {
FIELD_NAME_STRINGS = IntStream.range(0, FIELD_NAME_CACHE_SIZE).mapToObj(Tools::fieldNameString0).toArray(String[]::new);
FIELD_NAMES = IntStream.range(0, FIELD_NAME_CACHE_SIZE).mapToObj(i -> name(FIELD_NAME_STRINGS[i])).toArray(Name[]::new);
}
static final SortField sortField(OrderField field) {
if (field instanceof SortField s)
return s;
else if (field instanceof Field f)
return f.sortDefault();
else
throw new IllegalArgumentException("Field not supported : " + field);
}
static final SortField>[] sortFields(OrderField>[] fields) {
if (fields instanceof SortField>[] s)
return s;
else
return map(fields, o -> sortField(o), SortField[]::new);
}
static final List> sortFields(Collection extends OrderField>> fields) {
return Tools.map(fields, (OrderField> o) -> sortField(o));
}
// TODO: Check if these field names are ever really needed, or if we can just use the C field names
private static final String fieldNameString0(int index) {
return "v" + index;
}
static final String fieldNameString(int index) {
return index < FIELD_NAME_CACHE_SIZE ? FIELD_NAME_STRINGS[index] : fieldNameString0(index);
}
static final Name fieldName(int index) {
return index < FIELD_NAME_CACHE_SIZE ? FIELD_NAMES[index] : name(fieldNameString0(index));
}
static final Name[] fieldNames(int length) {
Name[] result = new Name[length];
for (int i = 0; i < length; i++)
result[i] = fieldName(i);
return result;
}
static final String[] fieldNameStrings(int length) {
String[] result = new String[length];
for (int i = 0; i < length; i++)
result[i] = fieldNameString(i);
return result;
}
static final Field>[] fields(int length) {
return fields(length, SQLDataType.OTHER);
}
@SuppressWarnings("unchecked")
static final Field[] fields(int length, DataType type) {
Field[] result = new Field[length];
for (int i = 0; i < length; i++)
result[i] = DSL.field(fieldName(i), type);
return result;
}
static final boolean reference(Field> field) {
return field instanceof TableField
|| field instanceof SQLField && ((SQLField>) field).delegate.isName;
}
static final Field unqualified(Field field) {
return field instanceof TableField ? DSL.field(field.getUnqualifiedName(), field.getDataType()) : field;
}
static final SortField unqualified(SortField field) {
return field.$field(unqualified(field.$field()));
}
static final List> unaliasedFields(Collection extends Field>> fields) {
return map(fields, (f, i) -> DSL.field(fieldName(i), f.getDataType()).as(f));
}
static final ReferenceImpl aliasedKey(ForeignKey key, Table child, Table parent) {
// [#10603] [#5050] TODO: Solve aliasing constraints more generically
// [#8762] We can't dereference child.fields() or parent.fields() here yet, because this method is being called by
// the TableImpl constructor, meaning the fields are not initialised yet.
return new ReferenceImpl<>(
child,
key.getQualifiedName(),
Tools.fieldsByName(child, key.getFieldsArray()),
key.getKey(),
Tools.fieldsByName(parent, key.getKeyFieldsArray()),
key.enforced()
);
}
static final List> aliasedFields(Collection extends Field>> fields) {
return map(fields, (f, i) -> f.as(fieldName(i)));
}
static final List> fieldsByName(String[] fieldNames) {
return fieldsByName(null, fieldNames);
}
static final Field>[] fieldsByName(Name tableName, int length) {
Field>[] result = new Field[length];
if (tableName == null)
for (int i = 0; i < length; i++)
result[i] = DSL.field(fieldName(i));
else
for (int i = 0; i < length; i++)
result[i] = DSL.field(name(tableName, fieldName(i)));
return result;
}
@SuppressWarnings("unchecked")
static final TableField[] fieldsByName(Table tableName, Field>[] fieldNames) {
if (tableName == null)
return map(fieldNames, n -> (TableField) DSL.field(n.getUnqualifiedName(), n.getDataType()), TableField[]::new);
else
return map(fieldNames, n -> (TableField) DSL.field(tableName.getQualifiedName().append(n.getUnqualifiedName()), n.getDataType()), TableField[]::new);
}
static final List> fieldsByName(Name tableName, Name[] fieldNames) {
if (tableName == null)
return map(fieldNames, n -> DSL.field(n));
else
return map(fieldNames, n -> DSL.field(name(tableName, n)));
}
static final List> fieldsByName(String tableName, String[] fieldNames) {
if (StringUtils.isEmpty(tableName))
return map(fieldNames, n -> DSL.field(name(n)));
else
return map(fieldNames, n -> DSL.field(name(tableName, n)));
}
static final List> fieldsByName(Name[] names) {
return map(names, n -> DSL.field(n));
}
static final Name[] names(String[] names) {
return map(names, n -> DSL.name(n), Name[]::new);
}
static final List names(Collection> names) {
return map(names, n -> n instanceof Name name ? name : DSL.name(String.valueOf(n)));
}
static final String sanitiseName(Configuration configuration, String name) {
switch (configuration.family()) {
default:
return name;
}
}
static final List> jsonEntries(Field>[] entries) {
return Tools.map(entries, (Field> f) -> DSL.jsonEntry(f));
}
private static final IllegalArgumentException fieldExpected(Object value) {
return new IllegalArgumentException("Cannot interpret argument of type " + value.getClass() + " as a Field: " + value);
}
/**
* [#461] [#473] [#2597] [#8234] Some internals need a cast only if necessary.
*/
@SuppressWarnings("unchecked")
static final Field[] castAllIfNeeded(Field>[] fields, Class type) {
Field[] castFields = new Field[fields.length];
for (int i = 0; i < fields.length; i++)
castFields[i] = castIfNeeded(fields[i], type);
return castFields;
}
/**
* [#461] [#473] [#2597] [#8234] Some internals need a cast only if necessary.
*/
@SuppressWarnings("unchecked")
static final Field[] castAllIfNeeded(Field>[] fields, DataType type) {
Field[] castFields = new Field[fields.length];
for (int i = 0; i < fields.length; i++)
castFields[i] = castIfNeeded(fields[i], type);
return castFields;
}
/**
* [#461] [#473] [#2597] [#8234] Some internals need a cast only if necessary.
*/
@SuppressWarnings("unchecked")
static final Field castIfNeeded(Field> field, Class type) {
if (field.getType().equals(type))
return (Field) field;
else
return field.cast(type);
}
/**
* [#461] [#473] [#2597] [#8234] Some internals need a cast only if necessary.
*/
@SuppressWarnings("unchecked")
static final Field castIfNeeded(Field> field, DataType type) {
if (field.getDataType().equals(type))
return (Field) field;
else
return field.cast(type);
}
/**
* [#461] [#473] [#2597] [#8234] Some internals need a cast only if necessary.
*/
@SuppressWarnings("unchecked")
static final Field castIfNeeded(Field> field, Field type) {
if (field.getDataType().equals(type.getDataType()))
return (Field) field;
else
return field.cast(type);
}
static final Field castNullLiteralIfNeeded(Context> ctx, Field field) {
switch (ctx.family()) {
case HSQLDB:
case POSTGRES:
case YUGABYTEDB:
// [#16367] The NULL literal defaults to type TEXT in PostgreSQL.
// if we know the data type, we should cast it explicitly
if (ctx.subquery()) {
Field f = unalias(field);
if (isInlineVal1(ctx, f, v -> v == null)
&& !f.getDataType().isOther()
&& !f.getDataType().isString()
) {
Field cast = f.cast(f.getDataType());
return f == field ? cast : cast.as(field);
}
}
break;
}
return field;
}
// The following overloads help performance by avoiding runtime data type lookups
// ------------------------------------------------------------------------------
static final Param field(byte value) {
return val((Object) value, SQLDataType.TINYINT);
}
static final Param field(Byte value) {
return val((Object) value, SQLDataType.TINYINT);
}
static final Param field(UByte value) {
return val((Object) value, SQLDataType.TINYINTUNSIGNED);
}
static final Param field(short value) {
return val((Object) value, SQLDataType.SMALLINT);
}
static final Param field(Short value) {
return val((Object) value, SQLDataType.SMALLINT);
}
static final Param field(UShort value) {
return val((Object) value, SQLDataType.SMALLINTUNSIGNED);
}
static final Param field(int value) {
return val((Object) value, SQLDataType.INTEGER);
}
static final Param field(Integer value) {
return val((Object) value, SQLDataType.INTEGER);
}
static final Param field(UInteger value) {
return val((Object) value, SQLDataType.INTEGERUNSIGNED);
}
static final Param field(long value) {
return val((Object) value, SQLDataType.BIGINT);
}
static final Param field(Long value) {
return val((Object) value, SQLDataType.BIGINT);
}
static final Param field(ULong value) {
return val((Object) value, SQLDataType.BIGINTUNSIGNED);
}
static final Param field(float value) {
return val((Object) value, SQLDataType.REAL);
}
static final Param field(Float value) {
return val((Object) value, SQLDataType.REAL);
}
static final Param field(double value) {
return val((Object) value, SQLDataType.DOUBLE);
}
static final Param field(Double value) {
return val((Object) value, SQLDataType.DOUBLE);
}
static final Param field(boolean value) {
return val((Object) value, SQLDataType.BOOLEAN);
}
static final Param field(Boolean value) {
return val((Object) value, SQLDataType.BOOLEAN);
}
static final Param field(BigDecimal value) {
return val((Object) value, SQLDataType.DECIMAL);
}
static final Param field(BigInteger value) {
return val((Object) value, SQLDataType.DECIMAL_INTEGER);
}
static final Param field(byte[] value) {
return val((Object) value, SQLDataType.VARBINARY);
}
static final Param field(String value) {
return val((Object) value, SQLDataType.VARCHAR);
}
static final Param field(Date value) {
return val((Object) value, SQLDataType.DATE);
}
static final Param