2

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?

2 Answers2

1

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.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
0

select * from MON$DATABASE would query "OIT, OAT, Next"

Or you can get even more details by select * from MON$TRANSACTIONS

See more in c:\Program Files\Firebird\Firebird_2_1\doc\README.monitoring_tables.txt

MON$DATABASE (connected database)
  - MON$DATABASE_NAME (database pathname or alias)
  - MON$PAGE_SIZE (page size)
  - MON$ODS_MAJOR (major ODS version)
  - MON$ODS_MINOR (minor ODS version)
  - MON$OLDEST_TRANSACTION (OIT number)
  - MON$OLDEST_ACTIVE (OAT number)
  - MON$OLDEST_SNAPSHOT (OST number)
  - MON$NEXT_TRANSACTION (next transaction number)
  - MON$PAGE_BUFFERS (number of pages allocated in the cache)
  - MON$SQL_DIALECT (SQL dialect of the database)
  - MON$SHUTDOWN_MODE (current shutdown mode)
      0: online
      1: multi-user shutdown
      2: single-user shutdown
      3: full shutdown
  - MON$SWEEP_INTERVAL (sweep interval)
  - MON$READ_ONLY (read-only flag)
  - MON$FORCED_WRITES (sync writes flag)
  - MON$RESERVE_SPACE (reserve space flag)
  - MON$CREATION_DATE (creation date/time)
  - MON$PAGES (number of pages allocated on disk)
  - MON$BACKUP_STATE (current physical backup state)
      0: normal
      1: stalled
      2: merge
  - MON$STAT_ID (statistics ID)

MON$TRANSACTIONS (started transactions)
  - MON$TRANSACTION_ID (transaction ID)
  - MON$ATTACHMENT_ID (attachment ID)
  - MON$STATE (transaction state)
      0: idle
      1: active
  - MON$TIMESTAMP (transaction start date/time)
  - MON$TOP_TRANSACTION (top transaction)
  - MON$OLDEST_TRANSACTION (local OIT number)
  - MON$OLDEST_ACTIVE (local OAT number)
  - MON$ISOLATION_MODE (isolation mode)
      0: consistency
      1: concurrency
      2: read committed record version
      3: read committed no record version
  - MON$LOCK_TIMEOUT (lock timeout)
      -1: infinite wait
      0: no wait
      N: timeout N
  - MON$READ_ONLY (read-only flag)
  - MON$AUTO_COMMIT (auto-commit flag)
  - MON$AUTO_UNDO (auto-undo flag)
  - MON$STAT_ID (statistics ID)

Note 1: some data in those tables would only be available when you are connected via SYSDBA or RDB$ADMIN or database owner user. Example: attachments(connections) table would have other users connections invisible and skipped for non-admin user's requests.

Note 2: reading from monitoring table might be relatively slow, especially from tables containing connections-related (AKA attachments-related) info. Slow and blocking at that. So it is not recommended to do reading monitoring tables too often.

Arioch 'The
  • 15,799
  • 35
  • 62
  • No. $MON tables is the worst method because its slowdown... It is better to decode database header structure to get these values from database file (I can access to it) but I'm not sure - if FB engine + OS update it properly on all systems – Сергей Никитин Mar 23 '17 at 12:06
  • they do update it properly - when they want to do it. It is reading them without flushing server and OS caches first which is not proper. Anyway, reading the single row from MON$DATABASSE should not be a big slowdown. FB3 also tried to speed up mon tables and deprecate services API, so you have to have backup plan anyway. Also you can discuss it with Firebird developers themselves on sql.ru/forum/interbase – Arioch 'The Mar 23 '17 at 16:57
  • Then slowdown is a very relative term, if you do this check 1000 times a second - it is one case, if you do it one time per thousand seconds - quite another. – Arioch 'The Mar 23 '17 at 16:59