/* * 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 * * http://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 * ASL 2.0 and offer limited warranties, support, maintenance, and commercial * database integrations. * * For more information, please visit: http://www.jooq.org/licenses * * * * * * * * * * * * * * * * */ 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.singletonList; import static java.util.stream.Collectors.joining; // ... // ... // ... // ... // ... // ... import static org.jooq.SQLDialect.DERBY; // ... 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.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.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.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.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.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.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.Tools.DataKey.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.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.Converter; 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.Fields; import org.jooq.ForeignKey; 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.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.SchemaMapping; 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.TableField; import org.jooq.TableRecord; import org.jooq.UDT; 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.ParseNameCase; import org.jooq.conf.RenderDefaultNullability; import org.jooq.conf.RenderQuotedNames; import org.jooq.conf.Settings; import org.jooq.conf.SettingsTools; import org.jooq.conf.ThrowExceptions; import org.jooq.exception.DataAccessException; import org.jooq.exception.DataTypeException; import org.jooq.exception.DetachedException; import org.jooq.exception.ExceptionTools; import org.jooq.exception.MappingException; import org.jooq.exception.NoDataFoundException; import org.jooq.exception.TemplatingException; import org.jooq.exception.TooManyRowsException; import org.jooq.impl.QOM.GenerationOption; 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() // ------------------------------------------------------------------------ /** * 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 { /** * [#1537] This constant is used internally by jOOQ to omit the RETURNING * clause in {@link DSLContext#batchStore(UpdatableRecord...)} calls for * {@link SQLDialect#POSTGRES}. */ DATA_OMIT_RETURNING_CLAUSE, /** * [#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: *

*/ 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, /** * [#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, /** * [#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, /** * [#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, /** * [#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, /** * [#5191] Whether INSERT RETURNING is being emulated for bulk insertions. */ DATA_EMULATE_BULK_INSERT_RETURNING, /** * [#9925] In some cases the AS keyword is required for * aliasing, e.g. XML. */ DATA_AS_REQUIRED, /** * [#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, /** * [#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 } /** * Keys for {@link Configuration#data()}, which may be referenced frequently * and are thus stored in an {@link EnumMap} for speedier access. */ enum DataKey { /** * The level of anonymous block nesting, in case we're generating a block. */ DATA_BLOCK_NESTING, /** * [#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, /** * [#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, /** * [#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, /** * [#8917] An internal schema mapping that overrides any user-defined * schema mappings. */ DATA_SCHEMA_MAPPING, } /** * 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 DataExtendedKey { /** * [#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, } // ------------------------------------------------------------------------ // 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: *

*

* [#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, HSQLDB); static final Set NO_SUPPORT_BINARY_TYPE_LENGTH = SQLDialect.supportedBy(POSTGRES, 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); static final Set DEFAULT_TIMESTAMP_NOT_NULL = SQLDialect.supportedBy(MARIADB); // ------------------------------------------------------------------------ // 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, type.getRecordType(), (AbstractRow) type.fieldsRow(), configuration); } /** * Create a new record. */ static final RecordDelegate newRecord(boolean fetched, Class type, AbstractRow fields, Configuration configuration) { return newRecord(fetched, recordFactory(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> fields) { return row0(fields.toArray(EMPTY_FIELD)); } static final AbstractRow row0(Field... fields) { return row0(new FieldsImpl<>(fields)); } static final Class 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(Class type, AbstractRow 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 constructor = 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); } } } /** * [#2700] [#3582] If a POJO attribute is NULL, but the column is NOT NULL * then we should let the database apply DEFAULT values */ static final void resetChangedOnNotNull(Record record) { int size = record.size(); for (int i = 0; i < size; i++) if (record.get(i) == null) if (!record.field(i).getDataType().nullable()) record.changed(i, false); } /** * 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 Converter converter(Configuration configuration, T instance, Class tType, Class uType) { Converter result = configuration(configuration).converterProvider().provide(tType, uType); if (result == null) result = CTX.configuration().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; } /** * Get a converter from a {@link ConverterProvider} or null if * no converter could be provided. */ static final Converter converterOrFail(Configuration configuration, T instance, Class tType, Class uType) { Converter 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 Converter 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> 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) return (SortField) field; else if (field instanceof Field) return ((Field) field).sortDefault(); else throw new IllegalArgumentException("Field not supported : " + field); } static final SortField[] sortFields(OrderField[] fields) { if (fields instanceof SortField[]) return (SortField[]) fields; else return map(fields, o -> sortField(o), SortField[]::new); } static final List> sortFields(Collection> fields) { return Tools.map(fields, (OrderField o) -> sortField(o)); } 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 Field unqualified(Field field) { return DSL.field(field.getUnqualifiedName(), field.getDataType()); } static final SortField unqualified(SortField field) { SortFieldImpl i = (SortFieldImpl) field; return i.transform(unqualified(i.getField())); } static final List> unaliasedFields(Collection> 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> fields) { return map(fields, (f, i) -> f.as(fieldName(i))); } static final Field[] 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 Field[] fieldsByName(Name tableName, Name[] fieldNames) { if (tableName == null) return map(fieldNames, n -> DSL.field(n), Field[]::new); else return map(fieldNames, n -> DSL.field(name(tableName, n)), Field[]::new); } static final Field[] fieldsByName(String tableName, String[] fieldNames) { if (StringUtils.isEmpty(tableName)) return map(fieldNames, n -> DSL.field(name(n)), Field[]::new); else return map(fieldNames, n -> DSL.field(name(tableName, n)), Field[]::new); } static final Field[] fieldsByName(Name[] names) { return map(names, n -> DSL.field(n), Field[]::new); } 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) n : DSL.name(String.valueOf(n))); } static final List> jsonEntries(Field[] entries) { return Tools.map(entries, 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 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); } /** * Be sure that a given object is a field. * * @param value The argument object * @return The argument object itself, if it is a {@link Field}, or a bind * value created from the argument object. */ @SuppressWarnings("unchecked") static final Field field(T value) { // Fields can be mixed with constant values if (value instanceof Field) return (Field) value; // [#6362] [#8220] Single-column selects can be considered fields, too else if (value instanceof Select && Tools.degree((Select) value) == 1) return DSL.field((Select>) value); // [#4771] Any other QueryPart type is not supported here else if (value instanceof QueryPart) throw fieldExpected(value); else return val(value); } // 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