You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RelationshipCreator.java 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. /*
  2. Copyright (c) 2016 James Ahlborn
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package com.healthmarketscience.jackcess.impl;
  14. import java.io.IOException;
  15. import java.util.ArrayList;
  16. import java.util.Collection;
  17. import java.util.HashSet;
  18. import java.util.List;
  19. import java.util.Set;
  20. import com.healthmarketscience.jackcess.IndexBuilder;
  21. import com.healthmarketscience.jackcess.RelationshipBuilder;
  22. /**
  23. *
  24. * @author James Ahlborn
  25. */
  26. public class RelationshipCreator extends DBMutator
  27. {
  28. private final static int CASCADE_FLAGS =
  29. RelationshipImpl.CASCADE_DELETES_FLAG |
  30. RelationshipImpl.CASCADE_UPDATES_FLAG |
  31. RelationshipImpl.CASCADE_NULL_FLAG;
  32. private TableImpl _primaryTable;
  33. private TableImpl _secondaryTable;
  34. private RelationshipBuilder _relationship;
  35. private List<ColumnImpl> _primaryCols;
  36. private List<ColumnImpl> _secondaryCols;
  37. private int _flags;
  38. // - primary table must have unique index
  39. // - primary table index name ".rC", ".rD"...
  40. // - secondary index name "<PTable><STable>"
  41. // - add <name>1, <name>2 after names to make unique (index names and
  42. // relationship names)
  43. public RelationshipCreator(DatabaseImpl database)
  44. {
  45. super(database);
  46. }
  47. public TableImpl getPrimaryTable() {
  48. return _primaryTable;
  49. }
  50. public TableImpl getSecondaryTable() {
  51. return _secondaryTable;
  52. }
  53. public RelationshipImpl createRelationshipImpl(String name) {
  54. RelationshipImpl newRel = new RelationshipImpl(
  55. name, _secondaryTable, _primaryTable, _flags,
  56. _secondaryCols, _primaryCols);
  57. return newRel;
  58. }
  59. /**
  60. * Creates the relationship in the database.
  61. * @usage _advanced_method_
  62. */
  63. public RelationshipImpl createRelationship(RelationshipBuilder relationship)
  64. throws IOException
  65. {
  66. _relationship = relationship;
  67. validate();
  68. _flags = _relationship.getFlags();
  69. // need to determine the one-to-one flag on our own
  70. if(isOneToOne()) {
  71. _flags |= RelationshipImpl.ONE_TO_ONE_FLAG;
  72. }
  73. getPageChannel().startExclusiveWrite();
  74. try {
  75. RelationshipImpl newRel = getDatabase().writeRelationship(this);
  76. // FIXME, handle indexes
  77. return newRel;
  78. } finally {
  79. getPageChannel().finishWrite();
  80. }
  81. }
  82. private void validate() throws IOException {
  83. _primaryTable = getDatabase().getTable(_relationship.getToTable());
  84. _secondaryTable = getDatabase().getTable(_relationship.getFromTable());
  85. if((_primaryTable == null) || (_secondaryTable == null)) {
  86. throw new IllegalArgumentException(withErrorContext(
  87. "Two valid tables are required in relationship"));
  88. }
  89. _primaryCols = getColumns(_primaryTable, _relationship.getToColumns());
  90. _secondaryCols = getColumns(_secondaryTable, _relationship.getFromColumns());
  91. if((_primaryCols == null) || (_primaryCols.isEmpty()) ||
  92. (_secondaryCols == null) || (_secondaryCols.isEmpty())) {
  93. throw new IllegalArgumentException(withErrorContext(
  94. "Missing columns in relationship"));
  95. }
  96. if(_primaryCols.size() != _secondaryCols.size()) {
  97. throw new IllegalArgumentException(withErrorContext(
  98. "Must have same number of columns on each side of relationship"));
  99. }
  100. for(int i = 0; i < _primaryCols.size(); ++i) {
  101. ColumnImpl pcol = _primaryCols.get(i);
  102. ColumnImpl scol = _primaryCols.get(i);
  103. if(pcol.getType() != scol.getType()) {
  104. throw new IllegalArgumentException(withErrorContext(
  105. "Matched columns must have the same data type"));
  106. }
  107. }
  108. if(!_relationship.hasReferentialIntegrity()) {
  109. if((_relationship.getFlags() & CASCADE_FLAGS) != 0) {
  110. throw new IllegalArgumentException(withErrorContext(
  111. "Cascade flags cannot be enabled if referential integrity is not enforced"));
  112. }
  113. return;
  114. }
  115. // - same number cols
  116. // - cols come from right tables, tables from right db
  117. // - (cols can be duped in index)
  118. // - cols have same data types
  119. // - if enforce, require unique index on primary,
  120. // - auto-create index on secondary
  121. // - advanced, check for enforce cycles?
  122. // - index must be ascending
  123. // FIXME
  124. }
  125. private IndexBuilder createPrimaryIndex() {
  126. String name = getUniqueIndexName(_primaryTable);
  127. // FIXME?
  128. return createIndex(name, _primaryCols).setUnique();
  129. }
  130. private IndexBuilder createSecondaryIndex() {
  131. String name = getUniqueIndexName(_secondaryTable);
  132. // FIXME?
  133. return createIndex(name, _primaryCols);
  134. }
  135. private static IndexBuilder createIndex(String name, List<ColumnImpl> cols) {
  136. IndexBuilder idx = new IndexBuilder(name);
  137. for(ColumnImpl col : cols) {
  138. idx.addColumns(col.getName());
  139. }
  140. return idx;
  141. }
  142. private String getUniqueIndexName(TableImpl table) {
  143. Set<String> idxNames = TableUpdater.getIndexNames(table, null);
  144. boolean isPrimary = (table == _primaryTable);
  145. String baseName = null;
  146. String suffix = null;
  147. if(isPrimary) {
  148. // primary naming scheme: ".rC", ".rD", "rE" ...
  149. baseName = ".r";
  150. suffix = "C";
  151. } else {
  152. // secondary naming scheme: "<t1><t2>", "<t1><t2>1", "<t1><t2>2"
  153. baseName = _primaryTable.getName() + _secondaryTable.getName();
  154. suffix = "";
  155. }
  156. int count = 0;
  157. while(true) {
  158. String idxName = baseName + suffix;
  159. if(!idxNames.contains(idxName.toUpperCase())) {
  160. return idxName;
  161. }
  162. ++count;
  163. if(isPrimary) {
  164. char c = (char)(suffix.charAt(0) + 1);
  165. if(c == '[') {
  166. c = 'a';
  167. }
  168. suffix = "" + c;
  169. } else {
  170. suffix = "" + count;
  171. }
  172. }
  173. }
  174. private static List<ColumnImpl> getColumns(TableImpl table, List<String> colNames) {
  175. List<ColumnImpl> cols = new ArrayList<ColumnImpl>();
  176. for(String colName : colNames) {
  177. cols.add(table.getColumn(colName));
  178. }
  179. return cols;
  180. }
  181. private static Collection<String> getColumnNames(
  182. List<ColumnImpl> cols, Collection<String> colNames) {
  183. for(ColumnImpl col : cols) {
  184. colNames.add(col.getName());
  185. }
  186. return colNames;
  187. }
  188. private boolean isOneToOne() {
  189. // a relationship is one to one if the two sides of the relationship have
  190. // unique indexes on the relevant columns
  191. IndexImpl idx = _primaryTable.findIndexForColumns(
  192. getColumnNames(_primaryCols, new HashSet<String>()), true);
  193. if(idx == null) {
  194. return false;
  195. }
  196. idx = _secondaryTable.findIndexForColumns(
  197. getColumnNames(_secondaryCols, new HashSet<String>()), true);
  198. return (idx != null);
  199. }
  200. private static String getTableErrorContext(
  201. TableImpl table, List<ColumnImpl> cols,
  202. String tableName, Collection<String> colNames) {
  203. if(table != null) {
  204. tableName = table.getName();
  205. }
  206. if(cols != null) {
  207. colNames = getColumnNames(cols, new ArrayList<String>());
  208. }
  209. return CustomToStringStyle.valueBuilder(tableName)
  210. .append(null, cols)
  211. .toString();
  212. }
  213. private String withErrorContext(String msg) {
  214. return msg + "(Rel=" +
  215. getTableErrorContext(_primaryTable, _primaryCols,
  216. _relationship.getToTable(),
  217. _relationship.getToColumns()) + " <- " +
  218. getTableErrorContext(_secondaryTable, _secondaryCols,
  219. _relationship.getFromTable(),
  220. _relationship.getFromColumns()) + ")";
  221. }
  222. }