javatransactionsfirebirdjaybird

Jaybird 3 and Firebird transaction information


In previous version of jaybird (2.2) I was able to execute Services API to Firebird server to get active transaction markers: OIT, OAT, Next, etc.

In version 3.0, I can't find out how to do it properly. There are only ISC-constants (like isc_info_oldest_snapshot) but no methods.

So, I see one way to do that: Get query of database header by StatisticsManager. But it is not so easy, because it will return text that needs to be parsed:

    StatisticsManager SM = new FBStatisticsManager();  //"PURE_JAVA", "NATIVE", "EMBEDDED"

    SM.setHost("localhost");
    SM.setUser("sysdba");
    SM.setPort(3053);
    SM.setPassword("masterkey");
    SM.setDatabase("c:\\Firebird\\3.0.2\\examples\\empbuild\\EMPLOYEE.FDB");

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    SM.setLogger(baos);
    SM.getHeaderPage();
    String outputstr2 = new String( baos.toByteArray(), java.nio.charset.StandardCharsets.UTF_8 );

and now I need to parse the text:

Database "C:\FIREBIRD\3.0.2\EXAMPLES\EMPBUILD\EMPLOYEE.FDB"
Database header page information:
   Flags            0
   Generation        806
   System Change Number    12
   Page size        8192
   ODS version        12.0
   Oldest transaction    520
   Oldest active        521
   Oldest snapshot        521
   Next transaction    521
   Sequence number        0
   Next attachment ID    857
   Implementation        HW=AMD/Intel/x64 little-endian OS=Windows CC=MSVC
   Shadow count        0
   Page buffers        0
   Next header page    0
   Database dialect    3
   Creation date        Apr 15, 2016 17:38:34
   Attributes        

   Variable header data:
   Database backup GUID:    {6F41E937-76D5-4C67-6CAE-F8556AD27BEE}
   Database GUID:    {EE5B2713-7B17-43B0-0CB3-0616B4B8A63D}
   *END*

May be it is possible to get direct values?

upd: Old version of code was:

/** [ActiveCount, OAT, OST, OIT, Next] */
public static int[] getTxInfo( final GDS gds,
                               final String host,
                               final int port,
                               final String databasePath,
                               final String user,
                               final String password ) throws Exception {
    final byte[] queryItems = {
            ISCConstants.isc_info_oldest_transaction,
            ISCConstants.isc_info_oldest_active,
            ISCConstants.isc_info_oldest_snapshot,
            ISCConstants.isc_info_next_transaction,
            ISCConstants.isc_info_active_transactions,
            ISCConstants.isc_info_end
    };
    byte[] response = queryDB(
            gds, host, port, databasePath, user, password,
            queryItems, DEFAULT_BUFFER_SIZE
    );
    int i = 0;
    final int[] result = new int[5];
    while ( response[i] != ISCConstants.isc_info_end ) {
        final byte code = response[i++];
        switch ( code ) {
            case ISCConstants.isc_info_active_transactions: {
                //здесь идет столько блоков isc_info_active_transactions, сколько
                //реально активных транзакций в данный момент
                final int valueLen = gds.iscVaxInteger( response, i, LENGTH_LEN );
                i += LENGTH_LEN;
                //final int res = gds.iscVaxInteger( response, i, valueLen );
                i += valueLen;
                result[0]++;
                break;
            }
            case ISCConstants.isc_info_oldest_active: {
                final int valueLen = gds.iscVaxInteger( response, i, LENGTH_LEN );
                i += LENGTH_LEN;
                final int res = gds.iscVaxInteger( response, i, valueLen );
                i += valueLen;
                result[1] = res;
                break;
            }
            case ISCConstants.isc_info_oldest_snapshot: {
                final int valueLen = gds.iscVaxInteger( response, i, LENGTH_LEN );
                i += LENGTH_LEN;
                final int res = gds.iscVaxInteger( response, i, valueLen );
                i += valueLen;
                result[2] = res;
                break;
            }
            case ISCConstants.isc_info_oldest_transaction: {
                final int valueLen = gds.iscVaxInteger( response, i, LENGTH_LEN );
                i += LENGTH_LEN;
                final int res = gds.iscVaxInteger( response, i, valueLen );
                i += valueLen;
                result[3] = res;
                break;
            }
            case ISCConstants.isc_info_next_transaction: {
                final int valueLen = gds.iscVaxInteger( response, i, LENGTH_LEN );
                i += LENGTH_LEN;
                final int res = gds.iscVaxInteger( response, i, valueLen );
                i += valueLen;
                result[4] = res;
                break;
            }
            case ISCConstants.isc_info_truncated: {
                //этот код означает "буфер слишком маленький, дайте больше"
                //обычно это бывает когда слишком много активных транзакций

                //сначала пробуем увеличить буфер
                if ( response.length == DEFAULT_BUFFER_SIZE ) {
                    response = queryDB(
                            gds, host, port, databasePath, user, password,
                            queryItems, 32 * DEFAULT_BUFFER_SIZE
                    );
                    result[0] = 0;//на всякий случай
                    //начинаем разбор заново
                    i = 0;
                } else {
                    //32Кб буфера оказалось тоже недостаточно -- пичалька. Но
                    //делать нечего -- просто обойдемся без числа активных транзакций
                    response = queryDB(
                            gds, host, port, databasePath, user, password,
                            new byte[]{
                                    ISCConstants.isc_info_oldest_transaction,
                                    ISCConstants.isc_info_oldest_active,
                                    ISCConstants.isc_info_oldest_snapshot,
                                    ISCConstants.isc_info_next_transaction,
                                    ISCConstants.isc_info_end
                            }, DEFAULT_BUFFER_SIZE
                    );
                    result[0] = -1;
                    //начинаем разбор заново
                    i = 0;
                }
                break;
            }

            default:
                throw new FBSQLException( "Unrecognized response code: " + code + " (response=" + Arrays.toString( result ) + ")" );
        }
    }
    return result;
}

where

public static byte[] queryDB( final GDS gds,
                              final String host,
                              final int port,
                              final String databasePath,
                              final String user,    
                              final String password,
                              final byte[] queryItems,
                              final int bufferLength ) throws Exception {
    return doWithDB(
            gds, host, port, databasePath, user, password,
            new DBOperation<byte[]>() {
                public byte[] doWithDB( final GDS gds,
                                        final IscDbHandle db ) throws GDSException {
                    return gds.iscDatabaseInfo(
                            db,
                            queryItems,
                            bufferLength
                    );
                }
            }
    );
}

Is it enough?


Solution

  • I have added the functionality to Jaybird 3.0.0, see this commit.

    I have added two ways to get this information:

    1. Using StatisticsManager:

      StatisticsManager statsMan = new FBStatisticsManager();
      statsMan.setHost("localhost");
      statsMan.setDatabase("/path/to/your.fdb");
      statsMan.setUser("youruser");
      statsMan.setPassword("yourpassword"); 
      DatabaseTransactionInfo info = statsMan.getDatabaseTransactionInfo();
      
    2. A convenience method to use an existing Connection to get this information:

      try (Connection connection = dataSource.getConnection()) {
          DatabaseTransactionInfo info = FBStatisticsManager
                  .getDatabaseTransactionInfo(connection);
      }
      

      The only requirement is that the connection instance unwraps to the FirebirdConnection interface.

    For a future version I will consider if I can expose the database information query facility in a more general way so it is not necessary to use the internal FbDatabase interface.