Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  package de.zalando.typemapper.core.db;
  
  import java.sql.ResultSet;
  
  import java.util.Arrays;
  import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 
 
 public class DbTypeRegister {
 
     private static final Logger LOG = LoggerFactory.getLogger(DbTypeRegister.class);
 
     // optimize for concurrent reads, since we should have a small amount of writes.
     // Use the copy on write pattern concurrency pattern.
     // Use volatile variable to guaranty that any thread that reads the field will see the most recently written value
     private static volatile Map<StringDbTypeRegisterregisters = ImmutableMap.of();
 
     private static final Object typeIdToFQNLock = new Object();
     private static final Object typeRegisterLock = new Object();
 
     private final Map<StringDbTypetypeByName;
     private final List<StringsearchPath;
 
     private final Map<StringStringtypeFQN;
 
     private final Map<IntegerStringtypeIdToFQN;
 
     public DbTypeRegister(final Connection connectionthrows SQLException {
         PreparedStatement statement = null;
         ResultSet resultSet = null;
         try {
              = getSearchPath(connection);
              = new HashMap<StringDbType>();
 
              = new ConcurrentHashMap<>();
 
             final HashMap<StringList<String>> typeNameToFQN = new HashMap<StringList<String>>();
 
             //J-
             statement = connection.prepareStatement(
                     "select tn.nspname as type_schema, " +
                     "t.typname as type_name, " +
                     "t.oid as type_id, " +
                     "t.typtype as type_type, " +
                     "a.attname as att_name, " +
                     "(CASE WHEN ((t2.typelem <> (0)::oid) AND (t2.typlen = -1)) " +
                     "THEN 'ARRAY'::text  WHEN (tn2.nspname = 'pg_catalog'::name) " +
                     "THEN format_type(a.atttypid, NULL::integer) " +
                     "ELSE 'USER-DEFINED'::text END) as att_type, " +
                     "t2.typname, " +
                     "t2.oid, " +
                     "a.attnum as att_position, " +
                     "t.typelem <> 0 AND t.typlen = (-1) as is_array, " +
                     "t.typelem " +
                     "from pg_type as t " +
                     "join pg_namespace as tn on t.typnamespace = tn.oid " +
                     "left join pg_class c on t.typrelid = c.oid "+
                     "left join pg_attribute as a on a.attrelid = t.typrelid and a.attnum > 0 and not a.attisdropped " +
                     "left join pg_type as t2 on a.atttypid = t2.oid " +
                     "left join pg_namespace as tn2 on t2.typnamespace = tn2.oid " +
                     "where (t.typtype = 'e' OR (t.typtype = 'c' AND c.relkind = 'c'::char) OR (t.typelem <> 0 AND  t.typlen = (-1) AND t.typtype = 'b'))" +
                     "and tn.nspname not in ( 'pg_catalog', 'pg_toast', 'information_schema' ) " +
                     "and t.typowner > 0 " +
                     "order by is_array, t.typowner, type_schema, type_name, att_position");
             //J+
             resultSet = statement.executeQuery();
             while (resultSet.next()) {
                 int i = 1;
                 final String typeSchema = resultSet.getString(i++);
                 final String typeName = resultSet.getString(i++);
                 final int typeId = resultSet.getInt(i++);
                 final String typeType = resultSet.getString(i++);
                 final String fieldName = resultSet.getString(i++);
                 final String fieldType = resultSet.getString(i++);
                 final String fieldTypeName = resultSet.getString(i++);
                 final int fieldTypeId = resultSet.getInt(i++);
                 final int fieldPosition = resultSet.getInt(i++);
                 final boolean isArray = resultSet.getBoolean(i++);
                 final int typeElem = resultSet.getInt(i++);
 
                 addField(typeSchematypeNametypeIdfieldNamefieldPositionfieldTypefieldTypeNamefieldTypeId,
                     typeTypeisArraytypeNameToFQNtypeElem);
             }
 
              = buildTypeFQN(typeNameToFQN);
         } finally {
            if (resultSet != null) {
                resultSet.close();
            }
            if (statement != null) {
                statement.close();
            }
        }
    }
    public static List<StringgetSearchPath(final Connection connectionthrows SQLException {
        final ResultSet searchPathResult = connection.createStatement().executeQuery("show search_path;");
        searchPathResult.next();
        final String searchPathStr = searchPathResult.getString(1);
        return Arrays.asList(searchPathStr.split("\\s*,\\s*"));
    }
    public List<StringgetSearchPath() {
        return ;
    }
    public Map<StringDbTypegetTypes() {
        return ;
    }
    private void addField(final String typeSchemafinal String typeNamefinal int typeIdfinal String fieldName,
            final int fieldPositionfinal String fieldTypefinal String fieldTypeNamefinal int fieldTypeId,
            final String typeTypefinal boolean isArrayfinal Map<StringList<String>> typeNameToFQN,
            final int typeElem) {
        if (isArray) {
            // if it's an array, we should resolve the element type
            String typeFQN = .get(typeId);
            if (typeFQN == null) {
                // non arrays are processed first
                typeFQN = .get(typeElem);
                if (typeFQN != null) {
                    .put(typeIdtypeFQN);
                }
            }
        } else {
            final String typeFQN = getTypeIdentifier(typeSchematypeName);
            DbType type = .get(typeFQN);
            if (type == null) {
                type = new DbType(typeSchematypeNametypeId);
                addType(typetypeNameToFQN);
            }
            // do we have an enum?
            if ("e".equals(typeType)) {
                type.addField(new DbTypeField(typeName, 1, "enum""enum"fieldTypeId));
            } else {
                if (null != fieldName) {
                    type.addField(new DbTypeField(fieldNamefieldPositionfieldTypefieldTypeNamefieldTypeId));
                } else {
                    .warn("{}.{} has no attributes!"typeSchematypeName);
                }
            }
        }
    }
    private void addType(final DbType typefinal Map<StringList<String>> typeNameToFQN) {
        final String id = getTypeIdentifier(type.getSchema(), type.getName());
        .put(idtype);
        .put(type.getId(), id);
        List<Stringlist = typeNameToFQN.get(type.getName());
        if (list == null) {
            list = new LinkedList<String>();
            typeNameToFQN.put(type.getName(), list);
        }
        list.add(id);
    }
    private Map<StringStringbuildTypeFQN(final Map<StringList<String>> typeNameToFQN) {
        final ImmutableMap.Builder<StringStringresult = ImmutableMap.builder();
        for (final Entry<StringList<String>> entry : typeNameToFQN.entrySet()) {
            final List<Stringtypes = entry.getValue();
            // This should be improved. If the sproc specifies the schema of one type, we might end up using the wrong
            // type (with the same name and different schema) because we "resolve" the conflict using the search path.
            // Main problems:
            // 1 - One might hard code the schema on the sproc and SP might use the wrong type
            // 2 - We might have the same jdbc URL with different search paths
            final String fqn = types.size() == 1 ? types.get(0) : SearchPathSchemaFilter.filter(types);
            if (fqn != null) {
                // if it's null, we shouldn't put it on the map
                result.put(entry.getKey(), fqn);
            }
        }
        return result.build();
    }
    public static String getTypeIdentifier(final String typeSchemafinal String typeName) {
        return typeSchema + "." + typeName;
    }
    public static DbType getDbType(final String namefinal Connection connectionthrows SQLException {
        final DbTypeRegister register = getRegistry(connection);
        // fqName concept is wrong. we should know not only the name, but the schema as well. This should be reworked.
        final String fqName = register.typeFQN.get(name);
        return fqName == null ? null : register.typeByName.get(fqName);
    }
    public static DbType getDbType(final int idfinal Connection connectionthrows SQLException {
        final DbTypeRegister register = getRegistry(connection);
        DbType type = null;
        String typeFQN = register.typeIdToFQN.get(id);
        if (typeFQN == null) {
            boolean load = false;
            // only query once
            synchronized () {
                typeFQN = register.typeIdToFQN.get(id);
                if (typeFQN == null) {
                    load = true;
                    // try to load
                    final String sql =                                                   //
                        "          SELECT tn.nspname AS type_schema ,"                   //
                            + "           (CASE"                                         //
                            + "                WHEN t2.typname IS NULL"                  //
                            + "                THEN t.typname"                           //
                            + "                ELSE t2.typname"                          //
                            + "            END) AS type_name"                            //
                            + "      FROM pg_type AS t"                                  //
                            + "      JOIN pg_namespace AS tn ON t.typnamespace = tn.oid" //
                            + " LEFT JOIN pg_type AS t2 ON t.typelem <> 0 AND t.typlen = (-1) AND t.typelem = t2.oid"
                            + "     WHERE t.oid = ?";
                    ResultSet res = null;
                    PreparedStatement ps = null;
                    try {
                        ps = connection.prepareStatement(sql);
                        ps.setInt(1, id);
                        res = ps.executeQuery();
                        if (res.next()) {
                            typeFQN = getTypeIdentifier(res.getString(1), res.getString(2));
                        }
                    } finally {
                        if (res != null) {
                            res.close();
                        }
                        if (ps != null) {
                            ps.close();
                        }
                    }
                    if (typeFQN != null) {
                        register.typeIdToFQN.put(idtypeFQN);
                    }
                }
            }
            if (load) {
                .info("Type with oid {} not found in local cache."id);
            }
        }
        if (typeFQN != null) {
            type = register.typeByName.get(typeFQN);
        }
        return type;
    }
    // copy on write design pattern. Slow on writes, but really fast on reads. This pattern fits our
    // situation because we are not expecting so many different jdbc urls.
    public static DbTypeRegister getRegistry(final Connection connectionthrows SQLException {
        // if connection URL is null we can't proceed. fail fast
        Preconditions.checkNotNull(connection);
        final String connectionURL = connection.getMetaData().getURL();
        Preconditions.checkNotNull(connection.getMetaData().getURL(), "connection URL is null");
        // check if we have the value in memory
        DbTypeRegister cachedRegisters = .get(connectionURL);
        // First check (no locking)
        if (cachedRegisters == null) {
            synchronized () {
                cachedRegisters = .get(connectionURL);
                // Second check (with locking)
                if (cachedRegisters == null) {
                    cachedRegisters = new DbTypeRegister(connection);
                    // read optimization. We are not expecting so many different DBMS URLs, so read is really fast and
                    // the write is a little bit slower which is ok because most of the
                    // times we will use what we have in memory.  Copy all previous entries to the new immutable map.
                    // This soudnd't happen very often.
                     = ImmutableMap.<StringDbTypeRegister>builder().putAll()
                                            .put(connectionURLcachedRegisters).build();
                }
            }
        }
        return cachedRegisters;
    }
    public static void reInitRegister(final Connection connectionthrows SQLException {
         = ImmutableMap.of(connection.getMetaData().getURL(), new DbTypeRegister(connection));
    }
New to GrepCode? Check out our FAQ X