All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nimbusds.infinispan.persistence.sql.JOOQFixes Maven / Gradle / Ivy

package com.nimbusds.infinispan.persistence.sql;


import com.google.common.base.Splitter;
import net.jcip.annotations.ThreadSafe;
import org.infinispan.persistence.spi.PersistenceException;
import org.jooq.Field;
import org.jooq.Merge;
import org.jooq.SQLDialect;

import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * Fixes incorrect SQL statements generated by jOOQ.
 */
@ThreadSafe
class JOOQFixes {
	
	
	/**
	 * The SQL dialects that require a merge fix.
	 */
	static final Set REQUIRE_MERGE_FIX = Set.of(SQLDialect.POSTGRES_9_3, SQLDialect.POSTGRES_9_4, SQLDialect.POSTGRES_9_5);
	
	
	/**
	 * Flag if an PostgreSQL merge statement fix is required.
	 */
	private final boolean pgMergeFixRequired;
	
	
	/**
	 * Flag if an Oracle CLOB literal fix is required.
	 */
	private final boolean oracleClobFixRequired;
	
	
	/**
	 * The cutoff length of Oracle string literals. The Oracle
	 * {@code MAX_STRING_SIZE = STANDARD = 4000 bytes}, halve to allow for
	 * a mix UTF multibyte chars.
	 *
	 * 

See https://docs.oracle.com/database/121/SQLRF/sql_elements001.htm#SQLRF55623 */ static final int ORACLE_STRING_LITERAL_CUTOFF_LENGTH = 2000; /** * The primary key constraint name, {@code null} if N/A. */ private final String pkConstraintName; /** * Creates a new instance for applying fixes to jOOQ generated SQL * statements. * * @param sqlDialect The configured SQL dialect. Must not be * {@code null}. * @param createTableStmt The create table SQL statement. Must not be * {@code null}. */ public JOOQFixes(final SQLDialect sqlDialect, final String createTableStmt) { if (REQUIRE_MERGE_FIX.contains(sqlDialect)) { pkConstraintName = parsePrimaryKeyConstraintName(createTableStmt); if (pkConstraintName == null) { throw new PersistenceException("Couldn't determine primary key constraint name from SQL create table statement: " + createTableStmt); } pgMergeFixRequired = true; } else { pgMergeFixRequired = false; pkConstraintName = null; } oracleClobFixRequired = SQLDialect.ORACLE.equals(sqlDialect); } /** * Fixes the specified SQL merge statement for PostgreSQL databases. * * @param mergeStatement The ready merge statement. Must not be * {@code null}. * * @return The (fixed) SQL statement ready for execution. */ String fixMergeStatement(final Merge mergeStatement) { if (! pgMergeFixRequired) { return mergeStatement.toString(); } return mergeStatement.toString().replace("on conflict ([unknown primary key])", "on conflict on constraint " + pkConstraintName); } /** * Parses the primary key constraint name from the specified create * table SQL statement. * * @param createTableStatement The create table SQL statement. * * @return The primary key constraint name, {@code null} if missing or * not found. */ static String parsePrimaryKeyConstraintName(final String createTableStatement) { Pattern p = Pattern.compile(".*CONSTRAINT\\s+(\\w*)\\s+PRIMARY", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); Matcher m = p.matcher(createTableStatement); if (! m.find()) { return null; } return m.group(1); } /** * Prepares an optional Oracle database write with CLOBs. * * @param sqlRecord The SQL record. * @param chunksMap The chunks map to set for later use by * {@link #completeOracleWriteClob}. * * @return The potentially modified SQL record. */ SQLRecord prepareOracleWriteCLOB(final SQLRecord sqlRecord, final AtomicReference>> chunksMap) { if (! oracleClobFixRequired) { return sqlRecord; } chunksMap.set(new HashMap<>()); Map,Object> fields = new LinkedHashMap<>(); sqlRecord.getFields().entrySet().forEach((Consumer, ?>>) en -> { if (en.getValue() instanceof String && ((String)en.getValue()).length() > ORACLE_STRING_LITERAL_CUTOFF_LENGTH) { List chunks = splitChunksForOracleWriteClob((String) en.getValue()); // Replace value with random code and map it to the chunks list String codeToReplace = UUID.randomUUID().toString(); chunksMap.get().put(codeToReplace, chunks); fields.put(en.getKey(), codeToReplace); } else { fields.put(en.getKey(), en.getValue()); } }); return new ImmutableSQLRecord(sqlRecord.getKeyColumns(), fields); } /** * Completes an optional Oracle database write with CLOBs. * * @param sqlStatement The SQL statement. * @param chunksMap The chunks map to use. * * @return The potentially modified SQL record. */ String completeOracleWriteClob(final String sqlStatement, final Map> chunksMap) { if (! oracleClobFixRequired) { return sqlStatement; } String out = sqlStatement; for (Map.Entry> en: chunksMap.entrySet()) { out = out.replace("'" + en.getKey() + "'", concatChunksForOracleWriteClob(en.getValue())); } return out; } /** * Creates an Oracle TO_CLOB concatenation. * * @param chunks The string chunks, empty list if none. * * @return The Oracle TO_CLOB concatenation, empty string if none. */ static String concatChunksForOracleWriteClob(final List chunks) { var sb = new StringBuilder(); for (int i=0; i 0) { sb.append("||"); } sb.append("TO_CLOB('"); sb.append(chunks.get(i).replace("'", "''")); // Quotes must be escaped sb.append("')"); } return sb.toString(); } /** * Splits the specified string for Oracle TO_CLOB concatenation. * * @param s The string to split. * * @return The string list. */ static List splitChunksForOracleWriteClob(final String s) { List out = new LinkedList<>(); Splitter.fixedLength(ORACLE_STRING_LITERAL_CUTOFF_LENGTH).split(s).forEach( out::add ); return out; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy