/** * Reads the DatabaseMetaData for the details of this table including primary keys and indexes. * * @param metaData the database meta data */ void read(DatabaseMetaData metaData) throws SQLException { ResultSet rs = null; // primary keys try { rs = metaData.getPrimaryKeys(null, schema, table); while (rs.next()) { String c = rs.getString("COLUMN_NAME"); primaryKeys.add(c); } closeSilently(rs); // indexes rs = metaData.getIndexInfo(null, schema, table, false, true); indexes = Utils.newHashMap(); while (rs.next()) { IndexInspector info = new IndexInspector(rs); if (info.type.equals(IndexType.UNIQUE)) { String name = info.name.toLowerCase(); if (name.startsWith("primary") || name.startsWith("sys_idx_sys_pk") || name.startsWith("sql") || name.endsWith("_pkey")) { // skip primary key indexes continue; } } if (indexes.containsKey(info.name)) { indexes.get(info.name).addColumn(rs); } else { indexes.put(info.name, info); } } closeSilently(rs); // columns rs = metaData.getColumns(null, schema, table, null); columns = Utils.newHashMap(); while (rs.next()) { ColumnInspector col = new ColumnInspector(); col.name = rs.getString("COLUMN_NAME"); col.type = rs.getString("TYPE_NAME"); col.clazz = ModelUtils.getClassForSqlType(col.type, dateTimeClass); col.size = rs.getInt("COLUMN_SIZE"); col.nullable = rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable; try { Object autoIncrement = rs.getObject("IS_AUTOINCREMENT"); if (autoIncrement instanceof Boolean) { col.isAutoIncrement = (Boolean) autoIncrement; } else if (autoIncrement instanceof String) { String val = autoIncrement.toString().toLowerCase(); col.isAutoIncrement = val.equals("true") | val.equals("yes"); } else if (autoIncrement instanceof Number) { Number n = (Number) autoIncrement; col.isAutoIncrement = n.intValue() > 0; } } catch (SQLException s) { // throw s; } if (primaryKeys.size() == 1) { if (col.name.equalsIgnoreCase(primaryKeys.get(0))) { col.isPrimaryKey = true; } } if (!col.isAutoIncrement) { col.defaultValue = rs.getString("COLUMN_DEF"); } columns.put(col.name.toLowerCase(), col); } } finally { closeSilently(rs); } }
private StatementBuilder generateColumn( Set<String> imports, ColumnInspector col, boolean trimStrings) { StatementBuilder sb = new StatementBuilder(); Class<?> clazz = col.clazz; String column = ModelUtils.convertColumnToFieldName(col.name.toLowerCase()); sb.append('\t'); if (clazz == null) { // unsupported type clazz = Object.class; sb.append("// unsupported type " + col.type); } else { // Imports // don't import primitives, java.lang classes, or byte [] if (clazz.getPackage() == null) { } else if (clazz.getPackage().getName().equals("java.lang")) { } else if (clazz.equals(byte[].class)) { } else { imports.add(clazz.getCanonicalName()); } // @IQColumn sb.append('@').append(IQColumn.class.getSimpleName()); // IQColumn annotation parameters AnnotationBuilder ap = new AnnotationBuilder(); // IQColumn.name if (!col.name.equalsIgnoreCase(column)) { ap.addParameter("name", col.name); } // IQColumn.primaryKey // composite primary keys are annotated on the table if (col.isPrimaryKey && primaryKeys.size() == 1) { ap.addParameter("primaryKey=true"); } // IQColumn.length if ((clazz == String.class) && (col.size > 0) && (col.size < Integer.MAX_VALUE)) { ap.addParameter("length", col.size); // IQColumn.trim if (trimStrings) { ap.addParameter("trim=true"); } } else { // IQColumn.AutoIncrement if (col.isAutoIncrement) { ap.addParameter("autoIncrement=true"); } } // IQColumn.nullable if (!col.nullable) { ap.addParameter("nullable=false"); } // IQColumn.defaultValue if (!isNullOrEmpty(col.defaultValue)) { ap.addParameter("defaultValue=\"" + col.defaultValue + "\""); } // add leading and trailing () if (ap.length() > 0) { ap.insert(0, '('); ap.append(')'); } sb.append(ap); } sb.append(eol); // variable declaration sb.append("\t" + "public "); sb.append(clazz.getSimpleName()); sb.append(' '); sb.append(column); sb.append(';'); sb.append(eol).append(eol); return sb; }
/** * Validates a column against the model's field definition. Checks for existence, supported type, * type mapping, default value, defined lengths, primary key, autoincrement. */ private void validate( List<ValidationRemark> remarks, FieldDefinition fieldDef, boolean throwError) { // unknown field if (!columns.containsKey(fieldDef.columnName.toLowerCase())) { // unknown column mapping remarks.add(error(table, fieldDef, "Does not exist in database!").throwError(throwError)); return; } ColumnInspector col = columns.get(fieldDef.columnName.toLowerCase()); Class<?> fieldClass = fieldDef.field.getType(); Class<?> jdbcClass = ModelUtils.getClassForSqlType(col.type, dateTimeClass); // supported type check // iciql maps to VARCHAR for unsupported types. if (fieldDef.dataType.equals("VARCHAR") && (fieldClass != String.class)) { remarks.add( error( table, fieldDef, "iciql does not currently implement support for " + fieldClass.getName()) .throwError(throwError)); } // number types if (!fieldClass.equals(jdbcClass)) { if (Number.class.isAssignableFrom(fieldClass)) { remarks.add( warn( table, col, format( "Precision mismatch: ModelObject={0}, ColumnObject={1}", fieldClass.getSimpleName(), jdbcClass.getSimpleName()))); } else { if (!Date.class.isAssignableFrom(jdbcClass)) { remarks.add( warn( table, col, format( "Object Mismatch: ModelObject={0}, ColumnObject={1}", fieldClass.getSimpleName(), jdbcClass.getSimpleName()))); } } } // string types if (fieldClass == String.class) { if ((fieldDef.length != col.size) && (col.size < Integer.MAX_VALUE)) { remarks.add( warn( table, col, format( "{0}.length={1}, ColumnMaxLength={2}", IQColumn.class.getSimpleName(), fieldDef.length, col.size))); } if (fieldDef.length > 0 && !fieldDef.trim) { remarks.add( consider( table, col, format( "{0}.trim=true will prevent IciqlExceptions on" + " INSERT or UPDATE, but will clip data!", IQColumn.class.getSimpleName()))); } } // numeric autoIncrement if (fieldDef.isAutoIncrement != col.isAutoIncrement) { remarks.add( warn( table, col, format( "{0}.autoIncrement={1}" + " while Column autoIncrement={2}", IQColumn.class.getSimpleName(), fieldDef.isAutoIncrement, col.isAutoIncrement))); } // default value if (!col.isAutoIncrement && !col.isPrimaryKey) { String defaultValue = null; if (fieldDef.defaultValue != null && fieldDef.defaultValue instanceof String) { defaultValue = fieldDef.defaultValue.toString(); } // check Model.defaultValue format if (!ModelUtils.isProperlyFormattedDefaultValue(defaultValue)) { remarks.add( error( table, col, format( "{0}.defaultValue=\"{1}\"" + " is improperly formatted!", IQColumn.class.getSimpleName(), defaultValue)) .throwError(throwError)); // next field return; } // compare Model.defaultValue to Column.defaultValue if (isNullOrEmpty(defaultValue) && !isNullOrEmpty(col.defaultValue)) { // Model.defaultValue is NULL, Column.defaultValue is NOT NULL remarks.add( warn( table, col, format( "{0}.defaultValue=\"\"" + " while column default=\"{1}\"", IQColumn.class.getSimpleName(), col.defaultValue))); } else if (!isNullOrEmpty(defaultValue) && isNullOrEmpty(col.defaultValue)) { // Column.defaultValue is NULL, Model.defaultValue is NOT NULL remarks.add( warn( table, col, format( "{0}.defaultValue=\"{1}\"" + " while column default=\"\"", IQColumn.class.getSimpleName(), defaultValue))); } else if (!isNullOrEmpty(defaultValue) && !isNullOrEmpty(col.defaultValue)) { if (!defaultValue.equals(col.defaultValue)) { // Model.defaultValue != Column.defaultValue remarks.add( warn( table, col, format( "{0}.defaultValue=\"{1}\"" + " while column default=\"{2}\"", IQColumn.class.getSimpleName(), defaultValue, col.defaultValue))); } } // sanity check Model.defaultValue literal value if (!ModelUtils.isValidDefaultValue(fieldDef.field.getType(), defaultValue)) { remarks.add( error( table, col, format( "{0}.defaultValue=\"{1}\" is invalid!", IQColumn.class.getSimpleName(), defaultValue))); } } }
/** * Generates a model (class definition) from this table. The model includes indexes, primary keys, * default values, lengths, and nullables. information. * * <p>The caller may optionally set a destination package name, whether or not to include the * schema name (setting schema can be a problem when using the model between databases), and if to * automatically trim strings for those that have a maximum length. * * <p> * * @param packageName * @param annotateSchema * @param trimStrings * @return a complete model (class definition) for this table as a string */ String generateModel(String packageName, boolean annotateSchema, boolean trimStrings) { // import statements Set<String> imports = Utils.newHashSet(); imports.add(Serializable.class.getCanonicalName()); imports.add(IQSchema.class.getCanonicalName()); imports.add(IQTable.class.getCanonicalName()); imports.add(IQIndexes.class.getCanonicalName()); imports.add(IQIndex.class.getCanonicalName()); imports.add(IQColumn.class.getCanonicalName()); imports.add(IndexType.class.getCanonicalName()); // fields StringBuilder fields = new StringBuilder(); List<ColumnInspector> sortedColumns = Utils.newArrayList(columns.values()); Collections.sort(sortedColumns); for (ColumnInspector col : sortedColumns) { fields.append(generateColumn(imports, col, trimStrings)); } // build complete class definition StringBuilder model = new StringBuilder(); if (!isNullOrEmpty(packageName)) { // package model.append("package " + packageName + ";"); model.append(eol).append(eol); } // imports List<String> sortedImports = new ArrayList<String>(imports); Collections.sort(sortedImports); for (String imp : sortedImports) { model.append("import ").append(imp).append(';').append(eol); } model.append(eol); // @IQSchema if (annotateSchema && !isNullOrEmpty(schema)) { model.append('@').append(IQSchema.class.getSimpleName()); model.append('('); AnnotationBuilder ap = new AnnotationBuilder(); ap.addParameter(null, schema); model.append(ap); model.append(')').append(eol); } // @IQTable model.append('@').append(IQTable.class.getSimpleName()); model.append('('); // IQTable annotation parameters AnnotationBuilder ap = new AnnotationBuilder(); ap.addParameter("name", table); if (primaryKeys.size() > 1) { ap.addParameter("primaryKey", primaryKeys); } // finish @IQTable annotation model.append(ap); model.append(')').append(eol); // @IQIndexes // @IQIndex String indexAnnotations = generateIndexAnnotations(); if (!StringUtils.isNullOrEmpty(indexAnnotations)) { model.append(indexAnnotations); } // class declaration String clazzName = ModelUtils.convertTableToClassName(table); model.append(format("public class {0} implements Serializable '{'", clazzName)).append(eol); model.append(eol); model.append("\tprivate static final long serialVersionUID = 1L;").append(eol); model.append(eol); // field declarations model.append(fields); // default constructor model.append("\t" + "public ").append(clazzName).append("() {").append(eol); model.append("\t}").append(eol); // end of class body model.append('}'); model.trimToSize(); return model.toString(); }