6775 lines
205 KiB
Java
6775 lines
205 KiB
Java
/*
|
|
* 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.
|
|
* <p>
|
|
* 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:
|
|
* <ul>
|
|
* <li>{@link SQLDialect#ACCESS} : 768</li>
|
|
* <li>{@link SQLDialect#ASE} : 2000</li>
|
|
* <li>{@link SQLDialect#INGRES} : 1024</li>
|
|
* <li>{@link SQLDialect#ORACLE} : 32767</li>
|
|
* <li>{@link SQLDialect#POSTGRES} : 32767</li>
|
|
* <li>{@link SQLDialect#SQLITE} : 999</li>
|
|
* <li>{@link SQLDialect#SQLSERVER} : 2100</li>
|
|
* </ul>
|
|
*/
|
|
DATA_FORCE_STATIC_STATEMENT,
|
|
|
|
/**
|
|
* [#2665] Omit the emission of clause events by {@link QueryPart}s.
|
|
* <p>
|
|
* 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.
|
|
* <p>
|
|
* 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.
|
|
* <p>
|
|
* In some dialects, a <code>FINAL TABLE (INSERT ...)</code> clause exists, which
|
|
* corresponds to the PostgreSQL <code>INSERT .. RETURNING</code> 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
|
|
* <code>ORDER BY</code> 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 <code>SELECT</code> 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 <code>INSERT .. SELECT</code> statement.
|
|
*/
|
|
DATA_INSERT_SELECT,
|
|
|
|
/**
|
|
* [#2995] An <code>INSERT INTO t SELECT</code> statement. Without any
|
|
* explicit column list, the <code>SELECT</code> 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 <code>AS</code> 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 <code>ROW</code> types).
|
|
*/
|
|
DATA_MULTISET_CONTENT,
|
|
|
|
/**
|
|
* [#12072] In some cases, it's recommended to generate an explicit
|
|
* <code>ELSE NULL</code> clause in a <code>CASE</code> 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.
|
|
* <p>
|
|
* The window definitions declared in the <code>WINDOW</code> clause are
|
|
* needed in the <code>SELECT</code> 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
|
|
* <code>ORDER BY</code> 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.
|
|
* <p>
|
|
* This needs to be done e.g. to emulate inline table valued parameters
|
|
* in SQL Server:
|
|
* <p>
|
|
* <code><pre>
|
|
* -- With TVP bind variable:
|
|
* SELECT * FROM func (?)
|
|
*
|
|
* -- Inlining TVP bind variable:
|
|
* DECLARE @t TABLE_TYPE;
|
|
* INSERT INTO @t VALUES (?),(?),...,(?);
|
|
* SELECT * FROM func (@t)
|
|
* </pre></code>
|
|
*/
|
|
DATA_PREPEND_SQL,
|
|
|
|
/**
|
|
* [#12092] Sometimes, it is necessary to append some SQL to the
|
|
* generated SQL.
|
|
* <p>
|
|
* 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.
|
|
* <p>
|
|
* <code><pre>
|
|
* SET @t = @@group_concat_max_len;
|
|
* SET @@group_concat_max_len = 4294967295;
|
|
* SELECT group_concat(...);
|
|
* SET @@group_concat_max_len = @t;
|
|
* </pre></code>
|
|
*/
|
|
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 <code>SELECT</code>
|
|
* 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 <code>[a] LIKE [b] ESCAPE [...]</code>
|
|
* clauses.
|
|
*/
|
|
static final char ESCAPE = '!';
|
|
|
|
/**
|
|
* A lock for the initialisation of other static members
|
|
*/
|
|
private static final Object initLock = new Object();
|
|
|
|
/**
|
|
* Indicating whether JPA (<code>jakarta.persistence</code>) is on the
|
|
* classpath.
|
|
*/
|
|
private static volatile JPANamespace jpaNamespace;
|
|
|
|
/**
|
|
* Indicating whether Kotlin (<code>kotlin.*</code>) 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.
|
|
* <p>
|
|
* 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:
|
|
* <a href=
|
|
* "https://www.postgresql.org/docs/9.5/static/functions-json.html">https://www.postgresql.org/docs/current/static/functions-json.html</a>,
|
|
* <a href=
|
|
* "https://www.postgresql.org/docs/current/static/ltree.html">https://www.postgresql.org/docs/current/static/ltree.html</a>,
|
|
* <a href=
|
|
* "https://www.postgresql.org/docs/current/static/functions-geometry.html">https://www.postgresql.org/docs/current/static/functions-geometry.html</a>.
|
|
* <p>
|
|
* [#5307] Known PostgreSQL JSON operators:
|
|
* <ul>
|
|
* <li>?|</li>
|
|
* <li>?&</li>
|
|
* </ul>
|
|
* <p>
|
|
* [#7035] Known PostgreSQL LTREE operators:
|
|
* <ul>
|
|
* <li>? (we cannot handle this one)</li>
|
|
* <li>?@></li>
|
|
* <li>?<@</li>
|
|
* <li>?~</li>
|
|
* <li>?@</li>
|
|
* </ul>
|
|
* <p>
|
|
* [#7037] Known PostgreSQL Geometry operators:
|
|
* <ul>
|
|
* <li>?#</li>
|
|
* <li>?-</li>
|
|
* <li>?|</li>
|
|
* </ul>
|
|
*/
|
|
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.
|
|
* <p>
|
|
* [#11442] The above NON_BIND_VARIABLE_SUFFIXES leads to false positives,
|
|
* such as <code>"?<>"</code>, 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.
|
|
* <code>HEX_DIGITS[15] == 'f'</code>.
|
|
*/
|
|
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<SQLDialect> REQUIRES_BACKSLASH_ESCAPING = SQLDialect.supportedBy(MARIADB, MYSQL);
|
|
static final Set<SQLDialect> NO_SUPPORT_NULL = SQLDialect.supportedBy(DERBY, FIREBIRD, HSQLDB);
|
|
static final Set<SQLDialect> NO_SUPPORT_BINARY_TYPE_LENGTH = SQLDialect.supportedBy(POSTGRES, YUGABYTEDB);
|
|
static final Set<SQLDialect> NO_SUPPORT_CAST_TYPE_IN_DDL = SQLDialect.supportedBy(MARIADB, MYSQL);
|
|
static final Set<SQLDialect> SUPPORT_NON_BIND_VARIABLE_SUFFIXES = SQLDialect.supportedBy(POSTGRES, YUGABYTEDB);
|
|
static final Set<SQLDialect> SUPPORT_POSTGRES_LITERALS = SQLDialect.supportedBy(POSTGRES, YUGABYTEDB);
|
|
static final Set<SQLDialect> DEFAULT_BEFORE_NULL = SQLDialect.supportedBy(FIREBIRD, HSQLDB);
|
|
static final Set<SQLDialect> NO_SUPPORT_TIMESTAMP_PRECISION = SQLDialect.supportedBy(DERBY);
|
|
static final Set<SQLDialect> 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<Row> rows(Result<?> result) {
|
|
return map(result, r -> r.valuesRow());
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Create a new record
|
|
*/
|
|
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, Class<R> type) {
|
|
return newRecord(fetched, type, null);
|
|
}
|
|
|
|
/**
|
|
* Create a new record.
|
|
*/
|
|
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, Class<R> type, AbstractRow<R> fields) {
|
|
return newRecord(fetched, type, fields, null);
|
|
}
|
|
|
|
/**
|
|
* Create a new {@link Table} or {@link UDT} record.
|
|
*/
|
|
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, RecordQualifier<R> type) {
|
|
return newRecord(fetched, type, null);
|
|
}
|
|
|
|
/**
|
|
* Create a new {@link Table} or {@link UDT} record.
|
|
*/
|
|
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, RecordQualifier<R> type, Configuration configuration) {
|
|
return newRecord(fetched, type.getRecordType(), (AbstractRow<R>) type.fieldsRow(), configuration);
|
|
}
|
|
|
|
/**
|
|
* Create a new record.
|
|
*/
|
|
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, Class<? extends R> type, AbstractRow<? extends R> fields, Configuration configuration) {
|
|
return newRecord(fetched, recordFactory(type, fields), configuration);
|
|
}
|
|
|
|
/**
|
|
* Create a new record.
|
|
*/
|
|
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, Supplier<R> factory, Configuration configuration) {
|
|
return new RecordDelegate<>(configuration, factory, fetched);
|
|
}
|
|
|
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
|
static final <R extends Record> AbstractRow<R> row0(FieldsImpl<R> 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<R>) 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 <R extends Record> Supplier<R> recordFactory(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 = 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 <code>null</code>.
|
|
*/
|
|
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 <code>null</code>.
|
|
*/
|
|
static final Configuration configuration(Attachable attachable) {
|
|
return configuration(attachable.configuration());
|
|
}
|
|
|
|
/**
|
|
* Get a configuration or a new {@link DefaultConfiguration} if
|
|
* <code>null</code>.
|
|
*/
|
|
static final Configuration configuration(Configuration configuration) {
|
|
return configuration != null ? configuration : new DefaultConfiguration();
|
|
}
|
|
|
|
/**
|
|
* Get a configuration or a new {@link DefaultConfiguration} if
|
|
* <code>null</code>.
|
|
*/
|
|
static final Configuration configuration(Scope scope) {
|
|
return configuration(scope != null ? scope.configuration() : null);
|
|
}
|
|
|
|
/**
|
|
* Get a converter from a {@link ConverterProvider} or <code>null</code> if
|
|
* no converter could be provided.
|
|
*/
|
|
static final <T, U> Converter<T, U> converter(Configuration configuration, T instance, Class<T> tType, Class<U> uType) {
|
|
Converter<T, U> 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<U> 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<T>) (instance == null ? Object.class : instance.getClass()), uType);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get a converter from a {@link ConverterProvider} or <code>null</code> if
|
|
* no converter could be provided.
|
|
*/
|
|
static final <T, U> Converter<T, U> converterOrFail(Configuration configuration, T instance, Class<T> tType, Class<U> uType) {
|
|
Converter<T, U> 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 <T, U> Converter<T, U> converterOrFail(Attachable attachable, T instance, Class<T> tType, Class<U> uType) {
|
|
return converterOrFail(configuration(attachable), instance, tType, uType);
|
|
}
|
|
|
|
/**
|
|
* Get a configuration's settings or default settings if the configuration
|
|
* is <code>null</code>.
|
|
*/
|
|
static final Settings settings(Attachable attachable) {
|
|
return configuration(attachable).settings();
|
|
}
|
|
|
|
/**
|
|
* Get a configuration's settings or default settings if the configuration
|
|
* is <code>null</code>.
|
|
*/
|
|
static final Settings settings(Configuration configuration) {
|
|
return configuration(configuration).settings();
|
|
}
|
|
|
|
static final <T> T attach(Attachable attachable, Configuration configuration, Supplier<T> 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 <T> SortField<T> sortField(OrderField<T> field) {
|
|
if (field instanceof SortField)
|
|
return (SortField<T>) field;
|
|
else if (field instanceof Field)
|
|
return ((Field<T>) 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<SortField<?>> sortFields(Collection<? extends OrderField<?>> 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 <T> Field<T>[] fields(int length, DataType<T> type) {
|
|
Field<T>[] result = new Field[length];
|
|
|
|
for (int i = 0; i < length; i++)
|
|
result[i] = DSL.field(fieldName(i), type);
|
|
|
|
return result;
|
|
}
|
|
|
|
static final <T> Field<T> unqualified(Field<T> field) {
|
|
return DSL.field(field.getUnqualifiedName(), field.getDataType());
|
|
}
|
|
|
|
static final <T> SortField<T> unqualified(SortField<T> field) {
|
|
SortFieldImpl<T> i = (SortFieldImpl<T>) field;
|
|
return i.transform(unqualified(i.getField()));
|
|
}
|
|
|
|
static final List<Field<?>> unaliasedFields(Collection<? extends Field<?>> fields) {
|
|
return map(fields, (f, i) -> DSL.field(fieldName(i), f.getDataType()).as(f));
|
|
}
|
|
|
|
static final <R extends Record, O extends Record> ReferenceImpl<R, O> aliasedKey(ForeignKey<R, O> key, Table<R> child, Table<O> 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<Field<?>> aliasedFields(Collection<? extends Field<?>> 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 <R extends Record> TableField<R, ?>[] fieldsByName(Table<R> tableName, Field<?>[] fieldNames) {
|
|
if (tableName == null)
|
|
return map(fieldNames, n -> (TableField<R, ?>) DSL.field(n.getUnqualifiedName(), n.getDataType()), TableField[]::new);
|
|
else
|
|
return map(fieldNames, n -> (TableField<R, ?>) 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<Name> names(Collection<?> names) {
|
|
return map(names, n -> n instanceof Name ? (Name) n : DSL.name(String.valueOf(n)));
|
|
}
|
|
|
|
static final List<JSONEntry<?>> 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 <T> Field<T>[] castAllIfNeeded(Field<?>[] fields, Class<T> type) {
|
|
Field<T>[] 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 <T> Field<T> castIfNeeded(Field<?> field, Class<T> type) {
|
|
if (field.getType().equals(type))
|
|
return (Field<T>) field;
|
|
else
|
|
return field.cast(type);
|
|
}
|
|
|
|
/**
|
|
* [#461] [#473] [#2597] [#8234] Some internals need a cast only if necessary.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
static final <T> Field<T> castIfNeeded(Field<?> field, DataType<T> type) {
|
|
if (field.getDataType().equals(type))
|
|
return (Field<T>) field;
|
|
else
|
|
return field.cast(type);
|
|
}
|
|
|
|
/**
|
|
* [#461] [#473] [#2597] [#8234] Some internals need a cast only if necessary.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
static final <T> Field<T> castIfNeeded(Field<?> field, Field<T> type) {
|
|
if (field.getDataType().equals(type.getDataType()))
|
|
return (Field<T>) 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 <T> Field<T> field(T value) {
|
|
|
|
// Fields can be mixed with constant values
|
|
if (value instanceof Field<?>)
|
|
return (Field<T>) 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<Record1<T>>) 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<Byte> field(byte value) {
|
|
return val((Object) value, SQLDataType.TINYINT);
|
|
}
|
|
|
|
static final Param<Byte> field(Byte value) {
|
|
return val((Object) value, SQLDataType.TINYINT);
|
|
}
|
|
|
|
static final Param<UByte> field(UByte value) {
|
|
return val((Object) value, SQLDataType.TINYINTUNSIGNED);
|
|
}
|
|
|
|
static final Param<Short> field(short value) {
|
|
return val((Object) value, SQLDataType.SMALLINT);
|
|
}
|
|
|
|
static final Param<Short> field(Short value) {
|
|
return val((Object) value, SQLDataType.SMALLINT);
|
|
}
|
|
|
|
static final Param<UShort> field(UShort value) {
|
|
return val((Object) value, SQLDataType.SMALLINTUNSIGNED);
|
|
}
|
|
|
|
static final Param<Integer> field(int value) {
|
|
return val((Object) value, SQLDataType.INTEGER);
|
|
}
|
|
|
|
static final Param<Integer> field(Integer value) {
|
|
return val((Object) value, SQLDataType.INTEGER);
|
|
}
|
|
|
|
static final Param<UInteger> field(UInteger value) {
|
|
return val((Object) value, SQLDataType.INTEGERUNSIGNED);
|
|
}
|
|
|
|
static final Param<Long> field(long value) {
|
|
return val((Object) value, SQLDataType.BIGINT);
|
|
}
|
|
|
|
static final Param<Long> field(Long value) {
|
|
return val((Object) value, SQLDataType.BIGINT);
|
|
}
|
|
|
|
static final Param<ULong> field(ULong value) {
|
|
return val((Object) value, SQLDataType.BIGINTUNSIGNED);
|
|
}
|
|
|
|
static final Param<Float> field(float value) {
|
|
return val((Object) value, SQLDataType.REAL);
|
|
}
|
|
|
|
static final Param<Float> field(Float value) {
|
|
return val((Object) value, SQLDataType.REAL);
|
|
}
|
|
|
|
static final Param<Double> field(double value) {
|
|
return val((Object) value, SQLDataType.DOUBLE);
|
|
}
|
|
|
|
static final Param<Double> field(Double value) {
|
|
return val((Object) value, SQLDataType.DOUBLE);
|
|
}
|
|
|
|
static final Param<Boolean> field(boolean value) {
|
|
return val((Object) value, SQLDataType.BOOLEAN);
|
|
}
|
|
|
|
static final Param<Boolean> field(Boolean value) {
|
|
return val((Object) value, SQLDataType.BOOLEAN);
|
|
}
|
|
|
|
static final Param<BigDecimal> field(BigDecimal value) {
|
|
return val((Object) value, SQLDataType.DECIMAL);
|
|
}
|
|
|
|
static final Param<BigInteger> field(BigInteger value) {
|
|
return val((Object) value, SQLDataType.DECIMAL_INTEGER);
|
|
}
|
|
|
|
static final Param<byte[]> field(byte[] value) {
|
|
return val((Object) value, SQLDataType.VARBINARY);
|
|
}
|
|
|
|
static final Param<String> field(String value) {
|
|
return val((Object) value, SQLDataType.VARCHAR);
|
|
}
|
|
|
|
static final Param<Date> field(Date value) {
|
|
return val((Object) value, SQLDataType.DATE);
|
|
}
|
|
|
|
static final Param<Time> field(Time value) {
|
|
return val((Object) value, SQLDataType.TIME);
|
|
}
|
|
|
|
static final Param<Timestamp> field(Timestamp value) {
|
|
return val((Object) value, SQLDataType.TIMESTAMP);
|
|
}
|
|
|
|
static final Param<LocalDate> field(LocalDate value) {
|
|
return val((Object) value, SQLDataType.LOCALDATE);
|
|
}
|
|
|
|
static final Param<LocalTime> field(LocalTime value) {
|
|
return val((Object) value, SQLDataType.LOCALTIME);
|
|
}
|
|
|
|
static final Param<LocalDateTime> field(LocalDateTime value) {
|
|
return val((Object) value, SQLDataType.LOCALDATETIME);
|
|
}
|
|
|
|
static final Param<OffsetTime> field(OffsetTime value) {
|
|
return val((Object) value, SQLDataType.OFFSETTIME);
|
|
}
|
|
|
|
static final Param<OffsetDateTime> field(OffsetDateTime value) {
|
|
return val((Object) value, SQLDataType.OFFSETDATETIME);
|
|
}
|
|
|
|
static final Param<Instant> field(Instant value) {
|
|
return val((Object) value, SQLDataType.INSTANT);
|
|
}
|
|
|
|
static final Param<UUID> field(UUID value) {
|
|
return val((Object) value, SQLDataType.UUID);
|
|
}
|
|
|
|
static final Param<JSON> field(JSON value) {
|
|
return val((Object) value, SQLDataType.JSON);
|
|
}
|
|
|
|
static final Param<JSONB> field(JSONB value) {
|
|
return val((Object) value, SQLDataType.JSONB);
|
|
}
|
|
|
|
static final Param<XML> field(XML value) {
|
|
return val((Object) value, SQLDataType.XML);
|
|
}
|
|
|
|
/**
|
|
* @deprecated - This method is probably called by mistake (ambiguous static import).
|
|
*/
|
|
@Deprecated
|
|
static final Field<Object> field(Name name) {
|
|
return DSL.field(name);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static final <T> Field<T> field(Object value, Field<T> field) {
|
|
|
|
// Fields can be mixed with constant values
|
|
if (value instanceof Field<?>)
|
|
return (Field<T>) 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<Record1<T>>) value);
|
|
|
|
// [#4771] Any other QueryPart type is not supported here
|
|
else if (value instanceof QueryPart)
|
|
throw fieldExpected(value);
|
|
|
|
else
|
|
return val(value, field);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static final <T> Field<T> field(Object value, Class<T> type) {
|
|
|
|
// Fields can be mixed with constant values
|
|
if (value instanceof Field<?>)
|
|
return (Field<T>) 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<Record1<T>>) value);
|
|
|
|
// [#4771] Any other QueryPart type is not supported here
|
|
else if (value instanceof QueryPart)
|
|
throw fieldExpected(value);
|
|
|
|
else
|
|
return val(value, type);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static final <T> Field<T> field(Object value, DataType<T> type) {
|
|
|
|
// Fields can be mixed with constant values
|
|
if (value instanceof Field<?>)
|
|
return (Field<T>) 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<Record1<T>>) value);
|
|
|
|
// [#4771] Any other QueryPart type is not supported here
|
|
else if (value instanceof QueryPart)
|
|
throw fieldExpected(value);
|
|
|
|
else
|
|
return val(value, type);
|
|
}
|
|
|
|
static final <T> List<Field<T>> fields(T[] values) {
|
|
return asList(fieldsArray(values));
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static final <T> Field<T>[] fieldsArray(T[] values) {
|
|
return map(values, v -> field(v), Field[]::new);
|
|
}
|
|
|
|
static final <T> List<Field<T>> fields(Object[] values, Field<T> field) {
|
|
if (field == null)
|
|
return new ArrayList<>();
|
|
else
|
|
return map(values, v -> field(v, field));
|
|
}
|
|
|
|
static final List<Field<?>> fields(Object[] values, Field<?>[] fields) {
|
|
return asList(fieldsArray(values, fields));
|
|
}
|
|
|
|
static final Field<?>[] fieldsArray(Object[] values, Field<?>[] fields) {
|
|
return map(values, (v, i) -> field(v, fields[i]), Field[]::new);
|
|
}
|
|
|
|
static final <T> List<Field<T>> fields(Object[] values, DataType<T> type) {
|
|
return asList(fieldsArray(values, type));
|
|
}
|
|
|
|
static final <T> List<Field<T>> fields(Collection<?> values, DataType<T> type) {
|
|
return map(values, v -> field(v, type));
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static final <T> Field<T>[] fieldsArray(Object[] values, DataType<T> type) {
|
|
return map(values, v -> field(v, type), Field[]::new);
|
|
}
|
|
|
|
static final List<Field<?>> fields(Object[] values, DataType<?>[] types) {
|
|
return asList(fieldsArray(values, types));
|
|
}
|
|
|
|
static final Field<?>[] fieldsArray(Object[] values, DataType<?>[] types) {
|
|
return map(values, (v, i) -> field(v, types[i]), Field[]::new);
|
|
}
|
|
|
|
static final IllegalArgumentException indexFail(Fields row, Field<?> field) {
|
|
return new IllegalArgumentException("Field (" + field + ") is not contained in Row " + row);
|
|
}
|
|
|
|
static final int indexOrFail(Fields row, Field<?> field) {
|
|
int result = row.indexOf(field);
|
|
|
|
if (result < 0)
|
|
throw indexFail(row, field);
|
|
|
|
return result;
|
|
}
|
|
|
|
static final IllegalArgumentException indexFail(Fields row, String fieldName) {
|
|
throw new IllegalArgumentException("Field (" + fieldName + ") is not contained in Row " + row);
|
|
}
|
|
|
|
static final int indexOrFail(Fields row, String fieldName) {
|
|
int result = row.indexOf(fieldName);
|
|
|
|
if (result < 0)
|
|
throw indexFail(row, fieldName);
|
|
|
|
return result;
|
|
}
|
|
|
|
static final IllegalArgumentException indexFail(Fields row, Name fieldName) {
|
|
throw new IllegalArgumentException("Field (" + fieldName + ") is not contained in Row " + row);
|
|
}
|
|
|
|
static final int indexOrFail(Fields row, Name fieldName) {
|
|
int result = row.indexOf(fieldName);
|
|
|
|
if (result < 0)
|
|
throw indexFail(row, fieldName);
|
|
|
|
return result;
|
|
}
|
|
|
|
static final IllegalArgumentException indexFail(Fields row, int fieldIndex) {
|
|
throw new IllegalArgumentException("Field (" + fieldIndex + ") is not contained in Row " + row);
|
|
}
|
|
|
|
static final int indexOrFail(Fields row, int fieldIndex) {
|
|
Field<?> result = row.field(fieldIndex);
|
|
|
|
if (result == null)
|
|
throw indexFail(row, fieldIndex);
|
|
|
|
return fieldIndex;
|
|
}
|
|
|
|
private static final <T> List<T> newListWithCapacity(Iterable<?> it) {
|
|
return it instanceof Collection ? new ArrayList<>(((Collection<?>) it).size()) : new ArrayList<>();
|
|
}
|
|
|
|
static final <T, R> R apply(@Nullable T t, Function<? super @NotNull T, ? extends R> f) {
|
|
return t == null ? null : f.apply(t);
|
|
}
|
|
|
|
static final <T> T let(T t, Consumer<? super T> consumer) {
|
|
consumer.accept(t);
|
|
return t;
|
|
}
|
|
|
|
static final <T, E extends Exception> boolean anyMatch(T[] array, ThrowingPredicate<? super T, E> test) throws E {
|
|
return findAny(array, test, t -> TRUE) != null;
|
|
}
|
|
|
|
static final <T, E extends Exception> boolean anyMatch(T[] array, ThrowingIntPredicate<? super T, E> test) throws E {
|
|
return findAny(array, test, t -> TRUE) != null;
|
|
}
|
|
|
|
static final <T, E extends Exception> boolean anyMatch(Iterable<? extends T> it, ThrowingPredicate<? super T, E> test) throws E {
|
|
return findAny(it, test, t -> TRUE) != null;
|
|
}
|
|
|
|
static final <T, E extends Exception> boolean anyMatch(Iterable<? extends T> it, ThrowingIntPredicate<? super T, E> test) throws E {
|
|
return findAny(it, test, t -> TRUE) != null;
|
|
}
|
|
|
|
static final <T, E extends Exception> T findAny(T[] array, ThrowingPredicate<? super T, E> test) throws E {
|
|
return findAny(array, test, t -> t);
|
|
}
|
|
|
|
static final <T, E extends Exception> T findAny(T[] array, ThrowingIntPredicate<? super T, E> test) throws E {
|
|
return findAny(array, test, t -> t);
|
|
}
|
|
|
|
static final <T, E extends Exception> T findAny(Iterable<? extends T> it, ThrowingPredicate<? super T, E> test) throws E {
|
|
return findAny(it, test, t -> t);
|
|
}
|
|
|
|
static final <T, E extends Exception> T findAny(Iterable<? extends T> it, ThrowingIntPredicate<? super T, E> test) throws E {
|
|
return findAny(it, test, t -> t);
|
|
}
|
|
|
|
static final <T, U, E extends Exception> U findAny(T[] array, ThrowingPredicate<? super T, E> test, ThrowingFunction<? super T, ? extends U, E> function) throws E {
|
|
if (array != null)
|
|
for (T t : array)
|
|
if (test.test(t))
|
|
return function.apply(t);
|
|
|
|
return null;
|
|
}
|
|
|
|
static final <T, U, E extends Exception> U findAny(T[] array, ThrowingIntPredicate<? super T, E> test, ThrowingFunction<? super T, ? extends U, E> function) throws E {
|
|
if (array != null) {
|
|
int i = 0;
|
|
|
|
for (T t : array)
|
|
if (test.test(t, i++))
|
|
return function.apply(t);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
static final <T, U, E extends Exception> U findAny(Iterable<? extends T> it, ThrowingPredicate<? super T, E> test, ThrowingFunction<? super T, ? extends U, E> function) throws E {
|
|
if (it != null)
|
|
for (T t : it)
|
|
if (test.test(t))
|
|
return function.apply(t);
|
|
|
|
return null;
|
|
}
|
|
|
|
static final <T, U, E extends Exception> U findAny(Iterable<? extends T> it, ThrowingIntPredicate<? super T, E> test, ThrowingFunction<? super T, ? extends U, E> function) throws E {
|
|
if (it != null) {
|
|
int i = 0;
|
|
|
|
for (T t : it)
|
|
if (test.test(t, i++))
|
|
return function.apply(t);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
static final Condition allNull(Field<?>[] fields) {
|
|
return DSL.and(map(fields, Field::isNull));
|
|
}
|
|
|
|
static final Condition allNotNull(Field<?>[] fields) {
|
|
return DSL.and(map(fields, Field::isNotNull));
|
|
}
|
|
|
|
/**
|
|
* Like <code>Stream.of(array).map(mapper).toArray(constructor)</code> but
|
|
* without the entire stream pipeline.
|
|
*/
|
|
static final <T, U, E extends Exception> U[] map(T[] array, ThrowingFunction<? super T, ? extends U, E> mapper, IntFunction<U[]> constructor) throws E {
|
|
if (array == null)
|
|
return constructor.apply(0);
|
|
|
|
U[] result = constructor.apply(array.length);
|
|
for (int i = 0; i < array.length; i++)
|
|
result[i] = mapper.apply(array[i]);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Like <code>Stream.of(array).map(mapper).toArray(constructor)</code> but
|
|
* without the entire stream pipeline.
|
|
*/
|
|
static final <U, E extends Exception> U[] map(int[] array, ThrowingIntFunction<? extends U, E> mapper, IntFunction<U[]> constructor) throws E {
|
|
if (array == null)
|
|
return constructor.apply(0);
|
|
|
|
U[] result = constructor.apply(array.length);
|
|
for (int i = 0; i < array.length; i++)
|
|
result[i] = mapper.apply(array[i]);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Like <code>Stream.of(array).map(mapper).toArray(constructor)</code> but
|
|
* without the entire stream pipeline.
|
|
*/
|
|
static final <T, U, E extends Exception> U[] map(Collection<? extends T> collection, ThrowingFunction<? super T, ? extends U, E> mapper, IntFunction<U[]> constructor) throws E {
|
|
if (collection == null)
|
|
return constructor.apply(0);
|
|
|
|
U[] result = constructor.apply(collection.size());
|
|
int i = 0;
|
|
for (T t : collection)
|
|
result[i++] = mapper.apply(t);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Like
|
|
* <code>Stream.of(array).zipWithIndex().map(mapper).toArray(constructor)</code>
|
|
* but without the entire stream pipeline.
|
|
*/
|
|
static final <T, U, E extends Exception> U[] map(T[] array, ThrowingObjIntFunction<? super T, ? extends U, E> mapper, IntFunction<U[]> constructor) throws E {
|
|
if (array == null)
|
|
return constructor.apply(0);
|
|
|
|
U[] result = constructor.apply(array.length);
|
|
for (int i = 0; i < array.length; i++)
|
|
result[i] = mapper.apply(array[i], i);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Like
|
|
* <code>Stream.of(array).zipWithIndex().map(mapper).toArray(constructor)</code>
|
|
* but without the entire stream pipeline.
|
|
*/
|
|
static final <U, E extends Exception> U[] map(int[] array, ThrowingIntIntFunction<? extends U, E> mapper, IntFunction<U[]> constructor) throws E {
|
|
if (array == null)
|
|
return constructor.apply(0);
|
|
|
|
U[] result = constructor.apply(array.length);
|
|
for (int i = 0; i < array.length; i++)
|
|
result[i] = mapper.apply(array[i], i);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Like
|
|
* <code>Stream.of(array).zipWithIndex().map(mapper).toArray(constructor)</code>
|
|
* but without the entire stream pipeline.
|
|
*/
|
|
static final <T, U, E extends Exception> U[] map(Collection<? extends T> collection, ThrowingObjIntFunction<? super T, ? extends U, E> mapper, IntFunction<U[]> constructor) throws E {
|
|
if (collection == null)
|
|
return constructor.apply(0);
|
|
|
|
U[] result = constructor.apply(collection.size());
|
|
int i = 0;
|
|
for (T t : collection)
|
|
result[i] = mapper.apply(t, i++);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Like <code>Stream.of(array).map(mapper).toList()</code> but
|
|
* without the entire stream pipeline.
|
|
*/
|
|
static final <T, U, E extends Exception> List<U> map(T[] array, ThrowingFunction<? super T, ? extends U, E> mapper) throws E {
|
|
if (array == null)
|
|
return emptyList();
|
|
|
|
List<U> result = new ArrayList<>(array.length);
|
|
for (T t : array)
|
|
result.add(mapper.apply(t));
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Like <code>Stream.of(array).map(mapper).toList()</code> but
|
|
* without the entire stream pipeline.
|
|
*/
|
|
static final <U, E extends Exception> List<U> map(int[] array, ThrowingIntFunction<? extends U, E> mapper) throws E {
|
|
if (array == null)
|
|
return emptyList();
|
|
|
|
List<U> result = new ArrayList<>(array.length);
|
|
for (int t : array)
|
|
result.add(mapper.apply(t));
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Like <code>Stream.of(array).map(mapper).toList()</code> but
|
|
* without the entire stream pipeline.
|
|
*/
|
|
static final <T, U, E extends Exception> List<U> map(Iterable<? extends T> it, ThrowingFunction<? super T, ? extends U, E> mapper) throws E {
|
|
if (it == null)
|
|
return emptyList();
|
|
|
|
List<U> result = newListWithCapacity(it);
|
|
for (T t : it)
|
|
result.add(mapper.apply(t));
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Like <code>Stream.of(array).map(mapper).toList()</code> but
|
|
* without the entire stream pipeline.
|
|
*/
|
|
static final <T, U, E extends Exception> List<U> flatMap(Iterable<? extends T> it, ThrowingFunction<? super T, ? extends List<? extends U>, E> mapper) throws E {
|
|
if (it == null)
|
|
return emptyList();
|
|
|
|
List<U> result = newListWithCapacity(it);
|
|
for (T t : it)
|
|
result.addAll(mapper.apply(t));
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Like <code>Stream.of(array).zipWithIndex().map(mapper).toList()</code>
|
|
* but without the entire stream pipeline.
|
|
*/
|
|
static final <T, U, E extends Exception> List<U> map(T[] array, ThrowingObjIntFunction<? super T, ? extends U, E> mapper) throws E {
|
|
if (array == null)
|
|
return emptyList();
|
|
|
|
List<U> result = new ArrayList<>(array.length);
|
|
for (int i = 0; i < array.length; i++)
|
|
result.add(mapper.apply(array[i], i));
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Like <code>Stream.of(array).zipWithIndex().map(mapper).toList()</code>
|
|
* but without the entire stream pipeline.
|
|
*/
|
|
static final <U, E extends Exception> List<U> map(int[] array, ThrowingIntIntFunction<? extends U, E> mapper) throws E {
|
|
if (array == null)
|
|
return emptyList();
|
|
|
|
List<U> result = new ArrayList<>(array.length);
|
|
for (int i = 0; i < array.length; i++)
|
|
result.add(mapper.apply(array[i], i));
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Like <code>Stream.of(array).map(mapper).toList()</code> but
|
|
* without the entire stream pipeline.
|
|
*/
|
|
static final <T, U, E extends Exception> List<U> map(Iterable<? extends T> it, ThrowingObjIntFunction<? super T, ? extends U, E> mapper) throws E {
|
|
if (it == null)
|
|
return emptyList();
|
|
|
|
List<U> result = newListWithCapacity(it);
|
|
int i = 0;
|
|
for (T t : it)
|
|
result.add(mapper.apply(t, i++));
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Reverse an array.
|
|
*/
|
|
@SafeVarargs
|
|
static final <T> T[] reverse(T... array) {
|
|
if (array == null)
|
|
return null;
|
|
|
|
for (int i = 0; i < array.length / 2; i++) {
|
|
T tmp = array[i];
|
|
array[i] = array[array.length - i - 1];
|
|
array[array.length - i - 1] = tmp;
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
static final <T, U> Iterator<U> iterator(Iterator<? extends T> iterator, Function<? super T, ? extends U> mapper) {
|
|
return new Iterator<U>() {
|
|
@Override
|
|
public boolean hasNext() {
|
|
return iterator.hasNext();
|
|
}
|
|
|
|
@Override
|
|
public U next() {
|
|
return mapper.apply(iterator.next());
|
|
}
|
|
|
|
@Override
|
|
public void remove() {
|
|
iterator.remove();
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Reverse iterate over an array.
|
|
*/
|
|
@SafeVarargs
|
|
static final <T> Iterable<T> reverseIterable(final T... array) {
|
|
return reverseIterable(Arrays.asList(array));
|
|
}
|
|
|
|
/**
|
|
* Reverse iterate over an array.
|
|
*/
|
|
@SafeVarargs
|
|
static final <T> Iterator<T> reverseIterator(T... array) {
|
|
return reverseIterator(Arrays.asList(array));
|
|
}
|
|
|
|
/**
|
|
* Reverse iterate over a list.
|
|
*/
|
|
static final <T> Iterable<T> reverseIterable(List<T> list) {
|
|
return () -> reverseIterator(list);
|
|
}
|
|
|
|
/**
|
|
* Reverse iterate over a list.
|
|
*/
|
|
static final <T> Iterator<T> reverseIterator(List<T> list) {
|
|
return new Iterator<T>() {
|
|
final ListIterator<T> li = list.listIterator(list.size());
|
|
|
|
@Override
|
|
public boolean hasNext() {
|
|
return li.hasPrevious();
|
|
}
|
|
|
|
@Override
|
|
public T next() {
|
|
return li.previous();
|
|
}
|
|
|
|
@Override
|
|
public void remove() {
|
|
li.remove();
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Use this rather than {@link Arrays#asList(Object...)} for
|
|
* <code>null</code>-safety
|
|
*/
|
|
@SafeVarargs
|
|
static final <T> List<T> list(T... array) {
|
|
return array == null ? emptyList() : asList(array);
|
|
}
|
|
|
|
/**
|
|
* Turn a {@link Record} into a {@link Map}
|
|
*/
|
|
static final Map<Field<?>, Object> mapOfChangedValues(Record record) {
|
|
Map<Field<?>, Object> result = new LinkedHashMap<>();
|
|
int size = record.size();
|
|
|
|
for (int i = 0; i < size; i++)
|
|
if (record.changed(i))
|
|
result.put(record.field(i), record.get(i));
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Extract the first item from an iterable or <code>null</code>, if there is
|
|
* no such item, or if iterable itself is <code>null</code>
|
|
*/
|
|
static final <T> T first(Iterable<? extends T> iterable) {
|
|
if (iterable == null)
|
|
return null;
|
|
|
|
Iterator<? extends T> it = iterable.iterator();
|
|
if (it.hasNext())
|
|
return it.next();
|
|
else
|
|
return null;
|
|
}
|
|
|
|
static final <T> T last(Collection<T> collection) {
|
|
if (collection.isEmpty())
|
|
return null;
|
|
else if (collection instanceof List)
|
|
return ((List<T>) collection).get(collection.size() - 1);
|
|
|
|
T last = null;
|
|
for (Iterator<T> it = collection.iterator(); it.hasNext(); last = it.next());
|
|
return last;
|
|
}
|
|
|
|
/**
|
|
* Sets the statement's fetch size to the given value or
|
|
* {@link org.jooq.conf.Settings#getFetchSize()} if {@code 0}.
|
|
* <p>
|
|
* This method should not be called before {@link ExecuteContext#statement(PreparedStatement)}.
|
|
*/
|
|
static final void setFetchSize(ExecuteContext ctx, int fetchSize) throws SQLException {
|
|
// [#1263] [#4753] Allow for negative fetch sizes to support some non-standard
|
|
// MySQL feature, where Integer.MIN_VALUE is used
|
|
int f = SettingsTools.getFetchSize(fetchSize, ctx.settings());
|
|
if (f != 0) {
|
|
if (log.isDebugEnabled())
|
|
log.debug("Setting fetch size", f);
|
|
|
|
PreparedStatement statement = ctx.statement();
|
|
if (statement != null)
|
|
statement.setFetchSize(f);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the only element from a list or <code>null</code>, or throw an
|
|
* exception.
|
|
*
|
|
* @param list The list
|
|
* @return The only element from the list or <code>null</code>
|
|
* @throws TooManyRowsException Thrown if the list contains more than one
|
|
* element
|
|
* @deprecated - [#8881] - Do not reuse this method as it doesn't properly
|
|
* manage the {@link ExecuteListener#exception(ExecuteContext)}
|
|
* lifecycle event. Use {@link #fetchOne(Cursor)} instead.
|
|
*/
|
|
@Deprecated
|
|
static final <R extends Record> R filterOne(List<R> list) throws TooManyRowsException {
|
|
int size = list.size();
|
|
|
|
if (size == 0)
|
|
return null;
|
|
else if (size == 1)
|
|
return list.get(0);
|
|
else
|
|
throw new TooManyRowsException("Too many rows selected : " + size);
|
|
}
|
|
|
|
/**
|
|
* Get the only element from a cursor or <code>null</code>, or throw an
|
|
* exception.
|
|
* <p>
|
|
* [#2373] This method will always close the argument cursor, as it is
|
|
* supposed to be completely consumed by this method.
|
|
*
|
|
* @param cursor The cursor
|
|
* @return The only element from the cursor or <code>null</code>
|
|
* @throws TooManyRowsException Thrown if the cursor returns more than one
|
|
* element
|
|
*/
|
|
static final <R extends Record> R fetchOne(Cursor<R> cursor) throws TooManyRowsException {
|
|
return fetchOne(cursor, false);
|
|
}
|
|
|
|
/**
|
|
* Get the only element from a cursor or <code>null</code>, or throw an
|
|
* exception.
|
|
* <p>
|
|
* [#2373] This method will always close the argument cursor, as it is
|
|
* supposed to be completely consumed by this method.
|
|
*
|
|
* @param cursor The cursor
|
|
* @param hasLimit1 Whether a LIMIT clause is present that guarantees at
|
|
* most one row
|
|
* @return The only element from the cursor or <code>null</code>
|
|
* @throws TooManyRowsException Thrown if the cursor returns more than one
|
|
* element
|
|
*/
|
|
static final <R extends Record> R fetchOne(Cursor<R> cursor, boolean hasLimit1) throws TooManyRowsException {
|
|
try {
|
|
|
|
// [#7001] Fetching at most two rows rather than at most one row
|
|
// (and then checking of additional rows) improves debug logs
|
|
// [#7430] Avoid fetching the second row (additional overhead) if
|
|
// there is a guarantee of at most one row
|
|
Result<R> result = cursor.fetchNext(hasLimit1 ? 1 : 2);
|
|
int size = result.size();
|
|
|
|
if (size == 0)
|
|
return null;
|
|
else if (size == 1)
|
|
return result.get(0);
|
|
else
|
|
throw exception(cursor, new TooManyRowsException("Cursor returned more than one result"));
|
|
}
|
|
finally {
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the only element from a cursor, or throw an exception.
|
|
* <p>
|
|
* [#2373] This method will always close the argument cursor, as it is
|
|
* supposed to be completely consumed by this method.
|
|
*
|
|
* @param cursor The cursor
|
|
* @return The only element from the cursor
|
|
* @throws NoDataFoundException Thrown if the cursor did not return any rows
|
|
* @throws TooManyRowsException Thrown if the cursor returns more than one
|
|
* element
|
|
*/
|
|
static final <R extends Record> R fetchSingle(Cursor<R> cursor) throws NoDataFoundException, TooManyRowsException {
|
|
return fetchSingle(cursor, false);
|
|
}
|
|
|
|
/**
|
|
* Get the only element from a cursor, or throw an exception.
|
|
* <p>
|
|
* [#2373] This method will always close the argument cursor, as it is
|
|
* supposed to be completely consumed by this method.
|
|
*
|
|
* @param cursor The cursor
|
|
* @param hasLimit1 Whether a LIMIT clause is present that guarantees at
|
|
* most one row
|
|
* @return The only element from the cursor
|
|
* @throws NoDataFoundException Thrown if the cursor did not return any rows
|
|
* @throws TooManyRowsException Thrown if the cursor returns more than one
|
|
* element
|
|
*/
|
|
static final <R extends Record> R fetchSingle(Cursor<R> cursor, boolean hasLimit1) throws NoDataFoundException, TooManyRowsException {
|
|
try {
|
|
|
|
// [#7001] Fetching at most two rows rather than at most one row
|
|
// (and then checking of additional rows) improves debug logs
|
|
// [#7430] Avoid fetching the second row (additional overhead) if
|
|
// there is a guarantee of at most one row
|
|
Result<R> result = cursor.fetchNext(hasLimit1 ? 1 : 2);
|
|
int size = result.size();
|
|
|
|
if (size == 0)
|
|
throw exception(cursor, new NoDataFoundException("Cursor returned no rows"));
|
|
else if (size == 1)
|
|
return result.get(0);
|
|
else
|
|
throw exception(cursor, new TooManyRowsException("Cursor returned more than one result"));
|
|
}
|
|
finally {
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
private static final RuntimeException exception(Cursor<?> cursor, RuntimeException e) {
|
|
|
|
// [#8877] Make sure these exceptions pass through ExecuteListeners as well
|
|
if (cursor instanceof CursorImpl) { CursorImpl<?> c = (CursorImpl<?>) cursor;
|
|
c.ctx.exception(e);
|
|
c.listener.exception(c.ctx);
|
|
return c.ctx.exception();
|
|
}
|
|
else
|
|
return e;
|
|
}
|
|
|
|
static final void visitSubquery(Context<?> ctx, QueryPart query) {
|
|
visitSubquery(ctx, query, true);
|
|
}
|
|
|
|
static final void visitSubquery(Context<?> ctx, QueryPart query, boolean parentheses) {
|
|
|
|
|
|
|
|
|
|
|
|
if (parentheses)
|
|
ctx.sql('(');
|
|
|
|
ctx.subquery(true)
|
|
.formatIndentStart()
|
|
.formatNewLine()
|
|
.visit(query)
|
|
.formatIndentEnd()
|
|
.formatNewLine()
|
|
.subquery(false);
|
|
|
|
if (parentheses)
|
|
ctx.sql(')');
|
|
}
|
|
|
|
/**
|
|
* Visit each query part from a collection, given a context.
|
|
*/
|
|
static final <C extends Context<? super C>> C visitAll(C ctx, Collection<? extends QueryPart> parts) {
|
|
if (parts != null)
|
|
for (QueryPart part : parts)
|
|
ctx.visit(part);
|
|
|
|
return ctx;
|
|
}
|
|
|
|
/**
|
|
* Visit each query part from an array, given a context.
|
|
*/
|
|
static final <C extends Context<? super C>> C visitAll(C ctx, QueryPart[] parts) {
|
|
if (parts != null)
|
|
for (QueryPart part : parts)
|
|
ctx.visit(part);
|
|
|
|
return ctx;
|
|
}
|
|
|
|
/**
|
|
* Render and bind a list of {@link QueryPart} to plain SQL
|
|
* <p>
|
|
* This will perform two actions:
|
|
* <ul>
|
|
* <li>When {@link RenderContext} is provided, it will render plain SQL to
|
|
* the context, substituting {numbered placeholders} and bind values if
|
|
* {@link RenderContext#inline()} is set</li>
|
|
* <li>When {@link BindContext} is provided, it will bind the list of
|
|
* {@link QueryPart} according to the {numbered placeholders} and bind
|
|
* values in the sql string</li>
|
|
* </ul>
|
|
*/
|
|
@SuppressWarnings("null")
|
|
static final void renderAndBind(Context<?> ctx, String sql, List<QueryPart> substitutes) {
|
|
RenderContext render = ctx instanceof RenderContext ? (RenderContext) ctx : null;
|
|
BindContext bind = ctx instanceof BindContext ? (BindContext) ctx : null;
|
|
|
|
int substituteIndex = 0;
|
|
char[] sqlChars = sql.toCharArray();
|
|
|
|
// [#1593] Create a dummy renderer if we're in bind mode
|
|
if (render == null) render = new DefaultRenderContext(bind.configuration());
|
|
|
|
SQLDialect family = render.family();
|
|
boolean mysql = SUPPORTS_HASH_COMMENT_SYNTAX.contains(render.dialect());
|
|
char[][][] quotes = QUOTES.get(family);
|
|
|
|
// [#3630] Depending on this setting, we need to consider backslashes as escape characters within string literals.
|
|
boolean needsBackslashEscaping = needsBackslashEscaping(ctx.configuration());
|
|
|
|
characterLoop:
|
|
for (int i = 0; i < sqlChars.length; i++) {
|
|
|
|
// [#1797] [#9651] Skip content inside of single-line comments, e.g.
|
|
// select 1 x -- what's this ?'?
|
|
// from t_book -- what's that ?'?
|
|
// where id = ?
|
|
if (peek(sqlChars, i, TOKEN_SINGLE_LINE_COMMENT) ||
|
|
peek(sqlChars, i, TOKEN_SINGLE_LINE_COMMENT_C) ||
|
|
|
|
// [#4182] MySQL also supports # as a comment character, and requires
|
|
// -- to be followed by a whitespace, although the latter is also not
|
|
// handled correctly by the MySQL JDBC driver (yet). See
|
|
// http://bugs.mysql.com/bug.php?id=76623
|
|
(mysql && peek(sqlChars, i, TOKEN_HASH))) {
|
|
|
|
// Consume the complete comment
|
|
for (; i < sqlChars.length && sqlChars[i] != '\r' && sqlChars[i] != '\n'; render.sql(sqlChars[i++]));
|
|
|
|
// Consume the newline character
|
|
if (i < sqlChars.length) render.sql(sqlChars[i]);
|
|
}
|
|
|
|
// [#1797] Skip content inside of multi-line comments, e.g.
|
|
// select 1 x /* what's this ?'?
|
|
// I don't know ?'? */
|
|
// from t_book where id = ?
|
|
else if (peek(sqlChars, i, TOKEN_MULTI_LINE_COMMENT_OPEN)) {
|
|
int nestedMultilineCommentLevel = 1;
|
|
|
|
// Consume the complete comment
|
|
do {
|
|
render.sql(sqlChars[i++]);
|
|
|
|
if (peek(sqlChars, i, TOKEN_MULTI_LINE_COMMENT_OPEN))
|
|
nestedMultilineCommentLevel++;
|
|
else if (peek(sqlChars, i, TOKEN_MULTI_LINE_COMMENT_CLOSE))
|
|
nestedMultilineCommentLevel--;
|
|
|
|
}
|
|
while (nestedMultilineCommentLevel != 0);
|
|
|
|
// Consume the comment delimiter
|
|
render.sql(sqlChars[i]);
|
|
}
|
|
|
|
// [#1031] [#1032] Skip ? inside of string literals, e.g.
|
|
// insert into x values ('Hello? Anybody out there?');
|
|
else if (sqlChars[i] == '\'') {
|
|
|
|
// Consume the initial string literal delimiter
|
|
render.sql(sqlChars[i++]);
|
|
|
|
// Consume the whole string literal
|
|
for (;;) {
|
|
|
|
// [#9648] The "string literal" might not be one, if we're inside
|
|
// of some vendor specific comment syntax
|
|
if (i >= sqlChars.length)
|
|
break characterLoop;
|
|
|
|
// [#3000] [#3630] Consume backslash-escaped characters if needed
|
|
else if (sqlChars[i] == '\\' && needsBackslashEscaping)
|
|
render.sql(sqlChars[i++]);
|
|
|
|
// Consume an escaped apostrophe
|
|
else if (peek(sqlChars, i, TOKEN_ESCAPED_APOS))
|
|
render.sql(sqlChars[i++]);
|
|
|
|
// Break on the terminal string literal delimiter
|
|
else if (peek(sqlChars, i, TOKEN_APOS))
|
|
break;
|
|
|
|
// Consume string literal content
|
|
render.sql(sqlChars[i++]);
|
|
}
|
|
|
|
// Consume the terminal string literal delimiter
|
|
render.sql(sqlChars[i]);
|
|
}
|
|
|
|
// [#6704] PostgreSQL supports additional quoted string literals, which we must skip: E'...'
|
|
else if ((sqlChars[i] == 'e' || sqlChars[i] == 'E')
|
|
&& SUPPORT_POSTGRES_LITERALS.contains(ctx.dialect())
|
|
&& i + 1 < sqlChars.length
|
|
&& sqlChars[i + 1] == '\'') {
|
|
|
|
// Consume the initial string literal delimiters
|
|
render.sql(sqlChars[i++]);
|
|
render.sql(sqlChars[i++]);
|
|
|
|
// Consume the whole string literal
|
|
for (;;) {
|
|
|
|
// [#3000] [#3630] Consume backslash-escaped characters if needed
|
|
if (sqlChars[i] == '\\')
|
|
render.sql(sqlChars[i++]);
|
|
|
|
// Consume an escaped apostrophe
|
|
else if (peek(sqlChars, i, TOKEN_ESCAPED_APOS))
|
|
render.sql(sqlChars[i++]);
|
|
|
|
// Break on the terminal string literal delimiter
|
|
else if (peek(sqlChars, i, TOKEN_APOS))
|
|
break;
|
|
|
|
// Consume string literal content
|
|
render.sql(sqlChars[i++]);
|
|
}
|
|
|
|
// Consume the terminal string literal delimiter
|
|
render.sql(sqlChars[i]);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// [#3297] Skip ? inside of quoted identifiers, e.g.
|
|
// update x set v = "Column Name with a ? (question mark)"
|
|
else if (peekAny(sqlChars, i, quotes[QUOTE_START_DELIMITER])) {
|
|
|
|
// Main identifier delimiter or alternative one?
|
|
int delimiter = 0;
|
|
for (int d = 0; d < quotes[QUOTE_START_DELIMITER].length; d++) {
|
|
if (peek(sqlChars, i, quotes[QUOTE_START_DELIMITER][d])) {
|
|
delimiter = d;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Consume the initial identifier delimiter
|
|
for (int d = 0; d < quotes[QUOTE_START_DELIMITER][delimiter].length; d++)
|
|
render.sql(sqlChars[i++]);
|
|
|
|
// Consume the whole identifier
|
|
identifierLoop:
|
|
for (;;) {
|
|
|
|
|
|
// [#9648] The "identifier" might not be one, if we're inside
|
|
// of some vendor specific comment syntax
|
|
if (i >= sqlChars.length)
|
|
break characterLoop;
|
|
|
|
// Consume an escaped quote
|
|
else if (peek(sqlChars, i, quotes[QUOTE_END_DELIMITER_ESCAPED][delimiter])) {
|
|
for (int d = 0; d < quotes[QUOTE_END_DELIMITER_ESCAPED][delimiter].length; d++)
|
|
render.sql(sqlChars[i++]);
|
|
|
|
continue identifierLoop;
|
|
}
|
|
|
|
// Break on the terminal identifier delimiter
|
|
else if (peek(sqlChars, i, quotes[QUOTE_END_DELIMITER][delimiter]))
|
|
break identifierLoop;
|
|
|
|
// Consume identifier content
|
|
render.sql(sqlChars[i++]);
|
|
}
|
|
|
|
// Consume the terminal identifier delimiter
|
|
for (int d = 0; d < quotes[QUOTE_END_DELIMITER][delimiter].length; d++) {
|
|
if (d > 0)
|
|
i++;
|
|
|
|
render.sql(sqlChars[i]);
|
|
}
|
|
}
|
|
|
|
// Inline bind variables only outside of string literals
|
|
else if (substituteIndex < substitutes.size() &&
|
|
((sqlChars[i] == '?')
|
|
|
|
// [#4131] Named bind variables of the form :identifier
|
|
// Watch out for the PostgreSQL cast operator ::
|
|
|| (sqlChars[i] == ':'
|
|
&& i + 1 < sqlChars.length && isJavaIdentifierPart(sqlChars[i + 1])
|
|
&&(i - 1 < 0 || sqlChars[i - 1] != ':')))) {
|
|
|
|
// [#5307] Consume PostgreSQL style operators. These aren't bind variables!
|
|
if (sqlChars[i] == '?' && i + 1 < sqlChars.length && SUPPORT_NON_BIND_VARIABLE_SUFFIXES.contains(ctx.dialect())) {
|
|
|
|
nonBindSuffixLoop:
|
|
for (char[] candidate : NON_BIND_VARIABLE_SUFFIXES) {
|
|
if (peek(sqlChars, i + 1, candidate)) {
|
|
for (char[] exclude : BIND_VARIABLE_SUFFIXES)
|
|
if (peek(sqlChars, i + 1, exclude))
|
|
continue nonBindSuffixLoop;
|
|
|
|
for (int j = i; i - j <= candidate.length; i++)
|
|
render.sql(sqlChars[i]);
|
|
|
|
render.sql(sqlChars[i]);
|
|
continue characterLoop;
|
|
}
|
|
}
|
|
}
|
|
|
|
// [#4131] Consume the named bind variable
|
|
if (sqlChars[i] == ':')
|
|
while (i + 1 < sqlChars.length && isJavaIdentifierPart(sqlChars[i + 1]))
|
|
i++;
|
|
|
|
QueryPart substitute = substitutes.get(substituteIndex++);
|
|
|
|
if (render.paramType() == INLINED || render.paramType() == NAMED || render.paramType() == NAMED_OR_INLINED) {
|
|
render.visit(substitute);
|
|
}
|
|
else {
|
|
CastMode previous = render.castMode();
|
|
render.castMode(CastMode.NEVER)
|
|
.visit(substitute)
|
|
.castMode(previous);
|
|
}
|
|
|
|
if (bind != null)
|
|
bind.visit(substitute);
|
|
}
|
|
|
|
// [#1432] Inline substitues for {numbered placeholders} outside of string literals
|
|
else if (sqlChars[i] == '{') {
|
|
|
|
// [#1461] Be careful not to match any JDBC escape syntax
|
|
if (peekAny(sqlChars, i, JDBC_ESCAPE_PREFIXES, true)) {
|
|
render.sql(sqlChars[i]);
|
|
}
|
|
|
|
// Consume the whole token
|
|
else {
|
|
int start = ++i;
|
|
for (; i < sqlChars.length && sqlChars[i] != '}'; i++);
|
|
int end = i;
|
|
|
|
// Try getting the {numbered placeholder}
|
|
Integer index = Ints.tryParse(sql, start, end);
|
|
if (index != null) {
|
|
if (index < 0 || index >= substitutes.size())
|
|
throw new TemplatingException("No substitute QueryPart provided for placeholder {" + index + "} in plain SQL template: " + sql);
|
|
|
|
QueryPart substitute = substitutes.get(index);
|
|
render.visit(substitute);
|
|
|
|
if (bind != null)
|
|
bind.visit(substitute);
|
|
}
|
|
else {
|
|
// Then we're dealing with a {keyword}
|
|
render.visit(DSL.keyword(sql.substring(start, end)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Any other character
|
|
else {
|
|
render.sql(sqlChars[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Whether backslash escaping is needed in inlined string literals.
|
|
*/
|
|
static final boolean needsBackslashEscaping(Configuration configuration) {
|
|
BackslashEscaping escaping = getBackslashEscaping(configuration.settings());
|
|
return escaping == ON || (escaping == DEFAULT && REQUIRES_BACKSLASH_ESCAPING.contains(configuration.dialect()));
|
|
}
|
|
|
|
/**
|
|
* Peek for a string at a given <code>index</code> of a <code>char[]</code>
|
|
*
|
|
* @param sqlChars The char array to peek into
|
|
* @param index The index within the char array to peek for a string
|
|
* @param peek The string to peek for
|
|
*/
|
|
static final boolean peek(char[] sqlChars, int index, char[] peek) {
|
|
return peek(sqlChars, index, peek, false);
|
|
}
|
|
|
|
/**
|
|
* Peek for a string at a given <code>index</code> of a <code>char[]</code>
|
|
*
|
|
* @param sqlChars The char array to peek into
|
|
* @param index The index within the char array to peek for a string
|
|
* @param peek The string to peek for
|
|
* @param anyWhitespace A whitespace character in <code>peekAny</code>
|
|
* represents "any" whitespace character as defined in
|
|
* {@link #WHITESPACE_CHARACTERS}, or in Java Regex "\s".
|
|
*/
|
|
static final boolean peek(char[] sqlChars, int index, char[] peek, boolean anyWhitespace) {
|
|
|
|
peekArrayLoop:
|
|
for (int i = 0; i < peek.length; i++) {
|
|
if (index + i >= sqlChars.length)
|
|
return false;
|
|
|
|
if (sqlChars[index + i] != peek[i]) {
|
|
|
|
// [#3430] In some cases, we don't care about the type of whitespace.
|
|
if (anyWhitespace && peek[i] == ' ')
|
|
for (char c : WHITESPACE_CHARACTERS)
|
|
if (sqlChars[index + i] == c)
|
|
continue peekArrayLoop;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Peek for several strings at a given <code>index</code> of a <code>char[]</code>
|
|
*
|
|
* @param sqlChars The char array to peek into
|
|
* @param index The index within the char array to peek for a string
|
|
* @param peekAny The strings to peek for
|
|
*/
|
|
static final boolean peekAny(char[] sqlChars, int index, char[][] peekAny) {
|
|
return peekAny(sqlChars, index, peekAny, false);
|
|
}
|
|
|
|
/**
|
|
* Peek for several strings at a given <code>index</code> of a
|
|
* <code>char[]</code>
|
|
*
|
|
* @param sqlChars The char array to peek into
|
|
* @param index The index within the char array to peek for a string
|
|
* @param peekAny The strings to peek for
|
|
* @param anyWhitespace A whitespace character in <code>peekAny</code>
|
|
* represents "any" whitespace character as defined in
|
|
* {@link #WHITESPACE_CHARACTERS}, or in Java Regex "\s".
|
|
*/
|
|
static final boolean peekAny(char[] sqlChars, int index, char[][] peekAny, boolean anyWhitespace) {
|
|
for (char[] peek : peekAny)
|
|
if (peek(sqlChars, index, peek, anyWhitespace))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Create {@link QueryPart} objects from bind values or substitutes
|
|
*/
|
|
static final List<QueryPart> queryParts(Object... substitutes) {
|
|
// [#724] When bindings is null, this is probably due to API-misuse
|
|
// The user probably meant new Object[] { null }
|
|
if (substitutes == null) {
|
|
return queryParts(new Object[] { null });
|
|
}
|
|
else {
|
|
List<QueryPart> result = new ArrayList<>(substitutes.length);
|
|
|
|
for (Object substitute : substitutes) {
|
|
|
|
// [#1432] Distinguish between QueryParts and other objects
|
|
if (substitute instanceof QueryPart) { QueryPart q = (QueryPart) substitute;
|
|
result.add(q);
|
|
}
|
|
else {
|
|
@SuppressWarnings("unchecked")
|
|
Class<Object> type = (Class<Object>) (substitute != null ? substitute.getClass() : Object.class);
|
|
result.add(new Val<>(substitute, DSL.getDataType(type)));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static final <T> T[] combine(T value, T[] array) {
|
|
T[] result = (T[]) java.lang.reflect.Array.newInstance(array.getClass().getComponentType(), array.length + 1);
|
|
result[0] = value;
|
|
System.arraycopy(array, 0, result, 1, array.length);
|
|
return result;
|
|
}
|
|
|
|
static final <T> T[] combine(T[] array, T value) {
|
|
T[] result = Arrays.copyOf(array, array.length + 1);
|
|
result[array.length] = value;
|
|
return result;
|
|
}
|
|
|
|
static final <T> T[] combine(T[] a1, T[] a2) {
|
|
T[] result = Arrays.copyOf(a1, a1.length + a2.length);
|
|
System.arraycopy(a2, 0, result, a1.length, a2.length);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Combine a field with an array of fields
|
|
*/
|
|
static final Field<?>[] combine(Field<?> field, Field<?>... fields) {
|
|
if (fields == null) {
|
|
return new Field[] { field };
|
|
}
|
|
else {
|
|
Field<?>[] result = new Field<?>[fields.length + 1];
|
|
result[0] = field;
|
|
System.arraycopy(fields, 0, result, 1, fields.length);
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Combine a field with an array of fields
|
|
*/
|
|
static final Field<?>[] combine(Field<?> field1, Field<?> field2, Field<?>... fields) {
|
|
if (fields == null) {
|
|
return new Field[] { field1, field2 };
|
|
}
|
|
else {
|
|
Field<?>[] result = new Field<?>[fields.length + 2];
|
|
result[0] = field1;
|
|
result[1] = field2;
|
|
System.arraycopy(fields, 0, result, 2, fields.length);
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Combine a field with an array of fields
|
|
*/
|
|
static final Field<?>[] combine(Field<?> field1, Field<?> field2, Field<?> field3, Field<?>... fields) {
|
|
if (fields == null) {
|
|
return new Field[] { field1, field2, field3 };
|
|
}
|
|
else {
|
|
Field<?>[] result = new Field<?>[fields.length + 3];
|
|
result[0] = field1;
|
|
result[1] = field2;
|
|
result[2] = field3;
|
|
System.arraycopy(fields, 0, result, 3, fields.length);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Translate a {@link R2dbcException} to a {@link DataAccessException}
|
|
*/
|
|
static final RuntimeException translate(String sql, Throwable t) {
|
|
if (t instanceof R2dbcException)
|
|
return translate(sql, (R2dbcException) t);
|
|
else if (t instanceof SQLException)
|
|
return translate(sql, (SQLException) t);
|
|
else if (t instanceof RuntimeException)
|
|
return translate(sql, (RuntimeException) t);
|
|
else if (t != null)
|
|
return new DataAccessException("SQL [" + sql + "]; Unspecified Throwable", t);
|
|
else
|
|
return new DataAccessException("SQL [" + sql + "]; Unspecified Throwable");
|
|
}
|
|
|
|
/**
|
|
* Translate a {@link R2dbcException} to a {@link DataAccessException}
|
|
*/
|
|
static final DataAccessException translate(String sql, R2dbcException e) {
|
|
if (e != null)
|
|
return new DataAccessException("SQL [" + sql + "]; " + e.getMessage(), e);
|
|
else
|
|
return new DataAccessException("SQL [" + sql + "]; Unspecified R2dbcException");
|
|
}
|
|
|
|
/**
|
|
* Translate a {@link SQLException} to a {@link DataAccessException}
|
|
*/
|
|
static final DataAccessException translate(String sql, SQLException e) {
|
|
if (e != null)
|
|
return new DataAccessException("SQL [" + sql + "]; " + e.getMessage(), e);
|
|
else
|
|
return new DataAccessException("SQL [" + sql + "]; Unspecified SQLException");
|
|
}
|
|
|
|
/**
|
|
* Translate a {@link RuntimeException} to a {@link DataAccessException}
|
|
*/
|
|
static final RuntimeException translate(String sql, RuntimeException e) {
|
|
if (e != null)
|
|
return e;
|
|
else
|
|
return new DataAccessException("SQL [" + sql + "]; Unspecified RuntimeException");
|
|
}
|
|
|
|
/**
|
|
* Safely close a statement
|
|
*/
|
|
static final void safeClose(ExecuteListener listener, ExecuteContext ctx) {
|
|
safeClose(listener, ctx, false);
|
|
}
|
|
|
|
/**
|
|
* Safely close a statement
|
|
*/
|
|
static final void safeClose(ExecuteListener listener, ExecuteContext ctx, boolean keepStatement) {
|
|
safeClose(listener, ctx, keepStatement, true);
|
|
}
|
|
|
|
/**
|
|
* Safely close a statement
|
|
*/
|
|
static final void safeClose(ExecuteListener listener, ExecuteContext ctx, boolean keepStatement, boolean keepResultSet) {
|
|
// [#2523] Set JDBC objects to null, to prevent repeated closing
|
|
JDBCUtils.safeClose(ctx.resultSet());
|
|
ctx.resultSet(null);
|
|
|
|
PreparedStatement statement = ctx.statement();
|
|
if (statement != null)
|
|
consumeWarnings(ctx, listener);
|
|
|
|
// [#385] Close statements only if not requested to keep open
|
|
if (!keepStatement) {
|
|
if (statement != null) {
|
|
JDBCUtils.safeClose(statement);
|
|
ctx.statement(null);
|
|
}
|
|
|
|
// [#3234] We must ensure that any connection we may still have will be released,
|
|
// in the event of an exception
|
|
else {
|
|
Connection connection = localConnection();
|
|
|
|
// [#4277] We must release the connection on the ExecuteContext's
|
|
// ConnectionProvider, as the ctx.configuration().connectionProvider()
|
|
// is replaced by a ExecuteContextConnectionProvider instance.
|
|
if (connection != null && ((DefaultExecuteContext) ctx).connectionProvider != null)
|
|
((DefaultExecuteContext) ctx).connectionProvider.release(connection);
|
|
}
|
|
}
|
|
|
|
// [#1868] [#2373] Terminate ExecuteListener lifecycle, if needed
|
|
if (keepResultSet)
|
|
listener.end(ctx);
|
|
|
|
// [#1326] Clean up any potentially remaining temporary lobs
|
|
DefaultExecuteContext.clean();
|
|
}
|
|
|
|
/**
|
|
* Type-safely copy a value from one record to another
|
|
*/
|
|
static final <T> void setValue(Record target, Field<T> targetField, Record source, Field<?> sourceField) {
|
|
setValue(target, targetField, source.get(sourceField));
|
|
}
|
|
|
|
/**
|
|
* Type-safely copy a value from one record to another
|
|
*/
|
|
static final <T> void setValue(AbstractRecord target, Field<T> targetField, int targetIndex, Record source, int sourceIndex) {
|
|
setValue(target, targetField, targetIndex, source.get(sourceIndex));
|
|
}
|
|
|
|
/**
|
|
* Type-safely set a value to a record
|
|
*/
|
|
static final <T> void setValue(Record target, Field<T> targetField, Object value) {
|
|
target.set(targetField, targetField.getDataType().convert(value));
|
|
}
|
|
|
|
/**
|
|
* Type-safely set a value to a record
|
|
*/
|
|
static final <T> void setValue(AbstractRecord target, Field<T> targetField, int targetIndex, Object value) {
|
|
target.set(targetField, targetIndex, targetField.getDataType().convert(value));
|
|
}
|
|
|
|
/**
|
|
* [#2591] Type-safely copy a value from one record to another, preserving flags.
|
|
*/
|
|
static final <T> void copyValue(AbstractRecord target, Field<T> targetField, Record source, Field<?> sourceField) {
|
|
DataType<T> targetType = targetField.getDataType();
|
|
|
|
int targetIndex = indexOrFail(target.fieldsRow(), targetField);
|
|
int sourceIndex = indexOrFail(source.fieldsRow(), sourceField);
|
|
|
|
target.values[targetIndex] = targetType.convert(source.get(sourceIndex));
|
|
target.originals[targetIndex] = targetType.convert(source.original(sourceIndex));
|
|
target.changed.set(targetIndex, source.changed(sourceIndex));
|
|
}
|
|
|
|
/**
|
|
* Map a {@link Catalog} according to the configured {@link org.jooq.SchemaMapping}
|
|
*/
|
|
static final Catalog getMappedCatalog(Scope scope, Catalog catalog) {
|
|
if (scope != null) {
|
|
org.jooq.SchemaMapping mapping = (SchemaMapping) scope.data(DataKey.DATA_SCHEMA_MAPPING);
|
|
|
|
if (mapping == null)
|
|
mapping = scope.configuration().schemaMapping();
|
|
|
|
if (mapping != null)
|
|
return mapping.map(catalog);
|
|
}
|
|
|
|
return catalog;
|
|
}
|
|
|
|
/**
|
|
* Map a {@link Schema} according to the configured {@link org.jooq.SchemaMapping}
|
|
*/
|
|
static final Schema getMappedSchema(Scope scope, Schema schema) {
|
|
if (scope != null) {
|
|
org.jooq.SchemaMapping mapping = (SchemaMapping) scope.data(DataKey.DATA_SCHEMA_MAPPING);
|
|
|
|
if (mapping == null)
|
|
mapping = scope.configuration().schemaMapping();
|
|
|
|
if (mapping != null)
|
|
return mapping.map(schema);
|
|
}
|
|
|
|
return schema;
|
|
}
|
|
|
|
/**
|
|
* Map a {@link Table} according to the configured {@link org.jooq.SchemaMapping}
|
|
*/
|
|
static final <R extends Record> Table<R> getMappedTable(Scope scope, Table<R> table) {
|
|
if (scope != null) {
|
|
org.jooq.SchemaMapping mapping = (SchemaMapping) scope.data(DataKey.DATA_SCHEMA_MAPPING);
|
|
|
|
if (mapping == null)
|
|
mapping = scope.configuration().schemaMapping();
|
|
|
|
if (mapping != null)
|
|
return mapping.map(table);
|
|
}
|
|
|
|
return table;
|
|
}
|
|
|
|
/**
|
|
* Map an {@link QualifiedRecord} according to the configured
|
|
* {@link org.jooq.SchemaMapping}
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
static final String getMappedUDTName(Scope scope, Class<? extends QualifiedRecord<?>> type) {
|
|
return getMappedUDTName(scope, Tools.newRecord(false, (Class<QualifiedRecord<?>>) type).operate(null));
|
|
}
|
|
|
|
/**
|
|
* Map an {@link QualifiedRecord} according to the configured
|
|
* {@link org.jooq.SchemaMapping}
|
|
*/
|
|
static final String getMappedUDTName(Scope scope, QualifiedRecord<?> record) {
|
|
RecordQualifier<?> udt = record.getQualifier();
|
|
Schema mapped = getMappedSchema(scope, udt.getSchema());
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
if (mapped != null && !"".equals(mapped.getName()))
|
|
sb.append(mapped.getName()).append('.');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sb.append(record.getQualifier().getName());
|
|
|
|
|
|
|
|
|
|
|
|
return sb.toString();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static final DSLContext CTX = DSL.using(new DefaultConfiguration());
|
|
|
|
/**
|
|
* A possibly inefficient but stable way to generate an alias for any
|
|
* {@link QueryPart}.
|
|
* <p>
|
|
* Stability is important to profit from execution plan caching. Equal query
|
|
* parts must produce the same alias every time.
|
|
*/
|
|
static final String autoAlias(QueryPart part) {
|
|
return "alias_" + hash(part);
|
|
}
|
|
|
|
/**
|
|
* A possibly inefficient but stable way to generate an alias for any
|
|
* {@link QueryPart}.
|
|
* <p>
|
|
* Stability is important to profit from execution plan caching. Equal query
|
|
* parts must produce the same alias every time.
|
|
*/
|
|
static final Name autoAliasName(QueryPart part) {
|
|
return DSL.name(autoAlias(part));
|
|
}
|
|
|
|
/**
|
|
* Return a non-negative hash code for a {@link QueryPart}, taking into
|
|
* account FindBugs' <code>RV_ABSOLUTE_VALUE_OF_HASHCODE</code> pattern
|
|
*/
|
|
static final int hash(QueryPart part) {
|
|
|
|
// [#6025] Prevent unstable alias generation for derived tables due to
|
|
// inlined bind variables in hashCode() calculation
|
|
// [#6175] TODO: Speed this up with a faster way to calculate a hash code
|
|
return 0x7FFFFFF & CTX.render(part).hashCode();
|
|
}
|
|
|
|
/**
|
|
* Utility method to escape string fields, or cast other fields
|
|
*/
|
|
static final Field<String> escapeForLike(Field<?> field) {
|
|
return escapeForLike(field, new DefaultConfiguration());
|
|
}
|
|
|
|
/**
|
|
* Utility method to escape string fields, or cast other fields
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
static final Field<String> escapeForLike(Field<?> field, Configuration configuration) {
|
|
if (nullSafeDataType(field).isString()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
return escape((Field<String>) field, ESCAPE);
|
|
}
|
|
}
|
|
else {
|
|
return castIfNeeded(field, String.class);
|
|
}
|
|
}
|
|
|
|
static final boolean isParam(Field<?> field) {
|
|
return field instanceof Param;
|
|
}
|
|
|
|
static final boolean isVal(Field<?> field) {
|
|
return field instanceof Val
|
|
|| field instanceof ConvertedVal && ((ConvertedVal<?>) field).delegate instanceof Val;
|
|
}
|
|
|
|
static final boolean isWindow(QueryPart part) {
|
|
return part instanceof AbstractWindowFunction && ((AbstractWindowFunction<?>) part).isWindow();
|
|
}
|
|
|
|
static final boolean isSimple(Context<?> ctx, QueryPart part) {
|
|
return part instanceof SimpleQueryPart && ((SimpleQueryPart) part).isSimple(ctx);
|
|
}
|
|
|
|
static final boolean isSimple(Context<?> ctx, QueryPart... parts) {
|
|
for (QueryPart part : parts)
|
|
if (!isSimple(ctx, part))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static final boolean isRendersSeparator(QueryPart part) {
|
|
return part instanceof SeparatedQueryPart && ((SeparatedQueryPart) part).rendersSeparator();
|
|
}
|
|
|
|
static final boolean isPossiblyNullable(Field<?> f) {
|
|
return f instanceof AbstractField && ((AbstractField<?>) f).isPossiblyNullable();
|
|
}
|
|
|
|
static final Val<?> extractVal(Field<?> field) {
|
|
return field instanceof Val
|
|
? (Val<?>) field
|
|
: field instanceof ConvertedVal
|
|
? (Val<?>) ((ConvertedVal<?>) field).delegate
|
|
: null;
|
|
}
|
|
|
|
static final boolean hasDefaultConverter(Field<?> field) {
|
|
return field.getConverter() instanceof IdentityConverter;
|
|
}
|
|
|
|
static final <T> T extractParamValue(Field<T> field) {
|
|
if (isParam(field))
|
|
return ((Param<T>) field).getValue();
|
|
else
|
|
return null;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
|
static final <R extends Record> SelectQueryImpl<R> selectQueryImpl(QueryPart part) {
|
|
if (part instanceof SelectQueryImpl)
|
|
return (SelectQueryImpl) part;
|
|
else if (part instanceof SelectImpl)
|
|
return (SelectQueryImpl<R>) ((SelectImpl) part).getDelegate();
|
|
else if (part instanceof ScalarSubquery)
|
|
return selectQueryImpl(((ScalarSubquery<?>) part).query);
|
|
else if (part instanceof QuantifiedSelectImpl)
|
|
return selectQueryImpl(((QuantifiedSelectImpl<?>) part).query);
|
|
else
|
|
return null;
|
|
}
|
|
|
|
static final AbstractResultQuery<?> abstractResultQuery(Query query) {
|
|
if (query instanceof AbstractResultQuery)
|
|
return (AbstractResultQuery<?>) query;
|
|
else if (query instanceof AbstractDelegatingQuery)
|
|
return abstractResultQuery(((AbstractDelegatingQuery<?, ?>) query).getDelegate());
|
|
else
|
|
return null;
|
|
}
|
|
|
|
static final InsertQueryImpl<?> insertQueryImpl(Query query) {
|
|
AbstractDMLQuery<?> result = abstractDMLQuery(query);
|
|
|
|
if (result instanceof InsertQueryImpl)
|
|
return (InsertQueryImpl<?>) result;
|
|
else
|
|
return null;
|
|
}
|
|
|
|
static final UpdateQueryImpl<?> updateQueryImpl(Query query) {
|
|
AbstractDMLQuery<?> result = abstractDMLQuery(query);
|
|
|
|
if (result instanceof UpdateQueryImpl)
|
|
return (UpdateQueryImpl<?>) result;
|
|
else
|
|
return null;
|
|
}
|
|
|
|
static final DeleteQueryImpl<?> deleteQueryImpl(Query query) {
|
|
AbstractDMLQuery<?> result = abstractDMLQuery(query);
|
|
|
|
if (result instanceof DeleteQueryImpl)
|
|
return (DeleteQueryImpl<?>) result;
|
|
else
|
|
return null;
|
|
}
|
|
|
|
static final AbstractDMLQuery<?> abstractDMLQuery(Query query) {
|
|
if (query instanceof AbstractDMLQuery)
|
|
return (AbstractDMLQuery<?>) query;
|
|
else if (query instanceof AbstractDelegatingDMLQuery)
|
|
return abstractDMLQuery(((AbstractDelegatingDMLQuery<?, ?>) query).getDelegate());
|
|
else if (query instanceof DMLQueryAsResultQuery)
|
|
return ((DMLQueryAsResultQuery<?, ?>) query).getDelegate();
|
|
else
|
|
return null;
|
|
}
|
|
|
|
static final int degree(ResultQuery<?> query) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return query.fields().length;
|
|
}
|
|
|
|
static final List<DataType<?>> dataTypes(ResultQuery<?> query) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return map(query.fields(), f -> f.getDataType());
|
|
}
|
|
|
|
static final DataType<?> scalarType(ResultQuery<?> query) {
|
|
List<DataType<?>> list = dataTypes(query);
|
|
|
|
if (list.size() != 1)
|
|
throw new IllegalStateException("Only single-column selects have a scalar type");
|
|
|
|
return list.get(0);
|
|
}
|
|
|
|
/**
|
|
* Add primary key conditions to a query
|
|
*/
|
|
static final void addConditions(org.jooq.ConditionProvider query, Record record, Field<?>... keys) {
|
|
for (Field<?> field : keys)
|
|
addCondition(query, record, field);
|
|
}
|
|
|
|
/**
|
|
* Add a field condition to a query
|
|
*/
|
|
static final <T> void addCondition(org.jooq.ConditionProvider provider, Record record, Field<T> field) {
|
|
|
|
// [#2764] If primary keys are allowed to be changed, the
|
|
if (updatablePrimaryKeys(settings(record)))
|
|
provider.addConditions(condition(field, record.original(field)));
|
|
else
|
|
provider.addConditions(condition(field, record.get(field)));
|
|
}
|
|
|
|
/**
|
|
* Create a <code>null</code>-safe condition.
|
|
*/
|
|
static final <T> Condition condition(Field<T> field, T value) {
|
|
return (value == null) ? field.isNull() : field.eq(value);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// XXX: Reflection utilities used for POJO mapping
|
|
// ------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Check if JPA classes can be loaded. This is only done once per JVM!
|
|
*/
|
|
static final JPANamespace jpaNamespace() {
|
|
if (jpaNamespace == null) {
|
|
synchronized (initLock) {
|
|
if (jpaNamespace == null) {
|
|
try {
|
|
Class.forName(Column.class.getName());
|
|
jpaNamespace = JPANamespace.JAKARTA;
|
|
}
|
|
catch (Throwable e) {
|
|
try {
|
|
Class.forName("javax.persistence.Column");
|
|
jpaNamespace = JPANamespace.JAVAX;
|
|
JooqLogger.getLogger(Tools.class, "isJPAAvailable", 1).info("javax.persistence.Column was found on the classpath instead of jakarta.persistence.Column. jOOQ 3.16 requires you to upgrade to Jakarta EE if you wish to use JPA annotations in your DefaultRecordMapper");
|
|
}
|
|
catch (Throwable ignore) {
|
|
jpaNamespace = JPANamespace.NONE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return jpaNamespace;
|
|
}
|
|
|
|
enum JPANamespace {
|
|
JAVAX, JAKARTA, NONE
|
|
}
|
|
|
|
static final boolean isKotlinAvailable() {
|
|
if (isKotlinAvailable == null) {
|
|
synchronized (initLock) {
|
|
if (isKotlinAvailable == null) {
|
|
try {
|
|
if (ktJvmClassMapping() != null) {
|
|
if (ktKClasses() != null) {
|
|
isKotlinAvailable = true;
|
|
}
|
|
else {
|
|
isKotlinAvailable = false;
|
|
log.info("Kotlin is available, but not kotlin-reflect. Add the kotlin-reflect dependency to better use Kotlin features like data classes");
|
|
}
|
|
}
|
|
else {
|
|
isKotlinAvailable = false;
|
|
}
|
|
}
|
|
catch (ReflectException e) {
|
|
isKotlinAvailable = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return isKotlinAvailable;
|
|
}
|
|
|
|
static final Reflect ktJvmClassMapping() {
|
|
if (ktJvmClassMapping == null) {
|
|
synchronized (initLock) {
|
|
if (ktJvmClassMapping == null) {
|
|
try {
|
|
ktJvmClassMapping = Reflect.onClass("kotlin.jvm.JvmClassMappingKt");
|
|
}
|
|
catch (ReflectException ignore) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ktJvmClassMapping;
|
|
}
|
|
|
|
static final Reflect ktKClasses() {
|
|
if (ktKClasses == null) {
|
|
synchronized (initLock) {
|
|
if (ktKClasses == null) {
|
|
try {
|
|
ktKClasses = Reflect.onClass("kotlin.reflect.full.KClasses");
|
|
}
|
|
catch (ReflectException ignore) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ktKClasses;
|
|
}
|
|
|
|
static final Reflect ktKClass() {
|
|
if (ktKClass == null) {
|
|
synchronized (initLock) {
|
|
if (ktKClass == null) {
|
|
try {
|
|
ktKClass = Reflect.onClass("kotlin.reflect.KClass");
|
|
}
|
|
catch (ReflectException ignore) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ktKClass;
|
|
}
|
|
|
|
static final Reflect ktKTypeParameter() {
|
|
if (ktKTypeParameter == null) {
|
|
synchronized (initLock) {
|
|
if (ktKTypeParameter == null) {
|
|
try {
|
|
ktKTypeParameter = Reflect.onClass("kotlin.reflect.KTypeParameter");
|
|
}
|
|
catch (ReflectException ignore) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ktKTypeParameter;
|
|
}
|
|
|
|
/**
|
|
* Check whether <code>type</code> has any {@link Column} annotated members
|
|
* or methods
|
|
*/
|
|
static final boolean hasColumnAnnotations(final Configuration configuration, final Class<?> type) {
|
|
return Cache.run(configuration, () -> {
|
|
switch (Tools.jpaNamespace()) {
|
|
case JAVAX:
|
|
if (anyMatch(type.getAnnotations(), a -> a.annotationType().getName().startsWith("javax.persistence.")))
|
|
JooqLogger.getLogger(Tools.class, "hasColumnAnnotations", 1).warn("Type " + type + " is annotated with javax.persistence annotation for usage in DefaultRecordMapper, but starting from jOOQ 3.16, only JakartaEE annotations are supported.");
|
|
|
|
return false;
|
|
|
|
case JAKARTA:
|
|
|
|
// An @Entity or @Table usually has @Column annotations, too
|
|
if (type.getAnnotation(Entity.class) != null)
|
|
return true;
|
|
|
|
if (type.getAnnotation(jakarta.persistence.Table.class) != null)
|
|
return true;
|
|
|
|
if (anyMatch(getInstanceMembers(type), m ->
|
|
m.getAnnotation(Column.class) != null
|
|
|| m.getAnnotation(Id.class) != null))
|
|
return true;
|
|
else
|
|
return anyMatch(getInstanceMethods(type), m -> m.getAnnotation(Column.class) != null);
|
|
|
|
case NONE:
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
}, REFLECTION_CACHE_HAS_COLUMN_ANNOTATIONS, () -> type);
|
|
}
|
|
|
|
static final <T extends AccessibleObject> T accessible(T object, boolean makeAccessible) {
|
|
return makeAccessible ? Reflect.accessible(object) : object;
|
|
}
|
|
|
|
/**
|
|
* Get all members annotated with a given column name
|
|
*/
|
|
static final List<java.lang.reflect.Field> getAnnotatedMembers(
|
|
final Configuration configuration,
|
|
final Class<?> type,
|
|
final String name,
|
|
final boolean makeAccessible
|
|
) {
|
|
return Cache.run(configuration, () -> {
|
|
List<java.lang.reflect.Field> result = new ArrayList<>();
|
|
|
|
for (java.lang.reflect.Field member : getInstanceMembers(type)) {
|
|
Column column = member.getAnnotation(Column.class);
|
|
|
|
if (column != null) {
|
|
if (namesMatch(name, column.name()))
|
|
result.add(accessible(member, makeAccessible));
|
|
}
|
|
|
|
else {
|
|
Id id = member.getAnnotation(Id.class);
|
|
|
|
if (id != null)
|
|
if (namesMatch(name, member.getName()))
|
|
result.add(accessible(member, makeAccessible));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}, REFLECTION_CACHE_GET_ANNOTATED_MEMBERS, () -> Cache.key(type, name));
|
|
}
|
|
|
|
private static final boolean namesMatch(String name, String annotation) {
|
|
|
|
// [#4128] JPA @Column.name() properties are case-insensitive, unless
|
|
// the names are quoted using double quotes.
|
|
return annotation.startsWith("\"")
|
|
? ('"' + name + '"').equals(annotation)
|
|
: name.equalsIgnoreCase(annotation);
|
|
}
|
|
|
|
/**
|
|
* Get all members matching a given column name
|
|
*/
|
|
static final List<java.lang.reflect.Field> getMatchingMembers(
|
|
final Configuration configuration,
|
|
final Class<?> type,
|
|
final String name,
|
|
final boolean makeAccessible
|
|
) {
|
|
return Cache.run(configuration, () -> {
|
|
List<java.lang.reflect.Field> result = new ArrayList<>();
|
|
|
|
// [#1942] Caching these values before the field-loop significantly
|
|
// accerates POJO mapping
|
|
String camelCaseLC = StringUtils.toCamelCaseLC(name);
|
|
|
|
for (java.lang.reflect.Field member : getInstanceMembers(type))
|
|
if (name.equals(member.getName()))
|
|
result.add(accessible(member, makeAccessible));
|
|
else if (camelCaseLC.equals(member.getName()))
|
|
result.add(accessible(member, makeAccessible));
|
|
|
|
return result;
|
|
}, REFLECTION_CACHE_GET_MATCHING_MEMBERS, () -> Cache.key(type, name));
|
|
}
|
|
|
|
/**
|
|
* Get all setter methods annotated with a given column name
|
|
*/
|
|
static final List<Method> getAnnotatedSetters(
|
|
final Configuration configuration,
|
|
final Class<?> type,
|
|
final String name,
|
|
final boolean makeAccessible
|
|
) {
|
|
return Cache.run(configuration, () -> {
|
|
Set<SourceMethod> set = new LinkedHashSet<>();
|
|
|
|
for (Method method : getInstanceMethods(type)) {
|
|
Column column = method.getAnnotation(Column.class);
|
|
|
|
if (column != null && namesMatch(name, column.name())) {
|
|
|
|
// Annotated setter
|
|
if (method.getParameterTypes().length == 1) {
|
|
set.add(new SourceMethod(accessible(method, makeAccessible)));
|
|
}
|
|
|
|
// Annotated getter with matching setter
|
|
else if (method.getParameterTypes().length == 0) {
|
|
String m = method.getName();
|
|
String suffix = m.startsWith("get")
|
|
? m.substring(3)
|
|
: m.startsWith("is")
|
|
? m.substring(2)
|
|
: null;
|
|
|
|
if (suffix != null) {
|
|
try {
|
|
|
|
// [#7953] [#8496] Search the hierarchy for a matching setter
|
|
Method setter = getInstanceMethod(type, "set" + suffix, new Class[] { method.getReturnType() });
|
|
|
|
// Setter annotation is more relevant
|
|
if (setter.getAnnotation(Column.class) == null)
|
|
set.add(new SourceMethod(accessible(setter, makeAccessible)));
|
|
}
|
|
catch (NoSuchMethodException ignore) {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return SourceMethod.methods(set);
|
|
}, REFLECTION_CACHE_GET_ANNOTATED_SETTERS, () -> Cache.key(type, name));
|
|
}
|
|
|
|
/**
|
|
* Get the first getter method annotated with a given column name
|
|
*/
|
|
static final Method getAnnotatedGetter(
|
|
final Configuration configuration,
|
|
final Class<?> type,
|
|
final String name,
|
|
final boolean makeAccessible
|
|
) {
|
|
return Cache.run(configuration, () -> {
|
|
for (Method method : getInstanceMethods(type)) {
|
|
Column column = method.getAnnotation(Column.class);
|
|
|
|
if (column != null && namesMatch(name, column.name())) {
|
|
|
|
// Annotated getter
|
|
if (method.getParameterTypes().length == 0) {
|
|
return accessible(method, makeAccessible);
|
|
}
|
|
|
|
// Annotated setter with matching getter
|
|
else if (method.getParameterTypes().length == 1) {
|
|
String m = method.getName();
|
|
|
|
if (m.startsWith("set")) {
|
|
try {
|
|
Method getter1 = type.getMethod("get" + m.substring(3));
|
|
|
|
// Getter annotation is more relevant
|
|
if (getter1.getAnnotation(Column.class) == null)
|
|
return accessible(getter1, makeAccessible);
|
|
}
|
|
catch (NoSuchMethodException ignore1) {}
|
|
|
|
try {
|
|
Method getter2 = type.getMethod("is" + m.substring(3));
|
|
|
|
// Getter annotation is more relevant
|
|
if (getter2.getAnnotation(Column.class) == null)
|
|
return accessible(getter2, makeAccessible);
|
|
}
|
|
catch (NoSuchMethodException ignore2) {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}, REFLECTION_CACHE_GET_ANNOTATED_GETTER, () -> Cache.key(type, name));
|
|
}
|
|
|
|
/**
|
|
* Get all setter methods matching a given column name
|
|
*/
|
|
static final List<Method> getMatchingSetters(
|
|
final Configuration configuration,
|
|
final Class<?> type,
|
|
final String name,
|
|
final boolean makeAccessible
|
|
) {
|
|
return Cache.run(configuration, () -> {
|
|
|
|
// [#8460] Prevent duplicate methods in the call hierarchy
|
|
Set<SourceMethod> set = new LinkedHashSet<>();
|
|
|
|
// [#1942] Caching these values before the method-loop significantly
|
|
// accerates POJO mapping
|
|
String camelCase = StringUtils.toCamelCase(name);
|
|
String camelCaseLC = StringUtils.toLC(camelCase);
|
|
|
|
for (Method method : getInstanceMethods(type)) {
|
|
Class<?>[] parameterTypes = method.getParameterTypes();
|
|
|
|
if (parameterTypes.length == 1)
|
|
if (name.equals(method.getName()))
|
|
set.add(new SourceMethod(accessible(method, makeAccessible)));
|
|
else if (camelCaseLC.equals(method.getName()))
|
|
set.add(new SourceMethod(accessible(method, makeAccessible)));
|
|
else if (("set" + name).equals(method.getName()))
|
|
set.add(new SourceMethod(accessible(method, makeAccessible)));
|
|
else if (("set" + camelCase).equals(method.getName()))
|
|
set.add(new SourceMethod(accessible(method, makeAccessible)));
|
|
}
|
|
|
|
return SourceMethod.methods(set);
|
|
}, REFLECTION_CACHE_GET_MATCHING_SETTERS, () -> Cache.key(type, name));
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the first getter method matching a given column name
|
|
*/
|
|
static final Method getMatchingGetter(
|
|
final Configuration configuration,
|
|
final Class<?> type,
|
|
final String name,
|
|
final boolean makeAccessible
|
|
) {
|
|
return Cache.run(configuration, () -> {
|
|
// [#1942] Caching these values before the method-loop significantly
|
|
// accerates POJO mapping
|
|
String camelCase = StringUtils.toCamelCase(name);
|
|
String camelCaseLC = StringUtils.toLC(camelCase);
|
|
|
|
for (Method method : getInstanceMethods(type))
|
|
if (method.getParameterTypes().length == 0)
|
|
if (name.equals(method.getName()))
|
|
return accessible(method, makeAccessible);
|
|
else if (camelCaseLC.equals(method.getName()))
|
|
return accessible(method, makeAccessible);
|
|
else if (("get" + name).equals(method.getName()))
|
|
return accessible(method, makeAccessible);
|
|
else if (("get" + camelCase).equals(method.getName()))
|
|
return accessible(method, makeAccessible);
|
|
else if (("is" + name).equals(method.getName()))
|
|
return accessible(method, makeAccessible);
|
|
else if (("is" + camelCase).equals(method.getName()))
|
|
return accessible(method, makeAccessible);
|
|
|
|
return null;
|
|
}, REFLECTION_CACHE_GET_MATCHING_GETTER, () -> Cache.key(type, name));
|
|
}
|
|
|
|
/**
|
|
* A wrapper class that re-implements {@link Method#equals(Object)} and
|
|
* {@link Method#hashCode()} based only on the "source signature" (name,
|
|
* parameter types), instead of the "binary signature" (declaring class,
|
|
* name, return type, parameter types).
|
|
*/
|
|
private static final class SourceMethod {
|
|
final Method method;
|
|
|
|
SourceMethod(Method method) {
|
|
this.method = method;
|
|
}
|
|
|
|
static List<Method> methods(Collection<? extends SourceMethod> methods) {
|
|
return map(methods, s -> s.method);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
final int prime = 31;
|
|
int result = 1;
|
|
result = prime * result + ((method == null) ? 0 : method.getName().hashCode());
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (obj instanceof SourceMethod) { SourceMethod s = (SourceMethod) obj;
|
|
Method other = s.method;
|
|
|
|
if (method.getName().equals(other.getName())) {
|
|
Class<?>[] p1 = method.getParameterTypes();
|
|
Class<?>[] p2 = other.getParameterTypes();
|
|
|
|
return Arrays.equals(p1, p2);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return method.toString();
|
|
}
|
|
}
|
|
|
|
private static final Method getInstanceMethod(Class<?> type, String name, Class<?>[] parameters) throws NoSuchMethodException {
|
|
|
|
// first priority: find a public method with exact signature match in class hierarchy
|
|
try {
|
|
return type.getMethod(name, parameters);
|
|
}
|
|
|
|
// second priority: find a private method with exact signature match on declaring class
|
|
catch (NoSuchMethodException e) {
|
|
do {
|
|
try {
|
|
return type.getDeclaredMethod(name, parameters);
|
|
}
|
|
catch (NoSuchMethodException ignore) {}
|
|
|
|
type = type.getSuperclass();
|
|
}
|
|
while (type != null);
|
|
|
|
throw new NoSuchMethodException();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* All the public and declared methods of a type.
|
|
* <p>
|
|
* This method returns each method only once. Public methods are returned
|
|
* first in the resulting set while declared methods are returned
|
|
* afterwards, from lowest to highest type in the type hierarchy.
|
|
*/
|
|
private static final Set<Method> getInstanceMethods(Class<?> type) {
|
|
Set<Method> result = new LinkedHashSet<>();
|
|
|
|
for (Method method : type.getMethods())
|
|
if ((method.getModifiers() & Modifier.STATIC) == 0)
|
|
result.add(method);
|
|
|
|
do
|
|
for (Method method : type.getDeclaredMethods())
|
|
if ((method.getModifiers() & Modifier.STATIC) == 0)
|
|
result.add(method);
|
|
while ((type = type.getSuperclass()) != null);
|
|
|
|
return result;
|
|
}
|
|
|
|
private static final List<java.lang.reflect.Field> getInstanceMembers(Class<?> type) {
|
|
List<java.lang.reflect.Field> result = new ArrayList<>();
|
|
|
|
for (java.lang.reflect.Field field : type.getFields())
|
|
if ((field.getModifiers() & Modifier.STATIC) == 0)
|
|
result.add(field);
|
|
|
|
do
|
|
for (java.lang.reflect.Field field : type.getDeclaredFields())
|
|
if ((field.getModifiers() & Modifier.STATIC) == 0)
|
|
result.add(field);
|
|
while ((type = type.getSuperclass()) != null);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get a property name associated with a getter/setter method name.
|
|
*/
|
|
static final String getPropertyName(String methodName) {
|
|
String name = methodName;
|
|
|
|
if (name.startsWith("is") && name.length() > 2)
|
|
name = name.substring(2, 3).toLowerCase() + name.substring(3);
|
|
else if (name.startsWith("get") && name.length() > 3)
|
|
name = name.substring(3, 4).toLowerCase() + name.substring(4);
|
|
else if (name.startsWith("set") && name.length() > 3)
|
|
name = name.substring(3, 4).toLowerCase() + name.substring(4);
|
|
|
|
return name;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// XXX: JDBC helper methods
|
|
// ------------------------------------------------------------------------
|
|
|
|
/**
|
|
* [#3011] [#3054] [#6390] [#6413] Consume additional exceptions if there
|
|
* are any and append them to the <code>previous</code> exception's
|
|
* {@link SQLException#getNextException()} list.
|
|
*/
|
|
static final void consumeExceptions(Configuration configuration, PreparedStatement stmt, SQLException previous) {
|
|
|
|
// [#6413] Don't consume any additional exceptions if we're throwing only the first.
|
|
ThrowExceptions exceptions = configuration.settings().getThrowExceptions();
|
|
if (exceptions == THROW_FIRST)
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
/**
|
|
* [#3076] Consume warnings from a {@link Statement} and notify listeners.
|
|
*/
|
|
static final void consumeWarnings(ExecuteContext ctx, ExecuteListener listener) {
|
|
|
|
// [#3558] In some databases (e.g. MySQL), the call to PreparedStatement.getWarnings() issues
|
|
// a separate SHOW WARNINGS query. Users may want to avoid this query, explicitly
|
|
if (!Boolean.FALSE.equals(ctx.settings().isFetchWarnings())) {
|
|
try {
|
|
ctx.sqlWarning(ctx.statement().getWarnings());
|
|
}
|
|
|
|
// [#3741] In MySQL, calling SHOW WARNINGS on open streaming result sets can cause issues.
|
|
// while this has been resolved, we should expect the above call to cause other issues, too
|
|
catch (SQLException e) {
|
|
ctx.sqlWarning(new SQLWarning("Could not fetch SQLWarning", e));
|
|
}
|
|
}
|
|
|
|
if (ctx.sqlWarning() != null)
|
|
listener.warning(ctx);
|
|
}
|
|
|
|
/**
|
|
* [#5666] Handle the complexity of each dialect's understanding of
|
|
* correctly calling {@link PreparedStatement#execute()}}.
|
|
*/
|
|
static final SQLException executeStatementAndGetFirstResultSet(ExecuteContext ctx, int skipUpdateCounts) throws SQLException {
|
|
PreparedStatement stmt = ctx.statement();
|
|
|
|
try {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// [#5764] [#11243]
|
|
// Statement batches (or triggers) may produce unexpected update
|
|
// counts, which we have to fetch first, prior to accessing the
|
|
// first ResultSet. Unexpected result sets could be produced as
|
|
// well, but it's much harder to skip them.
|
|
if (skipUpdateCounts > 0) {
|
|
|
|
fetchLoop:
|
|
for (int i = 0; i < maxConsumedResults; i++) {
|
|
boolean result = (i == 0)
|
|
? stmt.execute()
|
|
: stmt.getMoreResults();
|
|
|
|
// The first ResultSet
|
|
if (result) {
|
|
ctx.resultSet(stmt.getResultSet());
|
|
break fetchLoop;
|
|
}
|
|
|
|
// Some DML statement whose results we want to skip
|
|
else {
|
|
int updateCount = stmt.getUpdateCount();
|
|
|
|
// Store the first update count, in case
|
|
if (i == 0) {
|
|
ctx.resultSet(null);
|
|
ctx.rows(updateCount);
|
|
}
|
|
|
|
if (updateCount == -1 || skipUpdateCounts-- == 0)
|
|
break fetchLoop;
|
|
}
|
|
}
|
|
}
|
|
|
|
// [#1232] Avoid executeQuery() in order to handle queries that may
|
|
// not return a ResultSet, e.g. SQLite's pragma foreign_key_list(table)
|
|
else if (stmt.execute()) {
|
|
ctx.resultSet(stmt.getResultSet());
|
|
}
|
|
|
|
else {
|
|
ctx.resultSet(null);
|
|
ctx.rows(stmt.getUpdateCount());
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// [#3011] [#3054] [#6390] [#6413] Consume additional exceptions if there are any
|
|
catch (SQLException e) {
|
|
if (ctx.settings().getThrowExceptions() != THROW_NONE) {
|
|
consumeExceptions(ctx.configuration(), ctx.statement(), e);
|
|
throw e;
|
|
}
|
|
else {
|
|
return e;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* [#3681] Consume all {@link ResultSet}s from a JDBC {@link Statement}.
|
|
*/
|
|
static final void consumeResultSets(ExecuteContext ctx, ExecuteListener listener, Results results, Intern intern, SQLException prev) throws SQLException {
|
|
boolean anyResults = false;
|
|
int i;
|
|
int rows = (ctx.resultSet() == null) ? ctx.rows() : 0;
|
|
|
|
for (i = 0; i < maxConsumedResults; i++) {
|
|
try {
|
|
if (ctx.resultSet() != null) {
|
|
anyResults = true;
|
|
|
|
Field<?>[] fields = new MetaDataFieldProvider(ctx.configuration(), ctx.resultSet().getMetaData()).getFields();
|
|
Cursor<Record> c = new CursorImpl<>(ctx, listener, fields, intern != null ? intern.internIndexes(fields) : null, true, false);
|
|
results.resultsOrRows().add(new ResultOrRowsImpl(c.fetch()));
|
|
}
|
|
else if (prev == null) {
|
|
if (rows != -1)
|
|
results.resultsOrRows().add(new ResultOrRowsImpl(rows));
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (ctx.statement().getMoreResults()) {
|
|
ctx.resultSet(ctx.statement().getResultSet());
|
|
}
|
|
else {
|
|
rows = ctx.statement().getUpdateCount();
|
|
ctx.rows(rows);
|
|
|
|
if (rows != -1)
|
|
ctx.resultSet(null);
|
|
else
|
|
break;
|
|
}
|
|
|
|
prev = null;
|
|
}
|
|
|
|
// [#3011] [#3054] [#6390] [#6413] Consume additional exceptions if there are any
|
|
catch (SQLException e) {
|
|
prev = e;
|
|
|
|
if (ctx.settings().getThrowExceptions() == THROW_NONE) {
|
|
ctx.sqlException(e);
|
|
results.resultsOrRows().add(new ResultOrRowsImpl(Tools.translate(ctx.sql(), e)));
|
|
}
|
|
else {
|
|
consumeExceptions(ctx.configuration(), ctx.statement(), e);
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i == maxConsumedResults)
|
|
log.warn("Maximum consumed results reached: " + maxConsumedResults + ". This is probably a bug. Please report to https://github.com/jOOQ/jOOQ/issues/new");
|
|
|
|
// Call this only when there was at least one ResultSet.
|
|
if (anyResults) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ctx.statement().getMoreResults(Statement.CLOSE_ALL_RESULTS);
|
|
}
|
|
|
|
// [#6413] For consistency reasons, any exceptions that have been placed in ResultOrRow elements must
|
|
// be linked, just as if they were collected using ThrowExceptions == THROW_ALL
|
|
if (ctx.settings().getThrowExceptions() == THROW_NONE) {
|
|
SQLException s1 = null;
|
|
|
|
for (ResultOrRows r : results.resultsOrRows()) {
|
|
DataAccessException d = r.exception();
|
|
|
|
if (d != null && d.getCause() instanceof SQLException) {
|
|
SQLException s2 = (SQLException) d.getCause();
|
|
|
|
if (s1 != null)
|
|
s1.setNextException(s2);
|
|
|
|
s1 = s2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static final Pattern NEW_LINES = Pattern.compile("[\\r\\n]+");
|
|
|
|
static final List<String[]> parseTXT(String string, String nullLiteral) {
|
|
String[] strings = NEW_LINES.split(string);
|
|
|
|
if (strings.length < 2)
|
|
throw new DataAccessException("String must contain at least two lines");
|
|
|
|
|
|
// [#2235] Distinguish between jOOQ's Result.format() and others
|
|
boolean formattedJOOQ = (string.charAt(0) == '+');
|
|
|
|
// [#6832] Distinguish between Oracle's format and others
|
|
boolean formattedOracle = (string.charAt(0) == '-');
|
|
|
|
// In jOOQ's Result.format(), that's line number one:
|
|
// 1: +----+------+----+
|
|
// 2: |ABC |XYZ |HEHE|
|
|
// 3: +----+------+----+
|
|
// 4: |Data|{null}|1234|
|
|
// 5: +----+------+----+
|
|
// 012345678901234567
|
|
// resulting in
|
|
// [{1,5} {6,12} {13,17}]
|
|
if (formattedJOOQ)
|
|
return parseTXTLines(nullLiteral, strings, PLUS_PATTERN, 0, 1, 3, strings.length - 1);
|
|
|
|
// In Oracle's format (e.g. when coming out of DBMS_XPLAN), that's line number one:
|
|
// 1: ------------------
|
|
// 2: |ABC |XYZ |HEHE|
|
|
// 3: ------------------
|
|
// 4: |Data|{null}|1234|
|
|
// 5: ------------------
|
|
// 012345678901234567
|
|
// resulting in
|
|
// [{1,5} {6,12} {13,17}]
|
|
else if (formattedOracle)
|
|
return parseTXTLines(nullLiteral, strings, PIPE_PATTERN, 1, 1, 3, strings.length - 1);
|
|
|
|
// In H2 format, that's line number two:
|
|
// 1: ABC XYZ HEHE
|
|
// 2: ----- ------- ----
|
|
// 3: Data {null} 1234
|
|
// 0123456789012345678
|
|
// resulting in
|
|
// [{0,5} {7,14} {15,19}]
|
|
else
|
|
return parseTXTLines(nullLiteral, strings, DASH_PATTERN, 1, 0, 2, strings.length);
|
|
}
|
|
|
|
private static final List<String[]> parseTXTLines(
|
|
String nullLiteral,
|
|
String[] strings,
|
|
Pattern pattern,
|
|
int matchLine,
|
|
int headerLine,
|
|
int dataLineStart,
|
|
int dataLineEnd) {
|
|
|
|
List<int[]> positions = new ArrayList<>();
|
|
Matcher m = pattern.matcher(strings[matchLine]);
|
|
|
|
while (m.find()) {
|
|
positions.add(new int[] { m.start(1), m.end(1) });
|
|
}
|
|
|
|
// Parse header line and data lines into string arrays
|
|
List<String[]> result = new ArrayList<>();
|
|
parseTXTLine(positions, result, strings[headerLine], nullLiteral);
|
|
|
|
for (int j = dataLineStart; j < dataLineEnd; j++)
|
|
parseTXTLine(positions, result, strings[j], nullLiteral);
|
|
|
|
return result;
|
|
}
|
|
|
|
private static final void parseTXTLine(List<int[]> positions, List<String[]> result, String string, String nullLiteral) {
|
|
String[] fields = new String[positions.size()];
|
|
result.add(fields);
|
|
int length = string.length();
|
|
|
|
for (int i = 0; i < fields.length; i++) {
|
|
int[] position = positions.get(i);
|
|
|
|
if (position[0] < length)
|
|
fields[i] = string.substring(position[0], Math.min(position[1], length)).trim();
|
|
else
|
|
fields[i] = null;
|
|
|
|
if (StringUtils.equals(fields[i], nullLiteral))
|
|
fields[i] = null;
|
|
}
|
|
}
|
|
|
|
private static final Pattern P_PARSE_HTML_ROW = Pattern.compile("<tr>(.*?)</tr>");
|
|
private static final Pattern P_PARSE_HTML_COL_HEAD = Pattern.compile("<th>(.*?)</th>");
|
|
private static final Pattern P_PARSE_HTML_COL_BODY = Pattern.compile("<td>(.*?)</td>");
|
|
|
|
static final List<String[]> parseHTML(String string) {
|
|
List<String[]> result = new ArrayList<>();
|
|
|
|
Matcher mRow = P_PARSE_HTML_ROW.matcher(string);
|
|
while (mRow.find()) {
|
|
String row = mRow.group(1);
|
|
List<String> col = new ArrayList<>();
|
|
|
|
// Header was not yet emitted
|
|
if (result.isEmpty()) {
|
|
Matcher mColHead = P_PARSE_HTML_COL_HEAD.matcher(row);
|
|
|
|
while (mColHead.find())
|
|
col.add(mColHead.group(1));
|
|
}
|
|
|
|
if (col.isEmpty()) {
|
|
Matcher mColBody = P_PARSE_HTML_COL_BODY.matcher(row);
|
|
|
|
while (mColBody.find())
|
|
col.add(mColBody.group(1));
|
|
|
|
if (result.isEmpty())
|
|
result.add(fieldNameStrings(col.size()));
|
|
}
|
|
|
|
result.add(col.toArray(EMPTY_STRING));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Wrap a runnable in a <code>BEGIN / END</code> anonymous block.
|
|
*/
|
|
static final void begin(Context<?> ctx, Consumer<? super Context<?>> runnable) {
|
|
begin(ctx);
|
|
runnable.accept(ctx);
|
|
end(ctx);
|
|
}
|
|
|
|
/**
|
|
* Generate the <code>BEGIN</code> part of an anonymous procedural block.
|
|
*/
|
|
private static final void begin(Context<?> ctx) {
|
|
switch (ctx.family()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case FIREBIRD:
|
|
ctx.visit(K_EXECUTE_BLOCK).formatSeparator()
|
|
.visit(K_AS).formatSeparator()
|
|
.visit(K_BEGIN).formatIndentStart().formatSeparator();
|
|
break;
|
|
|
|
case MARIADB:
|
|
ctx.visit(K_BEGIN).sql(' ').visit(K_NOT).sql(' ').visit(K_ATOMIC).formatIndentStart().formatSeparator();
|
|
break;
|
|
|
|
|
|
case POSTGRES:
|
|
case YUGABYTEDB:
|
|
if (increment(ctx.data(), DATA_BLOCK_NESTING))
|
|
ctx.visit(K_DO).sql(" $$").formatSeparator();
|
|
|
|
ctx.visit(K_BEGIN).formatIndentStart().formatSeparator();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate the <code>END</code> part of an anonymous procedural block.
|
|
*/
|
|
private static final void end(Context<?> ctx) {
|
|
switch (ctx.family()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case FIREBIRD:
|
|
case MARIADB:
|
|
ctx.formatIndentEnd().formatSeparator()
|
|
.visit(K_END);
|
|
break;
|
|
|
|
|
|
case POSTGRES:
|
|
case YUGABYTEDB:
|
|
ctx.formatIndentEnd().formatSeparator()
|
|
.visit(K_END);
|
|
|
|
if (decrement(ctx.data(), DATA_BLOCK_NESTING))
|
|
ctx.sql(" $$");
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wrap a statement in an <code>EXECUTE IMMEDIATE</code> statement.
|
|
*/
|
|
static final void executeImmediate(Context<?> ctx, Consumer<? super Context<?>> runnable) {
|
|
beginExecuteImmediate(ctx);
|
|
runnable.accept(ctx);
|
|
endExecuteImmediate(ctx);
|
|
}
|
|
|
|
/**
|
|
* Wrap a statement in an <code>EXECUTE IMMEDIATE</code> statement.
|
|
*/
|
|
static final void beginExecuteImmediate(Context<?> ctx) {
|
|
switch (ctx.family()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case FIREBIRD:
|
|
ctx.visit(K_EXECUTE_STATEMENT).sql(" '").stringLiteral(true).formatIndentStart().formatSeparator();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wrap a statement in an <code>EXECUTE IMMEDIATE</code> statement.
|
|
*/
|
|
static final void endExecuteImmediate(Context<?> ctx) {
|
|
ctx.formatIndentEnd().formatSeparator().stringLiteral(false).sql("';");
|
|
}
|
|
|
|
/**
|
|
* Wrap a <code>DROP .. IF EXISTS</code> statement with
|
|
* <code>BEGIN EXECUTE IMMEDIATE '...' EXCEPTION WHEN ... END;</code>, if
|
|
* <code>IF EXISTS</code> is not supported.
|
|
*/
|
|
static final void tryCatch(Context<?> ctx, DDLStatementType type, Consumer<? super Context<?>> runnable) {
|
|
tryCatch(ctx, type, null, null, runnable);
|
|
}
|
|
|
|
static final void tryCatch(Context<?> ctx, DDLStatementType type, Boolean container, Boolean element, Consumer<? super Context<?>> runnable) {
|
|
switch (ctx.family()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case FIREBIRD: {
|
|
begin(ctx, c -> {
|
|
executeImmediate(c, runnable);
|
|
|
|
c.formatSeparator()
|
|
.visit(K_WHEN).sql(" sqlcode -607 ").visit(K_DO).formatIndentStart().formatSeparator()
|
|
.visit(K_BEGIN).sql(' ').visit(K_END).formatIndentEnd();
|
|
});
|
|
break;
|
|
}
|
|
|
|
case MARIADB: {
|
|
List<String> sqlstates = new ArrayList<>();
|
|
|
|
// if (type == CREATE_SCHEMA)
|
|
// sqlstates.add("42710");
|
|
// else if (type == CREATE_SEQUENCE)
|
|
// sqlstates.add("42710");
|
|
// else if (type == CREATE_VIEW)
|
|
// sqlstates.add("42710");
|
|
// else
|
|
// if (type == ALTER_TABLE) {
|
|
// if (TRUE.equals(container))
|
|
// sqlstates.add("42704");
|
|
//
|
|
// if (TRUE.equals(element))
|
|
// sqlstates.add("42703");
|
|
// else if (FALSE.equals(element))
|
|
// sqlstates.add("42711");
|
|
// }
|
|
// else
|
|
sqlstates.add("42S02");
|
|
|
|
begin(ctx, c -> {
|
|
for (String sqlstate : sqlstates)
|
|
c.visit(keyword("declare continue handler for sqlstate")).sql(' ').visit(DSL.inline(sqlstate)).sql(' ').visit(K_BEGIN).sql(' ').visit(K_END).sql(';').formatSeparator();
|
|
|
|
runnable.accept(c);
|
|
c.sql(';');
|
|
});
|
|
break;
|
|
}
|
|
|
|
|
|
case POSTGRES:
|
|
case YUGABYTEDB: {
|
|
begin(ctx, c -> {
|
|
String sqlstate;
|
|
|
|
switch (type) {
|
|
case ALTER_DATABASE: sqlstate = "3D000"; break;
|
|
case ALTER_DOMAIN : sqlstate = "42704"; break;
|
|
case CREATE_DOMAIN : sqlstate = "42710"; break;
|
|
default : sqlstate = "42P07"; break;
|
|
}
|
|
|
|
runnable.accept(c);
|
|
|
|
c.sql(';').formatIndentEnd().formatSeparator()
|
|
.visit(K_EXCEPTION).formatIndentStart().formatSeparator()
|
|
.visit(K_WHEN).sql(' ').visit(K_SQLSTATE).sql(' ').visit(DSL.inline(sqlstate)).sql(' ').visit(K_THEN).sql(' ').visit(K_NULL).sql(';').formatIndentEnd();
|
|
});
|
|
break;
|
|
}
|
|
|
|
default:
|
|
runnable.accept(ctx);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static final void toSQLDDLTypeDeclarationForAddition(Context<?> ctx, DataType<?> type) {
|
|
toSQLDDLTypeDeclaration(ctx, type);
|
|
toSQLDDLTypeDeclarationIdentityBeforeNull(ctx, type);
|
|
|
|
|
|
|
|
|
|
// [#5356] Some dialects require the DEFAULT clause prior to the
|
|
// NULL constraints clause
|
|
if (DEFAULT_BEFORE_NULL.contains(ctx.dialect()))
|
|
toSQLDDLTypeDeclarationDefault(ctx, type);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
toSQLDDLTypeDeclarationForAdditionNullability(ctx, type);
|
|
|
|
if (!DEFAULT_BEFORE_NULL.contains(ctx.dialect()))
|
|
toSQLDDLTypeDeclarationDefault(ctx, type);
|
|
|
|
toSQLDDLTypeDeclarationIdentityAfterNull(ctx, type);
|
|
|
|
|
|
|
|
}
|
|
|
|
private static final void toSQLDDLTypeDeclarationForAdditionNullability(Context<?> ctx, DataType<?> type) {
|
|
switch (type.nullability()) {
|
|
case NOT_NULL:
|
|
ctx.sql(' ').visit(K_NOT_NULL);
|
|
break;
|
|
|
|
case NULL:
|
|
|
|
// [#3400] [#4321] [#7392] E.g. Derby, Firebird, HSQLDB do not support explicit nullability.
|
|
if (!NO_SUPPORT_NULL.contains(ctx.dialect()))
|
|
ctx.sql(' ').visit(K_NULL);
|
|
|
|
break;
|
|
|
|
// [#10937] The behaviour has been re-defined via Settings.renderDefaultNullability
|
|
case DEFAULT:
|
|
RenderDefaultNullability nullability = StringUtils.defaultIfNull(ctx.settings().getRenderDefaultNullability(), IMPLICIT_NULL);
|
|
|
|
switch (nullability) {
|
|
case EXPLICIT_NULL:
|
|
if (!NO_SUPPORT_NULL.contains(ctx.dialect()))
|
|
ctx.sql(' ').visit(K_NULL);
|
|
|
|
break;
|
|
|
|
case IMPLICIT_DEFAULT:
|
|
break;
|
|
|
|
case IMPLICIT_NULL:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// [#10937] MariaDB applies NOT NULL DEFAULT CURRENT_TIMESTAMP on
|
|
// TIMESTAMP columns in the absence of explicit nullability
|
|
if (DEFAULT_TIMESTAMP_NOT_NULL.contains(ctx.dialect()) && type.isTimestamp())
|
|
ctx.sql(' ').visit(K_NULL);
|
|
|
|
break;
|
|
|
|
default:
|
|
throw new UnsupportedOperationException("Nullability not supported: " + nullability);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
throw new UnsupportedOperationException("Nullability not supported: " + type.nullability());
|
|
}
|
|
}
|
|
|
|
private static final Set<SQLDialect> REQUIRE_IDENTITY_AFTER_NULL = SQLDialect.supportedBy(H2, MARIADB, MYSQL);
|
|
|
|
/**
|
|
* If a type is an identity type, some dialects require the relevant
|
|
* keywords before the [ NOT ] NULL constraint.
|
|
*/
|
|
static final void toSQLDDLTypeDeclarationIdentityBeforeNull(Context<?> ctx, DataType<?> type) {
|
|
if (REQUIRE_IDENTITY_AFTER_NULL.contains(ctx.dialect()))
|
|
return;
|
|
|
|
if (type.identity()) {
|
|
switch (ctx.family()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case CUBRID: ctx.sql(' ').visit(K_AUTO_INCREMENT); break;
|
|
|
|
case HSQLDB: ctx.sql(' ').visit(K_GENERATED).sql(' ').visit(K_BY).sql(' ').visit(K_DEFAULT).sql(' ').visit(K_AS).sql(' ').visit(K_IDENTITY).sql('(').visit(K_START_WITH).sql(" 1)"); break;
|
|
case SQLITE: ctx.sql(' ').visit(K_PRIMARY_KEY).sql(' ').visit(K_AUTOINCREMENT); break;
|
|
case POSTGRES:
|
|
switch (ctx.dialect()) {
|
|
|
|
|
|
|
|
|
|
|
|
case POSTGRES:
|
|
ctx.sql(' ').visit(K_GENERATED).sql(' ').visit(K_BY).sql(' ').visit(K_DEFAULT).sql(' ').visit(K_AS).sql(' ').visit(K_IDENTITY); break;
|
|
}
|
|
break;
|
|
|
|
|
|
case DERBY:
|
|
case FIREBIRD:
|
|
case YUGABYTEDB: ctx.sql(' ').visit(K_GENERATED).sql(' ').visit(K_BY).sql(' ').visit(K_DEFAULT).sql(' ').visit(K_AS).sql(' ').visit(K_IDENTITY); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If a type is an identity type, some dialects require the relevant
|
|
* keywords after the [ NOT ] NULL constraint.
|
|
*/
|
|
static final void toSQLDDLTypeDeclarationIdentityAfterNull(Context<?> ctx, DataType<?> type) {
|
|
if (!REQUIRE_IDENTITY_AFTER_NULL.contains(ctx.dialect()))
|
|
return;
|
|
|
|
if (type.identity()) {
|
|
|
|
// [#5062] H2's (and others') AUTO_INCREMENT flag is syntactically located *after* NULL flags.
|
|
switch (ctx.family()) {
|
|
|
|
|
|
|
|
case H2: ctx.sql(' ').visit(K_GENERATED).sql(' ').visit(K_BY).sql(' ').visit(K_DEFAULT).sql(' ').visit(K_AS).sql(' ').visit(K_IDENTITY); break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case MARIADB:
|
|
case MYSQL: ctx.sql(' ').visit(K_AUTO_INCREMENT); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final void toSQLDDLTypeDeclarationDefault(Context<?> ctx, DataType<?> type) {
|
|
if (type.defaulted())
|
|
ctx.sql(' ').visit(K_DEFAULT).sql(' ').visit(type.defaultValue());
|
|
}
|
|
|
|
static final void toSQLDDLTypeDeclaration(Context<?> ctx, DataType<?> type) {
|
|
// [#10376] TODO: Move some of this logic into DataType
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DataType<?> elementType = type instanceof ArrayDataType
|
|
? ((ArrayDataType<?>) type).elementType
|
|
: type;
|
|
|
|
// In some databases, identity is a type, not a flag.
|
|
if (type.identity()) {
|
|
switch (ctx.family()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
|
// [#5299] MySQL enum types
|
|
if (EnumType.class.isAssignableFrom(type.getType())) {
|
|
|
|
@SuppressWarnings("unchecked")
|
|
DataType<EnumType> enumType = (DataType<EnumType>) type;
|
|
|
|
switch (ctx.family()) {
|
|
|
|
|
|
case H2:
|
|
case MARIADB:
|
|
case MYSQL: {
|
|
ctx.visit(K_ENUM).sql('(');
|
|
|
|
String separator = "";
|
|
for (EnumType e : enumConstants(enumType)) {
|
|
ctx.sql(separator).visit(DSL.inline(e.getLiteral()));
|
|
separator = ", ";
|
|
}
|
|
|
|
ctx.sql(')');
|
|
return;
|
|
}
|
|
|
|
// [#7597] In PostgreSQL, the enum type reference should be used
|
|
|
|
|
|
case POSTGRES:
|
|
case YUGABYTEDB: {
|
|
|
|
// [#7597] but only if the EnumType.getSchema() value is present
|
|
// i.e. when it is a known, stored enum type
|
|
if (!storedEnumType(enumType))
|
|
type = emulateEnumType(enumType);
|
|
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
type = emulateEnumType(enumType);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// [#5807] These databases cannot use the DataType.getCastTypeName() (which is simply char in this case)
|
|
if (type.getType() == UUID.class && NO_SUPPORT_CAST_TYPE_IN_DDL.contains(ctx.dialect())) {
|
|
toSQLDDLTypeDeclaration(ctx, VARCHAR(36));
|
|
return;
|
|
}
|
|
|
|
// [#12019] [#12117] If dateAsTimestamp=true is active, we must declare a DATE instead.
|
|
if (type.isTimestamp() && (type.getBinding() instanceof DateAsTimestampBinding || type.getBinding() instanceof LocalDateAsLocalDateTimeBinding))
|
|
type = SQLDataType.DATE;
|
|
|
|
String typeName = type.getTypeName(ctx.configuration());
|
|
|
|
// [#8070] Make sure VARCHAR(n) ARRAY types are generated as such in HSQLDB
|
|
if (type.hasLength() || elementType.hasLength()) {
|
|
|
|
// [#6289] [#7191] Some databases don't support lengths on binary types
|
|
if (type.isBinary() && NO_SUPPORT_BINARY_TYPE_LENGTH.contains(ctx.dialect()))
|
|
ctx.sql(typeName);
|
|
|
|
|
|
|
|
|
|
else if (type.length() > 0)
|
|
ctx.sql(typeName).sql('(').sql(type.length()).sql(')');
|
|
|
|
// [#6745] [#9473] The DataType.getCastTypeName() cannot be used in some dialects, for DDL
|
|
else if (NO_SUPPORT_CAST_TYPE_IN_DDL.contains(ctx.dialect()))
|
|
if (type.isBinary())
|
|
ctx.sql(BLOB.getTypeName(ctx.configuration()));
|
|
else
|
|
ctx.sql(CLOB.getTypeName(ctx.configuration()));
|
|
|
|
// Some databases don't allow for length-less VARCHAR, VARBINARY types
|
|
else {
|
|
String castTypeName = type.getCastTypeName(ctx.configuration());
|
|
|
|
if (!typeName.equals(castTypeName))
|
|
ctx.sql(castTypeName);
|
|
else
|
|
ctx.sql(typeName);
|
|
}
|
|
}
|
|
else if (type.hasPrecision() && type.precision() > 0 && (!type.isTimestamp() || !NO_SUPPORT_TIMESTAMP_PRECISION.contains(ctx.dialect()))) {
|
|
|
|
// [#6745] [#9473] The DataType.getCastTypeName() cannot be used in some dialects, for DDL
|
|
if (NO_SUPPORT_CAST_TYPE_IN_DDL.contains(ctx.dialect()))
|
|
if (type.hasScale())
|
|
ctx.sql(typeName).sql('(').sql(type.precision()).sql(", ").sql(type.scale()).sql(')');
|
|
else
|
|
ctx.sql(typeName).sql('(').sql(type.precision()).sql(')');
|
|
|
|
|
|
|
|
|
|
else
|
|
ctx.sql(type.getCastTypeName(ctx.configuration()));
|
|
}
|
|
|
|
// [#6841] SQLite usually recognises int/integer as both meaning the same thing, but not in the
|
|
// context of an autoincrement column, in case of which explicit "integer" types are required.
|
|
else if (type.identity() && ctx.family() == SQLITE && type.isNumeric())
|
|
ctx.sql("integer");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
else
|
|
ctx.sql(typeName);
|
|
|
|
// [#8041] Character sets are vendor-specific storage clauses, which we might need to ignore
|
|
if (type.characterSet() != null && ctx.configuration().data("org.jooq.ddl.ignore-storage-clauses") == null)
|
|
ctx.sql(' ').visit(K_CHARACTER_SET).sql(' ').visit(type.characterSet());
|
|
|
|
// [#8011] Collations are vendor-specific storage clauses, which we might need to ignore
|
|
if (type.collation() != null && ctx.configuration().data("org.jooq.ddl.ignore-storage-clauses") == null)
|
|
ctx.sql(' ').visit(K_COLLATE).sql(' ').visit(type.collation());
|
|
}
|
|
|
|
static final boolean storedEnumType(DataType<EnumType> enumType) {
|
|
return enumConstants(enumType)[0].getSchema() != null;
|
|
}
|
|
|
|
private static final EnumType[] enumConstants(DataType<? extends EnumType> type) {
|
|
EnumType[] enums = type.getType().getEnumConstants();
|
|
|
|
if (enums == null)
|
|
throw new DataTypeException("EnumType must be a Java enum");
|
|
|
|
return enums;
|
|
}
|
|
|
|
static final DataType<String> emulateEnumType(DataType<? extends EnumType> type) {
|
|
return emulateEnumType(type, enumConstants(type));
|
|
}
|
|
|
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
|
private static final DataType<String> emulateEnumType(DataType<? extends EnumType> type, EnumType[] enums) {
|
|
int length = 0;
|
|
for (EnumType e : enums)
|
|
if (e.getLiteral() != null)
|
|
length = Math.max(length, e.getLiteral().length());
|
|
|
|
return VARCHAR(length).nullability(type.nullability()).defaultValue((Field) type.defaultValue());
|
|
}
|
|
|
|
static final <C extends Context<? extends C>> C prependSQL(C ctx, Query... queries) {
|
|
return preOrAppendSQL(DataKey.DATA_PREPEND_SQL, ctx, queries);
|
|
}
|
|
|
|
static final <C extends Context<? extends C>> C appendSQL(C ctx, Query... queries) {
|
|
return preOrAppendSQL(DataKey.DATA_APPEND_SQL, ctx, queries);
|
|
}
|
|
|
|
private static final <C extends Context<? extends C>> C preOrAppendSQL(DataKey key, C ctx, Query... queries) {
|
|
ctx.data().compute(key, (k, v) -> {
|
|
String sql = ctx.dsl().renderInlined(ctx.dsl().queries(queries));
|
|
|
|
if (v == null)
|
|
return sql;
|
|
else
|
|
return v + sql;
|
|
});
|
|
|
|
return ctx;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// XXX: ForkJoinPool ManagedBlock implementation
|
|
// -------------------------------------------------------------------------
|
|
|
|
static final <T> Supplier<T> blocking(Supplier<T> supplier) {
|
|
return blocking(supplier, false);
|
|
}
|
|
|
|
static final <T> Supplier<T> blocking(Supplier<T> supplier, boolean threadLocal) {
|
|
|
|
// [#5377] In ThreadLocal contexts (e.g. when using ThreadLocalTransactionprovider),
|
|
// no ManagedBlocker is needed as we're guaranteed by API contract to always
|
|
// remain on the same thread.
|
|
|
|
return threadLocal ? supplier : new Supplier<T>() {
|
|
volatile T asyncResult;
|
|
|
|
@Override
|
|
public T get() {
|
|
try {
|
|
ForkJoinPool.managedBlock(new ManagedBlocker() {
|
|
@Override
|
|
public boolean block() {
|
|
asyncResult = supplier.get();
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean isReleasable() {
|
|
return asyncResult != null;
|
|
}
|
|
});
|
|
}
|
|
catch (InterruptedException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
|
|
return asyncResult;
|
|
}
|
|
};
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static final <E extends EnumType> E[] enums(Class<? extends E> type) {
|
|
|
|
// Java implementation
|
|
if (Enum.class.isAssignableFrom(type)) {
|
|
return type.getEnumConstants();
|
|
}
|
|
|
|
// [#4427] Scala implementation
|
|
else {
|
|
try {
|
|
|
|
// There's probably a better way to do this:
|
|
// http://stackoverflow.com/q/36068089/521799
|
|
Class<?> companionClass = Thread.currentThread().getContextClassLoader().loadClass(type.getName() + "$");
|
|
java.lang.reflect.Field module = companionClass.getField("MODULE$");
|
|
Object companion = module.get(companionClass);
|
|
return (E[]) companionClass.getMethod("values").invoke(companion);
|
|
}
|
|
catch (Exception e) {
|
|
throw new MappingException("Error while looking up Scala enum", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Whether a Java type is suitable for {@link Types#TIME}.
|
|
*/
|
|
static final boolean isTime(Class<?> t) {
|
|
return t == Time.class || t == LocalTime.class;
|
|
}
|
|
|
|
/**
|
|
* Whether a Java type is suitable for {@link Types#TIMESTAMP}.
|
|
*/
|
|
static final boolean isTimestamp(Class<?> t) {
|
|
return t == Timestamp.class || t == LocalDateTime.class;
|
|
}
|
|
|
|
/**
|
|
* Whether a Java type is suitable for {@link Types#DATE}.
|
|
*/
|
|
static final boolean isDate(Class<?> t) {
|
|
return t == Date.class || t == LocalDate.class;
|
|
}
|
|
|
|
static final boolean hasAmbiguousNames(Collection<? extends Field<?>> fields) {
|
|
if (fields == null)
|
|
return false;
|
|
|
|
Set<String> names = new HashSet<>();
|
|
return anyMatch(fields, f -> !names.add(f.getName()));
|
|
}
|
|
|
|
static final SelectFieldOrAsterisk qualify(Table<?> table, SelectFieldOrAsterisk field) {
|
|
if (field instanceof Field)
|
|
return qualify(table, (Field<?>) field);
|
|
else if (field instanceof Asterisk)
|
|
return table.asterisk();
|
|
else if (field instanceof QualifiedAsterisk)
|
|
return table.asterisk();
|
|
// [#11812] TODO: handle field instanceof Row
|
|
else
|
|
throw new UnsupportedOperationException("Unsupported field : " + field);
|
|
}
|
|
|
|
static final <T> Field<T> qualify(Table<?> table, Field<T> field) {
|
|
Field<T> result = table.field(field);
|
|
|
|
if (result != null)
|
|
return result;
|
|
|
|
Name[] part = table.getQualifiedName().parts();
|
|
Name[] name = new Name[part.length + 1];
|
|
System.arraycopy(part, 0, name, 0, part.length);
|
|
name[part.length] = field.getUnqualifiedName();
|
|
|
|
return DSL.field(DSL.name(name), field.getDataType());
|
|
}
|
|
|
|
static final <T> Field<T> field(OrderField<T> orderField) {
|
|
if (orderField instanceof Field)
|
|
return (Field<T>) orderField;
|
|
else
|
|
return ((SortFieldImpl<T>) orderField).getField();
|
|
}
|
|
|
|
static final Field<?>[] fields(OrderField<?>[] orderFields) {
|
|
return map(orderFields, f -> field(f), Field[]::new);
|
|
}
|
|
|
|
static final List<Field<?>> fields(Collection<? extends OrderField<?>> orderFields) {
|
|
return map(orderFields, (OrderField<?> f) -> field(f));
|
|
}
|
|
|
|
static final <T> Field<T> unalias(Field<T> field) {
|
|
Field<T> result = aliased(field);
|
|
return result != null ? result : field;
|
|
}
|
|
|
|
static final <T> Field<T> aliased(Field<T> field) {
|
|
if (field instanceof FieldAlias)
|
|
return ((FieldAlias<T>) field).getAliasedField();
|
|
else
|
|
return null;
|
|
}
|
|
|
|
static final <R extends Record> Table<R> unalias(Table<R> table) {
|
|
Table<R> result = aliased(table);
|
|
return result != null ? result : table;
|
|
}
|
|
|
|
static final boolean isScalarSubquery(Field<?> field) {
|
|
// TODO: Replace other instanceof checks by this one
|
|
return uncoerce(field) instanceof ScalarSubquery;
|
|
}
|
|
|
|
static final Field<?> uncoerce(Field<?> field) {
|
|
return field instanceof Coerce ? ((Coerce<?>) field).field : field;
|
|
}
|
|
|
|
static final <R extends Record> Table<R> aliased(Table<R> table) {
|
|
if (table instanceof TableImpl)
|
|
return ((TableImpl<R>) table).getAliasedTable();
|
|
else if (table instanceof TableAlias)
|
|
return ((TableAlias<R>) table).getAliasedTable();
|
|
else
|
|
return null;
|
|
}
|
|
|
|
static final <R extends Record> Alias<Table<R>> alias(Table<R> table) {
|
|
if (table instanceof TableImpl)
|
|
return ((TableImpl<R>) table).alias;
|
|
else if (table instanceof TableAlias)
|
|
return ((TableAlias<R>) table).alias;
|
|
else
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Whether a counter is currently at the top level or not.
|
|
*
|
|
* @see #increment(Map, DataKey)
|
|
* @see #decrement(Map, DataKey)
|
|
*/
|
|
static final boolean toplevel(Map<Object, Object> data, DataKey key) {
|
|
Integer updateCounts = (Integer) data.get(key);
|
|
|
|
if (updateCounts == null)
|
|
throw new IllegalStateException();
|
|
else
|
|
return updateCounts.intValue() == 1;
|
|
}
|
|
|
|
/**
|
|
* Increment a counter, run a runnable, and decrement the counter again.
|
|
*/
|
|
static final void increment(Map<Object, Object> data, DataKey key, Runnable runnable) {
|
|
increment(data, key);
|
|
runnable.run();
|
|
decrement(data, key);
|
|
}
|
|
|
|
/**
|
|
* Increment a counter and return true if the counter was zero prior to
|
|
* incrementing.
|
|
*/
|
|
static final boolean increment(Map<Object, Object> data, DataKey key) {
|
|
boolean result = true;
|
|
Integer updateCounts = (Integer) data.get(key);
|
|
|
|
if (updateCounts == null)
|
|
updateCounts = 0;
|
|
else
|
|
result = false;
|
|
|
|
data.put(key, updateCounts + 1);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Decrement a counter and return true if the counter is zero after
|
|
* decrementing.
|
|
*/
|
|
static final boolean decrement(Map<Object, Object> data, DataKey key) {
|
|
boolean result = false;
|
|
Integer updateCounts = (Integer) data.get(key);
|
|
|
|
if (updateCounts == null || updateCounts == 0)
|
|
throw new IllegalStateException("Unmatching increment / decrement on key: " + key);
|
|
else if (updateCounts == 1)
|
|
result = true;
|
|
|
|
data.put(key, updateCounts - 1);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Look up a field in a table, or create a new qualified field from the table.
|
|
*/
|
|
static final Field<?> tableField(Table<?> table, Object field) {
|
|
if (field instanceof Field)
|
|
return (Field<?>) field;
|
|
else if (field instanceof Name) { Name n = (Name) field;
|
|
if (table.fieldsRow().size() == 0)
|
|
return DSL.field(table.getQualifiedName().append(n.unqualifiedName())) ;
|
|
else
|
|
return table.field(n);
|
|
}
|
|
else if (field instanceof String) { String s = (String) field;
|
|
if (table.fieldsRow().size() == 0)
|
|
return DSL.field(table.getQualifiedName().append(s));
|
|
else
|
|
return table.field(s);
|
|
}
|
|
else
|
|
throw new IllegalArgumentException("Field type not supported: " + field);
|
|
}
|
|
|
|
/**
|
|
* Convert a byte array to a hex encoded string.
|
|
*
|
|
* @param value the byte array
|
|
* @param len the number of bytes to encode
|
|
* @return the hex encoded string
|
|
*/
|
|
static final String convertBytesToHex(byte[] value, int offset, int len) {
|
|
len = Math.min(value.length - offset, len);
|
|
char[] buff = new char[2 * len];
|
|
char[] hex = HEX_DIGITS;
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
int c = value[i + offset] & 0xff;
|
|
buff[i + i] = hex[c >> 4];
|
|
buff[i + i + 1] = hex[c & 0xf];
|
|
}
|
|
|
|
return new String(buff);
|
|
}
|
|
|
|
/**
|
|
* Convert a byte array to a hex encoded string.
|
|
*
|
|
* @param value the byte array
|
|
* @return the hex encoded string
|
|
*/
|
|
static final String convertBytesToHex(byte[] value) {
|
|
return convertBytesToHex(value, 0, value.length);
|
|
}
|
|
|
|
/**
|
|
* Convert a hex encoded string to a byte array.
|
|
*
|
|
* @param value the hex encoded string
|
|
* @param len the number of bytes to encode
|
|
* @return the byte array
|
|
*/
|
|
static final byte[] convertHexToBytes(String value, int offset, int len) {
|
|
len = Math.min(value.length() / 2 - offset, len);
|
|
byte[] buff = new byte[len];
|
|
byte[] lookup = HEX_LOOKUP;
|
|
int max = lookup.length;
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
int pos = (i + offset) * 2;
|
|
char c1 = value.charAt(pos);
|
|
char c2 = value.charAt(pos + 1);
|
|
byte v1 = c1 < max ? lookup[c1] : 0;
|
|
byte v2 = c2 < max ? lookup[c2] : 0;
|
|
|
|
buff[i] = (byte) ((v1 << 4) + v2);
|
|
}
|
|
|
|
return buff;
|
|
}
|
|
|
|
/**
|
|
* Convert a hex encoded string to a byte array.
|
|
*
|
|
* @param value the hex encoded string
|
|
* @return the byte array
|
|
*/
|
|
static final byte[] convertHexToBytes(String value) {
|
|
return convertHexToBytes(value, 0, value.length());
|
|
}
|
|
|
|
static final boolean isNotEmpty(Collection<?> collection) {
|
|
return !isEmpty(collection);
|
|
}
|
|
|
|
static final boolean isEmpty(Collection<?> collection) {
|
|
return collection == null || collection.isEmpty();
|
|
}
|
|
|
|
static final boolean isNotEmpty(Iterable<?> it) {
|
|
return !isEmpty(it);
|
|
}
|
|
|
|
static final boolean isEmpty(Iterable<?> it) {
|
|
if (it == null)
|
|
return true;
|
|
else if (it instanceof Collection)
|
|
return isEmpty((Collection<?>) it);
|
|
|
|
Iterator<?> i = it.iterator();
|
|
return !i.hasNext();
|
|
}
|
|
|
|
static final boolean isNotEmpty(Object[] array) {
|
|
return !isEmpty(array);
|
|
}
|
|
|
|
static final boolean isEmpty(Object[] array) {
|
|
return array == null || array.length == 0;
|
|
}
|
|
|
|
static final boolean nonReplacingEmbeddable(Field<?> field) {
|
|
return field instanceof EmbeddableTableField && !((EmbeddableTableField<?, ?>) field).replacesFields;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static final Class<? extends AbstractRecord> embeddedRecordType(Field<?> field) {
|
|
return field instanceof EmbeddableTableField
|
|
? (Class<AbstractRecord>) ((EmbeddableTableField<?, ?>) field).recordType
|
|
: field instanceof Val && ((Val<?>) field).value instanceof EmbeddableRecord
|
|
? ((AbstractRecord) ((Val<?>) field).value).getClass()
|
|
: field.getDataType().isEmbeddable()
|
|
? ((Field<AbstractRecord>) field).getType()
|
|
: null;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static final Field<?>[] embeddedFields(Field<?> field) {
|
|
return field instanceof EmbeddableTableField
|
|
? ((EmbeddableTableField<?, ?>) field).fields
|
|
: field instanceof Val && ((Val<?>) field).value instanceof EmbeddableRecord
|
|
? ((EmbeddableRecord<?>) ((Val<?>) field).value).valuesRow().fields()
|
|
: field instanceof ScalarSubquery
|
|
? embeddedFields((ScalarSubquery<?>) field)
|
|
: field.getDataType().isEmbeddable()
|
|
? newInstance(((Field<EmbeddableRecord<?>>) field).getType()).valuesRow().fields()
|
|
: null;
|
|
}
|
|
|
|
static final Row embeddedFieldsRow(Row row) {
|
|
if (hasEmbeddedFields(row.fields()))
|
|
return row(map(flattenCollection(Arrays.asList(row.fields()), false, false), f -> f));
|
|
else
|
|
return row;
|
|
}
|
|
|
|
static final Field<?>[] embeddedFields(ScalarSubquery<?> field) {
|
|
|
|
// Split a scalar subquery of degree N into N scalar subqueries of degree 1
|
|
// In a few cases, there's a better solution that prevents the N subqueries,
|
|
// but this is a good default that works in many cases.
|
|
// [#8353] [#10522] [#10523] TODO: Factor out some of this logic and
|
|
// reuse it for the emulation of UPDATE .. SET row = (SELECT ..)
|
|
List<Field<?>> select = field.query.getSelect();
|
|
List<Field<?>> result = collect(flattenCollection(select, false, false));
|
|
Name tableName = name("t");
|
|
Name[] fieldNames = fieldNames(result.size());
|
|
Table<?> t = new AliasedSelect<>(field.query, true, true, fieldNames).as("t");
|
|
for (int i = 0; i < result.size(); i++)
|
|
result.set(i, DSL.field(DSL.select(DSL.field(tableName.append(fieldNames[i]), result.get(i).getDataType())).from(t)));
|
|
|
|
return result.toArray(EMPTY_FIELD);
|
|
}
|
|
|
|
private static final EmbeddableRecord<?> newInstance(Class<? extends EmbeddableRecord<?>> type) {
|
|
try {
|
|
return type.getDeclaredConstructor().newInstance();
|
|
}
|
|
catch (Exception e) {
|
|
throw new MappingException("Cannot create EmbeddableRecord type", e);
|
|
}
|
|
}
|
|
|
|
static final boolean hasEmbeddedFields(Field<?>[] fields) {
|
|
return anyMatch(fields, f -> f.getDataType().isEmbeddable());
|
|
}
|
|
|
|
static final boolean hasEmbeddedFields(Iterable<? extends Field<?>> fields) {
|
|
return anyMatch(fields, f -> f.getDataType().isEmbeddable());
|
|
}
|
|
|
|
static final <E> List<E> collect(Iterable<E> iterable) {
|
|
if (iterable instanceof List)
|
|
return (List<E>) iterable;
|
|
|
|
List<E> result = new ArrayList<>();
|
|
for (E e : iterable)
|
|
result.add(e);
|
|
|
|
return result;
|
|
}
|
|
|
|
static final <E> Iterator<E> filter(Iterator<E> iterator, Predicate<? super E> predicate) {
|
|
return filter(iterator, (e, i) -> predicate.test(e));
|
|
}
|
|
|
|
static final <E> Iterator<E> filter(Iterator<E> iterator, ObjIntPredicate<? super E> predicate) {
|
|
return new Iterator<E>() {
|
|
boolean uptodate;
|
|
E next;
|
|
int index;
|
|
|
|
private void move() {
|
|
if (!uptodate) {
|
|
uptodate = true;
|
|
|
|
while (iterator.hasNext()) {
|
|
next = iterator.next();
|
|
|
|
if (predicate.test(next, index++))
|
|
return;
|
|
}
|
|
|
|
uptodate = false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean hasNext() {
|
|
move();
|
|
return uptodate;
|
|
}
|
|
|
|
@Override
|
|
public E next() {
|
|
move();
|
|
if (!uptodate)
|
|
throw new NoSuchElementException();
|
|
uptodate = false;
|
|
return next;
|
|
}
|
|
};
|
|
}
|
|
|
|
static final <E> Iterable<E> filter(Iterable<E> iterable, Predicate<? super E> predicate) {
|
|
return () -> filter(iterable.iterator(), predicate);
|
|
}
|
|
|
|
static final <E> Iterable<E> filter(Iterable<E> iterable, ObjIntPredicate<? super E> predicate) {
|
|
return () -> filter(iterable.iterator(), predicate);
|
|
}
|
|
|
|
static final <E> Iterable<E> filter(E[] array, Predicate<? super E> predicate) {
|
|
return filter(asList(array), predicate);
|
|
}
|
|
|
|
static final <E> Iterable<E> filter(E[] array, ObjIntPredicate<? super E> predicate) {
|
|
return filter(asList(array), predicate);
|
|
}
|
|
|
|
/**
|
|
* Flatten out an {@link EmbeddableTableField}.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
static final <E extends Field<?>> Iterable<E> flatten(E field) {
|
|
|
|
// [#2530] [#6124] [#10481] TODO: Refactor and optimise these flattening algorithms
|
|
Iterator<E> it1 = singletonList(field).iterator();
|
|
|
|
// [#11729] Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=572873
|
|
Iterator<E> it2 = field.getDataType().isEmbeddable()
|
|
? new FlatteningIterator<E>(it1, (e, duplicates) -> (List<E>) Arrays.asList(embeddedFields(field)))
|
|
: it1;
|
|
|
|
return () -> it2;
|
|
}
|
|
|
|
/**
|
|
* Flatten out {@link EmbeddableTableField} elements contained in an
|
|
* ordinary iterable.
|
|
*/
|
|
static final Iterable<Field<?>> flattenCollection(
|
|
final Iterable<? extends Field<?>> iterable,
|
|
final boolean removeDuplicates,
|
|
final boolean flattenRowFields
|
|
) {
|
|
// [#2530] [#6124] [#10481] TODO: Refactor and optimise these flattening algorithms
|
|
return () -> new FlatteningIterator<>(iterable.iterator(), (e, duplicates) -> {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO [#10525] Should embedded records be emulated as RowField?
|
|
if (flattenRowFields && e instanceof RowField) {
|
|
List<Field<?>> result = new ArrayList<>();
|
|
|
|
for (Field<?> field : ((RowField<?, ?>) e).row().fields())
|
|
if (duplicates.test(field))
|
|
result.add(field);
|
|
|
|
return result;
|
|
}
|
|
|
|
return Tools.flatten(e);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Flatten out {@link EmbeddableTableField} elements contained in an entry
|
|
* set iterable, making sure no duplicate keys resulting from overlapping
|
|
* embeddables will be produced.
|
|
*/
|
|
static final Iterable<Entry<Field<?>, Field<?>>> flattenEntrySet(
|
|
final Iterable<Entry<Field<?>, Field<?>>> iterable,
|
|
final boolean removeDuplicates
|
|
) {
|
|
// [#2530] [#6124] [#10481] TODO: Refactor and optimise these flattening algorithms
|
|
// [#11729] Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=572873
|
|
return () -> new FlatteningIterator<>(iterable.iterator(), (e, duplicates) -> {
|
|
if (e.getKey() instanceof EmbeddableTableField) {
|
|
List<Entry<Field<?>, Field<?>>> result = new ArrayList<>();
|
|
Field<?>[] keys = embeddedFields(e.getKey());
|
|
Field<?>[] values = embeddedFields(e.getValue());
|
|
|
|
for (int i = 0; i < keys.length; i++)
|
|
|
|
|
|
|
|
result.add(new SimpleImmutableEntry<Field<?>, Field<?>>(
|
|
keys[i], values[i]
|
|
));
|
|
|
|
return result;
|
|
}
|
|
|
|
return null;
|
|
});
|
|
}
|
|
|
|
static final <T> Set<T> lazy(Set<T> set) {
|
|
return set == null ? new HashSet<>() : set;
|
|
}
|
|
|
|
/**
|
|
* A base implementation for {@link EmbeddableTableField} flattening
|
|
* iterators with a default implementation for {@link Iterator#remove()} for
|
|
* convenience in the Java 6 build.
|
|
*/
|
|
static final class FlatteningIterator<E> implements Iterator<E> {
|
|
private final Iterator<? extends E> delegate;
|
|
private final BiFunction<? super E, Predicate<Object>, ? extends Iterable<E>> flattener;
|
|
private final Predicate<Object> checkDuplicates;
|
|
private Iterator<E> flatten;
|
|
private E next;
|
|
private Set<Object> duplicates;
|
|
|
|
FlatteningIterator(Iterator<? extends E> delegate, BiFunction<? super E, Predicate<Object>, ? extends Iterable<E>> flattener) {
|
|
this.delegate = delegate;
|
|
this.flattener = flattener;
|
|
this.checkDuplicates = e -> (duplicates = lazy(duplicates)).add(e);
|
|
}
|
|
|
|
private final void move() {
|
|
if (next == null) {
|
|
if (flatten != null) {
|
|
if (flatten.hasNext()) {
|
|
next = flatten.next();
|
|
return;
|
|
}
|
|
else {
|
|
flatten = null;
|
|
}
|
|
}
|
|
|
|
if (delegate.hasNext()) {
|
|
next = delegate.next();
|
|
|
|
Iterable<E> flattened = flattener.apply(next, checkDuplicates);
|
|
if (flattened == null)
|
|
return;
|
|
|
|
next = null;
|
|
flatten = flattened.iterator();
|
|
move();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public final boolean hasNext() {
|
|
move();
|
|
return next != null;
|
|
}
|
|
|
|
@Override
|
|
public final E next() {
|
|
move();
|
|
|
|
if (next == null)
|
|
throw new NoSuchElementException();
|
|
|
|
E result = next;
|
|
next = null;
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public final void remove() {
|
|
throw new UnsupportedOperationException("remove");
|
|
}
|
|
}
|
|
|
|
static final boolean anyQuoted(Settings settings, Name... names) {
|
|
RenderQuotedNames renderQuotedNames = SettingsTools.getRenderQuotedNames(settings);
|
|
|
|
switch (renderQuotedNames) {
|
|
case ALWAYS:
|
|
return true;
|
|
case NEVER:
|
|
return false;
|
|
case EXPLICIT_DEFAULT_QUOTED:
|
|
case EXPLICIT_DEFAULT_UNQUOTED:
|
|
for (Name name : names) {
|
|
Name n = name.unqualifiedName();
|
|
|
|
switch (n.quoted()) {
|
|
case QUOTED:
|
|
return true;
|
|
case DEFAULT:
|
|
if (renderQuotedNames == EXPLICIT_DEFAULT_QUOTED)
|
|
return true;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static final String asString(Name name) {
|
|
if (!name.qualified())
|
|
return name.first();
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
Name[] parts = name.parts();
|
|
for (int i = 0; i < parts.length; i++) {
|
|
sb.append(parts[i].first());
|
|
if (i < parts.length - 1)
|
|
sb.append('.');
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
/**
|
|
* Normalise a name case depending on the dialect and the setting for
|
|
* {@link ParseNameCase}.
|
|
*/
|
|
static final String normaliseNameCase(Configuration configuration, String name, boolean quoted, Locale locale) {
|
|
switch (parseNameCase(configuration)) {
|
|
case LOWER_IF_UNQUOTED:
|
|
if (quoted)
|
|
return name;
|
|
|
|
// no-break
|
|
|
|
case LOWER:
|
|
return name.toLowerCase(locale);
|
|
|
|
case UPPER_IF_UNQUOTED:
|
|
if (quoted)
|
|
return name;
|
|
|
|
// no-break
|
|
case UPPER:
|
|
return name.toUpperCase(locale);
|
|
|
|
case AS_IS:
|
|
case DEFAULT:
|
|
default:
|
|
return name;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the {@link ParseNameCase}, looking up the default value from the
|
|
* parse dialect.
|
|
*/
|
|
private static final ParseNameCase parseNameCase(Configuration configuration) {
|
|
ParseNameCase result = defaultIfNull(configuration.settings().getParseNameCase(), ParseNameCase.DEFAULT);
|
|
|
|
if (result == ParseNameCase.DEFAULT) {
|
|
switch (defaultIfNull(configuration.settings().getParseDialect(), configuration.family()).family()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case POSTGRES:
|
|
case YUGABYTEDB:
|
|
return ParseNameCase.LOWER_IF_UNQUOTED;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case MARIADB:
|
|
case MYSQL:
|
|
case SQLITE:
|
|
return ParseNameCase.AS_IS;
|
|
|
|
default:
|
|
// The SQL standard uses upper case identifiers if unquoted
|
|
return ParseNameCase.UPPER_IF_UNQUOTED;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static final NestedCollectionEmulation emulateMultiset(Configuration configuration) {
|
|
NestedCollectionEmulation result = defaultIfNull(configuration.settings().getEmulateMultiset(), NestedCollectionEmulation.DEFAULT);
|
|
|
|
if (result == NestedCollectionEmulation.DEFAULT) {
|
|
switch (configuration.family()) {
|
|
|
|
|
|
|
|
case H2:
|
|
case POSTGRES:
|
|
case YUGABYTEDB:
|
|
return NestedCollectionEmulation.JSONB;
|
|
|
|
case MARIADB:
|
|
case MYSQL:
|
|
case SQLITE:
|
|
return NestedCollectionEmulation.JSON;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
return NestedCollectionEmulation.NATIVE;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static final DataType<?> dataType(Field<?> field) {
|
|
return dataType(OTHER, field, false);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static final <T> DataType<T> dataType(DataType<T> defaultType, Field<?> field, boolean preferDefault) {
|
|
return field == null
|
|
? defaultType
|
|
: preferDefault && field.getType() != defaultType.getType()
|
|
? defaultType.nullable(field.getDataType().nullable())
|
|
: (DataType<T>) field.getDataType();
|
|
}
|
|
|
|
static final <T> DataType<T> allNotNull(DataType<T> defaultType) {
|
|
return defaultType.notNull();
|
|
}
|
|
|
|
static final <T> DataType<T> allNotNull(DataType<T> defaultType, Field<?> f1) {
|
|
return dataType(defaultType, f1, true);
|
|
}
|
|
|
|
static final <T> DataType<T> allNotNull(DataType<T> defaultType, Field<?> f1, Field<?> f2) {
|
|
DataType<T> result = dataType(defaultType, f1, true);
|
|
|
|
if (result.nullable())
|
|
return result;
|
|
else if (dataType(f2).nullable())
|
|
return result.null_();
|
|
else
|
|
return result;
|
|
}
|
|
|
|
static final <T> DataType<T> allNotNull(DataType<T> defaultType, Field<?> f1, Field<?> f2, Field<?> f3) {
|
|
DataType<T> result = dataType(defaultType, f1, true);
|
|
|
|
if (result.nullable())
|
|
return result;
|
|
else if (dataType(f2).nullable())
|
|
return result.null_();
|
|
else if (dataType(f3).nullable())
|
|
return result.null_();
|
|
else
|
|
return result;
|
|
}
|
|
|
|
static final <T> DataType<T> allNotNull(DataType<T> defaultType, Field<?>... fields) {
|
|
DataType<T> result = dataType(defaultType, isEmpty(fields) ? null : fields[0], true);
|
|
|
|
if (result.nullable())
|
|
return result;
|
|
else
|
|
for (Field<?> field : fields)
|
|
if (dataType(field).nullable())
|
|
return result.null_();
|
|
|
|
return result;
|
|
}
|
|
|
|
static final <T> DataType<T> anyNotNull(DataType<T> defaultType, Field<?> f1) {
|
|
return dataType(defaultType, f1, false);
|
|
}
|
|
|
|
static final <T> DataType<T> anyNotNull(DataType<T> defaultType, Field<?> f1, Field<?> f2) {
|
|
DataType<T> result = dataType(defaultType, f1, false);
|
|
|
|
if (!result.nullable())
|
|
return result;
|
|
else if (!dataType(f2).nullable())
|
|
return result.notNull();
|
|
else
|
|
return result;
|
|
}
|
|
|
|
static final <T> DataType<T> anyNotNull(DataType<T> defaultType, Field<?> f1, Field<?> f2, Field<?> f3) {
|
|
DataType<T> result = dataType(defaultType, f1, false);
|
|
|
|
if (!result.nullable())
|
|
return result;
|
|
else if (!dataType(f2).nullable())
|
|
return result.notNull();
|
|
else if (!dataType(f3).nullable())
|
|
return result.notNull();
|
|
else
|
|
return result;
|
|
}
|
|
|
|
static final <T> DataType<T> anyNotNull(DataType<T> defaultType, Field<?>... fields) {
|
|
DataType<T> result = dataType(defaultType, isEmpty(fields) ? null : fields[0], false);
|
|
|
|
if (!result.nullable())
|
|
return result;
|
|
else
|
|
for (Field<?> field : fields)
|
|
if (!dataType(field).nullable())
|
|
return result.notNull();
|
|
|
|
return result;
|
|
}
|
|
|
|
static final <T> DataType<T> nullable(DataType<T> defaultType, Field<?> f1) {
|
|
return dataType(defaultType, f1, false).null_();
|
|
}
|
|
|
|
static final <T> DataType<T> nullable(DataType<T> defaultType, Field<?> f1, Field<?> f2) {
|
|
return dataType(defaultType, f1, false).null_();
|
|
}
|
|
|
|
static final <T> DataType<T> nullable(DataType<T> defaultType, Field<?> f1, Field<?> f2, Field<?> f3) {
|
|
return dataType(defaultType, f1, false).null_();
|
|
}
|
|
|
|
static final <T> DataType<T> nullable(DataType<T> defaultType, Field<?>... fields) {
|
|
return dataType(defaultType, isEmpty(fields) ? null : fields[0], false).null_();
|
|
}
|
|
|
|
static final <T> Field<T> nullSafe(Field<T> field) {
|
|
return field == null ? DSL.val((T) null) : field;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static final <T> Field<T> nullSafe(Field<T> field, DataType<?> type) {
|
|
return field == null
|
|
? (Field<T>) DSL.val((T) null, type)
|
|
: convertVal(field, type);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static final <T> Field<T> convertVal(Field<T> field, DataType<?> type) {
|
|
return isVal(field)
|
|
? (Field<T>) extractVal(field).convertTo(type)
|
|
: field;
|
|
}
|
|
|
|
static final Field<?>[] nullSafe(Field<?>... fields) {
|
|
if (fields == null)
|
|
return EMPTY_FIELD;
|
|
|
|
Field<?>[] result = new Field<?>[fields.length];
|
|
for (int i = 0; i < fields.length; i++)
|
|
result[i] = nullSafe(fields[i]);
|
|
|
|
return result;
|
|
}
|
|
|
|
static final Field<?>[] nullSafe(Field<?>[] fields, DataType<?> type) {
|
|
if (fields == null)
|
|
return EMPTY_FIELD;
|
|
|
|
Field<?>[] result = new Field<?>[fields.length];
|
|
for (int i = 0; i < fields.length; i++)
|
|
result[i] = nullSafe(fields[i], type);
|
|
|
|
return result;
|
|
}
|
|
|
|
static final List<Field<?>> nullSafeList(Field<?>... fields) {
|
|
if (fields == null)
|
|
return asList(EMPTY_FIELD);
|
|
else
|
|
return map(fields, f -> nullSafe(f));
|
|
}
|
|
|
|
static final List<Field<?>> nullSafeList(Field<?>[] fields, DataType<?> type) {
|
|
if (fields == null)
|
|
return asList(EMPTY_FIELD);
|
|
else
|
|
return map(fields, f -> nullSafe(f, type));
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static final <T> DataType<T> nullSafeDataType(Field<T> field) {
|
|
return (DataType<T>) (field == null ? SQLDataType.OTHER : field.getDataType());
|
|
}
|
|
|
|
static final <T> Field<T> nullSafeNotNull(Field<T> field, DataType<?> type) {
|
|
return nullableIf(false, nullSafe(field, type));
|
|
}
|
|
|
|
static final <T> Field<T> nullableIf(boolean nullable, Field<T> field) {
|
|
return isVal(field)
|
|
? extractVal(field).convertTo(field.getDataType().nullable(nullable))
|
|
: field;
|
|
}
|
|
|
|
// TODO: In a new expression tree model, we'll support generic visitors of some sort
|
|
// ---------------------------------------------------------------------------------
|
|
|
|
static final boolean containsDeclaredTable(Table<?> in, Table<?> search) {
|
|
return traverseJoins(in, false, r -> r, search(search, t -> t));
|
|
}
|
|
|
|
static final boolean containsDeclaredTable(Iterable<? extends Table<?>> in, Table<?> search) {
|
|
return traverseJoins(in, false, r -> r, search(search, t -> t));
|
|
}
|
|
|
|
private static final BiFunction<Boolean, Table<?>, Boolean> search(Table<?> table, Function<? super Table<?>, ? extends Table<?>> f) {
|
|
Table<?> unaliased = f.apply(table);
|
|
return (r, t) -> r || unaliased.equals(f.apply(t));
|
|
}
|
|
|
|
static final boolean containsUnaliasedTable(Table<?> in, Table<?> search) {
|
|
|
|
// [#6304] [#7626] Improved alias discovery
|
|
return traverseJoins(in, false, r -> r, search(search, Tools::unalias));
|
|
}
|
|
|
|
static final boolean containsUnaliasedTable(Iterable<? extends Table<?>> in, Table<?> search) {
|
|
|
|
// [#6304] [#7626] Improved alias discovery
|
|
return traverseJoins(in, false, r -> r, search(search, Tools::unalias));
|
|
}
|
|
|
|
static final void traverseJoins(Iterable<? extends Table<?>> i, Consumer<? super Table<?>> consumer) {
|
|
for (Table<?> t : i)
|
|
traverseJoins(t, consumer);
|
|
}
|
|
|
|
static final void traverseJoins(Table<?> t, Consumer<? super Table<?>> consumer) {
|
|
traverseJoins(t, null, null, (result, x) -> { consumer.accept(x); return result; });
|
|
}
|
|
|
|
static final <T> T traverseJoins(
|
|
Iterable<? extends Table<?>> i,
|
|
T result,
|
|
Predicate<? super T> abort,
|
|
BiFunction<? super T, ? super Table<?>, ? extends T> function
|
|
) {
|
|
for (Table<?> t : i)
|
|
if (abort != null && abort.test(result))
|
|
return result;
|
|
else
|
|
result = traverseJoins(t, result, abort, function);
|
|
|
|
return result;
|
|
}
|
|
|
|
static final <T> T traverseJoins(
|
|
Table<?> t,
|
|
T result,
|
|
Predicate<? super T> abort,
|
|
BiFunction<? super T, ? super Table<?>, ? extends T> function
|
|
) {
|
|
return traverseJoins(t, result, abort, null, null, null, function);
|
|
}
|
|
|
|
static final <T> T traverseJoins(
|
|
Iterable<? extends Table<?>> i,
|
|
T result,
|
|
Predicate<? super T> abort,
|
|
Predicate<? super JoinTable> recurseLhs,
|
|
Predicate<? super JoinTable> recurseRhs,
|
|
BiFunction<? super T, ? super JoinType, ? extends T> joinTypeFunction,
|
|
BiFunction<? super T, ? super Table<?>, ? extends T> tableFunction
|
|
) {
|
|
for (Table<?> t : i)
|
|
if (abort != null && abort.test(result))
|
|
return result;
|
|
else
|
|
result = traverseJoins(t, result, abort, recurseLhs, recurseRhs, joinTypeFunction, tableFunction);
|
|
|
|
return result;
|
|
}
|
|
|
|
static final <T> T traverseJoins(
|
|
Table<?> t,
|
|
T result,
|
|
Predicate<? super T> abort,
|
|
Predicate<? super JoinTable> recurseLhs,
|
|
Predicate<? super JoinTable> recurseRhs,
|
|
BiFunction<? super T, ? super JoinType, ? extends T> joinTypeFunction,
|
|
BiFunction<? super T, ? super Table<?>, ? extends T> tableFunction
|
|
) {
|
|
if (abort != null && abort.test(result))
|
|
return result;
|
|
|
|
if (t instanceof JoinTable) { JoinTable j = (JoinTable) t;
|
|
if (recurseLhs == null || recurseLhs.test(j)) {
|
|
result = traverseJoins(j.lhs, result, abort, recurseLhs, recurseRhs, joinTypeFunction, tableFunction);
|
|
|
|
if (abort != null && abort.test(result))
|
|
return result;
|
|
}
|
|
|
|
if (joinTypeFunction != null) {
|
|
result = joinTypeFunction.apply(result, j.type);
|
|
|
|
if (abort != null && abort.test(result))
|
|
return result;
|
|
}
|
|
|
|
if (recurseRhs == null || recurseRhs.test(j))
|
|
result = traverseJoins(j.rhs, result, abort, recurseLhs, recurseRhs, joinTypeFunction, tableFunction);
|
|
}
|
|
else if (tableFunction != null)
|
|
result = tableFunction.apply(result, t);
|
|
|
|
return result;
|
|
}
|
|
|
|
static final String capitalize(String string) {
|
|
return string.substring(0, 1).toUpperCase() + string.substring(1);
|
|
}
|
|
|
|
static final String decapitalize(String string) {
|
|
return string.substring(0, 1).toLowerCase() + string.substring(1);
|
|
}
|
|
|
|
static final String pascalCase(String snakeCase) {
|
|
return Stream.of(snakeCase.toLowerCase().split("_")).map(Tools::capitalize).collect(joining());
|
|
}
|
|
|
|
static final String camelCase(String snakeCase) {
|
|
return decapitalize(pascalCase(snakeCase));
|
|
}
|
|
|
|
static final String characterLiteral(char character) {
|
|
return "'" + ("" + character).replace("\\", "\\\\").replace("'", "\\'") + "'";
|
|
}
|
|
|
|
static final String stringLiteral(String string) {
|
|
return "\"" + string.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n\" + \n\"") + "\"";
|
|
}
|
|
|
|
/**
|
|
* Access to the JDK 9 {@link Matcher#replaceAll(Function)} function.
|
|
*/
|
|
static final String replaceAll(String string, Matcher matcher, Function<MatchResult, String> replacer) {
|
|
|
|
if (true)
|
|
return matcher.replaceAll(replacer);
|
|
|
|
|
|
// Java 8 version
|
|
boolean find = matcher.find();
|
|
if (find) {
|
|
StringBuffer sb = new StringBuffer();
|
|
|
|
do
|
|
matcher.appendReplacement(sb, replacer.apply(matcher));
|
|
while (find = matcher.find());
|
|
|
|
matcher.appendTail(sb);
|
|
return sb.toString();
|
|
}
|
|
|
|
return string;
|
|
}
|
|
|
|
static final int asInt(long value) {
|
|
return asInt(value, () -> new DataTypeException("Long value too large for int downcast: " + value));
|
|
}
|
|
|
|
static final <E extends Exception> int asInt(long value, Supplier<E> e) throws E {
|
|
if (value > Integer.MAX_VALUE)
|
|
throw e.get();
|
|
else
|
|
return (int) value;
|
|
}
|
|
|
|
/**
|
|
* Used to work around bugs in JDBC drivers, e.g.
|
|
* https://github.com/h2database/h2database/issues/3236
|
|
*/
|
|
static final <T, E extends Exception> T ignoreNPE(ThrowingSupplier<? extends T, ? extends E> supplier, Supplier<? extends T> ifNPE) throws E {
|
|
try {
|
|
return supplier.get();
|
|
}
|
|
catch (NullPointerException e) {
|
|
return ifNPE.get();
|
|
}
|
|
catch (Exception e) {
|
|
if (ExceptionTools.getCause(e, NullPointerException.class) != null)
|
|
return ifNPE.get();
|
|
else
|
|
throw e;
|
|
}
|
|
}
|
|
}
|