This is an automated email from the ASF dual-hosted git repository.

ArafatKhan2198 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 6cc8f31d2fe HDDS-16074. Make Recon RECON_TASK_STATUS column upgrade 
idempotent and fail on error. (#11060).
6cc8f31d2fe is described below

commit 6cc8f31d2fe7a75d044eef29f692da120e2af0ed
Author: Arafat2198 <[email protected]>
AuthorDate: Fri Aug 28 14:06:48 2026 +0530

    HDDS-16074. Make Recon RECON_TASK_STATUS column upgrade idempotent and fail 
on error. (#11060).
---
 .../recon/upgrade/ReconLayoutVersionManager.java   |  95 +++++++----
 .../upgrade/ReconTaskStatusTableUpgradeAction.java |  87 +++++++---
 .../upgrade/TestReconLayoutVersionManager.java     |  23 ++-
 .../TestReconTaskStatusTableUpgradeAction.java     | 176 +++++++++++++++++++++
 4 files changed, 328 insertions(+), 53 deletions(-)

diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconLayoutVersionManager.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconLayoutVersionManager.java
index cb12546771e..2d0915898f2 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconLayoutVersionManager.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconLayoutVersionManager.java
@@ -17,6 +17,7 @@
 
 package org.apache.hadoop.ozone.recon.upgrade;
 
+import com.google.common.annotations.VisibleForTesting;
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.util.Arrays;
@@ -42,6 +43,7 @@ public class ReconLayoutVersionManager {
   private final ReconSchemaVersionTableManager schemaVersionTableManager;
   private final ReconContext reconContext;
   private final DataSource dataSource;
+  private final ReconUpgradeAction taskStatusSchemaRepairAction;
 
   // Metadata Layout Version (MLV) of the Recon Metadata on disk
   private int currentMLV;
@@ -49,10 +51,20 @@ public class ReconLayoutVersionManager {
   public ReconLayoutVersionManager(ReconSchemaVersionTableManager 
schemaVersionTableManager,
                                    ReconContext reconContext, DataSource 
dataSource)
       throws SQLException {
+    this(schemaVersionTableManager, reconContext, dataSource,
+        new ReconTaskStatusTableUpgradeAction());
+  }
+
+  @VisibleForTesting
+  ReconLayoutVersionManager(
+      ReconSchemaVersionTableManager schemaVersionTableManager,
+      ReconContext reconContext, DataSource dataSource,
+      ReconUpgradeAction taskStatusSchemaRepairAction) throws SQLException {
     this.schemaVersionTableManager = schemaVersionTableManager;
     this.currentMLV = determineMLV();
     this.reconContext = reconContext;
     this.dataSource = dataSource;
+    this.taskStatusSchemaRepairAction = taskStatusSchemaRepairAction;
     ReconLayoutFeature.registerUpgradeActions();  // Register actions via 
annotation
   }
 
@@ -80,38 +92,47 @@ private int determineSLV() {
    * feature that is registered for finalization.
    */
   public void finalizeLayoutFeatures() {
-    // Get features that need finalization, sorted by version
-    List<ReconLayoutFeature> featuresToFinalize = getRegisteredFeatures();
-    LOG.debug("Starting finalization of {} features.", 
featuresToFinalize.size());
-
-    try (Connection connection = dataSource.getConnection()) {
-      connection.setAutoCommit(false); // Turn off auto-commit for 
transactional control
-
-      for (ReconLayoutFeature feature : featuresToFinalize) {
-        LOG.debug("Processing feature version: {}", feature.getVersion());
-        try {
-          // Fetch the action for the feature
-          Optional<ReconUpgradeAction> action = feature.getAction();
-          if (action.isPresent()) {
-            LOG.debug("Finalize action found for feature version: {}", 
feature.getVersion());
-            // Update the schema version in the database
-            updateSchemaVersion(feature.getVersion(), connection);
-
-            // Execute the upgrade action
-            action.get().execute(dataSource);
-
-            // Commit the transaction only if both operations succeed
-            connection.commit();
-            LOG.info("Feature versioned {} finalized successfully.", 
feature.getVersion());
-          } else {
-            LOG.info("No finalize action found for feature version: {}", 
feature.getVersion());
+    try {
+      repairTaskStatusSchemaIfRequired();
+
+      // Get features that need finalization, sorted by version
+      List<ReconLayoutFeature> featuresToFinalize = getRegisteredFeatures();
+      LOG.debug("Starting finalization of {} features.",
+          featuresToFinalize.size());
+
+      try (Connection connection = dataSource.getConnection()) {
+        connection.setAutoCommit(false);
+
+        for (ReconLayoutFeature feature : featuresToFinalize) {
+          LOG.debug("Processing feature version: {}", feature.getVersion());
+          try {
+            // Fetch the action for the feature
+            Optional<ReconUpgradeAction> action = feature.getAction();
+            if (action.isPresent()) {
+              LOG.debug("Finalize action found for feature version: {}",
+                  feature.getVersion());
+              // Update the schema version in the database
+              updateSchemaVersion(feature.getVersion(), connection);
+
+              // Execute the upgrade action
+              action.get().execute(dataSource);
+
+              // Commit the transaction only if both operations succeed
+              connection.commit();
+              LOG.info("Feature versioned {} finalized successfully.",
+                  feature.getVersion());
+            } else {
+              LOG.info("No finalize action found for feature version: {}",
+                  feature.getVersion());
+            }
+          } catch (Exception e) {
+            // Rollback pending changes for the current feature on failure
+            connection.rollback();
+            currentMLV = determineMLV();
+            LOG.error("Failed to finalize feature {}. Rolling back changes.",
+                feature.getVersion(), e);
+            throw e;
           }
-        } catch (Exception e) {
-          // Rollback any pending changes for the current feature due to 
failure
-          connection.rollback();
-          currentMLV = determineMLV(); // Rollback the MLV to the original 
value
-          LOG.error("Failed to finalize feature {}. Rolling back changes.", 
feature.getVersion(), e);
-          throw e;
         }
       }
     } catch (Exception e) {
@@ -123,6 +144,18 @@ public void finalizeLayoutFeatures() {
     }
   }
 
+  /**
+   * Repairs the RECON_TASK_STATUS schema based on the table's actual column
+   * state, independent of the stored layout version. Running this before 
feature
+   * finalization ensures the required columns exist and completes any 
partially
+   * applied migration, so a table left in an inconsistent state is recovered
+   * regardless of what the layout version reports.
+   */
+  private void repairTaskStatusSchemaIfRequired() throws Exception {
+    LOG.info("Checking whether the Recon task status schema requires repair.");
+    taskStatusSchemaRepairAction.execute(dataSource);
+  }
+
   /**
    * Returns a list of ReconLayoutFeature objects that are registered for 
finalization.
    */
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconTaskStatusTableUpgradeAction.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconTaskStatusTableUpgradeAction.java
index 17d64abb9c5..f619e26ebd6 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconTaskStatusTableUpgradeAction.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/upgrade/ReconTaskStatusTableUpgradeAction.java
@@ -21,10 +21,13 @@
 import static org.apache.ozone.recon.schema.SqlDbUtils.TABLE_EXISTS_CHECK;
 
 import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
 import java.sql.SQLException;
 import javax.sql.DataSource;
 import org.apache.ozone.recon.schema.ReconTaskSchemaDefinition;
 import org.jooq.DSLContext;
+import org.jooq.Field;
 import org.jooq.exception.DataAccessException;
 import org.jooq.impl.DSL;
 import org.jooq.impl.SQLDataType;
@@ -33,13 +36,20 @@
 
 /**
  * Upgrade action for TASK_STATUS_STATISTICS feature layout change, which adds
- * <code>last_task_run_status</code> and <code>current_task_run_status</code> 
columns to
- * {@link ReconTaskSchemaDefinition} in case it is missing .
+ * <code>last_task_run_status</code> and
+ * <code>is_current_task_running</code> columns to
+ * {@link ReconTaskSchemaDefinition} when they are missing.
  */
 @UpgradeActionRecon(feature = ReconLayoutFeature.TASK_STATUS_STATISTICS)
 public class ReconTaskStatusTableUpgradeAction implements ReconUpgradeAction {
 
-  private static final Logger LOG = 
LoggerFactory.getLogger(ReconTaskStatusTableUpgradeAction.class);
+  private static final Logger LOG =
+      LoggerFactory.getLogger(ReconTaskStatusTableUpgradeAction.class);
+  private static final String LAST_TASK_RUN_STATUS =
+      "last_task_run_status";
+  private static final String IS_CURRENT_TASK_RUNNING =
+      "is_current_task_running";
+  private static final int COLUMN_MISSING = -1;
 
   /**
    * Utility function to add provided column to RECON_TASK_STATUS table as 
INTEGER type.
@@ -63,33 +73,68 @@ private void setColumnAsNonNullable(DSLContext dslContext, 
String columnName) {
         .execute();
   }
 
+  /**
+   * Returns the JDBC nullability value for a column, or
+   * {@link #COLUMN_MISSING} if it does not exist.
+   */
+  private int getColumnNullability(Connection connection, String columnName)
+      throws SQLException {
+    DatabaseMetaData metaData = connection.getMetaData();
+    try (ResultSet columns = metaData.getColumns(null, null, null, null)) {
+      while (columns.next()) {
+        String table = columns.getString("TABLE_NAME");
+        String column = columns.getString("COLUMN_NAME");
+        if (RECON_TASK_STATUS_TABLE_NAME.equalsIgnoreCase(table)
+            && columnName.equalsIgnoreCase(column)) {
+          return columns.getInt("NULLABLE");
+        }
+      }
+    }
+    return COLUMN_MISSING;
+  }
+
+  /**
+   * Adds a missing column and completes any partially applied migration.
+   */
+  private void repairColumn(Connection connection, DSLContext dslContext,
+                            String columnName) throws SQLException {
+    int nullability = getColumnNullability(connection, columnName);
+    if (nullability == COLUMN_MISSING) {
+      LOG.info("Adding '{}' column to task status table.", columnName);
+      addColumnToTable(dslContext, columnName);
+      nullability = DatabaseMetaData.columnNullable;
+    }
+
+    if (nullability != DatabaseMetaData.columnNoNulls) {
+      Field<Integer> column =
+          DSL.field(DSL.name(columnName), SQLDataType.INTEGER);
+      int updatedRowCount = dslContext
+          .update(DSL.table(RECON_TASK_STATUS_TABLE_NAME))
+          .set(column, 0)
+          .where(column.isNull())
+          .execute();
+      LOG.info("Updated {} rows with a default value for '{}'.",
+          updatedRowCount, columnName);
+      setColumnAsNonNullable(dslContext, columnName);
+    }
+  }
+
   @Override
-  public void execute(DataSource dataSource) throws DataAccessException {
+  public void execute(DataSource dataSource) throws SQLException {
     try (Connection conn = dataSource.getConnection()) {
       if (!TABLE_EXISTS_CHECK.test(conn, RECON_TASK_STATUS_TABLE_NAME)) {
+        LOG.info("{} table does not exist; task status schema repair is not "
+            + "required.", RECON_TASK_STATUS_TABLE_NAME);
         return;
       }
 
       DSLContext dslContext = DSL.using(conn);
-      // JOOQ doesn't support Derby DB officially, there is no way to run 'ADD 
COLUMN' command in single call
-      // for multiple columns. Hence, we run it as two separate steps.
-      LOG.info("Adding 'last_task_run_status' column to task status table");
-      addColumnToTable(dslContext, "last_task_run_status");
-      LOG.info("Adding 'is_current_task_running' column to task status table");
-      addColumnToTable(dslContext, "is_current_task_running");
-
-      //Handle previous table values with new columns default values
-      int updatedRowCount = 
dslContext.update(DSL.table(RECON_TASK_STATUS_TABLE_NAME))
-          .set(DSL.field(DSL.name("last_task_run_status"), 
SQLDataType.INTEGER), 0)
-          .set(DSL.field(DSL.name("is_current_task_running"), 
SQLDataType.INTEGER), 0)
-          .execute();
-      LOG.info("Updated {} rows with default value for new columns", 
updatedRowCount);
-
-      // Now we will set the column as not-null to enforce constraints
-      setColumnAsNonNullable(dslContext, "last_task_run_status");
-      setColumnAsNonNullable(dslContext, "is_current_task_running");
+      repairColumn(conn, dslContext, LAST_TASK_RUN_STATUS);
+      repairColumn(conn, dslContext, IS_CURRENT_TASK_RUNNING);
     } catch (SQLException | DataAccessException ex) {
       LOG.error("Error while upgrading RECON_TASK_STATUS table.", ex);
+      throw new SQLException(
+          "Failed to repair the RECON_TASK_STATUS table.", ex);
     }
   }
 }
diff --git 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/upgrade/TestReconLayoutVersionManager.java
 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/upgrade/TestReconLayoutVersionManager.java
index 5f2d451dd06..9234e1c541b 100644
--- 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/upgrade/TestReconLayoutVersionManager.java
+++ 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/upgrade/TestReconLayoutVersionManager.java
@@ -85,8 +85,10 @@ public void setUp() throws SQLException {
     // Define the custom features to be returned
     mockedEnum.when(ReconLayoutFeature::values).thenReturn(new 
ReconLayoutFeature[]{feature1, feature2});
 
+    // Inject a no-op task-status schema repair action so these tests exercise
+    // only the layout finalization flow, independent of the real action's SQL.
     layoutVersionManager = new 
ReconLayoutVersionManager(schemaVersionTableManager, mock(ReconContext.class),
-        mockDataSource);
+        mockDataSource, mock(ReconUpgradeAction.class));
 
     when(scmFacadeMock.getDataSource()).thenReturn(mockDataSource);
     when(mockDataSource.getConnection()).thenReturn(mockConnection);
@@ -294,6 +296,25 @@ public void testNoUpgradeActionsNeeded() throws 
SQLException {
     verify(schemaVersionTableManager, never()).updateSchemaVersion(anyInt(), 
eq(mockConnection));
   }
 
+  @Test
+  public void testTaskStatusRepairDoesNotChangeMlv() throws Exception {
+    ReconUpgradeAction repairAction = mock(ReconUpgradeAction.class);
+    when(schemaVersionTableManager.getCurrentSchemaVersion()).thenReturn(2);
+    mockedEnum.when(ReconLayoutFeature::values)
+        .thenReturn(new ReconLayoutFeature[]{});
+
+    ReconLayoutVersionManager manager = new ReconLayoutVersionManager(
+        schemaVersionTableManager, mock(ReconContext.class), mockDataSource,
+        repairAction);
+
+    manager.finalizeLayoutFeatures();
+
+    verify(repairAction).execute(mockDataSource);
+    verify(schemaVersionTableManager, never())
+        .updateSchemaVersion(anyInt(), eq(mockConnection));
+    assertEquals(2, manager.getCurrentMLV());
+  }
+
   /**
    * Tests the scenario where the first two features are finalized,
    * and then a third feature is introduced. Ensures that only the
diff --git 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/upgrade/TestReconTaskStatusTableUpgradeAction.java
 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/upgrade/TestReconTaskStatusTableUpgradeAction.java
new file mode 100644
index 00000000000..2e91375ef8f
--- /dev/null
+++ 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/upgrade/TestReconTaskStatusTableUpgradeAction.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.recon.upgrade;
+
+import static 
org.apache.ozone.recon.schema.ReconTaskSchemaDefinition.RECON_TASK_STATUS_TABLE_NAME;
+import static org.jooq.impl.DSL.field;
+import static org.jooq.impl.DSL.name;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import javax.sql.DataSource;
+import org.apache.hadoop.ozone.recon.persistence.AbstractReconSqlDBTest;
+import org.jooq.DSLContext;
+import org.jooq.SQLDialect;
+import org.jooq.impl.DSL;
+import org.jooq.impl.SQLDataType;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for ReconTaskStatusTableUpgradeAction.
+ */
+public class TestReconTaskStatusTableUpgradeAction
+    extends AbstractReconSqlDBTest {
+
+  private static final String LAST_TASK_RUN_STATUS =
+      "last_task_run_status";
+  private static final String IS_CURRENT_TASK_RUNNING =
+      "is_current_task_running";
+
+  private DataSource dataSource;
+  private DSLContext dslContext;
+  private ReconTaskStatusTableUpgradeAction upgradeAction;
+
+  @BeforeEach
+  public void setUp() throws SQLException {
+    dataSource = getDataSource();
+    // Use the Derby dialect explicitly. The shared DSLContext from the base
+    // class uses SQLDialect.DEFAULT, which renders Derby-incompatible DDL
+    // (e.g. "DROP TABLE IF EXISTS" and "bigint null"); a Derby-dialect context
+    // generates SQL that the embedded Derby database accepts.
+    dslContext = DSL.using(dataSource, SQLDialect.DERBY);
+    upgradeAction = new ReconTaskStatusTableUpgradeAction();
+    createLegacyTaskStatusTable();
+  }
+
+  @Test
+  public void testRepairsLegacyTaskStatusTable() throws Exception {
+    upgradeAction.execute(dataSource);
+
+    assertTrue(columnExists(LAST_TASK_RUN_STATUS));
+    assertTrue(columnExists(IS_CURRENT_TASK_RUNNING));
+    assertFalse(columnIsNullable(LAST_TASK_RUN_STATUS));
+    assertFalse(columnIsNullable(IS_CURRENT_TASK_RUNNING));
+    assertEquals(0, getStatusValue(LAST_TASK_RUN_STATUS));
+    assertEquals(0, getStatusValue(IS_CURRENT_TASK_RUNNING));
+  }
+
+  @Test
+  public void testRepairIsIdempotentAndPreservesValues() throws Exception {
+    upgradeAction.execute(dataSource);
+    setStatusValue(LAST_TASK_RUN_STATUS, 7);
+    setStatusValue(IS_CURRENT_TASK_RUNNING, 1);
+
+    assertDoesNotThrow(() -> upgradeAction.execute(dataSource));
+
+    assertEquals(7, getStatusValue(LAST_TASK_RUN_STATUS));
+    assertEquals(1, getStatusValue(IS_CURRENT_TASK_RUNNING));
+  }
+
+  @Test
+  public void testCompletesPartiallyAppliedRepair() throws Exception {
+    dslContext.alterTable(RECON_TASK_STATUS_TABLE_NAME)
+        .addColumn(LAST_TASK_RUN_STATUS,
+            SQLDataType.INTEGER.nullable(true))
+        .execute();
+    setStatusValue(LAST_TASK_RUN_STATUS, 7);
+
+    upgradeAction.execute(dataSource);
+
+    assertTrue(columnExists(IS_CURRENT_TASK_RUNNING));
+    assertFalse(columnIsNullable(LAST_TASK_RUN_STATUS));
+    assertFalse(columnIsNullable(IS_CURRENT_TASK_RUNNING));
+    assertEquals(7, getStatusValue(LAST_TASK_RUN_STATUS));
+    assertEquals(0, getStatusValue(IS_CURRENT_TASK_RUNNING));
+  }
+
+  @Test
+  public void testDatabaseFailureIsPropagated() throws SQLException {
+    DataSource failingDataSource = mock(DataSource.class);
+    when(failingDataSource.getConnection())
+        .thenThrow(new SQLException("Database unavailable"));
+
+    assertThrows(SQLException.class,
+        () -> upgradeAction.execute(failingDataSource));
+  }
+
+  private void createLegacyTaskStatusTable() throws SQLException {
+    // The base class always creates RECON_TASK_STATUS before this runs, so a
+    // plain DROP TABLE is safe (no IF EXISTS needed).
+    dslContext.dropTable(RECON_TASK_STATUS_TABLE_NAME).execute();
+    dslContext.createTable(RECON_TASK_STATUS_TABLE_NAME)
+        .column("task_name", SQLDataType.VARCHAR(766).nullable(false))
+        .column("last_updated_timestamp", SQLDataType.BIGINT)
+        .column("last_updated_seq_number", SQLDataType.BIGINT)
+        .constraint(DSL.constraint("pk_task_name").primaryKey("task_name"))
+        .execute();
+    dslContext.insertInto(DSL.table(RECON_TASK_STATUS_TABLE_NAME))
+        .columns(field(name("task_name")),
+            field(name("last_updated_timestamp")),
+            field(name("last_updated_seq_number")))
+        .values("OmDeltaRequest", 1L, 1L)
+        .execute();
+  }
+
+  private boolean columnExists(String columnName) throws SQLException {
+    return getColumnNullability(columnName) != -1;
+  }
+
+  private boolean columnIsNullable(String columnName) throws SQLException {
+    return getColumnNullability(columnName)
+        != DatabaseMetaData.columnNoNulls;
+  }
+
+  private int getColumnNullability(String columnName) throws SQLException {
+    try (Connection connection = dataSource.getConnection();
+         ResultSet columns = connection.getMetaData()
+             .getColumns(null, null, null, null)) {
+      while (columns.next()) {
+        if (RECON_TASK_STATUS_TABLE_NAME.equalsIgnoreCase(
+            columns.getString("TABLE_NAME"))
+            && columnName.equalsIgnoreCase(
+            columns.getString("COLUMN_NAME"))) {
+          return columns.getInt("NULLABLE");
+        }
+      }
+    }
+    return -1;
+  }
+
+  private void setStatusValue(String columnName, int value) {
+    dslContext.update(DSL.table(RECON_TASK_STATUS_TABLE_NAME))
+        .set(field(name(columnName), Integer.class), value)
+        .execute();
+  }
+
+  private int getStatusValue(String columnName) {
+    return dslContext.select(field(name(columnName), Integer.class))
+        .from(DSL.table(RECON_TASK_STATUS_TABLE_NAME))
+        .fetchOne(field(name(columnName), Integer.class));
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to