WordTable.java

/*
** Module   : WordTable.java
** Abstract : Word table data
**
** Copyright (c) 2021-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 IAS 20219314 Created initial version.
**     OM  20220817 WordTable must be dialect sensitive.
** 002 OM  20240718 Fixed the list of columns in foreign key when creating FK constraints.
*/

/*
** 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.orm;

import static com.goldencode.p2j.persist.orm.DDLGeneratorWorker.*;
import java.util.*;
import java.util.concurrent.atomic.*;
import java.util.stream.*;
import com.goldencode.p2j.persist.dialect.*;
import com.goldencode.p2j.schema.*;

/** Word table data */
public class WordTable
{
   /** The dialect used to generated DDL statements. */
   public final Dialect dialect;
   
   /** The name of the parent table. */
   public final String parentTableName;
   
   /** The field name. */
   public final String fieldName;
   
   /** Custom extent hints. */
   public final List<CustomExtentField> customExtentFields;
   
   /** The extent size. */
   public final long extent;
   
   /** Is this field a case-sensitive character? */
   public final boolean caseSensitive;
   
   /** The word table name. */
   public final String tableName;
   
   /** The word table PK name. */
   public final String pkName;
   
   /** The word table FK index name. */
   public final String fkIndexName;
   
   /** The word table FK constraint name. */
   public final String fkName;
   
   /** The name of the word table index on 'word' field. */
   public final String indexName;
   
   /** parent table trigger function name */
   public final String triggerFunctionName;
   
   /** parent table AFTER UPDATE trigger name */
   public final String afterUpdateTriggerName;
   
   /** parent table AFTER INSERT trigger name */
   public final String afterInsertTriggerName;
   
   /**
    * The constructor is private. Objects of this class can be constructed only using the {@link Builder}.
    *
    * @param   dialect
    *          The dialect used to generated DDL statements.
    * @param   parentTableName
    *          parent table name  
    * @param   fieldName
    *          field name
    * @param   customExtentFields
    *          custom extent hints
    * @param   extent 
    *          field extent
    * @param   caseSensitive
    *          field is case-sensitive flag
    * @param   tableName
    *          word table name
    * @param   pkName 
    *          word table PK name
    * @param   fkIndexName
    *          word table FK index name
    * @param   fkName
    *          word table FK constraint name
    * @param   indexName
    *          name of the word table index on 'word' field
    * @param   triggerFunctionName
    *          parent table trigger function name
    * @param   afterUpdateTriggerName
    *          parent table AFTER UPDATE trigger name
    * @param   afterInsertTriggerName
    *          parent table AFTER INSERT trigger name
    */
   private WordTable(
         Dialect dialect,
         String parentTableName, String fieldName, List<CustomExtentField> customExtentFields,
         long extent, boolean caseSensitive, 
         String tableName, String pkName, String fkIndexName, String fkName, String indexName,
         String triggerFunctionName, 
         String afterUpdateTriggerName, String afterInsertTriggerName)
   {
      this.dialect = dialect;
      this.parentTableName = parentTableName;
      this.fieldName = fieldName;
      this.customExtentFields = customExtentFields;
      this.extent = extent;
      this.caseSensitive = caseSensitive;
      this.tableName = tableName;
      this.pkName = pkName;
      this.fkIndexName = fkIndexName;
      this.fkName = fkName;
      this.indexName = indexName;
      this.triggerFunctionName = triggerFunctionName;
      this.afterUpdateTriggerName = afterUpdateTriggerName;
      this.afterInsertTriggerName = afterInsertTriggerName;
   }
   
   /**
    * Get drop PK DDL statement
    * @param   eoln
    *          OS-specific end of line terminator.
    * @param   suffix
    *          dialect-specific statement suffix
    * @return  drop PK DDL statement
   */
   public String dropPK(String eoln, String suffix)
   {
      return "alter table " + tableName + eoln +
             INDENT + "drop constraint if exists " + pkName + suffix + eoln;
   }

   /**
    * Get create PK DDL statement
    * 
    * @param   eoln
    *          The OS-specific end of line terminator.
    * @param   suffix .
    *          The dialect-specific statement suffix.
    *
    * @return  The DDL statement for dropping the PK.
    */
   public String createPK(String eoln, String suffix)
   {
      return "alter table " + tableName + eoln +
             INDENT + "add constraint " + pkName + eoln +
             INDENT + "primary key (parent__id, " +
                                    (extent == 0 ? "" : "list__index, ") +
                                    "word)" + suffix + eoln;
   }
   
   /**
    * Get drop FK DDL statement.
    * 
    * @param   eoln
    *          OS-specific end of line terminator.
    * @param   suffix
    *          dialect-specific statement suffix
    *
    * @return  The DDL statement for dropping the PK.
    */
   public String dropFK(String eoln, String suffix)
   {
      return "alter table " + tableName + eoln +
             INDENT + "drop constraint if exists " + fkName + suffix + eoln;
   }

   /**
    * Get create FK DDL statement.
    * 
    * @param   eoln
    *          OS-specific end of line terminator.
    *          
    * @return  The DDL statement for creating a FK constraint.
    */
   public String createFK(String eoln)
   {
      boolean singleFkField = extent == 0 || customExtentFields != null;
      return dialect.getAddForeignKeyConstraintString(
               tableName, fkName,
               singleFkField ? "parent__id" : "parent__id, list__index", // local fields
               parentTableName,
               singleFkField ? Session.PK : "parent__id, list__index",   // foreign fields
               INDENT, eoln) +
            dialect.getDelimiter() + eoln;
   }
   
   /**
    * Get drop FK index DDL statement.
    *
    * @param   eoln
    *          OS-specific end of line terminator.
    *
    * @return  The dialect specific DDL statement for dropping the FK index on a 'word' field.
    */
   public String dropFkIndex(String eoln)
   {
      return dialect.getDropIndexString(true, fkIndexName, tableName) + dialect.getDelimiter() + eoln;
   }
   
   /**
    * Get create FK index DDL statement
    * @param   eoln
    *          OS-specific end of line terminator.
    * @param   suffix
    *          dialect-specific statement suffix
    * @return  create FK index DDL statement
   */
   public String createFkIndex(String eoln, String suffix)
   {
      return "create index " + fkIndexName + " on " + tableName + " (parent__id" + 
             (extent == 0 || (customExtentFields != null) ? "" : ", list__index" ) + ")" + suffix + eoln;
   }
   
   /**
    * Get drop index on a 'word' field DDL statement.
    *
    * @param   eoln
    *          OS-specific end of line terminator.
    *
    * @return  The dialect specific DDL statement for dropping the  index on a 'word' field.
   */
   public String dropIndex(String eoln)
   {
      return dialect.getDropIndexString(true, indexName, tableName) + dialect.getDelimiter() + eoln;
   }
   
   /**
    * Get create index on a 'word' field DDL statement
    *
    * @param   eoln
    *          OS-specific end of line terminator.
    *
    * @param   suffix
    *          The dialect-specific statement suffix
    *
    * @return  The create-index on a 'word' field DDL statement
   */
   public String createIndex(String eoln, String suffix)
   {
      return "create index " + indexName + " on " + tableName + " (word)" + suffix + eoln;
   }
   
   /** Word table data builder */
   public static class Builder
   {
      /** The database objects' name builder. */
      private final NameBuilder nb;
      
      /** The original parent table name. */
      private final String originalParentTableName;
      
      /** The parent table name. */
      private String parentTableName;
      
      /** The field name. */
      private final String fieldName;
      
      /** Custom extent hints. */
      private List<CustomExtentField> customExtentFields = null;
      
      /** The extent size. */
      private final long extent;
      
      /** The field is case-sensitive flag. */
      private final boolean caseSensitive;
      
      /** The word table name. */
      private String tableName;
      
      /** The word table PK name. */
      private final String pkName;
      
      /** The word table FK index name. */
      private String fkIndexName;
      
      /** The word table FK constraint name. */
      private String fkName;
      
      /** The name of the word table index on 'word' field. */
      private String indexName;
      
      /** The parent table trigger function name. */
      private String triggerFunctionName;
      
      /** The parent  table AFTER INSERT trigger name. */
      private String afterInsertTriggerName;
      
      /** The parent table AFTER UPDATE trigger name. */
      private String afterUpdateTriggerName;
      
      /**
       * Constructor
       * 
       * @param nb
       *        The database objects' name builder.
       * @param parentTableName
       *        parent table name  
       * @param fieldName
       *        field name
       * @param extent 
       *        field extent
       * @param caseSensitive
       *        field is case-sensitive flag
       */
      public Builder(NameBuilder nb,
                     String parentTableName, String fieldName, long extent, boolean caseSensitive)
      {
         this.nb = nb;
         this.originalParentTableName = parentTableName;
         this.parentTableName = extent == 0 ? parentTableName : parentTableName + "__" + extent;
         this.fieldName = fieldName;
         this.extent = extent;
         this.caseSensitive = caseSensitive;
         this.tableName = nb.build(originalParentTableName, fieldName);
         this.pkName = nb.build("pk", originalParentTableName, fieldName);
         this.fkIndexName = nb.build("fkidx", originalParentTableName, fieldName);
         this.fkName = nb.build("fk", originalParentTableName, fieldName);
         this.indexName = nb.build("idx", originalParentTableName, fieldName);
         this.triggerFunctionName = nb.build(originalParentTableName, fieldName, "trg");
         this.afterInsertTriggerName = nb.build(originalParentTableName, fieldName, "ins"); 
         this.afterUpdateTriggerName = nb.build(originalParentTableName, fieldName, "upd"); 
      }
      
      /**
       * Set custom extent hints.
       * 
       * @param   value
       *          The custom extent hints.
       *
       * @return  {@code this} Builder instance.
       */
      public WordTable.Builder extentHintFields(List<ExtentHintField>  value)
      {
         this.parentTableName = originalParentTableName;
         AtomicInteger pos = new AtomicInteger(0);
         this.customExtentFields = value.stream().
               map(hint -> new CustomExtentField(
                     nb, parentTableName, hint.getName(), pos.incrementAndGet())).
               collect(Collectors.toList());
         return this;
      }
      
      /**
       * Set word table name.
       * 
       * @param   value
       *          The word table name.
       *
       * @return  {@code this} Builder instance.
       */
      public WordTable.Builder tableName(String value)
      {
         this.tableName = value;
         return this;
      }
      
      /**
       * Set word table FK index name.
       * 
       * @param   value
       *          The word table FK index name.
       *
       * @return  {@code this} Builder instance.
       */
      public WordTable.Builder fkIndexName(String value)
      {
         this.fkIndexName = value;
         return this;
      }
      
      /**
       * Set word table FK constraint name.
       * 
       * @param value
       *        word table FK constraint name.
       * @return <code>this</code> Builder instance
       */
      public WordTable.Builder fkName(String value)
      {
         this.fkName = value;
         return this;
      }
      
      /**
       * Set word table index name.
       * 
       * @param value
       *        word table index name.
       * @return <code>this</code> Builder instance
       */
      public WordTable.Builder indexName(String value)
      {
         this.indexName = value;
         return this;
      }
      
      /**
       * Set word table trigger function name.
       * 
       * @param value
       *        word table trigger function name,
       * @return <code>this</code> Builder instance
       */
      public WordTable.Builder triggerFunctionName(String value)
      {
         this.triggerFunctionName = value;
         return this;
      }
      
      /**
       * Set word table AFTER INSERT trigger name.
       * 
       * @param value
       *        word table AFTER INSERT trigger name.
       * @return <code>this</code> Builder instance
       */
      public WordTable.Builder afterInsertTriggerName(String value)
      {
         this.afterInsertTriggerName = value;
         return this;
      }
      
      /**
       * Set word table AFTER UPDATE trigger name.
       * 
       * @param value
       *        word table AFTER UPDATE trigger name.
       * @return <code>this</code> Builder instance
       */
      public WordTable.Builder afterUpdateTriggerName(String value)
      {
         this.afterUpdateTriggerName = value;
         return this;
      }
      
      /**
       * Build word table data.
       * 
       * @param   dialect
       *          The dialect used to generated DDL statements.
       * 
       * @return  An {@code WordTable} instance build based on the requested criteria.
       */
      public WordTable build(Dialect dialect)
      {
         return new WordTable(dialect, parentTableName, fieldName, customExtentFields, 
                   extent, caseSensitive, 
                   tableName, pkName, fkIndexName, fkName, indexName, 
                   triggerFunctionName, afterUpdateTriggerName, afterInsertTriggerName); 
      }
   }
}