View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.shardingsphere.data.pipeline.postgresql.ddlgenerator;
19  
20  import org.apache.shardingsphere.data.pipeline.postgresql.util.PostgreSQLPipelineFreemarkerManager;
21  import org.postgresql.jdbc.PgArray;
22  
23  import java.sql.Connection;
24  import java.sql.SQLException;
25  import java.util.Collection;
26  import java.util.LinkedHashMap;
27  import java.util.LinkedList;
28  import java.util.Map;
29  
30  /**
31   * Index SQL generator for PostgreSQL.
32   */
33  public final class PostgreSQLIndexSQLGenerator extends AbstractPostgreSQLDDLAdapter {
34      
35      private static final Integer PG_INDEX_INCLUDE_VERSION = 11;
36      
37      public PostgreSQLIndexSQLGenerator(final Connection connection, final int majorVersion, final int minorVersion) {
38          super(connection, majorVersion, minorVersion);
39      }
40      
41      /**
42       * Generate create index SQL.
43       * 
44       * @param context context
45       * @return generated SQL
46       * @throws SQLException SQL exception
47       */
48      public String generate(final Map<String, Object> context) throws SQLException {
49          StringBuilder result = new StringBuilder();
50          Collection<Map<String, Object>> indexNodes = getIndexNodes(context);
51          for (Map<String, Object> each : indexNodes) {
52              if (each.containsKey("is_inherited") && (Boolean) each.get("is_inherited")) {
53                  continue;
54              }
55              result.append(getIndexSql(context, each));
56          }
57          return result.toString().trim();
58      }
59      
60      private Collection<Map<String, Object>> getIndexNodes(final Map<String, Object> context) {
61          Map<String, Object> param = new LinkedHashMap<>();
62          param.put("tid", context.get("tid"));
63          return executeByTemplate(param, "component/indexes/%s/nodes.ftl");
64      }
65      
66      private String getIndexSql(final Map<String, Object> context, final Map<String, Object> indexNode) throws SQLException {
67          Map<String, Object> indexData = getIndexData(context, indexNode);
68          appendColumnDetails(indexData, (Long) indexNode.get("oid"));
69          if (getMajorVersion() >= PG_INDEX_INCLUDE_VERSION) {
70              appendIncludeDetails(indexData, (Long) indexNode.get("oid"));
71          }
72          return doGenerateIndexSql(indexData);
73      }
74      
75      private String doGenerateIndexSql(final Map<String, Object> indexData) {
76          String result = PostgreSQLPipelineFreemarkerManager.getSQLByVersion(indexData, "component/indexes/%s/create.ftl", getMajorVersion(), getMinorVersion());
77          result += System.lineSeparator();
78          result += PostgreSQLPipelineFreemarkerManager.getSQLByVersion(indexData, "component/indexes/%s/alter.ftl", getMajorVersion(), getMinorVersion());
79          return result;
80      }
81      
82      private Map<String, Object> getIndexData(final Map<String, Object> context, final Map<String, Object> indexNode) {
83          Collection<Map<String, Object>> indexProps = fetchIndexProperties(context, indexNode);
84          Map<String, Object> result = indexProps.iterator().next();
85          result.put("schema", context.get("schema"));
86          result.put("table", context.get("name"));
87          return result;
88      }
89      
90      private Collection<Map<String, Object>> fetchIndexProperties(final Map<String, Object> context, final Map<String, Object> indexNode) {
91          Map<String, Object> param = new LinkedHashMap<>();
92          param.put("did", context.get("did"));
93          param.put("tid", context.get("tid"));
94          param.put("idx", indexNode.get("oid"));
95          param.put("datlastsysoid", context.get("datlastsysoid"));
96          return executeByTemplate(param, "component/indexes/%s/properties.ftl");
97      }
98      
99      private void appendColumnDetails(final Map<String, Object> indexData, final Long indexId) throws SQLException {
100         Collection<Map<String, Object>> columnDetails = fetchColumnDetails(indexId);
101         Collection<Map<String, Object>> columns = new LinkedList<>();
102         Collection<String> columnDisplays = new LinkedList<>();
103         for (Map<String, Object> each : columnDetails) {
104             columns.add(getColumnData(indexData, each));
105             columnDisplays.add(getColumnPropertyDisplayData(each, indexData));
106         }
107         indexData.put("columns", columns);
108         indexData.put("columns_csv", String.join(", ", columnDisplays));
109     }
110     
111     private Map<String, Object> getColumnData(final Map<String, Object> indexData, final Map<String, Object> columnDetail) throws SQLException {
112         Map<String, Object> result = new LinkedHashMap<>();
113         result.put("colname", columnDetail.get("attdef"));
114         result.put("collspcname", columnDetail.get("collnspname"));
115         result.put("op_class", columnDetail.get("opcname"));
116         if ("btree".equals(indexData.get("amname"))) {
117             result.put("sort_order", isSortOrder(columnDetail));
118             result.put("nulls", isNulls(columnDetail));
119         }
120         return result;
121     }
122     
123     private boolean isSortOrder(final Map<String, Object> columnDetail) throws SQLException {
124         if (null == columnDetail.get("options")) {
125             return false;
126         }
127         String[] options = (String[]) ((PgArray) columnDetail.get("options")).getArray();
128         return options.length > 0 && "DESC".equals(options[0]);
129     }
130     
131     private Object isNulls(final Map<String, Object> columnDetail) throws SQLException {
132         if (null == columnDetail.get("options")) {
133             return false;
134         }
135         String[] options = (String[]) ((PgArray) columnDetail.get("options")).getArray();
136         return options.length > 1 && options[1].split(" ").length > 1 && "FIRST".equals(options[1].split(" ")[1]);
137     }
138     
139     private Collection<Map<String, Object>> fetchColumnDetails(final Long indexId) {
140         Map<String, Object> param = new LinkedHashMap<>();
141         param.put("idx", indexId);
142         return executeByTemplate(param, "component/indexes/%s/column_details.ftl");
143     }
144     
145     private String getColumnPropertyDisplayData(final Map<String, Object> columnDetail, final Map<String, Object> indexData) throws SQLException {
146         String result = (String) columnDetail.get("attdef");
147         if (null != columnDetail.get("collnspname")) {
148             result += " COLLATE " + columnDetail.get("collnspname");
149         }
150         if (null != columnDetail.get("opcname")) {
151             result += " " + columnDetail.get("opcname");
152         }
153         if ("btree".equals(indexData.get("amname"))) {
154             String[] options = (String[]) ((PgArray) columnDetail.get("options")).getArray();
155             if (options.length > 0) {
156                 result += " " + options[0];
157             }
158             if (options.length > 1) {
159                 result += " " + options[1];
160             }
161         }
162         return result;
163     }
164     
165     private void appendIncludeDetails(final Map<String, Object> indexData, final Long oid) {
166         Map<String, Object> param = new LinkedHashMap<>();
167         param.put("idx", oid);
168         Collection<Object> includes = new LinkedList<>();
169         for (Map<String, Object> each : executeByTemplate(param, "component/indexes/%s/include_details.ftl")) {
170             includes.add(each.get("colname"));
171         }
172         indexData.put("include", includes);
173     }
174 }