1. Project Clover database Fri Jul 17 2026 06:10:26 UTC
  2. Package guru.mikelue.foxglove.jdbc

File MetaUtils.java

 

Coverage histogram

../../../../img/srcFileCovDistChart9.png
52% of files have more coverage

Code metrics

24
78
9
1
224
173
29
0.37
8.67
9
3.22

Classes

Class Line # Actions
MetaUtils 22 78 0% 29 12
0.891891989.2%
 

Contributing tests

This file is covered by 65 tests. .

Source view

1    package guru.mikelue.foxglove.jdbc;
2   
3    import java.sql.DatabaseMetaData;
4    import java.sql.JDBCType;
5    import java.sql.ResultSetMetaData;
6    import java.sql.SQLException;
7    import java.util.*;
8    import java.util.regex.Pattern;
9   
10    import org.slf4j.Logger;
11    import org.slf4j.LoggerFactory;
12   
13    import guru.mikelue.foxglove.ColumnMeta;
14    import guru.mikelue.foxglove.setting.DataSettingInfo;
15   
16    import static guru.mikelue.foxglove.ColumnMeta.Property.*;
17    import static java.util.Collections.unmodifiableList;
18   
19    /**
20    * Utility class for metadata organizing.
21    */
 
22    final class MetaUtils {
23    private final static Logger logger = LoggerFactory.getLogger(MetaUtils.class);
24   
 
25  0 toggle private MetaUtils() {}
26   
 
27  92 toggle static List<ColumnMeta> filterColumns(
28    List<ColumnMeta> columnMetaList,
29    DataSettingInfo dataSetting, JdbcTableFacet tableFacet
30    ) {
31  92 var result = columnMetaList.stream()
32    .filter(
33    columnMeta -> {
34  1077 var inclusionMode = tableFacet.getColumnInclusion(columnMeta);
35   
36  1077 switch (inclusionMode) {
37  118 case Include:
38  118 return true;
39  41 case Exclude:
40  41 return false;
41  918 default:
42  918 break;
43    }
44   
45  918 return dataSetting.isAutoGenerating(columnMeta);
46    }
47    )
48    .toList();
49   
50  92 return unmodifiableList(result);
51    }
52   
 
53  110 toggle static List<ColumnMeta> getColumnMetaList(ResultSetMetaData rsMeta)
54    {
55  110 try {
56  110 return fetchColumnMetaListImpl(rsMeta);
57    } catch (SQLException e) {
58  0 throw new RuntimeJdbcException(e);
59    }
60    }
61   
 
62  18 toggle static List<ColumnMeta> getColumnMetaList(DatabaseMetaData dbMeta, String tableName)
63    {
64  18 try {
65  18 return fetchColumnMetaListImpl(dbMeta, tableName);
66    } catch (SQLException e) {
67  0 throw new RuntimeJdbcException(e);
68    }
69    }
70   
 
71  102 toggle static String buildInsertSql(DatabaseMetaData dbMetaData, String tableName, List<ColumnMeta> columnMetaList)
72    {
73  102 String quote;
74   
75  102 try {
76  102 quote = dbMetaData.getIdentifierQuoteString();
77    } catch (SQLException e) {
78  0 throw new RuntimeJdbcException(e);
79    }
80   
81  102 var columnNames = columnMetaList.stream()
82    .map(columnMeta -> quoteIdentifier(columnMeta.name(), quote))
83    .toList();
84   
85  102 return String.format(
86    "INSERT INTO %s (%s)\nVALUES (%s)",
87    quoteIdentifier(tableName, quote),
88    String.join(", ", columnNames),
89    String.join(", ", Collections.nCopies(columnNames.size(), "?"))
90    );
91    }
92   
 
93  110 toggle private static List<ColumnMeta> fetchColumnMetaListImpl(ResultSetMetaData rsMeta)
94    throws SQLException
95    {
96  110 var columnCount = rsMeta.getColumnCount();
97  110 var metaOfColumns = new ArrayList<ColumnMeta>(columnCount);
98   
99  257 for (int index = 1; index <= columnCount; ++index) {
100  147 var columnName = rsMeta.getColumnName(index);
101  147 var jdbcType = resolveJdbcType(rsMeta.getColumnType(index));
102  147 var typeName = rsMeta.getColumnTypeName(index).toLowerCase();
103  147 var size = rsMeta.getPrecision(index);
104  147 var decimalDigits = rsMeta.getScale(index);
105   
106  147 var properties = EnumSet.noneOf(ColumnMeta.Property.class);
107  147 if (rsMeta.isNullable(index) != DatabaseMetaData.columnNoNulls) {
108  145 properties.add(NULLABLE);
109    }
110  147 if (rsMeta.isAutoIncrement(index)) {
111  0 properties.add(AUTO_INCREMENT);
112    }
113   
114  147 var newColumnMeta = new ColumnMeta(
115    columnName, properties,
116    typeName, jdbcType,
117    size, decimalDigits
118    );
119   
120  147 logger.trace("Fetched column meta(ResultSet): {}", newColumnMeta);
121   
122  147 metaOfColumns.add(newColumnMeta);
123    }
124   
125  110 return unmodifiableList(metaOfColumns);
126    }
127   
 
128  18 toggle private static List<ColumnMeta> fetchColumnMetaListImpl(DatabaseMetaData dbMeta, String tableName)
129    throws SQLException
130    {
131  18 if (dbMeta.storesUpperCaseIdentifiers()) {
132  0 tableName = tableName.toUpperCase();
133  18 } else if (dbMeta.storesLowerCaseIdentifiers()) {
134  6 tableName = tableName.toLowerCase();
135    }
136   
137  18 var rsOfColumnMeta = dbMeta.getColumns(null, null, tableName, null);
138   
139  18 var metaOfColumns = new ArrayList<ColumnMeta>(rsOfColumnMeta.getFetchSize());
140   
141  93 while (rsOfColumnMeta.next()) {
142  75 var columnName = rsOfColumnMeta.getString("COLUMN_NAME");
143  75 var jdbcType = resolveJdbcType(rsOfColumnMeta.getInt("DATA_TYPE"));
144  75 var typeName = rsOfColumnMeta.getString("TYPE_NAME").toLowerCase();
145  75 var size = rsOfColumnMeta.getInt("COLUMN_SIZE");
146  75 var decimalDigits = rsOfColumnMeta.getInt("DECIMAL_DIGITS");
147   
148    /*
149    * TRUE/FALSE properties of a column
150    */
151  75 var nullable = rsOfColumnMeta.getInt("NULLABLE") != DatabaseMetaData.columnNoNulls;
152  75 var hasDefaultValue = rsOfColumnMeta.getString("COLUMN_DEF") != null;
153  75 var isAutoIncrement = "YES".equals(rsOfColumnMeta.getString("IS_AUTOINCREMENT"));
154  75 var isGeneratedColumn = "YES".equals(rsOfColumnMeta.getString("IS_GENERATEDCOLUMN"));
155  75 var properties = EnumSet.noneOf(ColumnMeta.Property.class);
156   
157  75 if (nullable) {
158  34 properties.add(NULLABLE);
159    }
160  75 if (hasDefaultValue) {
161  14 properties.add(DEFAULT_VALUE);
162    }
163  75 if (isAutoIncrement) {
164  5 properties.add(AUTO_INCREMENT);
165    }
166  75 if (isGeneratedColumn) {
167  2 properties.add(GENERATED);
168    }
169    // :~)
170   
171  75 var newColumnMeta = new ColumnMeta(
172    columnName, properties,
173    typeName, jdbcType,
174    size, decimalDigits
175    );
176   
177  75 logger.debug("Fetched column meta: {}", newColumnMeta);
178   
179  75 metaOfColumns.add(newColumnMeta);
180    }
181   
182  18 if (metaOfColumns.isEmpty()) {
183  0 logger.warn("No column metadata fetched for table: {}", tableName);
184    }
185   
186  18 return unmodifiableList(metaOfColumns);
187    }
188   
189    /*
190    * Converts the integral value of java.sql.Types to JDBCType
191    */
 
192  222 toggle private static JDBCType resolveJdbcType(int sqlType)
193    {
194  222 try {
195  222 return JDBCType.valueOf(sqlType);
196    } catch (IllegalArgumentException e) {
197  0 logger.warn("Unknown SQL type value: {}. Use OTHER as JDBCType.", sqlType);
198  0 return JDBCType.OTHER;
199    }
200    }
201   
202    private final static Set<String> RESERVED_KEYWORDS = Set.of(
203    "SELECT","FROM","WHERE","ORDER","GROUP","USER","TABLE","INDEX","KEY",
204    "DATE","TIME","YEAR","NAME","TYPE","VALUE","STATUS","DESC","ASC",
205    "COUNT","SUM","AVG","MIN","MAX","LEVEL","TOP","IDENTITY","WITH"
206    );
207    private final static Pattern IDENTIFIER_PATTERN = Pattern.compile(".*[-\\s].*");
208   
209    /**
210    * Only quote the identifier when it is a reserved keyword or contains special characters.
211    */
 
212  901 toggle private static String quoteIdentifier(String identifier, String quote)
213    {
214  901 if (
215  901 !RESERVED_KEYWORDS.contains(identifier.toUpperCase()) &&
216    !IDENTIFIER_PATTERN.matcher(identifier).matches() &&
217    !identifier.contains(quote)
218    ) {
219  865 return identifier;
220    }
221   
222  36 return quote + identifier.replace(quote, quote + quote) + quote;
223    }
224    }