Search code examples
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.

    If you are using Maven, you can try the latest Jaybird snapshot from the Sonatype OSS snapshot repository: https://oss.sonatype.org/content/repositories/snapshots (you need to add this snapshot repository to your maven config).

    <dependency>
        <groupId>org.firebirdsql.jdbc</groupId>
        <artifactId>jaybird-jdk18</artifactId>
        <version>3.0.0-SNAPSHOT</version>
    </dependency>
    

    Otherwise you can download the snapshot using:

    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.