1. Project Clover database Wed Nov 12 2025 05:07:35 UTC
  2. Package guru.mikelue.foxglove.jdbc

File MetaUtils.java

 

Coverage histogram

../../../../img/srcFileCovDistChart9.png
53% 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 64 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  91 toggle static List<ColumnMeta> filterColumns(
28    List<ColumnMeta> columnMetaList,
29    DataSettingInfo dataSetting, JdbcTableFacet tableFacet
30    ) {
31  91 var result = columnMetaList.stream()
32    .filter(
33    columnMeta -> {
34  1064 var inclusionMode = tableFacet.getColumnInclusion(columnMeta);
35   
36  1064 switch (inclusionMode) {
37  117 case Include:
38  117 return true;
39  41 case Exclude:
40  41 return false;
41  906 default:
42  906 break;
43    }
44   
45  906 return dataSetting.isAutoGenerating(columnMeta);
46    }
47    )
48    .toList();
49   
50  91 return unmodifiableList(result);
51    }
52   
 
53  109 toggle static List<ColumnMeta> getColumnMetaList(ResultSetMetaData rsMeta)
54    {
55  109 try {
56  109 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  101 toggle static String buildInsertSql(DatabaseMetaData dbMetaData, String tableName, List<ColumnMeta> columnMetaList)
72    {
73  101 String quote;
74   
75  101 try {
76  101 quote = dbMetaData.getIdentifierQuoteString();
77    } catch (SQLException e) {
78  0 throw new RuntimeJdbcException(e);
79    }
80   
81  101 var columnNames = columnMetaList.stream()
82    .map(columnMeta -> quoteIdentifier(columnMeta.name(), quote))
83    .toList();
84   
85  101 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  109 toggle private static List<ColumnMeta> fetchColumnMetaListImpl(ResultSetMetaData rsMeta)
94    throws SQLException
95    {
96  109 var columnCount = rsMeta.getColumnCount();
97  109 var metaOfColumns = new ArrayList<ColumnMeta>(columnCount);
98   
99  255 for (int index = 1; index <= columnCount; ++index) {
100  146 var columName = rsMeta.getColumnName(index);
101  146 var jdbcType = resolveJdbcType(rsMeta.getColumnType(index));
102  146 var typeName = rsMeta.getColumnTypeName(index);
103  146 var size = rsMeta.getPrecision(index);
104  146 var decimalDigits = rsMeta.getScale(index);
105   
106  146 var properties = EnumSet.noneOf(ColumnMeta.Property.class);
107  146 if (rsMeta.isNullable(index) != DatabaseMetaData.columnNoNulls) {
108  144 properties.add(NULLABLE);
109    }
110  146 if (rsMeta.isAutoIncrement(index)) {
111  0 properties.add(AUTO_INCREMENT);
112    }
113   
114  146 var newColumnMeta = new ColumnMeta(
115    columName, properties,
116    typeName, jdbcType,
117    size, decimalDigits
118    );
119   
120  146 logger.trace("Fetched column meta(ResultSet): {}", newColumnMeta);
121   
122  146 metaOfColumns.add(newColumnMeta);
123    }
124   
125  109 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 columName = rsOfColumnMeta.getString("COLUMN_NAME");
143  75 var jdbcType = resolveJdbcType(rsOfColumnMeta.getInt("DATA_TYPE"));
144  75 var typeName = rsOfColumnMeta.getString("TYPE_NAME");
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    columName, 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  221 toggle private static JDBCType resolveJdbcType(int sqlType)
193    {
194  221 try {
195  221 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  889 toggle private static String quoteIdentifier(String identifier, String quote)
213    {
214  889 if (
215  889 !RESERVED_KEYWORDS.contains(identifier.toUpperCase()) &&
216    !IDENTIFIER_PATTERN.matcher(identifier).matches() &&
217    !identifier.contains(quote)
218    ) {
219  853 return identifier;
220    }
221   
222  36 return quote + identifier.replace(quote, quote + quote) + quote;
223    }
224    }