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

org.jaxdb.jsql.PostgreSQLCompiler Maven / Gradle / Ivy

/* Copyright (c) 2017 JAX-DB
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * You should have received a copy of The MIT License (MIT) along with this
 * program. If not, see .
 */

package org.jaxdb.jsql;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Types;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.TemporalUnit;
import java.util.ArrayList;

import org.jaxdb.jsql.keyword.Select;
import org.jaxdb.vendor.DbVendor;
import org.jaxdb.vendor.Dialect;
import org.libj.io.Readers;
import org.libj.io.Streams;
import org.libj.io.UnsynchronizedStringReader;

final class PostgreSQLCompiler extends Compiler {
  PostgreSQLCompiler() {
    super(DbVendor.POSTGRE_SQL);
  }

  @Override
  void onConnect(final Connection connection) {
  }

  @Override
  void onRegister(final Connection connection) throws SQLException {
    try (final Statement statement = connection.createStatement()) {
      try {
        final StringBuilder b = new StringBuilder("BEGIN;\n");
        b.append("SELECT pg_advisory_xact_lock(2142616474639426746);\n");
        b.append("CREATE OR REPLACE FUNCTION MODULUS(dividend double precision, divisor double precision) RETURNS numeric AS $$\n");
        b.append("DECLARE\n");
        b.append("  factor double precision;\n");
        b.append("  result double precision;\n");
        b.append("BEGIN\n");
        b.append("  factor := dividend / divisor;\n");
        b.append("  IF factor < 0 THEN\n");
        b.append("    factor := CEIL(factor);\n");
        b.append("  ELSE\n");
        b.append("    factor := FLOOR(factor);\n");
        b.append("  END IF;\n");
        b.append("  RETURN dividend - divisor * factor;\n");
        b.append("END;\n");
        b.append("$$ LANGUAGE plpgsql;\n");
        b.append("END;");
        statement.execute(b.toString());
      }
      catch (final SQLException e) {
        if (!"X0Y68".equals(e.getSQLState())) // FUNCTION '*' already exists
          throw e;
      }

      try {
        final StringBuilder b = new StringBuilder("BEGIN;\n");
        b.append("SELECT pg_advisory_xact_lock(2142616474639426746);\n");
        b.append("CREATE OR REPLACE FUNCTION LOG2(num numeric) RETURNS numeric AS $$\n");
        b.append("DECLARE\n");
        b.append("  result double precision;\n");
        b.append("BEGIN\n");
        b.append("  RETURN LOG(2, num);\n");
        b.append("END;\n");
        b.append("$$ LANGUAGE plpgsql;\n");
        b.append("END;");
        statement.execute(b.toString());
      }
      catch (final SQLException e) {
        if (!"X0Y68".equals(e.getSQLState())) // FUNCTION '*' already exists
          throw e;
      }

      try {
        final StringBuilder b = new StringBuilder("BEGIN;\n");
        b.append("SELECT pg_advisory_xact_lock(2142616474639426746);\n");
        b.append("CREATE OR REPLACE FUNCTION LOG10(num numeric) RETURNS numeric AS $$\n");
        b.append("DECLARE\n");
        b.append("  result double precision;\n");
        b.append("BEGIN\n");
        b.append("  RETURN LOG(10, num);\n");
        b.append("END;\n");
        b.append("$$ LANGUAGE plpgsql;\n");
        b.append("END;");
        statement.execute(b.toString());
      }
      catch (final SQLException e) {
        if (!"X0Y68".equals(e.getSQLState())) // FUNCTION '*' already exists
          throw e;
      }
    }
  }

  @Override
  void setSessionId(final Statement statement, final String sessionId) throws SQLException {
    statement.execute(sessionId != null ? "SET SESSION jaxdb.session_id = '" + sessionId + "'" : "SET SESSION jaxdb.session_id = DEFAULT");
  }

  @Override
  StringBuilder translateEnum(final StringBuilder b, final data.ENUM from, final data.ENUM to) {
    b.append("::text::");
    q(b, to.type().getAnnotation(EntityEnum.Type.class).value());
    return b;
  }

  @Override
  void compileCaseElse(final data.Column variable, final data.Column _else, final Compilation compilation) throws IOException, SQLException {
    compilation.sql.append("CASE ");
    if (variable instanceof data.ENUM && _else instanceof data.CHAR)
      toChar((data.ENUM)variable, compilation);
    else
      variable.compile(compilation, true);
  }

  @Override
  void compileWhenThenElse(final ArrayList> whenThen, final data.Column _else, final Compilation compilation) throws IOException, SQLException {
    boolean castAllToChar = false;
    for (int i = 0, i$ = whenThen.size(); i < i$;) { // [RA]
      final data.Column when = whenThen.get(i++);
      final data.Column then = whenThen.get(i++);
      castAllToChar |= when instanceof data.CHAR || then instanceof data.CHAR || _else instanceof data.CHAR;
    }

    if (castAllToChar) {
      final StringBuilder sql = compilation.sql;
      for (int i = 0, i$ = whenThen.size(); i < i$;) { // [RA]
        final data.Column when = whenThen.get(i++);
        final data.Column then = whenThen.get(i++);
        sql.append(" WHEN ");
        if (when instanceof data.ENUM)
          toChar((data.ENUM)when, compilation);
        else
          when.compile(compilation, true);

        sql.append(" THEN ");
        if (then instanceof data.ENUM)
          toChar((data.ENUM)then, compilation);
        else
          then.compile(compilation, true);
      }

      sql.append(" ELSE ");
      if (_else instanceof data.ENUM)
        toChar((data.ENUM)_else, compilation);
      else
        _else.compile(compilation, true);
      sql.append(" END");
    }
    else {
      super.compileWhenThenElse(whenThen, _else, compilation);
    }
  }

  @Override
  void compile(final ExpressionImpl.Concat expression, final Compilation compilation) throws IOException, SQLException {
    final StringBuilder sql = compilation.sql;
    sql.append("CONCAT(");
    for (int i = 0, i$ = expression.a.length; i < i$; ++i) { // [A]
      final Subject arg = toSubject(expression.a[i]);
      if (i > 0)
        sql.append(", ");

      arg.compile(compilation, true);
      sql.append("::text");
    }
    sql.append(')');
  }

  @Override
  void compileIntervalAdd(final type.Column a, final Interval b, final Compilation compilation) throws IOException, SQLException {
    compileInterval(a, "+", b, compilation);
  }

  @Override
  void compileIntervalSub(final type.Column a, final Interval b, final Compilation compilation) throws IOException, SQLException {
    compileInterval(a, "-", b, compilation);
  }

  @Override
  void compileInterval(final type.Column a, final String o, final Interval b, final Compilation compilation) throws IOException, SQLException {
    // FIXME: {@link Interval#compile(Compilation,boolean)}
    final StringBuilder sql = compilation.sql;

    sql.append("((");
    toSubject(a).compile(compilation, true);
    sql.append(") ");
    sql.append(o);
    sql.append(" (");
    sql.append("INTERVAL '");
    final ArrayList units = b.getUnits();
    for (int i = 0, i$ = units.size(); i < i$; ++i) { // [RA]
      final TemporalUnit unit = units.get(i);
      if (i > 0)
        sql.append(' ');

      final long component;
      final String unitString;
      if (unit == Interval.Unit.MICROS) {
        component = b.get(unit);
        unitString = "MICROSECOND ";
      }
      else if (unit == Interval.Unit.MILLIS) {
        component = b.get(unit);
        unitString = "MILLISECOND ";
      }
      else if (unit == Interval.Unit.QUARTERS) {
        component = b.get(unit) * 3;
        unitString = "MONTH ";
      }
      else if (unit == Interval.Unit.CENTURIES) {
        component = b.get(unit) * 100;
        unitString = "YEARS ";
      }
      else if (unit == Interval.Unit.MILLENNIA) {
        component = b.get(unit) * 1000;
        unitString = "YEARS ";
      }
      else {
        component = b.get(unit);
        unitString = unit.toString();
      }

      sql.append(component).append(' ').append(unitString, 0, unitString.length() - 1);
    }

    sql.append("'))");
  }

  @Override
  StringBuilder getPreparedStatementMark(final StringBuilder b, final data.Column column) {
    b.append('?');
    if (column instanceof data.ENUM) {
      b.append("::");
      q(b, column.type().getAnnotation(EntityEnum.Type.class).value());
    }

    return b;
  }

  private static void toChar(final data.ENUM column, final Compilation compilation) throws IOException, SQLException {
    final StringBuilder sql = compilation.sql;
    sql.append("CAST(");
    column.compile(compilation, true);
    sql.append(" AS CHAR(").append(column.length()).append("))");
  }

  @Override
  final void compilePredicate(final ComparisonPredicate predicate, final Compilation compilation) throws IOException, SQLException {
    final Subject a = predicate.a;
    final Subject b = predicate.b;
    if (a.getClass() == b.getClass() || (!(a instanceof data.ENUM) && !(b instanceof data.ENUM))) {
      super.compilePredicate(predicate, compilation);
    }
    else {
      if (a instanceof data.ENUM)
        toChar((data.ENUM)a, compilation);
      else
        a.compile(compilation, true);

      final StringBuilder sql = compilation.sql;
      sql.append(' ');
      predicate.compile(null, sql, false);
      sql.append(' ');
      if (b instanceof data.ENUM)
        toChar((data.ENUM)b, compilation);
      else
        b.compile(compilation, true);
    }
  }

  @Override
  void compileMod(final type.Column a, final type.Column b, final Compilation compilation) throws IOException, SQLException {
    final StringBuilder sql = compilation.sql;
    sql.append("MODULUS(");
    toSubject(a).compile(compilation, true);
    sql.append(", ");
    toSubject(b).compile(compilation, true);
    sql.append(')');
  }

  private static void compileCastNumeric(final Subject dateType, final Compilation compilation) throws IOException, SQLException {
    if (!(dateType instanceof data.ApproxNumeric)) {
      dateType.compile(compilation, true);
    }
    else {
      final StringBuilder sql = compilation.sql;
      sql.append("CAST(");
      dateType.compile(compilation, true);
      sql.append(" AS NUMERIC)");
    }
  }

  private static void compileLog(final String sqlFunction, final Subject a, final Subject b, final Compilation compilation) throws IOException, SQLException {
    final StringBuilder sql = compilation.sql;
    sql.append(sqlFunction).append('(');
    compileCastNumeric(a, compilation);

    if (b != null) {
      sql.append(", ");
      compileCastNumeric(b, compilation);
    }

    sql.append(')');
  }

  @Override
  void compileLn(final type.Column a, final Compilation compilation) throws IOException, SQLException {
    compileLog("LN", toSubject(a), null, compilation);
  }

  @Override
  void compileLog(final type.Column a, final type.Column b, final Compilation compilation) throws IOException, SQLException {
    compileLog("LOG", toSubject(a), toSubject(b), compilation);
  }

  @Override
  void compileLog2(final type.Column a, final Compilation compilation) throws IOException, SQLException {
    compileLog("LOG2", toSubject(a), null, compilation);
  }

  @Override
  void compileLog10(final type.Column a, final Compilation compilation) throws IOException, SQLException {
    compileLog("LOG10", toSubject(a), null, compilation);
  }

  @Override
  void compileRound(final type.Column a, final type.Column b, final Compilation compilation) throws IOException, SQLException {
    final StringBuilder sql = compilation.sql;
    sql.append("ROUND(");
    if (b instanceof data.Numeric && !((data.Numeric)b).isNull() && ((data.Numeric)b).get().intValue() == 0) {
      toSubject(a).compile(compilation, true);
    }
    else {
      compileCastNumeric(toSubject(a), compilation);
      sql.append(", ");
      toSubject(b).compile(compilation, true);
    }
    sql.append(')');
  }

  @Override
  void setParameter(final data.CLOB column, final PreparedStatement statement, final int parameterIndex, final boolean isForUpdateWhere) throws IOException, SQLException {
    try (final Reader in = column.getForUpdateWhereGetOld(isForUpdateWhere)) {
      if (in != null)
        statement.setString(parameterIndex, Readers.readFully(in));
      else
        statement.setNull(parameterIndex, column.sqlType());
    }
  }

  @Override
  Reader getParameter(final data.CLOB clob, final ResultSet resultSet, final int columnIndex) throws SQLException {
    final String value = resultSet.getString(columnIndex);
    return value == null ? null : new UnsynchronizedStringReader(value);
  }

  @Override
  void setParameter(final data.BLOB column, final PreparedStatement statement, final int parameterIndex, final boolean isForUpdateWhere) throws IOException, SQLException {
    try (final InputStream in = column.getForUpdateWhereGetOld(isForUpdateWhere)) {
      if (in != null)
        statement.setBytes(parameterIndex, Streams.readBytes(in));
      else
        statement.setNull(parameterIndex, Types.BINARY);
    }
  }

  @Override
  LocalDateTime getParameter(final data.DATETIME dateTime, final ResultSet resultSet, final int columnIndex) throws SQLException {
    // FIXME: For some reason, the ResultSet.getTimestamp() or ResultSet.getObject() way to get this returns incorrect values for the `-(INTERVAL '200 years')` query
    final String value = resultSet.getString(columnIndex);
    return resultSet.wasNull() || value == null ? null : Dialect.dateTimeFromString(value);
  }

  @Override
  void updateColumn(final data.TIME column, final ResultSet resultSet, final int columnIndex) throws SQLException {
    final LocalTime value = column.get();
    if (value != null)
      resultSet.updateTime(columnIndex, Time.valueOf(value));
    else
      resultSet.updateNull(columnIndex);
  }

  @Override
  boolean aliasInForUpdate() {
    return true;
  }

  @Override
  void appendForOf(final StringBuilder sql, final data.Table table, final Compilation compilation) {
    compilation.getAlias(table).compile(compilation, false);
  }

  @Override
  @SuppressWarnings("rawtypes")
  void compileInsertOnConflict(final data.Column[] columns, final Select.untyped.SELECT select, final data.Column[] onConflict, final boolean doUpdate, final Compilation compilation) throws IOException, SQLException {
    if (select != null)
      compileInsertSelect(columns, select, false, compilation);
    else
      compileInsert(columns, false, compilation);

    final StringBuilder sql = compilation.sql;
    sql.append(" ON CONFLICT ");
    if (onConflict != null) {
      sql.append('(');
      for (int i = 0, i$ = onConflict.length; i < i$; ++i) { // [A]
        if (i > 0)
          sql.append(", ");

        onConflict[i].compile(compilation, false);
      }
      sql.append(')');
    }

    if (doUpdate) {
      sql.append(" DO UPDATE SET ");

      boolean modified = false;
      for (int i = 0, i$ = columns.length; i < i$; ++i) { // [A]
        final data.Column column = columns[i];
        if (column.primaryIndexType != null)
          continue;

        if (select != null) {
          if (modified)
            sql.append(", ");

          q(sql, column.name).append(" = EXCLUDED.");
          q(sql, column.name);
          modified = true;
        }
        else if (shouldUpdate(column, compilation)) {
          if (modified)
            sql.append(", ");

          q(sql, column.name).append(" = ");
          compilation.addParameter(column, false, false);
          modified = true;
        }
      }
    }
    else {
      sql.append(" DO NOTHING");
    }
  }

  private String getNames(final data.Column[] autos) {
    final StringBuilder b = new StringBuilder();
    for (int i = 0, i$ = autos.length; i < i$; ++i) { // [A]
      if (i > 0)
        b.append(", ");

      q(b, autos[i].name);
    }

    return b.toString();
  }

  @Override
  StringBuilder prepareSqlReturning(final StringBuilder sql, final data.Column[] autos) {
    return super.prepareSqlReturning(sql.append(" RETURNING ").append(getNames(autos)), autos);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy