001package org.itsallcode.jdbc.metadata; 002 003import java.sql.*; 004import java.util.Arrays; 005 006/** 007 * Description of a column. 008 * 009 * @param tableCatalog table catalog (may be {@code null}) 010 * @param tableSchema table schema (may be {@code null}) 011 * @param tableName table name 012 * @param columnName column name 013 * @param dataType SQL type 014 * @param typeName Data source dependent type name, for a UDT the type 015 * name is fully qualified 016 * @param columnSize column size. 017 * @param decimalDigits the number of fractional digits. {@code null} is 018 * returned for data types where DECIMAL_DIGITS is not 019 * applicable. 020 * @param numPrecisionRadix Radix (typically either 10 or 2) 021 * @param nullable is {@code NULL} allowed. 022 * @param remarks comment describing column (may be {@code null}) 023 * @param columnDef default value for the column, which should be 024 * interpreted as a string when the value is enclosed 025 * in single quotes (may be {@code null}) 026 * @param charOctetLength for char types the maximum number of bytes in the 027 * column 028 * @param ordinalPosition index of column in table (starting at 1) 029 * @param isNullable ISO rules are used to determine the nullability for 030 * a column. 031 * @param scopeCatalog catalog of table that is the scope of a reference 032 * attribute ({@code null} if DATA_TYPE isn't REF) 033 * @param scopeSchema schema of table that is the scope of a reference 034 * attribute ({@code null} if the DATA_TYPE isn't REF) 035 * @param scopeTable table name that this the scope of a reference 036 * attribute ({@code null} if the DATA_TYPE isn't REF) 037 * @param sourceDataType source type of a distinct type or user-generated Ref 038 * type, SQL type from java.sql.Types ({@code null} if 039 * DATA_TYPE isn't DISTINCT or user-generated REF) 040 * @param isAutoIncrement Indicates whether this column is auto incremented 041 * @param isGeneratedColumn Indicates whether this is a generated column 042 * @see DatabaseMetaData#getColumns(String, String, String, String) 043 */ 044public record ColumnMetaData( 045 String tableCatalog, 046 String tableSchema, 047 String tableName, 048 String columnName, 049 JDBCType dataType, 050 String typeName, 051 int columnSize, 052 int decimalDigits, 053 int numPrecisionRadix, 054 Nullability nullable, 055 String remarks, 056 String columnDef, 057 int charOctetLength, 058 int ordinalPosition, 059 ISONullability isNullable, 060 String scopeCatalog, 061 String scopeSchema, 062 String scopeTable, 063 short sourceDataType, 064 AutoIncrement isAutoIncrement, 065 Generated isGeneratedColumn) { 066 067 static ColumnMetaData create(final ResultSet rs) throws SQLException { 068 return new ColumnMetaData( 069 rs.getString("TABLE_CAT"), 070 rs.getString("TABLE_SCHEM"), 071 rs.getString("TABLE_NAME"), 072 rs.getString("COLUMN_NAME"), 073 JDBCType.valueOf(rs.getInt("DATA_TYPE")), 074 rs.getString("TYPE_NAME"), 075 rs.getInt("COLUMN_SIZE"), 076 rs.getInt("DECIMAL_DIGITS"), 077 rs.getInt("NUM_PREC_RADIX"), 078 Nullability.valueOf(rs.getInt("NULLABLE")), 079 rs.getString("REMARKS"), 080 rs.getString("COLUMN_DEF"), 081 rs.getInt("CHAR_OCTET_LENGTH"), 082 rs.getInt("ORDINAL_POSITION"), 083 ISONullability.valueOfNullability(rs.getString("IS_NULLABLE")), 084 rs.getString("SCOPE_CATALOG"), 085 rs.getString("SCOPE_SCHEMA"), 086 rs.getString("SCOPE_TABLE"), 087 rs.getShort("SOURCE_DATA_TYPE"), 088 AutoIncrement.valueOfAutoIncrement(rs.getString("IS_AUTOINCREMENT")), 089 Generated.valueOfGenerated(rs.getString("IS_GENERATEDCOLUMN"))); 090 } 091 092 /** 093 * Column nullability. 094 */ 095 public enum Nullability { 096 /** Column might not allow {@code NULL} values. */ 097 NO_NULLS(DatabaseMetaData.columnNoNulls), 098 /** Column definitely allows {@code NULL} values. */ 099 NULLABLE(DatabaseMetaData.columnNullable), 100 /** nullability unknown */ 101 NULLABLE_UNKNOWN(DatabaseMetaData.columnNullableUnknown); 102 103 private final int value; 104 105 Nullability(final int value) { 106 this.value = value; 107 } 108 109 static Nullability valueOf(final int value) { 110 return Arrays.stream(Nullability.values()) 111 .filter(n -> n.value == value) 112 .findFirst() 113 .orElseThrow(() -> new IllegalArgumentException( 114 "Unknown value %d for nullability".formatted(value))); 115 } 116 } 117 118 /** 119 * Column ISO nullability. 120 */ 121 public enum ISONullability { 122 /** Column can include NULLs. */ 123 NO_NULLS("YES"), 124 /** Column cannot include NULLs. */ 125 NULLABLE("NO"), 126 /** Nullability for the column is unknown */ 127 NULLABLE_UNKNOWN(""); 128 129 private final String value; 130 131 ISONullability(final String value) { 132 this.value = value; 133 } 134 135 static ISONullability valueOfNullability(final String value) { 136 return Arrays.stream(ISONullability.values()) 137 .filter(n -> n.value.equals(value)) 138 .findFirst() 139 .orElseThrow(() -> new IllegalArgumentException( 140 "Unknown value '%s' for ISO nullability".formatted(value))); 141 } 142 } 143 144 /** 145 * Indicates whether a column is auto incremented. 146 */ 147 public enum AutoIncrement { 148 /** Column is auto incremented. */ 149 AUTO_INCREMENT("YES"), 150 /** Column is not auto incremented. */ 151 NO_AUTO_INCREMENT("NO"), 152 /** It cannot be determined whether the column is auto incremented. */ 153 UNKNOWN(""); 154 155 private final String value; 156 157 AutoIncrement(final String value) { 158 this.value = value; 159 } 160 161 static AutoIncrement valueOfAutoIncrement(final String value) { 162 return Arrays.stream(AutoIncrement.values()) 163 .filter(n -> n.value.equals(value)) 164 .findFirst() 165 .orElseThrow(() -> new IllegalArgumentException( 166 "Unknown value '%s' for auto increment".formatted(value))); 167 } 168 } 169 170 /** 171 * Indicates whether this is a generated column. 172 */ 173 public enum Generated { 174 /** This a generated column. */ 175 GENERATED("YES"), 176 /** This not a generated column. */ 177 NOT_GENERATED("NO"), 178 /** It cannot be determined whether this is a generated column. */ 179 UNKNOWN(""); 180 181 private final String value; 182 183 Generated(final String value) { 184 this.value = value; 185 } 186 187 static Generated valueOfGenerated(final String value) { 188 return Arrays.stream(Generated.values()) 189 .filter(n -> n.value.equals(value)) 190 .findFirst() 191 .orElseThrow(() -> new IllegalArgumentException( 192 "Unknown value '%s' for column generated".formatted(value))); 193 } 194 } 195}