/* * 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 org.jooq.SQLDialect.CUBRID; import static org.jooq.SQLDialect.DERBY; import static org.jooq.SQLDialect.FIREBIRD; 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.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.reflectionCaching; 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.DDLStatementType.ALTER_INDEX; import static org.jooq.impl.DDLStatementType.ALTER_SCHEMA; import static org.jooq.impl.DDLStatementType.ALTER_SEQUENCE; 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.jsonEntry; import static org.jooq.impl.DSL.keyword; import static org.jooq.impl.DSL.name; import static org.jooq.impl.DSL.row; import static org.jooq.impl.DSL.select; import static org.jooq.impl.DSL.val; import static org.jooq.impl.DefaultExecuteContext.localConnection; 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_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_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_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_BY_DEFAULT_AS_IDENTITY; 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_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_THEN; import static org.jooq.impl.Keywords.K_THROW; import static org.jooq.impl.Keywords.K_WHEN; import static org.jooq.impl.ParserContext.SUPPORTS_HASH_COMMENT_SYNTAX; import static org.jooq.impl.SQLDataType.BLOB; import static org.jooq.impl.SQLDataType.CLOB; 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.VARCHAR; import static org.jooq.impl.SQLDataType.XML; import static org.jooq.impl.Tools.DataCacheKey.DATA_REFLECTION_CACHE_GET_ANNOTATED_GETTER; import static org.jooq.impl.Tools.DataCacheKey.DATA_REFLECTION_CACHE_GET_ANNOTATED_MEMBERS; import static org.jooq.impl.Tools.DataCacheKey.DATA_REFLECTION_CACHE_GET_ANNOTATED_SETTERS; import static org.jooq.impl.Tools.DataCacheKey.DATA_REFLECTION_CACHE_GET_MATCHING_GETTER; import static org.jooq.impl.Tools.DataCacheKey.DATA_REFLECTION_CACHE_GET_MATCHING_MEMBERS; import static org.jooq.impl.Tools.DataCacheKey.DATA_REFLECTION_CACHE_GET_MATCHING_SETTERS; import static org.jooq.impl.Tools.DataCacheKey.DATA_REFLECTION_CACHE_HAS_COLUMN_ANNOTATIONS; import static org.jooq.impl.Tools.DataKey.DATA_BLOCK_NESTING; import static org.jooq.tools.StringUtils.defaultIfNull; import java.io.Serializable; 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.ConcurrentHashMap; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinPool.ManagedBlocker; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; // ... 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.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.ForeignKey; import org.jooq.JSON; import org.jooq.JSONB; import org.jooq.JSONEntry; import org.jooq.Name; import org.jooq.OrderField; import org.jooq.Param; // ... import org.jooq.QualifiedAsterisk; import org.jooq.Query; import org.jooq.QueryPart; import org.jooq.Record; import org.jooq.Record1; import org.jooq.RecordType; import org.jooq.RenderContext; import org.jooq.RenderContext.CastMode; import org.jooq.Result; import org.jooq.ResultOrRows; 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.Table; import org.jooq.TableField; import org.jooq.TableRecord; import org.jooq.UDT; import org.jooq.UDTRecord; import org.jooq.UpdatableRecord; import org.jooq.XML; import org.jooq.conf.BackslashEscaping; 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.MappingException; import org.jooq.exception.NoDataFoundException; import org.jooq.exception.TemplatingException; import org.jooq.exception.TooManyRowsException; 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; /** * 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 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 String[] EMPTY_STRING = {}; static final Table[] EMPTY_TABLE = {}; 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, /** * [#7312] Allow for {@link ForceSettingsSignal} to be thrown in order * to override user-defined settings. */ DATA_FORCE_SETTINGS, /** * [#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, /** * [#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, /** * [#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, /** * [#1535] We're currently generating the window specification of a * window function that requires an ORDER BY clause. */ DATA_WINDOW_FUNCTION_REQUIRES_ORDER_BY, /** * [#9925] In some cases the AS keyword is required for aliasing, e.g. XML. */ DATA_AS_REQUIRED } /** * 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, /** * [#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 } /** * 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 } /** * [#2965] These are {@link ConcurrentHashMap}s containing caches for * reflection information. *

* new String() is used to allow for synchronizing on these * objects. */ enum DataCacheKey { DATA_REFLECTION_CACHE_GET_ANNOTATED_GETTER("org.jooq.configuration.reflection-cache.get-annotated-getter"), DATA_REFLECTION_CACHE_GET_ANNOTATED_MEMBERS("org.jooq.configuration.reflection-cache.get-annotated-members"), DATA_REFLECTION_CACHE_GET_ANNOTATED_SETTERS("org.jooq.configuration.reflection-cache.get-annotated-setters"), DATA_REFLECTION_CACHE_GET_MATCHING_GETTER("org.jooq.configuration.reflection-cache.get-matching-getter"), DATA_REFLECTION_CACHE_GET_MATCHING_MEMBERS("org.jooq.configuration.reflection-cache.get-matching-members"), DATA_REFLECTION_CACHE_GET_MATCHING_SETTERS("org.jooq.configuration.reflection-cache.get-matching-setters"), DATA_REFLECTION_CACHE_HAS_COLUMN_ANNOTATIONS("org.jooq.configuration.reflection-cache.has-column-annotations"), DATA_CACHE_RECORD_MAPPERS("org.jooq.configuration.cache.record-mappers"); final String key; DataCacheKey(String key) { this.key = key; } } // ------------------------------------------------------------------------ // 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 (javax.persistence) is on the * classpath. */ private static volatile Boolean isJPAAvailable; /** * 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 maxForceSettingsAttempts = 16; 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 = { { '?' }, { '|' }, { '&' }, { '@' }, { '<' }, { '~' }, { '#' }, { '-' } }; /** * All hexadecimal digits accessible through array index, e.g. * HEX_DIGITS[15] == 'f'. */ private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); private static final Set REQUIRES_BACKSLASH_ESCAPING = SQLDialect.supportedBy(MARIADB, MYSQL); private static final Set NO_SUPPORT_NULL = SQLDialect.supportedBy(DERBY, FIREBIRD, HSQLDB); private static final Set NO_SUPPORT_BINARY_TYPE_LENGTH = SQLDialect.supportedBy(POSTGRES); private static final Set NO_SUPPORT_CAST_TYPE_IN_DDL = SQLDialect.supportedBy(MARIADB, MYSQL); private static final Set SUPPORT_NON_BIND_VARIABLE_SUFFIXES = SQLDialect.supportedBy(POSTGRES); private static final Set DEFAULT_BEFORE_NULL = SQLDialect.supportedBy(FIREBIRD, HSQLDB); static final Set NO_SUPPORT_TIMESTAMP_PRECISION = SQLDialect.supportedBy(DERBY); private 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) { List rows = new ArrayList<>(result.size()); for (Record record : result) rows.add(record.valuesRow()); return rows; } /** * 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 record */ @SuppressWarnings("unchecked") static final RecordDelegate newRecord(boolean fetched, Table type, Configuration configuration) { return (RecordDelegate) newRecord(fetched, type.getRecordType(), (AbstractRow) type.fieldsRow(), configuration); } /** * Create a new UDT record */ static final > RecordDelegate newRecord(boolean fetched, UDT type) { return newRecord(fetched, type, null); } /** * Create a new UDT record */ static final > RecordDelegate newRecord(boolean fetched, UDT 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); } 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 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(final Class type, final 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); } /** * Extract the configuration from an attachable. */ static final Configuration getConfiguration(Attachable attachable) { return 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, Class tType, Class uType) { Converter result = configuration(configuration).converterProvider().provide(tType, uType); if (result == null) result = CTX.configuration().converterProvider().provide(tType, uType); return result; } /** * Get a converter from a {@link ConverterProvider} or null if * no converter could be provided. */ static final Converter converterOrFail(Configuration configuration, Class tType, Class uType) { Converter result = converter(configuration, 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(Scope scope, Class tType, Class uType) { return converterOrFail(configuration(scope), tType, uType); } /** * Get a converter from a {@link ConverterProvider}. */ static final Converter converterOrFail(Attachable attachable, Class tType, Class uType) { return converterOrFail(configuration(attachable), 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) { if (types == null) return null; DataType[] result = new DataType[types.length]; for (int i = 0; i < types.length; i++) if (types[i] != null) result[i] = getDataType(types[i]); else result[i] = getDataType(Object.class); return result; } static final DataType[] dataTypes(Field[] fields) { if (fields == null) return null; DataType[] result = new DataType[fields.length]; for (int i = 0; i < fields.length; i++) result[i] = fields[i].getDataType(); return result; } // ------------------------------------------------------------------------ // XXX: General utility methods // ------------------------------------------------------------------------ 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 == null) return null; if (fields instanceof SortField[]) return (SortField[]) fields; SortField[] result = new SortField[fields.length]; for (int i = 0; i < fields.length; i++) result[i] = sortField(fields[i]); return result; } static final List> sortFields(Collection> fields) { if (fields == null) return null; int size = fields.size(); List> result = new ArrayList<>(size); for (OrderField field : fields) result.add(sortField(field)); return result; } static final String fieldNameString(int index) { return "v" + index; } static final Name fieldName(int index) { return name(fieldNameString(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 Name[] fieldNames(Field[] fields) { if (fields == null) return null; Name[] result = new Name[fields.length]; for (int i = 0; i < fields.length; i++) result[i] = fields[i].getUnqualifiedName(); return result; } static final List fieldNames(Collection> fields) { if (fields == null) return null; List result = new ArrayList<>(fields.size()); for (Field field : fields) result.add(field.getUnqualifiedName()); return result; } static final String[] fieldNameStrings(Field[] fields) { if (fields == null) return null; String[] result = new String[fields.length]; for (int i = 0; i < fields.length; i++) result[i] = fields[i].getName(); 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[] fields) { if (fields == null) return null; Field[] result = new Field[fields.length]; for (int i = 0; i < fields.length; i++) result[i] = unqualified(fields[i]); return result; } static final List> unqualified(Collection> fields) { if (fields == null) return null; List> result = new ArrayList<>(fields.size()); for (Field field : fields) result.add(unqualified(field)); 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 SortField[] unqualified(SortField[] fields) { if (fields == null) return null; SortField[] result = new SortField[fields.length]; for (int i = 0; i < result.length; i++) result[i] = unqualified(fields[i]); return result; } static final Field[] unaliasedFields(Field[] fields) { if (fields == null) return null; Field[] result = new Field[fields.length]; for (int i = 0; i < fields.length; i++) result[i] = DSL.field(fieldName(i), fields[i].getDataType()).as(fields[i]); return result; } 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 Field[] aliasedFields(Field[] fields) { if (fields == null) return null; Field[] result = new Field[fields.length]; for (int i = 0; i < fields.length; i++) result[i] = fields[i].as(fieldName(i)); return result; } 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 (fieldNames == null) return null; TableField[] result = new TableField[fieldNames.length]; if (tableName == null) for (int i = 0; i < fieldNames.length; i++) result[i] = (TableField) DSL.field(fieldNames[i].getUnqualifiedName(), fieldNames[i].getDataType()); else for (int i = 0; i < fieldNames.length; i++) result[i] = (TableField) DSL.field(tableName.getQualifiedName().append(fieldNames[i].getUnqualifiedName()), fieldNames[i].getDataType()); return result; } static final Field[] fieldsByName(Name tableName, Name[] fieldNames) { if (fieldNames == null) return null; Field[] result = new Field[fieldNames.length]; if (tableName == null) for (int i = 0; i < fieldNames.length; i++) result[i] = DSL.field(fieldNames[i]); else for (int i = 0; i < fieldNames.length; i++) result[i] = DSL.field(name(tableName, fieldNames[i])); return result; } static final Field[] fieldsByName(String tableName, String[] fieldNames) { if (fieldNames == null) return null; Field[] result = new Field[fieldNames.length]; if (StringUtils.isEmpty(tableName)) for (int i = 0; i < fieldNames.length; i++) result[i] = DSL.field(name(fieldNames[i])); else for (int i = 0; i < fieldNames.length; i++) result[i] = DSL.field(name(tableName, fieldNames[i])); return result; } static final Field[] fieldsByName(Name[] names) { if (names == null) return null; Field[] result = new Field[names.length]; for (int i = 0; i < names.length; i++) result[i] = DSL.field(names[i]); return result; } static final Name[] names(String[] names) { if (names == null) return null; Name[] result = new Name[names.length]; for (int i = 0; i < names.length; i++) result[i] = DSL.name(names[i]); return result; } static final List names(Collection names) { if (names == null) return null; List result = new ArrayList<>(names.size()); for (Object o : names) result.add(o instanceof Name ? (Name) o : DSL.name(String.valueOf(o))); return result; } 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