|
11 | 11 | import java.util.Collections; |
12 | 12 | import java.util.HashMap; |
13 | 13 | import java.util.Iterator; |
| 14 | +import java.util.List; |
14 | 15 | import java.util.Locale; |
15 | 16 | import java.util.Map; |
16 | 17 | import java.util.SortedMap; |
|
22 | 23 |
|
23 | 24 | import com.flipkart.vitess.util.Constants; |
24 | 25 | import com.flipkart.vitess.util.MysqlDefs; |
| 26 | +import com.google.common.annotations.VisibleForTesting; |
25 | 27 | import com.youtube.vitess.proto.Query; |
26 | 28 |
|
27 | 29 | /** |
@@ -898,8 +900,196 @@ public ResultSet getVersionColumns(String catalog, String schema, String table) |
898 | 900 |
|
899 | 901 | public ResultSet getImportedKeys(String catalog, String schema, String table) |
900 | 902 | throws SQLException { |
901 | | - throw new SQLFeatureNotSupportedException( |
902 | | - Constants.SQLExceptionMessages.SQL_FEATURE_NOT_SUPPORTED); |
| 903 | + if (null == table) { |
| 904 | + throw new SQLException("Table Name Cannot be Null"); |
| 905 | + } |
| 906 | + ResultSet resultSet = null; |
| 907 | + VitessStatement vitessStatement = new VitessStatement(this.connection); |
| 908 | + ArrayList<ArrayList<String>> rows = new ArrayList<>(); |
| 909 | + try { |
| 910 | + resultSet = vitessStatement.executeQuery("SHOW CREATE TABLE " + this.quotedId + table + this.quotedId); |
| 911 | + while (resultSet.next()) { |
| 912 | + extractForeignKeyForTable(rows, resultSet.getString(2), catalog, table); |
| 913 | + } |
| 914 | + } finally { |
| 915 | + if (resultSet != null) { |
| 916 | + resultSet.close(); |
| 917 | + } |
| 918 | + } |
| 919 | + String[] columnNames = |
| 920 | + new String[] {"PKTABLE_CAT", "PKTABLE_SCHEM", "PKTABLE_NAME", "PKCOLUMN_NAME", "FKTABLE_CAT", |
| 921 | + "FKTABLE_SCHEM", "FKTABLE_NAME", "FKCOLUMN_NAME", "KEY_SEQ", "UPDATE_RULE", "DELETE_RULE", |
| 922 | + "FK_NAME", "PK_NAME", "DEFERRABILITY"}; |
| 923 | + Query.Type[] columnType = |
| 924 | + new Query.Type[] {Query.Type.CHAR, Query.Type.CHAR, Query.Type.CHAR, Query.Type.CHAR, |
| 925 | + Query.Type.CHAR, Query.Type.CHAR, Query.Type.CHAR, Query.Type.CHAR, Query.Type.INT16, |
| 926 | + Query.Type.INT16, Query.Type.INT16, Query.Type.CHAR, Query.Type.CHAR, Query.Type.INT16}; |
| 927 | + return new VitessResultSet(columnNames, columnType, rows, this.connection); |
| 928 | + } |
| 929 | + |
| 930 | + @VisibleForTesting |
| 931 | + void extractForeignKeyForTable(List<ArrayList<String>> rows, String createTableString, String catalog, String table) throws SQLException { |
| 932 | + StringTokenizer lineTokenizer = new StringTokenizer(createTableString, "\n"); |
| 933 | + |
| 934 | + while (lineTokenizer.hasMoreTokens()) { |
| 935 | + String line = lineTokenizer.nextToken().trim(); |
| 936 | + String constraintName = null; |
| 937 | + if (StringUtils.startsWithIgnoreCase(line, "CONSTRAINT")) { |
| 938 | + boolean usingBackTicks = true; |
| 939 | + int beginPos = com.flipkart.vitess.util.StringUtils.indexOfQuoteDoubleAware(line, this.quotedId, 0); |
| 940 | + if (beginPos == -1) { |
| 941 | + beginPos = line.indexOf("\""); |
| 942 | + usingBackTicks = false; |
| 943 | + } |
| 944 | + if (beginPos != -1) { |
| 945 | + int endPos; |
| 946 | + if (usingBackTicks) { |
| 947 | + endPos = com.flipkart.vitess.util.StringUtils.indexOfQuoteDoubleAware(line, this.quotedId, beginPos + 1); |
| 948 | + } else { |
| 949 | + endPos = com.flipkart.vitess.util.StringUtils.indexOfQuoteDoubleAware(line, "\"", beginPos + 1); |
| 950 | + } |
| 951 | + if (endPos != -1) { |
| 952 | + constraintName = line.substring(beginPos + 1, endPos); |
| 953 | + line = line.substring(endPos + 1, line.length()).trim(); |
| 954 | + } |
| 955 | + } |
| 956 | + } |
| 957 | + |
| 958 | + if (line.startsWith("FOREIGN KEY")) { |
| 959 | + if (line.endsWith(",")) { |
| 960 | + line = line.substring(0, line.length() - 1); |
| 961 | + } |
| 962 | + int indexOfFK = line.indexOf("FOREIGN KEY"); |
| 963 | + String localColumnName = null; |
| 964 | + String referencedCatalogName = com.flipkart.vitess.util.StringUtils.quoteIdentifier(catalog, this.quotedId); |
| 965 | + String referencedTableName = null; |
| 966 | + String referencedColumnName = null; |
| 967 | + if (indexOfFK != -1) { |
| 968 | + int afterFk = indexOfFK + "FOREIGN KEY".length(); |
| 969 | + |
| 970 | + int indexOfRef = com.flipkart.vitess.util.StringUtils.indexOfIgnoreCase(afterFk, line, "REFERENCES", this.quotedId, this.quotedId); |
| 971 | + if (indexOfRef != -1) { |
| 972 | + int indexOfParenOpen = line.indexOf('(', afterFk); |
| 973 | + int indexOfParenClose = com.flipkart.vitess.util.StringUtils.indexOfIgnoreCase(indexOfParenOpen, line, ")", this.quotedId, this.quotedId); |
| 974 | + localColumnName = line.substring(indexOfParenOpen + 1, indexOfParenClose); |
| 975 | + |
| 976 | + int afterRef = indexOfRef + "REFERENCES".length(); |
| 977 | + int referencedColumnBegin = com.flipkart.vitess.util.StringUtils.indexOfIgnoreCase(afterRef, line, "(", this.quotedId, this.quotedId); |
| 978 | + |
| 979 | + if (referencedColumnBegin != -1) { |
| 980 | + referencedTableName = line.substring(afterRef, referencedColumnBegin); |
| 981 | + int referencedColumnEnd = com.flipkart.vitess.util.StringUtils.indexOfIgnoreCase(referencedColumnBegin + 1, line, ")", this.quotedId, this.quotedId); |
| 982 | + if (referencedColumnEnd != -1) { |
| 983 | + referencedColumnName = line.substring(referencedColumnBegin + 1, referencedColumnEnd); |
| 984 | + } |
| 985 | + int indexOfCatalogSep = com.flipkart.vitess.util.StringUtils.indexOfIgnoreCase(0, referencedTableName, ".", this.quotedId, this.quotedId); |
| 986 | + if (indexOfCatalogSep != -1) { |
| 987 | + referencedCatalogName = referencedTableName.substring(0, indexOfCatalogSep); |
| 988 | + referencedTableName = referencedTableName.substring(indexOfCatalogSep + 1); |
| 989 | + } |
| 990 | + } |
| 991 | + } |
| 992 | + } |
| 993 | + if (constraintName == null) { |
| 994 | + constraintName = "not_available"; |
| 995 | + } |
| 996 | + List<String> localColumnsList = com.flipkart.vitess.util.StringUtils.split(localColumnName, ",", this.quotedId, this.quotedId); |
| 997 | + List<String> referColumnsList = com.flipkart.vitess.util.StringUtils.split(referencedColumnName, ",", this.quotedId, this.quotedId); |
| 998 | + if (localColumnsList.size() != referColumnsList.size()) { |
| 999 | + throw new SQLException("Mismatch columns list for foreign key local and reference columns"); |
| 1000 | + } |
| 1001 | + // Report a separate row for each column in the foreign key. All values the same except the column name. |
| 1002 | + for (int i = 0; i < localColumnsList.size(); i++) { |
| 1003 | + String localColumn = localColumnsList.get(i); |
| 1004 | + String referColumn = referColumnsList.get(i); |
| 1005 | + ArrayList<String> row = new ArrayList<>(14); |
| 1006 | + row.add(com.flipkart.vitess.util.StringUtils.unQuoteIdentifier(referencedCatalogName, this.quotedId)); // PKTABLE_CAT |
| 1007 | + row.add(null); // PKTABLE_SCHEM |
| 1008 | + row.add(com.flipkart.vitess.util.StringUtils.unQuoteIdentifier(referencedTableName, this.quotedId)); // PKTABLE_NAME |
| 1009 | + row.add(com.flipkart.vitess.util.StringUtils.unQuoteIdentifier(referColumn, this.quotedId)); // PKCOLUMN_NAME |
| 1010 | + row.add(catalog); // FKTABLE_CAT |
| 1011 | + row.add(null); // FKTABLE_SCHEM |
| 1012 | + row.add(table); // FKTABLE_NAME |
| 1013 | + row.add(com.flipkart.vitess.util.StringUtils.unQuoteIdentifier(localColumn, this.quotedId)); // FKCOLUMN_NAME |
| 1014 | + row.add(Integer.toString(i + 1)); // KEY_SEQ |
| 1015 | + int[] actions = getForeignKeyActions(line); |
| 1016 | + row.add(Integer.toString(actions[1])); // UPDATE_RULE |
| 1017 | + row.add(Integer.toString(actions[0])); // DELETE_RULE |
| 1018 | + row.add(constraintName); // FK_NAME |
| 1019 | + row.add(null); // PK_NAME |
| 1020 | + row.add(Integer.toString(java.sql.DatabaseMetaData.importedKeyNotDeferrable)); // DEFERRABILITY |
| 1021 | + rows.add(row); |
| 1022 | + } |
| 1023 | + } |
| 1024 | + } |
| 1025 | + } |
| 1026 | + |
| 1027 | + |
| 1028 | + /** |
| 1029 | + * Parses the constraint to see what actions are taking for update and delete, such as cascade. |
| 1030 | + * @param constraint |
| 1031 | + * the constraint to parse |
| 1032 | + * @return the code from {@link DatabaseMetaData} corresponding to the foreign actions for the constraint |
| 1033 | + */ |
| 1034 | + private int[] getForeignKeyActions(String constraint) { |
| 1035 | + int[] actions = new int[] { java.sql.DatabaseMetaData.importedKeyNoAction, java.sql.DatabaseMetaData.importedKeyNoAction }; |
| 1036 | + int lastParenIndex = constraint.lastIndexOf(")"); |
| 1037 | + if (lastParenIndex != (constraint.length() - 1)) { |
| 1038 | + String cascadeOptions = constraint.substring(lastParenIndex + 1).trim().toUpperCase(Locale.ENGLISH); |
| 1039 | + actions[0] = getCascadeDeleteOption(cascadeOptions); |
| 1040 | + actions[1] = getCascadeUpdateOption(cascadeOptions); |
| 1041 | + } |
| 1042 | + return actions; |
| 1043 | + } |
| 1044 | + |
| 1045 | + /** |
| 1046 | + * Parses the cascade option string and returns the DBMD constant that |
| 1047 | + * represents it (for deletes) |
| 1048 | + * |
| 1049 | + * @param cascadeOptions |
| 1050 | + * the comment from 'SHOW TABLE STATUS' |
| 1051 | + * @return the DBMD constant that represents the cascade option |
| 1052 | + */ |
| 1053 | + private int getCascadeDeleteOption(String cascadeOptions) { |
| 1054 | + int onDeletePos = cascadeOptions.indexOf("ON DELETE"); |
| 1055 | + if (onDeletePos != -1) { |
| 1056 | + String deleteOptions = cascadeOptions.substring(onDeletePos, cascadeOptions.length()); |
| 1057 | + if (deleteOptions.startsWith("ON DELETE CASCADE")) { |
| 1058 | + return java.sql.DatabaseMetaData.importedKeyCascade; |
| 1059 | + } else if (deleteOptions.startsWith("ON DELETE SET NULL")) { |
| 1060 | + return java.sql.DatabaseMetaData.importedKeySetNull; |
| 1061 | + } else if (deleteOptions.startsWith("ON DELETE RESTRICT")) { |
| 1062 | + return java.sql.DatabaseMetaData.importedKeyRestrict; |
| 1063 | + } else if (deleteOptions.startsWith("ON DELETE NO ACTION")) { |
| 1064 | + return java.sql.DatabaseMetaData.importedKeyNoAction; |
| 1065 | + } |
| 1066 | + } |
| 1067 | + return java.sql.DatabaseMetaData.importedKeyNoAction; |
| 1068 | + } |
| 1069 | + |
| 1070 | + /** |
| 1071 | + * Parses the cascade option string and returns the DBMD constant that |
| 1072 | + * represents it (for Updates) |
| 1073 | + * |
| 1074 | + * @param cascadeOptions |
| 1075 | + * the comment from 'SHOW TABLE STATUS' |
| 1076 | + * @return the DBMD constant that represents the cascade option |
| 1077 | + */ |
| 1078 | + private int getCascadeUpdateOption(String cascadeOptions) { |
| 1079 | + int onUpdatePos = cascadeOptions.indexOf("ON UPDATE"); |
| 1080 | + if (onUpdatePos != -1) { |
| 1081 | + String updateOptions = cascadeOptions.substring(onUpdatePos, cascadeOptions.length()); |
| 1082 | + if (updateOptions.startsWith("ON UPDATE CASCADE")) { |
| 1083 | + return java.sql.DatabaseMetaData.importedKeyCascade; |
| 1084 | + } else if (updateOptions.startsWith("ON UPDATE SET NULL")) { |
| 1085 | + return java.sql.DatabaseMetaData.importedKeySetNull; |
| 1086 | + } else if (updateOptions.startsWith("ON UPDATE RESTRICT")) { |
| 1087 | + return java.sql.DatabaseMetaData.importedKeyRestrict; |
| 1088 | + } else if (updateOptions.startsWith("ON UPDATE NO ACTION")) { |
| 1089 | + return java.sql.DatabaseMetaData.importedKeyNoAction; |
| 1090 | + } |
| 1091 | + } |
| 1092 | + return java.sql.DatabaseMetaData.importedKeyNoAction; |
903 | 1093 | } |
904 | 1094 |
|
905 | 1095 | public ResultSet getExportedKeys(String catalog, String schema, String table) |
|
0 commit comments