DirtyTempTableHelper.java

/*
** Module   : DirtyTempTableHelper.java
** Abstract : Helper class which generates temp table DDL
**
** Copyright (c) 2004-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 CA  20080424   @38110 Created initial version. Generates DDL for
**                           global temp tables with global indexes.
** 002 CA  20080430   @38158 Changes due to incorrect index creation: the
**                           indexes must be retrieved from the "main" 
**                           database. Added method to build the required
**                           "dirty" table and index.
** 003 ECF 20080522   @38603 Relocated createTable() helper method. A
**                           modified version of this method now resides
**                           in DefaultDirtyShareManager.
** 004 ECF 20080611   @38694 Changed invocation of queryIndexData(). We
**                           now use a common implementation in the
**                           Persistence class.
** 005 ECF 20090324   @42124 Set all dirty indices to be non-unique. It is
**                           possible for multiple records to be created
**                           simultaneously in different sessions whose
**                           default values would normally violate a unique
**                           index; however, until that index is explicitly
**                           updated, the default values must not violate the
**                           unique constraint in the dirty database.
** 006 ECF 20090511   @42152 Fixed create index statement generation. Avoid
**                           creating a superfluous index on the primary key.
**                           Avoid tacking an additional primary key component
**                           onto the end of indexes.
** 007 CA  20120710          During server startup, getIndexes will hide any
**                           persistence errors, as the terminal is not 
**                           configured yet - so, we need to explicitly log any
**                           persistence errors.
** 008 ECF 20140318          Reduced amount of index metadata queried from database to optimize
**                           performance. Replaced Apache commons logging with J2SE logging.
** 009 ECF 20140822          Added implementation of isNotNullAllowed.
** 010 EVL 20160223          Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 011 ECF 20160305          Improved concurrency of indexCache initialization.
** 012 OM  20200507          Upgrade to new persistence framework, without Hibernate.
** 013 OM  20200919          Improved access to dmo metadata.
** 014 OM  20200924          P2JIndexComponent carries multiple information to avoid map lookups for them.
** 015 SVL 20230110          P2JIndex.components() provides direct access to the components array in order to
**                           improve performance.
** 016 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.persist;

import java.util.*;
import java.util.concurrent.*;
import java.util.logging.*;
import com.goldencode.p2j.persist.orm.*;
import com.goldencode.p2j.util.logging.*;

/**
 * Helper class which generates and caches SQL phrases to create and drop
 * temporary tables, and to create indexes in those tables.
 * <p>
 * This class is responsible for global temporary tables support: the created
 * tables and indices will be global and available to all sessions.
 */
public final class DirtyTempTableHelper
extends TempTableHelper
{
   /** Logger */
   private static final CentralLogger LOG = CentralLogger.get(DirtyTempTableHelper.class.getName());
   
   /** 
    * Cache with the indexes retrieved from the database, for each table and database.
    */
   private static final Map<Database, Map<String, ArrayList<P2JIndex>>> indexCache =
      new ConcurrentHashMap<>();
   
   /**
    * Constructor.
    *
    * @param   dmoIface
    *          DMO interface which provides the public API to the temp table.
    * @param   database
    *          The database for which we generate the sql's.
    */
   DirtyTempTableHelper(Class<? extends DataModelObject> dmoIface, Database database)
   {
      super(dmoIface, database);
   }
   
   /**
    * This method will return the needed create table string, which is the 
    * "create temporary table" string.
    * 
    * @return  The create table string.
    */
   @Override
   String getCreateTableStatement()
   {
      return "create temporary table ";
   }
   
   /**
    * This method returns an empty string: the indexes must be global.
    * 
    * @return An empty string.
    */
   @Override
   String getAdditionalIndexSuffix()
   {
      return "";
   }
   
   /**
    * This method will return the indexes associated with a certain DMO, as
    * found in the associated database. It must be noted that those indexes
    * will not contain any foreign-key columns (columns with their name ending
    * with a "__fk" suffix); those columns are removed from the index metadata
    * and if an index contains only a such column, that index will not be 
    * returned.  Furthermore, all indexes will be set to be non-unique, since
    * newly created DMOs can be added from multiple sessions, and the default
    * values of unique properties could conflict.
    * <p>
    * Once retrieved, the indexes will be kept in a {@link #indexCache cache},
    * for each table, by database.
    * 
    * @param   schema
    *          The schema used for this DMO.
    * @param   dmoIface
    *          The DMO for which to retrieve the indexes.
    * 
    * @return  An iterator for the indexes associated with this DMO.
    * 
    * @throws  RuntimeException
    *          if there is any error retrieving the index metadata for the given schema and DMO.
    */
   @Override
   Iterator<P2JIndex> getIndexes(String schema, Class<? extends DataModelObject> dmoIface)
   {
      Database mainDB = new Database(database.getName());
      String table = getTableName(dmoIface);
      if (table == null)
      {
         LOG.log(Level.INFO, "NULL table for schema " + schema + "; DMO " + dmoIface.getName());
      }
      
      Map<String, ArrayList<P2JIndex>> cached = indexCache.get(mainDB);
      
      if (cached == null)
      {
         cached = new ConcurrentHashMap<>();
         Map<String, ArrayList<P2JIndex>> current = indexCache.putIfAbsent(mainDB, cached);
         if (current != null)
         {
            cached = current;
         }
      }
      
      // check if the indexes for the table already have been read
      if (cached.containsKey(table))
      {
         return cached.get(table).iterator();
      }
      
      // get the index list from the DmoMetadataManager; this will give us shared P2JIndex
      // instances, so we have to make copies to modify them
      Iterator<P2JIndex> indexIter = DmoMetadataManager.getDmoInfo(dmoIface).getDatabaseIndexes();
      ArrayList<P2JIndex> indexes = new ArrayList<>();
      
      // copy each index, remove the __fk columns if present, and ensure they are not set to be unique
      
      outer:
      while (indexIter.hasNext())
      {
         P2JIndex index = indexIter.next();
         P2JIndex copy = new P2JIndex(index);
         
         copy.setUnique(false);

         Iterator<P2JIndexComponent> compIter = copy.componentsIter();
         for (int i = 0; compIter.hasNext(); i++)
         {
            P2JIndexComponent idxComp = compIter.next();
            String compName = idxComp.getPropertyName();
            
            if (compName.endsWith("__fk")) // ORMHandler.FK_SUFFIX))
            {
               compIter.remove();
            }
            else if (i == 0 &&
                     DatabaseManager.PRIMARY_KEY.equals(compName) &&
                     !compIter.hasNext())
            {
               // ignore index on primary key; the dirty database will create such an index
               // automatically when the table is created.
               continue outer;
            }
         }
         
         // if the foreign key column this was the only component for this index, ignore the
         // index
         if (!index.componentsIter().hasNext())
         {
            continue;
         }
         
         copy.compact();
         indexes.add(copy);
      }
      
      indexes.trimToSize();
         
      // cache the found indexes
      ArrayList<P2JIndex> current = cached.putIfAbsent(table, indexes);
      if (current != null)
      {
         indexes = current;
      }
      
      return indexes.iterator();
   }
   
   /**
    * Indicate whether non-nullable columns are permitted.
    * 
    * @return  {@code true} if non-nullable columns are permitted, else {@code false}.
    */
   @Override
   boolean isNotNullAllowed()
   {
      return false;
   }
}