Repository: dbeaver/dbeaver
Pull request #36897: dbeaver/dbeaver#36814 Implemented support for editing table and column comments in the GBase8s driver
Author: login: Wintianis
Labels: name: external
Created: 2025-01-23T08:20:03Z | Merged: 2025-03-13T12:00:24Z

### Overview:
This PR introduces the ability to edit table and column comments in the GBase8s driver, addressing a feature gap in the database management capabilities.
### Key Changes:
- Implemented support for editing comments at the table and column levels.
### Related Issue:
Closes dbeaver/dbeaver#36814.
### Testing:
*  Created new columns with comments.
*  Added comments to existing columns.
*  Removed comments from columns.
*  Deleted columns with comments.
*  All test cases have been successfully validated and executed to ensure proper comment operations.
### Scope of Impact:
This change affects only the GBase8s driver and is backward-compatible.
### Checklist:
*  Local tests passed.
*  No breaking changes identified.


Diff:
diff --git a/plugins/org.jkiss.dbeaver.ext.gbase8s/plugin.xml b/plugins/org.jkiss.dbeaver.ext.gbase8s/plugin.xml
index 1224bb09a60f2..24c9abefd0270 100644
--- a/plugins/org.jkiss.dbeaver.ext.gbase8s/plugin.xml
+++ b/plugins/org.jkiss.dbeaver.ext.gbase8s/plugin.xml
@@ -38,8 +38,8 @@
 					webURL="https://cdn.gbase.cn/products/27/duljCAzDVSy_SLTpy7j1l/GBase%208s%20V8.8%20JDBC%20Driver%20%E7%A8%8B%E5%BA%8F%E5%91%98%E6%8C%87%E5%8D%97.pdf"
 					supportedPageFields="host,port,database,server"
 					categories="sql">
-					<property name="opt_goal" value="0" />
-					<property name="sqlMode" value="oracle" />
+					<property name="OPT_GOAL" value="0" />
+					<property name="SQLMODE" value="oracle" />
 
 					<property name="@dbeaver-default-database.meta.client.name.disable" value="true" />
 					<property name="@dbeaver-default-dataformat.type.timestamp.pattern" value="yyyy-MM-dd HH.mm.ss.ffffff" />
diff --git a/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/GBase8sConstants.java b/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/GBase8sConstants.java
new file mode 100644
index 0000000000000..2951f9249caf1
--- /dev/null
+++ b/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/GBase8sConstants.java
@@ -0,0 +1,30 @@
+/*
+ * DBeaver - Universal Database Manager
+ * Copyright (C) 2010-2024 DBeaver Corp and others
+ *
+ * Licensed 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.jkiss.dbeaver.ext.gbase8s;
+
+/**
+ * GBase8sConstants
+ */
+public class GBase8sConstants {
+
+    public static final String JDBC_SQL_MODE = "SQLMODE";
+    public static final String JDBC_SQL_MODE_ORACLE = "oracle";
+    public static final String JDBC_SQL_MODE_GBASE = "gbase";
+
+    public static final String SQL_TABLE_COMMENT = "COMMENT ON TABLE %s IS %s";
+}
diff --git a/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/GBase8sUtils.java b/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/GBase8sUtils.java
index 3834cccd74755..6fec15f8b5c04 100644
--- a/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/GBase8sUtils.java
+++ b/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/GBase8sUtils.java
@@ -20,7 +20,6 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 
 import org.jkiss.dbeaver.DBException;
 import org.jkiss.dbeaver.Log;
@@ -30,7 +29,7 @@
 import org.jkiss.dbeaver.ext.generic.model.GenericTrigger;
 import org.jkiss.dbeaver.model.DBPDataSourceContainer;
 import org.jkiss.dbeaver.model.DBUtils;
-import org.jkiss.dbeaver.model.connection.DBPDriver;
+import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
 import org.jkiss.dbeaver.model.exec.jdbc.JDBCPreparedStatement;
 import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet;
 import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession;
@@ -67,16 +66,25 @@ public static List<String> getSource(DBRProgressMonitor monitor, String sqlState
     }
 
     public static boolean isOracleSqlMode(DBPDataSourceContainer container) {
-        final DBPDriver driver = container.getDriver();
-        Map<String, Object> cprops = driver.getConnectionProperties();
-        for (Map.Entry<String, Object> entry : cprops.entrySet()) {
-            if (Objects.equals(entry.getKey().toLowerCase(), "sqlmode")) {
-                if (Objects.equals(entry.getValue().toString().trim().toLowerCase(), "oracle")) {
-                    return true;
-                }
+        DBPConnectionConfiguration configuration = container.getConnectionConfiguration();
+        String sqlMode = getSqlModeValue(configuration.getProperties());
+        if (sqlMode != null) {
+            return GBase8sConstants.JDBC_SQL_MODE_ORACLE.equalsIgnoreCase(sqlMode);
+        }
+        return GBase8sConstants.JDBC_SQL_MODE_ORACLE
+                .equalsIgnoreCase(getSqlModeValue(container.getDriver().getConnectionProperties()));
+    }
+
+    private static String getSqlModeValue(Map<String, ?> properties) {
+        if (properties == null || properties.isEmpty()) {
+            return null;
+        }
+        for (Map.Entry<String, ?> entry : properties.entrySet()) {
+            if (GBase8sConstants.JDBC_SQL_MODE.equalsIgnoreCase(entry.getKey())) {
+                return entry.getValue() != null ? entry.getValue().toString().trim() : null;
             }
         }
-        return false;
+        return null;
     }
 
     public static String listToString(List<String> value, String delimiter) {
diff --git a/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/edit/GBase8sTableColumnManager.java b/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/edit/GBase8sTableColumnManager.java
index e4c031923ea63..51795f6c009ec 100644
--- a/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/edit/GBase8sTableColumnManager.java
+++ b/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/edit/GBase8sTableColumnManager.java
@@ -22,10 +22,10 @@
 
 import org.jkiss.code.NotNull;
 import org.jkiss.dbeaver.DBException;
-import org.jkiss.dbeaver.ext.gbase8s.model.GBase8sTableColumn;
 import org.jkiss.dbeaver.ext.generic.edit.GenericTableColumnManager;
 import org.jkiss.dbeaver.ext.generic.model.GenericTableBase;
 import org.jkiss.dbeaver.ext.generic.model.GenericTableColumn;
+import org.jkiss.dbeaver.model.DBConstants;
 import org.jkiss.dbeaver.model.DBPEvaluationContext;
 import org.jkiss.dbeaver.model.DBUtils;
 import org.jkiss.dbeaver.model.edit.DBECommandContext;
@@ -35,6 +35,7 @@
 import org.jkiss.dbeaver.model.impl.edit.SQLDatabasePersistAction;
 import org.jkiss.dbeaver.model.impl.sql.edit.SQLObjectEditor;
 import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
+import org.jkiss.utils.CommonUtils;
 
 /**
  * @author Chao Tian
@@ -43,8 +44,50 @@ public class GBase8sTableColumnManager extends GenericTableColumnManager
         implements DBEObjectRenamer<GenericTableColumn> {
 
     @Override
-    protected void addObjectRenameActions(@NotNull DBRProgressMonitor monitor,
-            @NotNull DBCExecutionContext executionContext, @NotNull List<DBEPersistAction> actions,
+    protected void addObjectCreateActions(
+            @NotNull DBRProgressMonitor monitor,
+            @NotNull DBCExecutionContext executionContext,
+            @NotNull List<DBEPersistAction> actions,
+            @NotNull SQLObjectEditor<GenericTableColumn, GenericTableBase>.ObjectCreateCommand command,
+            @NotNull Map<String, Object> options) throws DBException {
+        super.addObjectCreateActions(monitor, executionContext, actions, command, options);
+        if (CommonUtils.isNotEmpty(command.getObject().getDescription())) {
+            addColumnCommentAction(actions, command.getObject(), command.getObject().getParentObject());
+        }
+    }
+
+    @Override
+    protected void addObjectModifyActions(
+            @NotNull DBRProgressMonitor monitor,
+            @NotNull DBCExecutionContext executionContext,
+            @NotNull List<DBEPersistAction> actionList,
+            @NotNull SQLObjectEditor<GenericTableColumn, GenericTableBase>.ObjectChangeCommand command,
+            @NotNull Map<String, Object> options) throws DBException {
+        GenericTableColumn column = command.getObject();
+        actionList.add(new SQLDatabasePersistAction("Modify column",
+                "ALTER TABLE " + column.getTable().getFullyQualifiedName(DBPEvaluationContext.DDL) + " MODIFY "
+                        + getNestedDeclaration(monitor, column.getTable(), command, options)));
+    }
+
+    @Override
+    protected void addObjectExtraActions(
+            @NotNull DBRProgressMonitor monitor,
+            @NotNull DBCExecutionContext executionContext,
+            @NotNull List<DBEPersistAction> actions,
+            @NotNull NestedObjectCommand<GenericTableColumn, PropertyHandler> command,
+            @NotNull Map<String, Object> options) throws DBException {
+        // Add column comment action if column description is specified
+        if (command.hasProperty(DBConstants.PROP_ID_DESCRIPTION)) {
+            GenericTableColumn column = command.getObject();
+            addColumnCommentAction(actions, column, column.getTable());
+        }
+    }
+
+    @Override
+    protected void addObjectRenameActions(
+            @NotNull DBRProgressMonitor monitor,
+            @NotNull DBCExecutionContext executionContext,
+            @NotNull List<DBEPersistAction> actions,
             @NotNull SQLObjectEditor<GenericTableColumn, GenericTableBase>.ObjectRenameCommand command,
             @NotNull Map<String, Object> options) {
         final GenericTableColumn column = command.getObject();
@@ -55,20 +98,11 @@ protected void addObjectRenameActions(@NotNull DBRProgressMonitor monitor,
     }
 
     @Override
-    public void renameObject(DBECommandContext commandContext, GenericTableColumn object, Map<String, Object> options,
+    public void renameObject(
+            DBECommandContext commandContext,
+            GenericTableColumn object,
+            Map<String, Object> options,
             String newName) throws DBException {
         processObjectRename(commandContext, object, options, newName);
     }
-
-    @Override
-    protected void addObjectModifyActions(@NotNull DBRProgressMonitor monitor,
-            @NotNull DBCExecutionContext executionContext, @NotNull List<DBEPersistAction> actionList,
-            @NotNull SQLObjectEditor<GenericTableColumn, GenericTableBase>.ObjectChangeCommand command,
-            @NotNull Map<String, Object> options) throws DBException {
-        final GBase8sTableColumn column = (GBase8sTableColumn) command.getObject();
-        actionList.add(new SQLDatabasePersistAction("Modify column",
-                "ALTER TABLE " + column.getTable().getFullyQualifiedName(DBPEvaluationContext.DDL) + " MODIFY "
-                        + getNestedDeclaration(monitor, column.getTable(), command, options)));
-
-    }
 }
diff --git a/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/edit/GBase8sTableManager.java b/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/edit/GBase8sTableManager.java
index 65e5f7f252b32..b0e5ef73ef23f 100644
--- a/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/edit/GBase8sTableManager.java
+++ b/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/edit/GBase8sTableManager.java
@@ -21,11 +21,18 @@
 import java.util.Map;
 
 import org.jkiss.code.NotNull;
+import org.jkiss.code.Nullable;
 import org.jkiss.dbeaver.DBException;
+import org.jkiss.dbeaver.ext.gbase8s.GBase8sConstants;
+import org.jkiss.dbeaver.ext.generic.edit.GenericTableColumnManager;
 import org.jkiss.dbeaver.ext.generic.edit.GenericTableManager;
 import org.jkiss.dbeaver.ext.generic.model.GenericDataSource;
 import org.jkiss.dbeaver.ext.generic.model.GenericStructContainer;
 import org.jkiss.dbeaver.ext.generic.model.GenericTableBase;
+import org.jkiss.dbeaver.ext.generic.model.GenericTableColumn;
+import org.jkiss.dbeaver.model.DBConstants;
+import org.jkiss.dbeaver.model.DBPEvaluationContext;
+import org.jkiss.dbeaver.model.DBPScriptObject;
 import org.jkiss.dbeaver.model.DBUtils;
 import org.jkiss.dbeaver.model.edit.DBECommandContext;
 import org.jkiss.dbeaver.model.edit.DBEObjectRenamer;
@@ -34,6 +41,8 @@
 import org.jkiss.dbeaver.model.impl.edit.SQLDatabasePersistAction;
 import org.jkiss.dbeaver.model.impl.sql.edit.SQLObjectEditor;
 import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
+import org.jkiss.dbeaver.model.sql.SQLUtils;
+import org.jkiss.utils.CommonUtils;
 
 /**
  * @author Chao Tian
@@ -56,8 +65,11 @@ protected boolean isIncludeDropInDDL(GenericTableBase table) {
     }
 
     @Override
-    public void renameObject(DBECommandContext commandContext, GenericTableBase object, Map<String, Object> options,
-            String newName) throws DBException {
+    public void renameObject(
+            @NotNull DBECommandContext commandContext,
+            @NotNull GenericTableBase object,
+            @NotNull Map<String, Object> options,
+            @Nullable String newName) throws DBException {
         if (object.isView()) {
             throw new DBException("View rename is not supported");
         }
@@ -65,8 +77,35 @@ public void renameObject(DBECommandContext commandContext, GenericTableBase obje
     }
 
     @Override
-    protected void addObjectRenameActions(@NotNull DBRProgressMonitor monitor,
-            @NotNull DBCExecutionContext executionContext, @NotNull List<DBEPersistAction> actions,
+    protected void addObjectExtraActions(
+            @NotNull DBRProgressMonitor monitor,
+            @NotNull DBCExecutionContext executionContext,
+            @NotNull List<DBEPersistAction> actions,
+            @NotNull NestedObjectCommand<GenericTableBase, PropertyHandler> command,
+            @NotNull Map<String, Object> options) throws DBException {
+        GenericTableBase tableBase = command.getObject();
+        boolean objectSave = CommonUtils.getOption(options, DBPScriptObject.OPTION_OBJECT_SAVE);
+        boolean includeComments = CommonUtils.getOption(options, DBPScriptObject.OPTION_INCLUDE_COMMENTS);
+        boolean hasDescription = !CommonUtils.isEmpty(tableBase.getDescription());
+        // Add table comment if needed
+        if ((objectSave && command.hasProperty(DBConstants.PROP_ID_DESCRIPTION)) || hasDescription) {
+            addTableCommentAction(actions, tableBase);
+        }
+        // Add column comments if needed
+        if (!tableBase.isPersisted() ? (objectSave || includeComments) : (!objectSave && includeComments)) {
+            for (GenericTableColumn column : CommonUtils.safeCollection(tableBase.getAttributes(monitor))) {
+                if (!CommonUtils.isEmpty(column.getDescription())) {
+                    GenericTableColumnManager.addColumnCommentAction(actions, column, column.getTable());
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void addObjectRenameActions(
+            @NotNull DBRProgressMonitor monitor,
+            @NotNull DBCExecutionContext executionContext,
+            @NotNull List<DBEPersistAction> actions,
             @NotNull SQLObjectEditor<GenericTableBase, GenericStructContainer>.ObjectRenameCommand command,
             @NotNull Map<String, Object> options) {
         final GenericDataSource dataSource = command.getObject().getDataSource();
@@ -80,4 +119,13 @@ protected void addObjectRenameActions(@NotNull DBRProgressMonitor monitor,
                                 + DBUtils.getQuotedIdentifier(dataSource, command.getOldName()) + " RENAME TO "
                                 + DBUtils.getQuotedIdentifier(dataSource, command.getNewName())));
     }
+
+    private void addTableCommentAction(
+            @NotNull List<DBEPersistAction> actionList,
+            @NotNull GenericTableBase table) {
+        String tableName = DBUtils.getObjectFullName(table, DBPEvaluationContext.DDL);
+        String commentSQL = String.format(GBase8sConstants.SQL_TABLE_COMMENT, tableName,
+                SQLUtils.quoteString(table, CommonUtils.notEmpty(table.getDescription())));
+        actionList.add(new SQLDatabasePersistAction("Comment on Table", commentSQL));
+    }
 }
diff --git a/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/model/GBase8sTable.java b/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/model/GBase8sTable.java
index 69c7ff161a191..f6387bb559766 100644
--- a/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/model/GBase8sTable.java
+++ b/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/model/GBase8sTable.java
@@ -20,6 +20,7 @@
 import org.jkiss.code.Nullable;
 import org.jkiss.dbeaver.ext.generic.model.GenericStructContainer;
 import org.jkiss.dbeaver.ext.generic.model.GenericTable;
+import org.jkiss.dbeaver.model.DBPScriptObject;
 import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet;
 
 /**
@@ -37,4 +38,13 @@ public GBase8sTable(GenericStructContainer container, String tableName, String t
         super(container, tableName, tableCatalogName, tableSchemaName);
     }
 
+    @Override
+    protected boolean isCacheDDL() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsObjectDefinitionOption(String option) {
+        return DBPScriptObject.OPTION_INCLUDE_COMMENTS.equals(option);
+    }
 }
diff --git a/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/model/meta/GBase8sMetaModel.java b/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/model/meta/GBase8sMetaModel.java
index 72c6c907d3815..4f08a477a995a 100644
--- a/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/model/meta/GBase8sMetaModel.java
+++ b/plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/model/meta/GBase8sMetaModel.java
@@ -19,6 +19,7 @@
 
 import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -76,7 +77,10 @@ public class GBase8sMetaModel extends GenericMetaModel {
 
     private static final Log log = Log.getLog(GBase8sMetaModel.class);
 
-    private static final String[] VALID_TABLE_TYPES = { "TABLE", "VIEW", "SYSTEM TABLE" };
+    // TABLE_TYPE: String {@code =>} table type. ('TABLE', 'VIEW', 'SYSTEM TABLE')
+    private static final String[] VALID_TABLE_TYPES = { "T", "V" };
+    // TABLE_TYPE: String {@code =>} table type. ('SYNONYM')
+    private static final String[] VALID_SYNONYM_TYPES = { "S" };
 
     public GBase8sMetaModel() {
         super();
@@ -199,9 +203,11 @@ public void loadProcedures(DBRProgressMonitor monitor, @NotNull GenericObjectCon
         try (JDBCSession session = DBUtils.openMetaSession(monitor, container, "Load procedures")) {
             if (hasProcedureSupport()) {
                 // Read procedures/functions
-                String query = "SELECT procid, procname, isproc, specificname, type, procflags, paramtypes::LVARCHAR AS columntypenames,"
-                        + " mode FROM sysprocedures ";
-                dbState = session.prepareStatement(query);
+                String sql = """
+                        SELECT procid, procname, isproc, specificname, type, procflags, paramtypes::LVARCHAR AS columntypenames, mode
+                        FROM sysprocedures
+                        """;
+                dbState = session.prepareStatement(sql);
                 dbResult = dbState.executeQuery();
 
                 while (dbResult.next()) {
@@ -279,7 +285,7 @@ public List<GBase8sTableTrigger> loadTriggers(DBRProgressMonitor monitor, @NotNu
     @Override
     public JDBCStatement prepareSynonymsLoadStatement(@NotNull JDBCSession session,
             @NotNull GenericStructContainer container) throws SQLException {
-        return prepareTSObjectLoadStatement(session, container, null, "%", new String[] { "SYNONYM" });
+        return prepareTSObjectLoadStatement(session, container, null, "%", VALID_SYNONYM_TYPES);
     }
 
     @Override
@@ -288,17 +294,82 @@ public JDBCStatement prepareTableLoadStatement(@NotNull JDBCSession session, @No
         return prepareTSObjectLoadStatement(session, owner, object, objectName, VALID_TABLE_TYPES);
     }
 
+    @Override
+    public JDBCStatement prepareTableColumnLoadStatement(
+            @NotNull JDBCSession session,
+            @NotNull GenericStructContainer owner,
+            @Nullable GenericTableBase forTable) throws SQLException {
+        String tableName = forTable == null ? owner.getDataSource().getAllObjectsPattern()
+                : JDBCUtils.escapeWildCards(session, forTable.getName());
+        String catalog = owner.getCatalog() == null ? null : owner.getCatalog().getName();
+        String schema = owner.getSchema() == null || DBUtils.isVirtualObject(owner.getSchema()) ? null
+                : JDBCUtils.escapeWildCards(session, owner.getSchema().getName());
+        boolean isOracleMode = GBase8sUtils.isOracleSqlMode(owner.getDataSource().getContainer());
+        String ownerPattern = "%s%s".formatted(isOracleMode ? schema : catalog, isOracleMode ? "." : ":");
+        String sql = """
+                SELECT
+                    t.tabname::VARCHAR(128) AS TABLE_NAME,
+                    c.colname::VARCHAR(128) AS COLUMN_NAME,
+                    get_data_type(c.coltype, c.extended_id, 0)::SMALLINT AS DATA_TYPE,
+                    0::SMALLINT AS SOURCE_DATA_TYPE,
+                    schema_coltypename(c.coltype, c.extended_id)::VARCHAR(128) AS TYPE_NAME,
+                    schema_precision(c.coltype, c.extended_id, c.collength)::INTEGER AS COLUMN_SIZE,
+                    schema_numscale(c.coltype, c.collength)::INTEGER AS DECIMAL_DIGITS,
+                    schema_numprecradix(c.coltype)::INTEGER AS NUM_PREC_RADIX,
+                    CASE d.type
+                        WHEN 'L' THEN get_default_value(c.coltype, c.extended_id, c.collength, d.default::lvarchar(256))::VARCHAR(254)
+                        WHEN 'E' THEN read_defaultstr(c.tabid, c.colno, c.coltype, c.collength, c.extended_id)::VARCHAR(32731)
+                        WHEN 'C' THEN 'current' || replace(get_colname(c.coltype, c.collength, c.extended_id, 1), schema_coltypename(c.coltype, c.extended_id), '')::VARCHAR(254)
+                        WHEN 'S' THEN 'dbservername'::VARCHAR(254)
+                        WHEN 'U' THEN 'user'::VARCHAR(254)
+                        WHEN 'T' THEN 'today'::VARCHAR(254)
+                        WHEN 'N' THEN 'null'::VARCHAR(10)
+                        ELSE null::VARCHAR(254)
+                    END AS COLUMN_DEF,
+                    cc.comments AS REMARKS,
+                    schema_isnullable(c.coltype)::INTEGER AS NULLABLE,
+                    schema_nullable(c.coltype)::VARCHAR(3) AS ISNULLABLE,
+                    c.coltype::INTEGER AS SQL_DATA_TYPE,
+                    schema_datetype(c.coltype, c.collength)::INTEGER AS DB_DATA_TYPE,
+                    schema_charlen(c.coltype, c.extended_id, c.collength)::INTEGER AS CHAR_OCTET_LENGTH,
+                    c.colno::integer AS ORDINAL_POSITION,
+                    schema_isautoincr(c.coltype)::VARCHAR(3) AS IS_AUTOINCREMENT,
+                    null::VARCHAR(254) AS IS_GENERATEDCOLUMN,
+                    c.extended_id::INTEGER AS EXTENDED_ID,
+                    c.colattr::INTEGER AS COLATTR,
+                    c.coltype
+                FROM
+                    %ssystables t,
+                    OUTER %ssysdefaults d,
+                    %ssyscolumns c,
+                    OUTER %ssyscolcomms cc
+                WHERE
+                    t.tabid = c.tabid
+                    AND d.tabid = t.tabid
+                    AND c.colno = d.colno
+                    AND t.tabid = cc.tabid
+                    AND c.colno = cc.colno
+                    AND t.tabname = ?
+                """
+                .formatted(ownerPattern, ownerPattern, ownerPattern, ownerPattern);
+        JDBCPreparedStatement dbStat = session.prepareStatement(sql);
+        dbStat.setString(1, tableName);
+        return dbStat;
+    }
+
     @Override
     public JDBCStatement prepareTableTriggersLoadStatement(@NotNull JDBCSession session,
             @NotNull GenericStructContainer container, @Nullable GenericTableBase table) throws SQLException {
-        String query = "SELECT T1.trigname as TRIGGER_NAME, T1.*, T2.tabname AS OWNER FROM systriggers AS T1, systables AS T2 "
-                + "WHERE T2.tabid = T1.tabid " + (table != null ? "AND T2.tabname = ?" : "");
-
+        String query = """
+                SELECT T1.trigname as TRIGGER_NAME, T1.*, T2.tabname AS OWNER
+                FROM systriggers AS T1, systables AS T2
+                WHERE T2.tabid = T1.tabid
+                %s
+                """.formatted(table != null ? "AND T2.tabname = ?" : "");
         JDBCPreparedStatement dbStat = session.prepareStatement(query);
         if (table != null) {
             dbStat.setString(1, table.getName());
         }
-
         return dbStat;
     }
 
@@ -323,12 +394,40 @@ JDBCStatement prepareTSObjectLoadStatement(@NotNull JDBCSession session, @NotNul
         }
 
         String catalog = owner.getCatalog() == null ? null : owner.getCatalog().getName();
-        String schemaPattern = owner.getSchema() == null || DBUtils.isVirtualObject(owner.getSchema()) ? null
+        String schema = owner.getSchema() == null || DBUtils.isVirtualObject(owner.getSchema()) ? null
                 : JDBCUtils.escapeWildCards(session, owner.getSchema().getName());
         boolean isOracleMode = GBase8sUtils.isOracleSqlMode(owner.getDataSource().getContainer());
-
-        return session.getMetaData().getTables(isOracleMode ? catalog : schemaPattern,
-                isOracleMode ? schemaPattern : catalog, tableNamePattern, types).getSourceStatement();
+        String ownerPattern = "%s%s".formatted(isOracleMode ? schema : catalog, isOracleMode ? "." : ":");
+        String sql = """
+                SELECT t.tabid, t.tabname AS TABLE_NAME, t.owner AS
+                    %s,
+                    CASE
+                        WHEN t.tabtype = 'T' AND t.tabid <= (SELECT tabid FROM systables WHERE trim(tabname) = 'VERSION') THEN 'SYSTEM TABLE'
+                        WHEN t.tabtype = 'V' AND t.tabid <= (SELECT tabid FROM systables WHERE trim(tabname) = 'VERSION') THEN 'SYSTEM VIEW'
+                        WHEN t.tabtype = 'T' THEN 'TABLE'
+                        WHEN t.tabtype = 'V' THEN 'VIEW'
+                        ELSE 'SYNONYM'
+                    END AS TABLE_TYPE,
+                    c.comments AS REMARKS
+                FROM %ssystables t
+                LEFT JOIN %ssyscomms c ON t.tabid = c.tabid
+                WHERE t.tabname LIKE ?
+                %s
+                """
+                .formatted(isOracleMode ? "TABLE_SCHEM" : "TABLE_CATALOG", ownerPattern, ownerPattern,
+                        (types != null
+                                ? " AND t.tabtype IN (" + String.join(", ", Collections.nCopies(types.length, "?"))
+                                        + ")"
+                                : ""));
+        JDBCPreparedStatement dbStat = session.prepareStatement(sql);
+        int paramIndex = 1;
+        dbStat.setString(paramIndex++, tableNamePattern);
+        if (types != null) {
+            for (String type : types) {
+                dbStat.setString(paramIndex++, type);
+            }
+        }
+        return dbStat;
     }
 
     @Override
@@ -351,6 +450,16 @@ public boolean hasFunctionSupport() {
         return true;
     }
 
+    @Override
+    public boolean isTableCommentEditable() {
+        return true;
+    }
+
+    @Override
+    public boolean isTableColumnCommentEditable() {
+        return true;
+    }
+
     @Override
     public boolean isTrimObjectNames() {
         return true;


Reviews:
user: login: ShadelessFox
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-01-23T11:01:27Z
user: login: ShadelessFox
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-01-24T11:57:59Z
user: login: E1izabeth
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-01-24T12:17:17Z
user: login: ShadelessFox
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-06T13:52:25Z
user: login: Wintianis
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-07T05:33:54Z
user: login: serge-rider
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-02-07T07:33:53Z
user: login: ShadelessFox
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-07T13:42:50Z
user: login: ShadelessFox
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-10T10:25:25Z
user: login: Wintianis
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-11T07:10:56Z
user: login: ShadelessFox
body: Looks good. Thank you for cooperation :)
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-02-11T10:42:57Z
user: login: E1izabeth
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-02-12T12:35:04Z
user: login: E1izabeth
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-02-27T09:04:56Z
user: login: ShadelessFox
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-02-27T10:41:36Z
user: login: Wintianis
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-28T02:26:41Z
user: login: ShadelessFox
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-28T11:16:13Z
user: login: Wintianis
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-01T08:36:42Z
user: login: ShadelessFox
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-01T16:15:49Z
user: login: labanovichttps
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T11:35:53Z
user: login: labanovichttps
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T11:38:17Z
user: login: labanovichttps
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T11:42:22Z
user: login: ShadelessFox
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T13:59:32Z
user: login: Wintianis
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T03:15:09Z
user: login: Wintianis
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T03:16:05Z
user: login: ShadelessFox
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-05T10:58:00Z
user: login: E1izabeth
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-05T10:58:30Z
user: login: labanovichttps
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T08:56:26Z
user: login: E1izabeth
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-13T09:23:38Z

Review comments:
diff_hunk: @@ -288,6 +292,64 @@ public JDBCStatement prepareTableLoadStatement(@NotNull JDBCSession session, @No
         return prepareTSObjectLoadStatement(session, owner, object, objectName, VALID_TABLE_TYPES);
     }
 
+    @Override
+    public JDBCStatement prepareTableColumnLoadStatement(@NotNull JDBCSession session,
+            @NotNull GenericStructContainer owner, @Nullable GenericTableBase forTable) throws SQLException {
+        String tableName = forTable == null ? owner.getDataSource().getAllObjectsPattern()
+                : JDBCUtils.escapeWildCards(session, forTable.getName());
+        String catalog = owner.getCatalog() == null ? null : owner.getCatalog().getName();
+        String schema = owner.getSchema() == null || DBUtils.isVirtualObject(owner.getSchema()) ? null
+                : JDBCUtils.escapeWildCards(session, owner.getSchema().getName());
+        boolean isOracleMode = GBase8sUtils.isOracleSqlMode(owner.getDataSource().getContainer());
+        String ownerPattern = isOracleMode ? schema + "." : catalog + ":";
+        String sql = "SELECT"
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/model/meta/GBase8sMetaModel.java
user: login: ShadelessFox
body: Consider using text blocks `"""`
created_at: 2025-01-23T10:58:33Z
author_association: MEMBER
diff_hunk: @@ -35,13 +34,25 @@
 import org.jkiss.dbeaver.model.impl.edit.SQLDatabasePersistAction;
 import org.jkiss.dbeaver.model.impl.sql.edit.SQLObjectEditor;
 import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
+import org.jkiss.utils.CommonUtils;
 
 /**
  * @author Chao Tian
  */
 public class GBase8sTableColumnManager extends GenericTableColumnManager
         implements DBEObjectRenamer<GenericTableColumn> {
 
+    @Override
+    protected void addObjectCreateActions(DBRProgressMonitor monitor, DBCExecutionContext executionContext,
+            List<DBEPersistAction> actions,
+            SQLObjectEditor<GenericTableColumn, GenericTableBase>.ObjectCreateCommand command,
+            Map<String, Object> options) throws DBException {
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/edit/GBase8sTableColumnManager.java
user: login: ShadelessFox
body: Formatting, missing annotations
```suggestion
    protected void addObjectCreateActions(
        @NotNull DBRProgressMonitor monitor,
        @NotNull DBCExecutionContext executionContext,
        @NotNull List<DBEPersistAction> actions,
        @NotNull SQLObjectEditor<GenericTableColumn, GenericTableBase>.ObjectCreateCommand command,
        @NotNull Map<String, Object> options
    ) throws DBException {
```
created_at: 2025-01-23T10:59:03Z
author_association: MEMBER
diff_hunk: @@ -323,12 +385,35 @@ JDBCStatement prepareTSObjectLoadStatement(@NotNull JDBCSession session, @NotNul
         }
 
         String catalog = owner.getCatalog() == null ? null : owner.getCatalog().getName();
-        String schemaPattern = owner.getSchema() == null || DBUtils.isVirtualObject(owner.getSchema()) ? null
+        String schema = owner.getSchema() == null || DBUtils.isVirtualObject(owner.getSchema()) ? null
                 : JDBCUtils.escapeWildCards(session, owner.getSchema().getName());
         boolean isOracleMode = GBase8sUtils.isOracleSqlMode(owner.getDataSource().getContainer());
-
-        return session.getMetaData().getTables(isOracleMode ? catalog : schemaPattern,
-                isOracleMode ? schemaPattern : catalog, tableNamePattern, types).getSourceStatement();
+        String ownerPattern = isOracleMode ? schema + "." : catalog + ":";
+        
+        String sql = "SELECT t.tabid, t.tabname AS TABLE_NAME, t.owner AS "
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/model/meta/GBase8sMetaModel.java
user: login: ShadelessFox
body: Consider using text blocks `"""`
created_at: 2025-01-23T11:00:25Z
author_association: MEMBER
diff_hunk: @@ -323,12 +385,35 @@ JDBCStatement prepareTSObjectLoadStatement(@NotNull JDBCSession session, @NotNul
         }
 
         String catalog = owner.getCatalog() == null ? null : owner.getCatalog().getName();
-        String schemaPattern = owner.getSchema() == null || DBUtils.isVirtualObject(owner.getSchema()) ? null
+        String schema = owner.getSchema() == null || DBUtils.isVirtualObject(owner.getSchema()) ? null
                 : JDBCUtils.escapeWildCards(session, owner.getSchema().getName());
         boolean isOracleMode = GBase8sUtils.isOracleSqlMode(owner.getDataSource().getContainer());
-
-        return session.getMetaData().getTables(isOracleMode ? catalog : schemaPattern,
-                isOracleMode ? schemaPattern : catalog, tableNamePattern, types).getSourceStatement();
+        String ownerPattern = isOracleMode ? schema + "." : catalog + ":";
+        
+        String sql = "SELECT t.tabid, t.tabname AS TABLE_NAME, t.owner AS "
+                + (isOracleMode ? "TABLE_SCHEM" : "TABLE_CATALOG") + ","
+                + " CASE WHEN t.tabtype = 'T' AND t.tabid <= (SELECT tabid FROM systables WHERE trim(tabname) = 'VERSION') THEN 'SYSTEM TABLE'"
+                + " WHEN t.tabtype = 'V' AND t.tabid <= (SELECT tabid FROM systables WHERE trim(tabname) = 'VERSION') THEN 'SYSTEM VIEW'"
+                + " WHEN t.tabtype = 'T' THEN 'TABLE'"
+                + " WHEN t.tabtype = 'V' THEN 'VIEW'"
+                + " ELSE 'SYNONYM'"
+                + " END AS TABLE_TYPE,"
+                + " c.comments AS REMARKS"
+                + " FROM " + ownerPattern + "systables t"
+                + " LEFT JOIN " + ownerPattern + "syscomms c ON t.tabid = c.tabid" + " WHERE t.tabname LIKE ?"
+                + (types != null ? " AND t.tabtype IN (" + String.join(", ", Collections.nCopies(types.length, "?")) + ")" : "");
+        JDBCPreparedStatement dbStat = session.prepareStatement(sql);
+        int paramIndex = 1;
+        dbStat.setString(paramIndex++, tableNamePattern);
+        if (types != null) {
+            for (String type : types) {
+                dbStat.setString(paramIndex++, type);
+            }
+        }
+        return dbStat;
+        // The getTables(...) interface returns a result that does not include information about table comments.
+//        return session.getMetaData().getTables(isOracleMode ? catalog : schemaPattern,
+//                isOracleMode ? schemaPattern : catalog, tableNamePattern, types).getSourceStatement();
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/model/meta/GBase8sMetaModel.java
user: login: ShadelessFox
body: Please remove the commented-out code. This explanation is nice, but rather pointless as custom implementations of meta-models are expected to override the default behavior.
created_at: 2025-01-23T11:01:18Z
author_association: MEMBER
diff_hunk: @@ -67,16 +69,20 @@ public static List<String> getSource(DBRProgressMonitor monitor, String sqlState
     }
 
     public static boolean isOracleSqlMode(DBPDataSourceContainer container) {
-        final DBPDriver driver = container.getDriver();
-        Map<String, Object> cprops = driver.getConnectionProperties();
-        for (Map.Entry<String, Object> entry : cprops.entrySet()) {
-            if (Objects.equals(entry.getKey().toLowerCase(), "sqlmode")) {
-                if (Objects.equals(entry.getValue().toString().trim().toLowerCase(), "oracle")) {
-                    return true;
-                }
+        DBPConnectionConfiguration configuration = container.getConnectionConfiguration();
+        Map<String, String> cprops = configuration.getProperties();
+        for (String key : cprops.keySet()) {
+            if ("sqlmode".equalsIgnoreCase(key)) {
+                return "oracle".equalsIgnoreCase(cprops.get(key).trim());
             }
         }
-        return false;
+        DBPDriver driver = container.getDriver();
+        Map<String, Object> driverProps = driver.getConnectionProperties();
+        return driverProps.values().stream()
+                .filter(Objects::nonNull)
+                .map(Object::toString)
+                .map(String::trim)
+                .anyMatch(value -> "oracle".equalsIgnoreCase(value));
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/GBase8sUtils.java
user: login: ShadelessFox
body: Why does it check only values without the key?
created_at: 2025-02-06T13:52:24Z
author_association: MEMBER
diff_hunk: @@ -67,16 +69,20 @@ public static List<String> getSource(DBRProgressMonitor monitor, String sqlState
     }
 
     public static boolean isOracleSqlMode(DBPDataSourceContainer container) {
-        final DBPDriver driver = container.getDriver();
-        Map<String, Object> cprops = driver.getConnectionProperties();
-        for (Map.Entry<String, Object> entry : cprops.entrySet()) {
-            if (Objects.equals(entry.getKey().toLowerCase(), "sqlmode")) {
-                if (Objects.equals(entry.getValue().toString().trim().toLowerCase(), "oracle")) {
-                    return true;
-                }
+        DBPConnectionConfiguration configuration = container.getConnectionConfiguration();
+        Map<String, String> cprops = configuration.getProperties();
+        for (String key : cprops.keySet()) {
+            if ("sqlmode".equalsIgnoreCase(key)) {
+                return "oracle".equalsIgnoreCase(cprops.get(key).trim());
             }
         }
-        return false;
+        DBPDriver driver = container.getDriver();
+        Map<String, Object> driverProps = driver.getConnectionProperties();
+        return driverProps.values().stream()
+                .filter(Objects::nonNull)
+                .map(Object::toString)
+                .map(String::trim)
+                .anyMatch(value -> "oracle".equalsIgnoreCase(value));
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/GBase8sUtils.java
user: login: Wintianis
body: I have addressed the issue with the logic where the key was not being considered. The code has been updated, and I would appreciate it if you could review it again.
created_at: 2025-02-07T05:33:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -67,16 +68,21 @@ public static List<String> getSource(DBRProgressMonitor monitor, String sqlState
     }
 
     public static boolean isOracleSqlMode(DBPDataSourceContainer container) {
-        final DBPDriver driver = container.getDriver();
-        Map<String, Object> cprops = driver.getConnectionProperties();
-        for (Map.Entry<String, Object> entry : cprops.entrySet()) {
-            if (Objects.equals(entry.getKey().toLowerCase(), "sqlmode")) {
-                if (Objects.equals(entry.getValue().toString().trim().toLowerCase(), "oracle")) {
-                    return true;
-                }
+        DBPConnectionConfiguration configuration = container.getConnectionConfiguration();
+        Map<String, String> cprops = configuration.getProperties();
+        for (String key : cprops.keySet()) {
+            if ("sqlmode".equalsIgnoreCase(key)) {
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/GBase8sUtils.java
user: login: serge-rider
body: Please move hardcoded sqlmode and oracle into constants
created_at: 2025-02-07T07:33:07Z
author_association: MEMBER
diff_hunk: @@ -78,11 +77,12 @@ public static boolean isOracleSqlMode(DBPDataSourceContainer container) {
         }
         DBPDriver driver = container.getDriver();
         Map<String, Object> driverProps = driver.getConnectionProperties();
-        return driverProps.values().stream()
-                .filter(Objects::nonNull)
-                .map(Object::toString)
-                .map(String::trim)
-                .anyMatch(value -> "oracle".equalsIgnoreCase(value));
+        for (String key : driverProps.keySet()) {
+            if ("sqlmode".equalsIgnoreCase(key)) {
+                return "oracle".equalsIgnoreCase(Objects.toString(driverProps.get(key), "").trim());
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/GBase8sUtils.java
user: login: ShadelessFox
body: The code above doesn't check for `null` values. Maybe it's better to extract that part to avoid repetition?
created_at: 2025-02-07T13:42:50Z
author_association: MEMBER
diff_hunk: @@ -67,16 +68,32 @@ public static List<String> getSource(DBRProgressMonitor monitor, String sqlState
     }
 
     public static boolean isOracleSqlMode(DBPDataSourceContainer container) {
-        final DBPDriver driver = container.getDriver();
-        Map<String, Object> cprops = driver.getConnectionProperties();
-        for (Map.Entry<String, Object> entry : cprops.entrySet()) {
-            if (Objects.equals(entry.getKey().toLowerCase(), "sqlmode")) {
-                if (Objects.equals(entry.getValue().toString().trim().toLowerCase(), "oracle")) {
-                    return true;
-                }
-            }
+        DBPConnectionConfiguration configuration = container.getConnectionConfiguration();
+        Map<String, String> cprops = configuration.getProperties();
+        if (containsSqlModeKey(cprops)) {
+            return isOracleSqlModeFromProperties(cprops);
         }
-        return false;
+        DBPDriver driver = container.getDriver();
+        Map<String, Object> driverProps = driver.getConnectionProperties();
+        // If driverProps does not have SQLMODE, return true. Default is Oracle mode.
+        if (!containsSqlModeKey(driverProps)) {
+            return true;
+        }
+        return isOracleSqlModeFromProperties(driverProps);
+    }
+
+    private static boolean containsSqlModeKey(Map<String, ?> properties) {
+        return properties != null && !properties.isEmpty() && properties.keySet().stream()
+                .anyMatch(key -> key != null && GBase8sConstants.JDBC_SQL_MODE.equalsIgnoreCase(key));
+    }
+
+    private static boolean isOracleSqlModeFromProperties(Map<String, ?> properties) {
+        return properties != null && !properties.isEmpty()
+                && properties.entrySet().stream()
+                        .anyMatch(entry -> entry.getKey() != null
+                                && GBase8sConstants.JDBC_SQL_MODE.equalsIgnoreCase(entry.getKey().toString())
+                                && GBase8sConstants.JDBC_SQL_MODE_ORACLE
+                                        .equalsIgnoreCase(Objects.toString(entry.getValue(), "").trim()));
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/GBase8sUtils.java
user: login: ShadelessFox
body: Can you please extract the value of `JDBC_SQL_MODE` and compare it instead of doing this again the second time? Why are you converting the key to a string even though it's already a string? Also, please stop abusing stream APIs.
created_at: 2025-02-10T10:23:03Z
author_association: MEMBER
diff_hunk: @@ -67,16 +68,32 @@ public static List<String> getSource(DBRProgressMonitor monitor, String sqlState
     }
 
     public static boolean isOracleSqlMode(DBPDataSourceContainer container) {
-        final DBPDriver driver = container.getDriver();
-        Map<String, Object> cprops = driver.getConnectionProperties();
-        for (Map.Entry<String, Object> entry : cprops.entrySet()) {
-            if (Objects.equals(entry.getKey().toLowerCase(), "sqlmode")) {
-                if (Objects.equals(entry.getValue().toString().trim().toLowerCase(), "oracle")) {
-                    return true;
-                }
-            }
+        DBPConnectionConfiguration configuration = container.getConnectionConfiguration();
+        Map<String, String> cprops = configuration.getProperties();
+        if (containsSqlModeKey(cprops)) {
+            return isOracleSqlModeFromProperties(cprops);
         }
-        return false;
+        DBPDriver driver = container.getDriver();
+        Map<String, Object> driverProps = driver.getConnectionProperties();
+        // If driverProps does not have SQLMODE, return true. Default is Oracle mode.
+        if (!containsSqlModeKey(driverProps)) {
+            return true;
+        }
+        return isOracleSqlModeFromProperties(driverProps);
+    }
+
+    private static boolean containsSqlModeKey(Map<String, ?> properties) {
+        return properties != null && !properties.isEmpty() && properties.keySet().stream()
+                .anyMatch(key -> key != null && GBase8sConstants.JDBC_SQL_MODE.equalsIgnoreCase(key));
+    }
+
+    private static boolean isOracleSqlModeFromProperties(Map<String, ?> properties) {
+        return properties != null && !properties.isEmpty()
+                && properties.entrySet().stream()
+                        .anyMatch(entry -> entry.getKey() != null
+                                && GBase8sConstants.JDBC_SQL_MODE.equalsIgnoreCase(entry.getKey().toString())
+                                && GBase8sConstants.JDBC_SQL_MODE_ORACLE
+                                        .equalsIgnoreCase(Objects.toString(entry.getValue(), "").trim()));
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/GBase8sUtils.java
user: login: Wintianis
body: I got it, I have already fixed it. If there are any further issues, please feel free to contact me.
created_at: 2025-02-11T07:10:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -26,4 +26,6 @@ public class GBase8sConstants {
     public static final String JDBC_SQL_MODE_ORACLE = "oracle";
     public static final String JDBC_SQL_MODE_GBASE = "gbase";
 
+    public static final String SQL_TABLE_COMMENT = "COMMENT ON TABLE %s IS '%s'";
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/GBase8sConstants.java
user: login: ShadelessFox
body: This is a potential place for an SQL injection. Please use `SQLUtils.quoteString` to escape string literals, and `DBUtils#getQuotedIdentifier` to escape object names.
created_at: 2025-02-27T10:41:29Z
author_association: MEMBER
diff_hunk: @@ -26,4 +26,6 @@ public class GBase8sConstants {
     public static final String JDBC_SQL_MODE_ORACLE = "oracle";
     public static final String JDBC_SQL_MODE_GBASE = "gbase";
 
+    public static final String SQL_TABLE_COMMENT = "COMMENT ON TABLE %s IS '%s'";
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/GBase8sConstants.java
user: login: Wintianis
body: I have fixed it. Please review the code.
created_at: 2025-02-28T02:26:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -120,9 +122,10 @@ private DBEPersistAction buildTableCommentAction(GenericTableBase table) {
     private DBEPersistAction buildColumnCommentAction(GenericTableColumn column) {
         String description = column.getDescription();
         if (CommonUtils.isNotEmpty(description)) {
-            String tableName = column.getTable().getFullyQualifiedName(DBPEvaluationContext.DDL);
-            String columnName = column.getName();
-            String commentSQL = String.format(GBase8sConstants.SQL_COLUMN_COMMENT, tableName, columnName, description);
+            String tableName = DBUtils.getObjectFullName(column.getTable(), DBPEvaluationContext.DDL);
+            String columnName = DBUtils.getQuotedIdentifier(column);
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/edit/GBase8sTableManager.java
user: login: ShadelessFox
body: If you call `DBUtils.getObjectFullName` on a column, it will return a fully-qualified identifier. 
created_at: 2025-02-28T11:16:13Z
author_association: MEMBER
diff_hunk: @@ -120,9 +122,10 @@ private DBEPersistAction buildTableCommentAction(GenericTableBase table) {
     private DBEPersistAction buildColumnCommentAction(GenericTableColumn column) {
         String description = column.getDescription();
         if (CommonUtils.isNotEmpty(description)) {
-            String tableName = column.getTable().getFullyQualifiedName(DBPEvaluationContext.DDL);
-            String columnName = column.getName();
-            String commentSQL = String.format(GBase8sConstants.SQL_COLUMN_COMMENT, tableName, columnName, description);
+            String tableName = DBUtils.getObjectFullName(column.getTable(), DBPEvaluationContext.DDL);
+            String columnName = DBUtils.getQuotedIdentifier(column);
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/edit/GBase8sTableManager.java
user: login: Wintianis
body: Thank you for your comment.
I agree with your suggestion. The method `DBUtils.getObjectFullName(column.getTable(), DBPEvaluationContext.DDL)` is intentionally used here to ensure that the fully-qualified table name is obtained instead of the column identifier. Please let me know if you have any further suggestions.
created_at: 2025-03-01T08:36:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -67,16 +66,25 @@ public static List<String> getSource(DBRProgressMonitor monitor, String sqlState
     }
 
     public static boolean isOracleSqlMode(DBPDataSourceContainer container) {
-        final DBPDriver driver = container.getDriver();
-        Map<String, Object> cprops = driver.getConnectionProperties();
-        for (Map.Entry<String, Object> entry : cprops.entrySet()) {
-            if (Objects.equals(entry.getKey().toLowerCase(), "sqlmode")) {
-                if (Objects.equals(entry.getValue().toString().trim().toLowerCase(), "oracle")) {
-                    return true;
-                }
+        DBPConnectionConfiguration configuration = container.getConnectionConfiguration();
+        String sqlMode = getSqlModeValue(configuration.getProperties());
+        if (sqlMode != null) {
+            return GBase8sConstants.JDBC_SQL_MODE_ORACLE.equalsIgnoreCase(sqlMode);
+        }
+        return GBase8sConstants.JDBC_SQL_MODE_ORACLE
+                .equalsIgnoreCase(getSqlModeValue(container.getDriver().getConnectionProperties()));
+    }
+
+    private static String getSqlModeValue(Map<String, ?> properties) {
+        if (properties == null || properties.isEmpty()) {
+            return null;
+        }
+        for (Map.Entry<String, ?> entry : properties.entrySet()) {
+            if (GBase8sConstants.JDBC_SQL_MODE.equalsIgnoreCase(entry.getKey())) {
+                return entry.getValue() != null ? entry.getValue().toString().trim() : null;
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/GBase8sUtils.java
user: login: labanovichttps
body: Use **org.jkiss.utils.CommonUtils#trim**
created_at: 2025-03-03T11:35:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -64,6 +69,28 @@ public void renameObject(DBECommandContext commandContext, GenericTableBase obje
         processObjectRename(commandContext, object, options, newName);
     }
 
+    @Override
+    protected void addStructObjectCreateActions(
+            DBRProgressMonitor monitor,
+            DBCExecutionContext executionContext,
+            List<DBEPersistAction> actions,
+            StructCreateCommand command,
+            Map<String, Object> options) throws DBException {
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/edit/GBase8sTableManager.java
user: login: labanovichttps
body: Add annotations(`@NotNull, @Nullable`), please
created_at: 2025-03-03T11:38:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -288,17 +294,82 @@ public JDBCStatement prepareTableLoadStatement(@NotNull JDBCSession session, @No
         return prepareTSObjectLoadStatement(session, owner, object, objectName, VALID_TABLE_TYPES);
     }
 
+    @Override
+    public JDBCStatement prepareTableColumnLoadStatement(@NotNull JDBCSession session,
+            @NotNull GenericStructContainer owner, @Nullable GenericTableBase forTable) throws SQLException {
+        String tableName = forTable == null ? owner.getDataSource().getAllObjectsPattern()
+                : JDBCUtils.escapeWildCards(session, forTable.getName());
+        String catalog = owner.getCatalog() == null ? null : owner.getCatalog().getName();
+        String schema = owner.getSchema() == null || DBUtils.isVirtualObject(owner.getSchema()) ? null
+                : JDBCUtils.escapeWildCards(session, owner.getSchema().getName());
+        boolean isOracleMode = GBase8sUtils.isOracleSqlMode(owner.getDataSource().getContainer());
+        String ownerPattern = """
+                %s%s
+                """.formatted(isOracleMode ? schema : catalog, isOracleMode ? "." : ":");
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/model/meta/GBase8sMetaModel.java
user: login: labanovichttps
body: If it's a simple string, maybe use this code.
` String ownerPattern = "%s%s".formatted(isOracleMode ? schema : catalog, isOracleMode ? "." : ":");`
created_at: 2025-03-03T11:42:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -67,16 +66,25 @@ public static List<String> getSource(DBRProgressMonitor monitor, String sqlState
     }
 
     public static boolean isOracleSqlMode(DBPDataSourceContainer container) {
-        final DBPDriver driver = container.getDriver();
-        Map<String, Object> cprops = driver.getConnectionProperties();
-        for (Map.Entry<String, Object> entry : cprops.entrySet()) {
-            if (Objects.equals(entry.getKey().toLowerCase(), "sqlmode")) {
-                if (Objects.equals(entry.getValue().toString().trim().toLowerCase(), "oracle")) {
-                    return true;
-                }
+        DBPConnectionConfiguration configuration = container.getConnectionConfiguration();
+        String sqlMode = getSqlModeValue(configuration.getProperties());
+        if (sqlMode != null) {
+            return GBase8sConstants.JDBC_SQL_MODE_ORACLE.equalsIgnoreCase(sqlMode);
+        }
+        return GBase8sConstants.JDBC_SQL_MODE_ORACLE
+                .equalsIgnoreCase(getSqlModeValue(container.getDriver().getConnectionProperties()));
+    }
+
+    private static String getSqlModeValue(Map<String, ?> properties) {
+        if (properties == null || properties.isEmpty()) {
+            return null;
+        }
+        for (Map.Entry<String, ?> entry : properties.entrySet()) {
+            if (GBase8sConstants.JDBC_SQL_MODE.equalsIgnoreCase(entry.getKey())) {
+                return entry.getValue() != null ? entry.getValue().toString().trim() : null;
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/GBase8sUtils.java
user: login: ShadelessFox
body: It expects a `String`
created_at: 2025-03-03T13:59:32Z
author_association: MEMBER
diff_hunk: @@ -288,17 +294,82 @@ public JDBCStatement prepareTableLoadStatement(@NotNull JDBCSession session, @No
         return prepareTSObjectLoadStatement(session, owner, object, objectName, VALID_TABLE_TYPES);
     }
 
+    @Override
+    public JDBCStatement prepareTableColumnLoadStatement(@NotNull JDBCSession session,
+            @NotNull GenericStructContainer owner, @Nullable GenericTableBase forTable) throws SQLException {
+        String tableName = forTable == null ? owner.getDataSource().getAllObjectsPattern()
+                : JDBCUtils.escapeWildCards(session, forTable.getName());
+        String catalog = owner.getCatalog() == null ? null : owner.getCatalog().getName();
+        String schema = owner.getSchema() == null || DBUtils.isVirtualObject(owner.getSchema()) ? null
+                : JDBCUtils.escapeWildCards(session, owner.getSchema().getName());
+        boolean isOracleMode = GBase8sUtils.isOracleSqlMode(owner.getDataSource().getContainer());
+        String ownerPattern = """
+                %s%s
+                """.formatted(isOracleMode ? schema : catalog, isOracleMode ? "." : ":");
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/model/meta/GBase8sMetaModel.java
user: login: Wintianis
body: As requested, I have modified the implementation.
created_at: 2025-03-05T03:15:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -64,6 +69,28 @@ public void renameObject(DBECommandContext commandContext, GenericTableBase obje
         processObjectRename(commandContext, object, options, newName);
     }
 
+    @Override
+    protected void addStructObjectCreateActions(
+            DBRProgressMonitor monitor,
+            DBCExecutionContext executionContext,
+            List<DBEPersistAction> actions,
+            StructCreateCommand command,
+            Map<String, Object> options) throws DBException {
path: plugins/org.jkiss.dbeaver.ext.gbase8s/src/org/jkiss/dbeaver/ext/gbase8s/edit/GBase8sTableManager.java
user: login: Wintianis
body: I have added the annotations.
created_at: 2025-03-05T03:16:05Z
author_association: CONTRIBUTOR

PR comments:
user: login: E1izabeth
created_at: 2025-01-24T12:18:03Z
body: Thanks for your contribution! We passed your pull request to out QA team for testing
author_association: MEMBER
user: login: Wintianis
created_at: 2025-02-06T05:57:59Z
body: I have fixed an identified issue. Please review.
author_association: CONTRIBUTOR
user: login: E1izabeth
created_at: 2025-02-12T12:35:28Z
body: Passed to QA team for testing
author_association: MEMBER
user: login: Wintianis
created_at: 2025-02-26T03:43:20Z
body: I have submitted the fix for the bug reported by QA. Please review the code. Apologies for any inconvenience caused. Thank you very much.
@ShadelessFox @E1izabeth @serge-rider 
author_association: CONTRIBUTOR
user: login: Wintianis
created_at: 2025-03-05T03:26:52Z
body: I have updated part of the comment DDL generation logic and added the `DBPScriptObject.OPTION_INCLUDE_COMMENTS` option button. Meanwhile, I moved the comment DDL generation code to `GBase8sTableManager.addObjectExtraActions` and `GBase8sTableColumnManager.addObjectExtraActions`, which might be a more standardized approach.  

If there is anything inappropriate, please feel free to point it out. Your review is always welcome.
author_association: CONTRIBUTOR
user: login: Wintianis
created_at: 2025-03-13T09:24:45Z
body: I have adjusted the logic for outputting column comments based on the testing suggestion. Please review the code.
author_association: CONTRIBUTOR
user: login: kseniaguzeeva
created_at: 2025-03-13T11:33:06Z
body: verified
author_association: CONTRIBUTOR