DbModelCommonMariadbMySql.java

/*
 * This file is part of Waarp Project (named also Waarp or GG).
 *
 *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
 *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
 * individual contributors.
 *
 *  All Waarp Project is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Waarp 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 * Waarp . If not, see <http://www.gnu.org/licenses/>.
 */
package org.waarp.common.database.model;

import org.waarp.common.database.DbPreparedStatement;
import org.waarp.common.database.DbRequest;
import org.waarp.common.database.DbSession;
import org.waarp.common.database.data.DbDataModel;
import org.waarp.common.database.exception.WaarpDatabaseNoConnectionException;
import org.waarp.common.database.exception.WaarpDatabaseNoDataException;
import org.waarp.common.database.exception.WaarpDatabaseSqlException;
import org.waarp.common.logging.SysErrLogger;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;

import java.sql.SQLException;
import java.sql.Types;
import java.util.concurrent.locks.ReentrantLock;

/**
 * MariaDB/MySQL common Database Model implementation
 */
public abstract class DbModelCommonMariadbMySql extends DbModelAbstract {
  /**
   * Internal Logger
   */
  private static final WaarpLogger logger =
      WaarpLoggerFactory.getLogger(DbModelCommonMariadbMySql.class);

  protected enum DBType {
    CHAR(Types.CHAR, " CHAR(3) "),
    VARCHAR(Types.VARCHAR, " VARCHAR(" + MAX_VARCHAR + ") "),
    /**
     * Used in replacement of VARCHAR for MYSQL/MARIADB (limitation of size
     * if
     * in Primary Key)
     */
    NVARCHAR(Types.VARCHAR, " VARCHAR(" + MAX_KEY_VARCHAR + ") "),
    LONGVARCHAR(Types.LONGVARCHAR, " TEXT "), BIT(Types.BIT, " " + "BOOLEAN "),
    TINYINT(Types.TINYINT, " TINYINT "), SMALLINT(Types.SMALLINT, " SMALLINT "),
    INTEGER(Types.INTEGER, " INTEGER "), BIGINT(Types.BIGINT, " BIGINT "),
    REAL(Types.REAL, " FLOAT "), DOUBLE(Types.DOUBLE, " DOUBLE "),
    VARBINARY(Types.VARBINARY, " VARBINARY(" + (MAX_BINARY * 2) + ") "),
    DATE(Types.DATE, " " + "DATE "),
    TIMESTAMP(Types.TIMESTAMP, " TIMESTAMP(3) ");

    public final int type;

    public final String constructor;

    DBType(final int type, final String constructor) {
      this.type = type;
      this.constructor = constructor;
    }

    public static String getType(final int sqltype) {
      switch (sqltype) {
        case Types.CHAR:
          return CHAR.constructor;
        case Types.VARCHAR:
          return VARCHAR.constructor;
        case Types.NVARCHAR:
          return NVARCHAR.constructor;
        case Types.LONGVARCHAR:
          return LONGVARCHAR.constructor;
        case Types.BIT:
          return BIT.constructor;
        case Types.TINYINT:
          return TINYINT.constructor;
        case Types.SMALLINT:
          return SMALLINT.constructor;
        case Types.INTEGER:
          return INTEGER.constructor;
        case Types.BIGINT:
          return BIGINT.constructor;
        case Types.REAL:
          return REAL.constructor;
        case Types.DOUBLE:
          return DOUBLE.constructor;
        case Types.VARBINARY:
          return VARBINARY.constructor;
        case Types.DATE:
          return DATE.constructor;
        case Types.TIMESTAMP:
          return TIMESTAMP.constructor;
        default:
          return null;
      }
    }
  }

  private final ReentrantLock lock = new ReentrantLock();

  @Override
  public void resetSequence(final DbSession session, final long newvalue)
      throws WaarpDatabaseNoConnectionException {
    final String action =
        "UPDATE Sequences SET seq = " + newvalue + " WHERE name = '" +
        DbDataModel.fieldseq + '\'';
    final DbRequest request = new DbRequest(session);
    try {
      request.query(action);
    } catch (final WaarpDatabaseNoConnectionException e) {
      logger.warn("ResetSequence Error", e);
    } catch (final WaarpDatabaseSqlException e) {
      logger.warn("ResetSequence Error", e);
    } finally {
      request.close();
    }
    logger.warn(action);
  }

  @Override
  public long nextSequence(final DbSession dbSession)
      throws WaarpDatabaseNoConnectionException, WaarpDatabaseSqlException,
             WaarpDatabaseNoDataException {
    lock.lock();
    try {
      long result;
      String action =
          "SELECT seq FROM Sequences WHERE name = '" + DbDataModel.fieldseq +
          "' FOR UPDATE";
      final DbPreparedStatement preparedStatement =
          new DbPreparedStatement(dbSession);
      try {
        dbSession.getConn().setAutoCommit(false);
      } catch (final SQLException ignored) {
        SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
      }
      try {
        preparedStatement.createPrepareStatement(action);
        // Limit the search
        preparedStatement.executeQuery();
        if (preparedStatement.getNext()) {
          try {
            result = preparedStatement.getResultSet().getLong(1);
          } catch (final SQLException e) {
            throw new WaarpDatabaseSqlException(e);
          }
        } else {
          throw new WaarpDatabaseNoDataException(
              "No sequence found. Must be initialized first");
        }
      } finally {
        preparedStatement.realClose();
      }
      action =
          "UPDATE Sequences SET seq = " + (result + 1) + " WHERE name = '" +
          DbDataModel.fieldseq + '\'';
      try {
        preparedStatement.createPrepareStatement(action);
        // Limit the search
        preparedStatement.executeUpdate();
      } finally {
        preparedStatement.realClose();
      }
      return result;
    } finally {
      try {
        dbSession.getConn().setAutoCommit(true);
      } catch (final SQLException ignored) {
        SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
      } finally {
        lock.unlock();
      }
    }
  }

  @Override
  protected final String validConnectionString() {
    return "select 1 from dual";
  }

  @Override
  public final String limitRequest(final String allfields, final String request,
                                   final int nb) {
    if (nb == 0) {
      return request;
    }
    return request + " LIMIT " + nb;
  }

}