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.core.metadata.generator;
19  
20  import com.google.common.base.Strings;
21  import lombok.extern.slf4j.Slf4j;
22  import org.apache.shardingsphere.data.pipeline.core.sqlbuilder.dialect.DialectPipelineSQLBuilder;
23  import org.apache.shardingsphere.infra.binder.context.statement.SQLStatementContext;
24  import org.apache.shardingsphere.infra.binder.context.statement.ddl.AlterTableStatementContext;
25  import org.apache.shardingsphere.infra.binder.context.statement.ddl.CommentStatementContext;
26  import org.apache.shardingsphere.infra.binder.context.statement.ddl.CreateIndexStatementContext;
27  import org.apache.shardingsphere.infra.binder.context.statement.ddl.CreateTableStatementContext;
28  import org.apache.shardingsphere.infra.binder.context.type.ConstraintAvailable;
29  import org.apache.shardingsphere.infra.binder.context.type.IndexAvailable;
30  import org.apache.shardingsphere.infra.binder.context.type.TableAvailable;
31  import org.apache.shardingsphere.infra.binder.engine.SQLBindEngine;
32  import org.apache.shardingsphere.infra.database.core.spi.DatabaseTypedSPILoader;
33  import org.apache.shardingsphere.infra.database.core.type.DatabaseType;
34  import org.apache.shardingsphere.infra.hint.HintValueContext;
35  import org.apache.shardingsphere.infra.metadata.database.schema.util.IndexMetaDataUtils;
36  import org.apache.shardingsphere.infra.parser.SQLParserEngine;
37  import org.apache.shardingsphere.infra.session.query.QueryContext;
38  import org.apache.shardingsphere.sql.parser.sql.common.segment.SQLSegment;
39  import org.apache.shardingsphere.sql.parser.sql.common.segment.ddl.constraint.ConstraintSegment;
40  import org.apache.shardingsphere.sql.parser.sql.common.segment.ddl.index.IndexSegment;
41  import org.apache.shardingsphere.sql.parser.sql.common.segment.generic.table.SimpleTableSegment;
42  import org.apache.shardingsphere.sql.parser.sql.common.segment.generic.table.TableNameSegment;
43  
44  import javax.sql.DataSource;
45  import java.sql.Connection;
46  import java.sql.SQLException;
47  import java.util.Collections;
48  import java.util.Comparator;
49  import java.util.Map;
50  import java.util.Map.Entry;
51  import java.util.Optional;
52  import java.util.TreeMap;
53  
54  /**
55   * Pipeline DDL generator.
56   */
57  @Slf4j
58  public final class PipelineDDLGenerator {
59      
60      private static final String DELIMITER = ";";
61      
62      private static final String SET_SEARCH_PATH_PREFIX = "set search_path";
63      
64      /**
65       * Generate logic DDL.
66       * 
67       * @param databaseType database type
68       * @param sourceDataSource source data source
69       * @param schemaName schema name
70       * @param sourceTableName source table name
71       * @param targetTableName target table name
72       * @param parserEngine parser engine
73       * @return DDL SQL
74       * @throws SQLException SQL exception 
75       */
76      public String generateLogicDDL(final DatabaseType databaseType, final DataSource sourceDataSource,
77                                     final String schemaName, final String sourceTableName, final String targetTableName, final SQLParserEngine parserEngine) throws SQLException {
78          long startTimeMillis = System.currentTimeMillis();
79          StringBuilder result = new StringBuilder();
80          for (String each : DatabaseTypedSPILoader.getService(DialectPipelineSQLBuilder.class, databaseType).buildCreateTableSQLs(sourceDataSource, schemaName, sourceTableName)) {
81              Optional<String> queryContext = decorate(databaseType, sourceDataSource, schemaName, targetTableName, parserEngine, each);
82              queryContext.ifPresent(optional -> result.append(optional).append(DELIMITER).append(System.lineSeparator()));
83          }
84          log.info("generateLogicDDL, databaseType={}, schemaName={}, sourceTableName={}, targetTableName={}, cost {} ms",
85                  databaseType.getType(), schemaName, sourceTableName, targetTableName, System.currentTimeMillis() - startTimeMillis);
86          return result.toString();
87      }
88      
89      private Optional<String> decorate(final DatabaseType databaseType, final DataSource dataSource, final String schemaName, final String targetTableName,
90                                        final SQLParserEngine parserEngine, final String sql) throws SQLException {
91          if (Strings.isNullOrEmpty(sql)) {
92              return Optional.empty();
93          }
94          String databaseName;
95          try (Connection connection = dataSource.getConnection()) {
96              databaseName = connection.getCatalog();
97          }
98          String result = decorateActualSQL(databaseName, targetTableName, parserEngine, sql.trim());
99          // TODO remove it after set search_path is supported.
100         if ("openGauss".equals(databaseType.getType())) {
101             return decorateOpenGauss(databaseName, schemaName, result, parserEngine);
102         }
103         return Optional.of(result);
104     }
105     
106     private String decorateActualSQL(final String databaseName, final String targetTableName, final SQLParserEngine parserEngine, final String sql) {
107         QueryContext queryContext = getQueryContext(databaseName, parserEngine, sql);
108         SQLStatementContext sqlStatementContext = queryContext.getSqlStatementContext();
109         Map<SQLSegment, String> replaceMap = new TreeMap<>(Comparator.comparing(SQLSegment::getStartIndex));
110         if (sqlStatementContext instanceof CreateTableStatementContext) {
111             appendFromIndexAndConstraint(replaceMap, targetTableName, sqlStatementContext);
112             appendFromTable(replaceMap, targetTableName, (TableAvailable) sqlStatementContext);
113         }
114         if (sqlStatementContext instanceof CommentStatementContext) {
115             appendFromTable(replaceMap, targetTableName, (TableAvailable) sqlStatementContext);
116         }
117         if (sqlStatementContext instanceof CreateIndexStatementContext) {
118             appendFromTable(replaceMap, targetTableName, (TableAvailable) sqlStatementContext);
119             appendFromIndexAndConstraint(replaceMap, targetTableName, sqlStatementContext);
120         }
121         if (sqlStatementContext instanceof AlterTableStatementContext) {
122             appendFromIndexAndConstraint(replaceMap, targetTableName, sqlStatementContext);
123             appendFromTable(replaceMap, targetTableName, (TableAvailable) sqlStatementContext);
124         }
125         return doDecorateActualTable(replaceMap, sql);
126     }
127     
128     private QueryContext getQueryContext(final String databaseName, final SQLParserEngine parserEngine, final String sql) {
129         SQLStatementContext sqlStatementContext = new SQLBindEngine(null, databaseName, new HintValueContext()).bind(parserEngine.parse(sql, false), Collections.emptyList());
130         return new QueryContext(sqlStatementContext, sql, Collections.emptyList(), new HintValueContext());
131     }
132     
133     private void appendFromIndexAndConstraint(final Map<SQLSegment, String> replaceMap, final String targetTableName, final SQLStatementContext sqlStatementContext) {
134         if (!(sqlStatementContext instanceof TableAvailable) || ((TableAvailable) sqlStatementContext).getTablesContext().getSimpleTableSegments().isEmpty()) {
135             return;
136         }
137         TableNameSegment tableNameSegment = ((TableAvailable) sqlStatementContext).getTablesContext().getSimpleTableSegments().iterator().next().getTableName();
138         if (!tableNameSegment.getIdentifier().getValue().equals(targetTableName)) {
139             if (sqlStatementContext instanceof IndexAvailable) {
140                 for (IndexSegment each : ((IndexAvailable) sqlStatementContext).getIndexes()) {
141                     String logicIndexName = IndexMetaDataUtils.getLogicIndexName(each.getIndexName().getIdentifier().getValue(), tableNameSegment.getIdentifier().getValue());
142                     replaceMap.put(each.getIndexName(), logicIndexName);
143                 }
144             }
145             if (sqlStatementContext instanceof ConstraintAvailable) {
146                 for (ConstraintSegment each : ((ConstraintAvailable) sqlStatementContext).getConstraints()) {
147                     String logicConstraint = IndexMetaDataUtils.getLogicIndexName(each.getIdentifier().getValue(), tableNameSegment.getIdentifier().getValue());
148                     replaceMap.put(each, logicConstraint);
149                 }
150             }
151         }
152     }
153     
154     private void appendFromTable(final Map<SQLSegment, String> replaceMap, final String targetTableName, final TableAvailable sqlStatementContext) {
155         for (SimpleTableSegment each : sqlStatementContext.getAllTables()) {
156             if (!targetTableName.equals(each.getTableName().getIdentifier().getValue())) {
157                 replaceMap.put(each.getTableName(), targetTableName);
158             }
159         }
160     }
161     
162     private String doDecorateActualTable(final Map<SQLSegment, String> replaceMap, final String sql) {
163         StringBuilder result = new StringBuilder();
164         int lastStopIndex = 0;
165         for (Entry<SQLSegment, String> entry : replaceMap.entrySet()) {
166             result.append(sql, lastStopIndex, entry.getKey().getStartIndex());
167             result.append(entry.getValue());
168             lastStopIndex = entry.getKey().getStopIndex() + 1;
169         }
170         if (lastStopIndex < sql.length()) {
171             result.append(sql, lastStopIndex, sql.length());
172         }
173         return result.toString();
174     }
175     
176     // TODO remove it after set search_path is supported.
177     private Optional<String> decorateOpenGauss(final String databaseName, final String schemaName, final String queryContext,
178                                                final SQLParserEngine parserEngine) {
179         if (queryContext.toLowerCase().startsWith(SET_SEARCH_PATH_PREFIX)) {
180             return Optional.empty();
181         }
182         return Optional.of(replaceTableNameWithPrefix(queryContext, schemaName + ".", databaseName, parserEngine));
183     }
184     
185     private String replaceTableNameWithPrefix(final String sql, final String prefix, final String databaseName, final SQLParserEngine parserEngine) {
186         QueryContext queryContext = getQueryContext(databaseName, parserEngine, sql);
187         SQLStatementContext sqlStatementContext = queryContext.getSqlStatementContext();
188         if (sqlStatementContext instanceof CreateTableStatementContext || sqlStatementContext instanceof CommentStatementContext
189                 || sqlStatementContext instanceof CreateIndexStatementContext || sqlStatementContext instanceof AlterTableStatementContext) {
190             if (sqlStatementContext.getTablesContext().getSimpleTableSegments().isEmpty()) {
191                 return sql;
192             }
193             if (sqlStatementContext.getTablesContext().getSchemaName().isPresent()) {
194                 return sql;
195             }
196             Map<SQLSegment, String> replaceMap = new TreeMap<>(Comparator.comparing(SQLSegment::getStartIndex));
197             TableNameSegment tableNameSegment = sqlStatementContext.getTablesContext().getSimpleTableSegments().iterator().next().getTableName();
198             replaceMap.put(tableNameSegment, prefix + tableNameSegment.getIdentifier().getValue());
199             return doDecorateActualTable(replaceMap, sql);
200         }
201         return sql;
202     }
203 }