// Path: src/main/java/net/nextpulse/jadmin/ColumnDefinition.java
// public class ColumnDefinition {
// 
//   /**
//    * Column name, case sensitive
//    */
//   private String name;
//   /**
//    * JAdmin-type of this column.
//    */
//   private ColumnType type;
// 
//   /**
//    * True if this column is part of the key identifying a row of the datasource containing this column.
//    */
//   private boolean keyColumn;
//   private boolean editable;
//   private List<InputValidationRule> validationRules = new ArrayList<>();
//   private InputTransformer inputTransformer;
//   private ColumnValueTransformer columnValueTransformer;
// 
//   public ColumnDefinition() {
//   }
// 
//   public ColumnDefinition(String name, ColumnType type) {
//     this.name = name;
//     this.type = type;
//   }
// 
//   public ColumnDefinition(String name, ColumnType type, boolean keyColumn, boolean editable) {
//     this.name = name;
//     this.type = type;
//     this.keyColumn = keyColumn;
//     this.editable = editable;
//   }
// 
//   public String getName() {
//     return name;
//   }
// 
//   public ColumnDefinition setName(String name) {
//     this.name = name;
//     return this;
//   }
// 
//   public ColumnType getType() {
//     return type;
//   }
// 
//   public void setType(ColumnType type) {
//     this.type = type;
//   }
// 
//   public boolean isKeyColumn() {
//     return keyColumn;
//   }
// 
//   public ColumnDefinition setKeyColumn(boolean keyColumn) {
//     this.keyColumn = keyColumn;
//     return this;
//   }
// 
//   public boolean isEditable() {
//     return editable;
//   }
// 
//   public ColumnDefinition setEditable(boolean editable) {
//     this.editable = editable;
//     return this;
//   }
// 
//   /**
//    * Adds the provided input validation rule to the list of rules.
//    *
//    * @param inputValidationRules  rule(s) that should be active for this column
//    */
//   public ColumnDefinition addValidationRules(InputValidationRule... inputValidationRules) {
//     validationRules.addAll(Arrays.asList(inputValidationRules));
//     return this;
//   }
//   
//   /**
//    * @return the configured validation rules
//    */
//   public List<InputValidationRule> getValidationRules() {
//     return validationRules;
//   }
//   
//   public InputTransformer getInputTransformer() {
//     return inputTransformer;
//   }
//   
//   public ColumnDefinition setInputTransformer(InputTransformer inputTransformer) {
//     this.inputTransformer = inputTransformer;
//     return this;
//   }
// 
//   public ColumnValueTransformer getColumnValueTransformer() {
//     return columnValueTransformer;
//   }
// 
//   public ColumnDefinition setColumnValueTransformer(ColumnValueTransformer columnValueTransformer) {
//     this.columnValueTransformer = columnValueTransformer;
//     return this;
//   }
// }
// 
// Path: src/main/java/net/nextpulse/jadmin/ColumnType.java
// public enum ColumnType {
//   string,
//   text,
//   integer,
//   bool,
//   datetime;
// 
// }
// 
// Path: src/main/java/net/nextpulse/jadmin/dao/DataAccessException.java
// public class DataAccessException extends Exception {
//   public DataAccessException(Exception cause) {
//     super(cause);
//   }
// 
//   public DataAccessException(String message) {
//     super(message);
//   }
// 
//   public DataAccessException(String message, Exception cause) {
//     super(message, cause);
//   }
// }


import net.nextpulse.jadmin.ColumnDefinition;
import net.nextpulse.jadmin.ColumnType;
import net.nextpulse.jadmin.dao.DataAccessException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
      logger.trace("Retrieving key columns for {}", tableName);
      keyColumns = getColumnDefinitions().stream().filter(ColumnDefinition::isKeyColumn).collect(Collectors.toList());
    }
    return keyColumns;
  }

  @Override
  public List<ColumnDefinition> getColumnDefinitions() throws DataAccessException {
    logger.trace("Retrieving column definitions for {}", tableName);

    // only retrieve the definitions if we haven't already done so
    if(columnDefinitions == null) {
      columnDefinitions = new ArrayList<>();
      Set<String> primaryKeys = new HashSet<>();

      try(Connection conn = dataSource.getConnection()) {
        // find the primary key columns
        ResultSet primaryKeyResultSet = conn.getMetaData().getPrimaryKeys(conn.getCatalog(), conn.getSchema(), tableName.toLowerCase());
        while(primaryKeyResultSet.next()) {
          String columnName = primaryKeyResultSet.getString(COLUMN_NAME);
          primaryKeys.add(columnName);
        }

        // iterate over all columns and mark the primary key columns as such
        ResultSet rs = conn.getMetaData().getColumns(conn.getCatalog(), conn.getSchema(), tableName.toLowerCase(), "%");
        while(rs.next()) {
          ColumnDefinition columnDefinition = new ColumnDefinition();
          String columnName = rs.getString(COLUMN_NAME);
          String typeName = rs.getString(TYPE_NAME).toLowerCase();
          columnDefinition.setName(columnName);
// Path: src/main/java/net/nextpulse/jadmin/ColumnDefinition.java
// public class ColumnDefinition {
// 
//   /**
//    * Column name, case sensitive
//    */
//   private String name;
//   /**
//    * JAdmin-type of this column.
//    */
//   private ColumnType type;
// 
//   /**
//    * True if this column is part of the key identifying a row of the datasource containing this column.
//    */
//   private boolean keyColumn;
//   private boolean editable;
//   private List<InputValidationRule> validationRules = new ArrayList<>();
//   private InputTransformer inputTransformer;
//   private ColumnValueTransformer columnValueTransformer;
// 
//   public ColumnDefinition() {
//   }
// 
//   public ColumnDefinition(String name, ColumnType type) {
//     this.name = name;
//     this.type = type;
//   }
// 
//   public ColumnDefinition(String name, ColumnType type, boolean keyColumn, boolean editable) {
//     this.name = name;
//     this.type = type;
//     this.keyColumn = keyColumn;
//     this.editable = editable;
//   }
// 
//   public String getName() {
//     return name;
//   }
// 
//   public ColumnDefinition setName(String name) {
//     this.name = name;
//     return this;
//   }
// 
//   public ColumnType getType() {
//     return type;
//   }
// 
//   public void setType(ColumnType type) {
//     this.type = type;
//   }
// 
//   public boolean isKeyColumn() {
//     return keyColumn;
//   }
// 
//   public ColumnDefinition setKeyColumn(boolean keyColumn) {
//     this.keyColumn = keyColumn;
//     return this;
//   }
// 
//   public boolean isEditable() {
//     return editable;
//   }
// 
//   public ColumnDefinition setEditable(boolean editable) {
//     this.editable = editable;
//     return this;
//   }
// 
//   /**
//    * Adds the provided input validation rule to the list of rules.
//    *
//    * @param inputValidationRules  rule(s) that should be active for this column
//    */
//   public ColumnDefinition addValidationRules(InputValidationRule... inputValidationRules) {
//     validationRules.addAll(Arrays.asList(inputValidationRules));
//     return this;
//   }
//   
//   /**
//    * @return the configured validation rules
//    */
//   public List<InputValidationRule> getValidationRules() {
//     return validationRules;
//   }
//   
//   public InputTransformer getInputTransformer() {
//     return inputTransformer;
//   }
//   
//   public ColumnDefinition setInputTransformer(InputTransformer inputTransformer) {
//     this.inputTransformer = inputTransformer;
//     return this;
//   }
// 
//   public ColumnValueTransformer getColumnValueTransformer() {
//     return columnValueTransformer;
//   }
// 
//   public ColumnDefinition setColumnValueTransformer(ColumnValueTransformer columnValueTransformer) {
//     this.columnValueTransformer = columnValueTransformer;
//     return this;
//   }
// }
// 
// Path: src/main/java/net/nextpulse/jadmin/ColumnType.java
// public enum ColumnType {
//   string,
//   text,
//   integer,
//   bool,
//   datetime;
// 
// }
// 
// Path: src/main/java/net/nextpulse/jadmin/dao/DataAccessException.java
// public class DataAccessException extends Exception {
//   public DataAccessException(Exception cause) {
//     super(cause);
//   }
// 
//   public DataAccessException(String message) {
//     super(message);
//   }
// 
//   public DataAccessException(String message, Exception cause) {
//     super(message, cause);
//   }
// }


// Path: src/main/java/net/nextpulse/jadmin/schema/GenericSQLSchemaProvider.java
import net.nextpulse.jadmin.ColumnDefinition;
import net.nextpulse.jadmin.ColumnType;
import net.nextpulse.jadmin.dao.DataAccessException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

      logger.trace("Retrieving key columns for {}", tableName);
      keyColumns = getColumnDefinitions().stream().filter(ColumnDefinition::isKeyColumn).collect(Collectors.toList());
    }
    return keyColumns;
  }

  @Override
  public List<ColumnDefinition> getColumnDefinitions() throws DataAccessException {
    logger.trace("Retrieving column definitions for {}", tableName);

    // only retrieve the definitions if we haven't already done so
    if(columnDefinitions == null) {
      columnDefinitions = new ArrayList<>();
      Set<String> primaryKeys = new HashSet<>();

      try(Connection conn = dataSource.getConnection()) {
        // find the primary key columns
        ResultSet primaryKeyResultSet = conn.getMetaData().getPrimaryKeys(conn.getCatalog(), conn.getSchema(), tableName.toLowerCase());
        while(primaryKeyResultSet.next()) {
          String columnName = primaryKeyResultSet.getString(COLUMN_NAME);
          primaryKeys.add(columnName);
        }

        // iterate over all columns and mark the primary key columns as such
        ResultSet rs = conn.getMetaData().getColumns(conn.getCatalog(), conn.getSchema(), tableName.toLowerCase(), "%");
        while(rs.next()) {
          ColumnDefinition columnDefinition = new ColumnDefinition();
          String columnName = rs.getString(COLUMN_NAME);
          String typeName = rs.getString(TYPE_NAME).toLowerCase();
          columnDefinition.setName(columnName);
          ColumnType columnType = sqlTypeToColumnType(typeName);