/**
   * Preconditions: 1. xid must in ended state.
   *
   * <p>Implementation deficiency preconditions: 1. this connection must have been used to run the
   * transaction
   *
   * <p>Postconditions: 1. Transaction is committed
   */
  private void commitOnePhase(Xid xid) throws XAException {
    try {
      // Check preconditions
      if (currentXid == null || !currentXid.equals(xid)) {
        // In fact, we don't know if xid is bogus, or if it just wasn't associated with this
        // connection.
        // Assume it's our fault.
        throw new PGXAException(
            GT.tr(
                "Not implemented: one-phase commit must be issued using the same connection that was used to start it"),
            XAException.XAER_RMERR);
      }
      if (state != STATE_ENDED)
        throw new PGXAException(GT.tr("commit called before end"), XAException.XAER_PROTO);

      // Preconditions are met. Commit
      state = STATE_IDLE;
      currentXid = null;

      conn.commit();
      conn.setAutoCommit(localAutoCommitMode);
    } catch (SQLException ex) {
      throw new PGXAException(GT.tr("Error during one-phase commit"), ex, XAException.XAER_RMERR);
    }
  }
  /**
   * Preconditions: 1. xid must be in prepared state in the server
   *
   * <p>Implementation deficiency preconditions: 1. Connection must be in idle state
   *
   * <p>Postconditions: 1. Transaction is committed
   */
  private void commitPrepared(Xid xid) throws XAException {
    try {
      // Check preconditions. The connection mustn't be used for another
      // other XA or local transaction, or the COMMIT PREPARED command
      // would mess it up.
      if (state != STATE_IDLE || conn.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE)
        throw new PGXAException(
            GT.tr("Not implemented: 2nd phase commit must be issued using an idle connection"),
            XAException.XAER_RMERR);

      String s = RecoveredXid.xidToString(xid);

      localAutoCommitMode = conn.getAutoCommit();
      conn.setAutoCommit(true);
      Statement stmt = conn.createStatement();
      try {
        stmt.executeUpdate("COMMIT PREPARED '" + s + "'");
      } finally {
        stmt.close();
        conn.setAutoCommit(localAutoCommitMode);
      }
    } catch (SQLException ex) {
      throw new PGXAException(
          GT.tr("Error committing prepared transaction"), ex, XAException.XAER_RMERR);
    }
  }
  /**
   * Preconditions: 1. xid is known to the RM or it's in prepared state
   *
   * <p>Implementation deficiency preconditions: 1. xid must be associated with this connection if
   * it's not in prepared state.
   *
   * <p>Postconditions: 1. Transaction is rolled back and disassociated from connection
   */
  public void rollback(Xid xid) throws XAException {
    if (logger.logDebug()) debug("rolling back xid = " + xid);

    // We don't explicitly check precondition 1.

    try {
      if (currentXid != null && xid.equals(currentXid)) {
        state = STATE_IDLE;
        currentXid = null;
        conn.rollback();
        conn.setAutoCommit(localAutoCommitMode);
      } else {
        String s = RecoveredXid.xidToString(xid);

        conn.setAutoCommit(true);
        Statement stmt = conn.createStatement();
        try {
          stmt.executeUpdate("ROLLBACK PREPARED '" + s + "'");
        } finally {
          stmt.close();
        }
      }
    } catch (SQLException ex) {
      throw new PGXAException(
          GT.tr("Error rolling back prepared transaction"), ex, XAException.XAER_RMERR);
    }
  }
  /**
   * Preconditions: 1. flags must be one of TMNOFLAGS, TMRESUME or TMJOIN 2. xid != null 3.
   * connection must not be associated with a transaction 4. the TM hasn't seen the xid before
   *
   * <p>Implementation deficiency preconditions: 1. TMRESUME not supported. 2. if flags is TMJOIN,
   * we must be in ended state, and xid must be the current transaction 3. unless flags is TMJOIN,
   * previous transaction using the connection must be committed or prepared or rolled back
   *
   * <p>Postconditions: 1. Connection is associated with the transaction
   */
  public void start(Xid xid, int flags) throws XAException {
    if (logger.logDebug()) debug("starting transaction xid = " + xid);

    // Check preconditions
    if (flags != XAResource.TMNOFLAGS && flags != XAResource.TMRESUME && flags != XAResource.TMJOIN)
      throw new PGXAException(GT.tr("Invalid flags"), XAException.XAER_INVAL);

    if (xid == null) throw new PGXAException(GT.tr("xid must not be null"), XAException.XAER_INVAL);

    if (state == STATE_ACTIVE)
      throw new PGXAException(
          GT.tr("Connection is busy with another transaction"), XAException.XAER_PROTO);

    // We can't check precondition 4 easily, so we don't. Duplicate xid will be catched in prepare
    // phase.

    // Check implementation deficiency preconditions
    if (flags == TMRESUME)
      throw new PGXAException(GT.tr("suspend/resume not implemented"), XAException.XAER_RMERR);

    // It's ok to join an ended transaction. WebLogic does that.
    if (flags == TMJOIN) {
      if (state != STATE_ENDED)
        throw new PGXAException(
            GT.tr("Transaction interleaving not implemented"), XAException.XAER_RMERR);

      if (!xid.equals(currentXid))
        throw new PGXAException(
            GT.tr("Transaction interleaving not implemented"), XAException.XAER_RMERR);
    } else if (state == STATE_ENDED)
      throw new PGXAException(
          GT.tr("Transaction interleaving not implemented"), XAException.XAER_RMERR);

    try {
      localAutoCommitMode = conn.getAutoCommit();
      conn.setAutoCommit(false);
    } catch (SQLException ex) {
      throw new PGXAException(GT.tr("Error disabling autocommit"), ex, XAException.XAER_RMERR);
    }

    // Preconditions are met, Associate connection with the transaction
    state = STATE_ACTIVE;
    currentXid = xid;
  }
  /**
   * Preconditions: 1. xid != null 2. xid is in ended state
   *
   * <p>Implementation deficiency preconditions: 1. xid was associated with this connection
   *
   * <p>Postconditions: 1. Transaction is prepared
   */
  public int prepare(Xid xid) throws XAException {
    if (logger.logDebug()) debug("preparing transaction xid = " + xid);

    // Check preconditions
    if (!currentXid.equals(xid)) {
      throw new PGXAException(
          GT.tr(
              "Not implemented: Prepare must be issued using the same connection that started the transaction"),
          XAException.XAER_RMERR);
    }
    if (state != STATE_ENDED)
      throw new PGXAException(GT.tr("Prepare called before end"), XAException.XAER_INVAL);

    state = STATE_IDLE;
    currentXid = null;

    if (!conn.haveMinimumServerVersion("8.1"))
      throw new PGXAException(
          GT.tr("Server versions prior to 8.1 do not support two-phase commit."),
          XAException.XAER_RMERR);

    try {
      String s = RecoveredXid.xidToString(xid);

      Statement stmt = conn.createStatement();
      try {
        stmt.executeUpdate("PREPARE TRANSACTION '" + s + "'");
      } finally {
        stmt.close();
      }
      conn.setAutoCommit(localAutoCommitMode);

      return XA_OK;
    } catch (SQLException ex) {
      throw new PGXAException(GT.tr("Error preparing transaction"), ex, XAException.XAER_RMERR);
    }
  }
  /**
   * Returns the fully-qualified name of the Java class whose instances are manufactured if the
   * method <code>ResultSet.getObject</code> is called to retrieve a value from the column. <code>
   * ResultSet.getObject</code> may return a subclass of the class returned by this method.
   *
   * @param column the first column is 1, the second is 2, ...
   * @return the fully-qualified name of the class in the Java programming language that would be
   *     used by the method <code>ResultSet.getObject</code> to retrieve the value in the specified
   *     column. This is the class name used for custom mapping.
   * @exception SQLException if a database access error occurs
   */
  public String getColumnClassName(int column) throws SQLException {
    Field field = getField(column);
    String result = connection.getTypeInfo().getJavaClass(field.getOID());

    if (result != null) return result;

    int sqlType = getSQLType(column);
    switch (sqlType) {
      case Types.ARRAY:
        return ("java.sql.Array");
      default:
        String type = getPGType(column);
        if ("unknown".equals(type)) {
          return ("java.lang.String");
        }
        return ("java.lang.Object");
    }
  }
  /**
   * Preconditions: 1. flag must be one of TMSTARTRSCAN, TMENDRSCAN, TMNOFLAGS or TMSTARTTRSCAN |
   * TMENDRSCAN 2. if flag isn't TMSTARTRSCAN or TMSTARTRSCAN | TMENDRSCAN, a recovery scan must be
   * in progress
   *
   * <p>Postconditions: 1. list of prepared xids is returned
   */
  public Xid[] recover(int flag) throws XAException {
    // Check preconditions
    if (flag != TMSTARTRSCAN
        && flag != TMENDRSCAN
        && flag != TMNOFLAGS
        && flag != (TMSTARTRSCAN | TMENDRSCAN))
      throw new PGXAException(GT.tr("Invalid flag"), XAException.XAER_INVAL);

    // We don't check for precondition 2, because we would have to add some additional state in
    // this object to keep track of recovery scans.

    // All clear. We return all the xids in the first TMSTARTRSCAN call, and always return
    // an empty array otherwise.
    if ((flag & TMSTARTRSCAN) == 0) return new Xid[0];
    else {
      try {
        Statement stmt = conn.createStatement();
        try {
          // If this connection is simultaneously used for a transaction,
          // this query gets executed inside that transaction. It's OK,
          // except if the transaction is in abort-only state and the
          // backed refuses to process new queries. Hopefully not a problem
          // in practise.
          ResultSet rs = stmt.executeQuery("SELECT gid FROM pg_prepared_xacts");
          LinkedList l = new LinkedList();
          while (rs.next()) {
            Xid recoveredXid = RecoveredXid.stringToXid(rs.getString(1));
            if (recoveredXid != null) l.add(recoveredXid);
          }
          rs.close();

          return (Xid[]) l.toArray(new Xid[l.size()]);
        } finally {
          stmt.close();
        }
      } catch (SQLException ex) {
        throw new PGXAException(GT.tr("Error during recover"), ex, XAException.XAER_RMERR);
      }
    }
  }
Example #8
0
 /** {@inheritDoc} PostgreSQL doesn't have unsigned numbers */
 public boolean isSigned(int param) throws SQLException {
   checkParamIndex(param);
   return _connection.getTypeInfo().isSigned(_oids[param - 1]);
 }
Example #9
0
 public String getParameterTypeName(int param) throws SQLException {
   checkParamIndex(param);
   return _connection.getTypeInfo().getPGType(_oids[param - 1]);
 }
 public PGXAConnection(BaseConnection conn) throws SQLException {
   super(conn, true, true);
   this.conn = conn;
   this.state = STATE_IDLE;
   this.logger = conn.getLogger();
 }
 /*
  * Does a column's case matter? ASSUMPTION: Any field that is
  * not obviously case insensitive is assumed to be case sensitive
  *
  * @param column the first column is 1, the second is 2...
  * @return true if so
  * @exception SQLException if a database access error occurs
  */
 public boolean isCaseSensitive(int column) throws SQLException {
   Field field = getField(column);
   return connection.getTypeInfo().isCaseSensitive(field.getOID());
 }
 protected int getSQLType(int columnIndex) throws SQLException {
   return connection.getTypeInfo().getSQLType(getField(columnIndex).getOID());
 }
 /*
  * What is a column's number of digits to the right of the
  * decimal point?
  *
  * @param column the first column is 1, the second is 2...
  * @return the scale
  * @exception SQLException if a database access error occurs
  */
 public int getScale(int column) throws SQLException {
   Field field = getField(column);
   return connection.getTypeInfo().getScale(field.getOID(), field.getMod());
 }
  private void fetchFieldMetaData() throws SQLException {
    if (fieldInfoFetched) return;

    fieldInfoFetched = true;

    StringBuffer sql = new StringBuffer();
    sql.append("SELECT c.oid, a.attnum, a.attname, c.relname, n.nspname, ");
    sql.append("a.attnotnull OR (t.typtype = 'd' AND t.typnotnull), ");
    sql.append("pg_catalog.pg_get_expr(d.adbin, d.adrelid) LIKE '%nextval(%' ");
    sql.append("FROM pg_catalog.pg_class c ");
    sql.append("JOIN pg_catalog.pg_namespace n ON (c.relnamespace = n.oid) ");
    sql.append("JOIN pg_catalog.pg_attribute a ON (c.oid = a.attrelid) ");
    sql.append("JOIN pg_catalog.pg_type t ON (a.atttypid = t.oid) ");
    sql.append(
        "LEFT JOIN pg_catalog.pg_attrdef d ON (d.adrelid = a.attrelid AND d.adnum = a.attnum) ");
    sql.append("JOIN (");

    // 7.4 servers don't support row IN operations (a,b) IN ((c,d),(e,f))
    // so we've got to fake that with a JOIN here.
    //
    boolean hasSourceInfo = false;
    for (int i = 0; i < fields.length; i++) {
      if (fields[i].getTableOid() == 0) continue;

      if (hasSourceInfo) sql.append(" UNION ALL ");

      sql.append("SELECT ");
      sql.append(fields[i].getTableOid());
      if (!hasSourceInfo) sql.append(" AS oid ");
      sql.append(", ");
      sql.append(fields[i].getPositionInTable());
      if (!hasSourceInfo) sql.append(" AS attnum");

      if (!hasSourceInfo) hasSourceInfo = true;
    }
    sql.append(") vals ON (c.oid = vals.oid AND a.attnum = vals.attnum) ");

    if (!hasSourceInfo) return;

    Statement stmt = connection.createStatement();
    ResultSet rs = stmt.executeQuery(sql.toString());
    while (rs.next()) {
      int table = (int) rs.getLong(1);
      int column = (int) rs.getLong(2);
      String columnName = rs.getString(3);
      String tableName = rs.getString(4);
      String schemaName = rs.getString(5);
      int nullable =
          rs.getBoolean(6) ? ResultSetMetaData.columnNoNulls : ResultSetMetaData.columnNullable;
      boolean autoIncrement = rs.getBoolean(7);
      for (int i = 0; i < fields.length; i++) {
        if (fields[i].getTableOid() == table && fields[i].getPositionInTable() == column) {
          fields[i].setColumnName(columnName);
          fields[i].setTableName(tableName);
          fields[i].setSchemaName(schemaName);
          fields[i].setNullable(nullable);
          fields[i].setAutoIncrement(autoIncrement);
        }
      }
    }
    stmt.close();
  }