feat(gbase8s): preload column defaults for new rows

This commit is contained in:
New2Niu
2026-09-17 11:54:40 +08:00
committed by GitHub
parent c0d6965b5f
commit de06808807
2 changed files with 130 additions and 6 deletions
@@ -16,6 +16,7 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -454,6 +455,7 @@ public final class Gbase8sAgent extends ConfiguredJdbcAgent {
Connection conn = requireConnection();
String owner = trim(schema);
Set<Integer> primaryKeyColumns = getPrimaryKeyColumnNumbers(conn, owner, table);
Map<String, String> columnDefaults = loadColumnDefaults(conn, owner, table);
List<Object> args = new ArrayList<>();
args.add(table);
StringBuilder sql = new StringBuilder("""
@@ -482,7 +484,7 @@ public final class Gbase8sAgent extends ConfiguredJdbcAgent {
name,
mapColType(baseType),
(coltype & 256) == 0,
null,
columnDefaults.get(name),
primaryKeyColumns.contains(rs.getInt("colno")),
null,
emptyToNull(trim(rs.getString("comments"))),
@@ -499,6 +501,38 @@ public final class Gbase8sAgent extends ConfiguredJdbcAgent {
}
}
private static Map<String, String> loadColumnDefaults(Connection conn, String owner, String table) {
List<Object> args = new ArrayList<>();
args.add(table);
StringBuilder sql = new StringBuilder("""
SELECT c.colname, e.default AS column_default
FROM systables t
JOIN syscolumns c ON t.tabid = c.tabid
JOIN sysdefaultsexpr e ON c.tabid = e.tabid AND c.colno = e.colno
WHERE t.tabname = ? AND e.type = 'T'
""".stripIndent().trim());
if (!owner.isEmpty()) {
sql.append(" AND t.owner = ?");
args.add(owner);
}
Map<String, String> defaults = new LinkedHashMap<>();
try (PreparedStatement stmt = conn.prepareStatement(sql.toString())) {
bind(stmt, args);
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
String value = rs.getString("column_default");
if (value != null) {
defaults.put(trim(rs.getString("colname")), value);
}
}
}
} catch (SQLException ignored) {
return Collections.emptyMap();
}
return defaults;
}
@Override
public List<IndexInfo> listIndexes(String schema, String table) {
try {
@@ -1028,7 +1062,7 @@ public final class Gbase8sAgent extends ConfiguredJdbcAgent {
sql.append(" AND tabtype IN (").append(String.join(", ", tabTypes)).append(")");
}
private static void bind(PreparedStatement stmt, List<Object> args) throws Exception {
private static void bind(PreparedStatement stmt, List<Object> args) throws SQLException {
for (int index = 0; index < args.size(); index += 1) {
stmt.setString(index + 1, String.valueOf(args.get(index)));
}
@@ -17,6 +17,7 @@ import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -344,6 +345,7 @@ class Gbase8sAgentTest {
{1, 0, null, null, null, null, null, null, null, null, null, null, null, null, null, null}
}
),
resultSet(new String[]{"colname", "column_default"}, new Object[][]{}),
resultSet(
new String[]{"colname", "coltype", "colno", "collength", "comments"},
new Object[][]{
@@ -356,11 +358,12 @@ class Gbase8sAgentTest {
List<ColumnInfo> columns = agent.getColumns("root", "products");
Assertions.assertEquals(2, sql.size());
Assertions.assertEquals(3, sql.size());
Assertions.assertTrue(sql.get(0).contains("FROM sysconstraints"), sql.get(0));
Assertions.assertTrue(sql.get(0).contains("t.owner = ?"), sql.get(0));
Assertions.assertTrue(sql.get(1).contains("FROM syscolumns"), sql.get(1));
Assertions.assertTrue(sql.get(1).contains("t.owner = ?"), sql.get(1));
Assertions.assertTrue(sql.get(1).contains("JOIN sysdefaultsexpr"), sql.get(1));
Assertions.assertTrue(sql.get(2).contains("FROM syscolumns"), sql.get(2));
Assertions.assertTrue(sql.get(2).contains("t.owner = ?"), sql.get(2));
Assertions.assertEquals(3, columns.size());
Assertions.assertEquals("product_id", columns.get(0).getName());
Assertions.assertEquals("INTEGER", columns.get(0).getData_type());
@@ -373,7 +376,68 @@ class Gbase8sAgentTest {
Assertions.assertEquals(2, columns.get(2).getNumeric_scale());
Assertions.assertEquals("Product identifier", columns.get(0).getComment());
Assertions.assertEquals("Unit price", columns.get(2).getComment());
Assertions.assertTrue(sql.get(1).contains("LEFT JOIN syscolcomms"), sql.get(1));
Assertions.assertTrue(sql.get(2).contains("LEFT JOIN syscolcomms"), sql.get(2));
}
@Test
void getColumnsLoadsDefaultsFromDefaultExpressionCatalog() throws Exception {
List<String> sql = new ArrayList<>();
Gbase8sAgent agent = new Gbase8sAgent();
TestSupport.setPrivateConnection(agent, preparedConnection(
sql,
resultSet(
new String[]{"part1", "part2", "part3", "part4", "part5", "part6", "part7", "part8", "part9", "part10", "part11", "part12", "part13", "part14", "part15", "part16"},
new Object[][]{{1, 0, null, null, null, null, null, null, null, null, null, null, null, null, null, null}}
),
resultSet(
new String[]{"colname", "column_default"},
new Object[][]{{"op_id", "'0'"}, {"created_at", "current_timestamp"}}
),
resultSet(
new String[]{"colname", "coltype", "colno", "collength", "comments"},
new Object[][]{
{"id", 258, 1, 4, null},
{"op_id", 2, 2, 4, null},
{"created_at", 10, 3, 8, null}
}
)
));
List<ColumnInfo> columns = agent.getColumns("root", "system_user");
Assertions.assertEquals(3, sql.size());
Assertions.assertTrue(sql.get(1).contains("JOIN sysdefaultsexpr"), sql.get(1));
Assertions.assertTrue(sql.get(1).contains("e.type = 'T'"), sql.get(1));
Assertions.assertTrue(sql.get(1).contains("e.default AS column_default"), sql.get(1));
Assertions.assertTrue(sql.get(1).contains("t.owner = ?"), sql.get(1));
Assertions.assertNull(columns.get(0).getColumn_default());
Assertions.assertEquals("'0'", columns.get(1).getColumn_default());
Assertions.assertEquals("current_timestamp", columns.get(2).getColumn_default());
}
@Test
void getColumnsFallsBackWhenDefaultCatalogQueryFails() {
List<String> sql = new ArrayList<>();
Gbase8sAgent agent = new Gbase8sAgent();
TestSupport.setPrivateConnection(agent, defaultQueryFailureConnection(
sql,
resultSet(
new String[]{"part1", "part2", "part3", "part4", "part5", "part6", "part7", "part8", "part9", "part10", "part11", "part12", "part13", "part14", "part15", "part16"},
new Object[][]{{1, 0, null, null, null, null, null, null, null, null, null, null, null, null, null, null}}
),
resultSet(
new String[]{"colname", "coltype", "colno", "collength", "comments"},
new Object[][]{{"id", 258, 1, 4, null}, {"name", 13, 2, 64, null}}
)
));
List<ColumnInfo> columns = agent.getColumns("root", "system_user");
Assertions.assertEquals(3, sql.size());
Assertions.assertTrue(sql.get(1).contains("JOIN sysdefaultsexpr"), sql.get(1));
Assertions.assertEquals(2, columns.size());
Assertions.assertNull(columns.get(0).getColumn_default());
Assertions.assertNull(columns.get(1).getColumn_default());
}
@Test
@@ -536,6 +600,32 @@ class Gbase8sAgentTest {
});
}
private static Connection defaultQueryFailureConnection(List<String> sql, ResultSet... resultSets) {
int[] resultIndex = {0};
return proxy(Connection.class, (method, args) -> {
if ("getCatalog".equals(method.getName())) {
return "appdb";
}
if ("prepareStatement".equals(method.getName())) {
String query = String.valueOf(args[0]);
sql.add(query);
return proxy(PreparedStatement.class, (statementMethod, statementArgs) -> {
if ("executeQuery".equals(statementMethod.getName())) {
if (query.contains("sysdefaultsexpr")) {
throw new SQLException("Backtick identifiers are not supported");
}
return resultSets[resultIndex[0]++];
}
return defaultValue(statementMethod.getReturnType());
});
}
if ("isClosed".equals(method.getName())) {
return false;
}
return defaultValue(method.getReturnType());
});
}
private static Connection schemaConnection(boolean supportsSchemasInDml, List<String> sql, ResultSet resultSet) {
DatabaseMetaData metadata = proxy(DatabaseMetaData.class, (method, args) -> {
if ("supportsSchemasInDataManipulation".equals(method.getName())) {