1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.healthmarketscience.jackcess.impl;
18
19 import java.io.IOException;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.Set;
26 import java.util.TreeSet;
27
28 import com.healthmarketscience.jackcess.Column;
29 import com.healthmarketscience.jackcess.ConstraintViolationException;
30 import com.healthmarketscience.jackcess.Index;
31 import com.healthmarketscience.jackcess.IndexCursor;
32 import com.healthmarketscience.jackcess.Row;
33 import com.healthmarketscience.jackcess.Table;
34 import com.healthmarketscience.jackcess.util.CaseInsensitiveColumnMatcher;
35 import com.healthmarketscience.jackcess.util.ColumnMatcher;
36 import com.healthmarketscience.jackcess.util.Joiner;
37
38
39
40
41
42
43
44
45 final class FKEnforcer
46 {
47
48
49 private static final ColumnMatcher MATCHER =
50 CaseInsensitiveColumnMatcher.INSTANCE;
51
52 private final TableImpl _table;
53 private List<ColumnImpl> _cols;
54 private List<Joiner> _primaryJoinersChkUp;
55 private List<Joiner> _primaryJoinersChkDel;
56 private List<Joiner> _primaryJoinersDoUp;
57 private List<Joiner> _primaryJoinersDoDel;
58 private List<Joiner> _primaryJoinersDoNull;
59 private List<Joiner> _secondaryJoiners;
60
61 FKEnforcer(TableImpl table) {
62 _table = table;
63
64
65 initColumns();
66 }
67
68 private void initColumns() {
69 Set<ColumnImpl> cols = new TreeSet<ColumnImpl>();
70 for(IndexImpl idx : _table.getIndexes()) {
71 IndexImpl.ForeignKeyReference ref = idx.getReference();
72 if(ref != null) {
73
74
75 for(IndexData.ColumnDescriptor iCol : idx.getColumns()) {
76 cols.add(iCol.getColumn());
77 }
78 }
79 }
80 _cols = !cols.isEmpty() ?
81 Collections.unmodifiableList(new ArrayList<ColumnImpl>(cols)) :
82 Collections.<ColumnImpl>emptyList();
83 }
84
85
86
87
88 void reset() {
89
90 initColumns();
91
92
93 _primaryJoinersChkUp = null;
94 _primaryJoinersChkDel = null;
95 _primaryJoinersDoUp = null;
96 _primaryJoinersDoDel = null;
97 _primaryJoinersDoNull = null;
98 _secondaryJoiners = null;
99 }
100
101
102
103
104 private void initialize() throws IOException {
105 if(_secondaryJoiners != null) {
106
107 return;
108 }
109
110
111 _primaryJoinersChkUp = new ArrayList<Joiner>(1);
112 _primaryJoinersChkDel = new ArrayList<Joiner>(1);
113 _primaryJoinersDoUp = new ArrayList<Joiner>(1);
114 _primaryJoinersDoDel = new ArrayList<Joiner>(1);
115 _primaryJoinersDoNull = new ArrayList<Joiner>(1);
116 _secondaryJoiners = new ArrayList<Joiner>(1);
117
118 for(IndexImpl idx : _table.getIndexes()) {
119 IndexImpl.ForeignKeyReference ref = idx.getReference();
120 if(ref != null) {
121
122 Joiner joiner = Joiner.create(idx);
123 if(ref.isPrimaryTable()) {
124 if(ref.isCascadeUpdates()) {
125 _primaryJoinersDoUp.add(joiner);
126 } else {
127 _primaryJoinersChkUp.add(joiner);
128 }
129 if(ref.isCascadeDeletes()) {
130 _primaryJoinersDoDel.add(joiner);
131 } else if(ref.isCascadeNullOnDelete()) {
132 _primaryJoinersDoNull.add(joiner);
133 } else {
134 _primaryJoinersChkDel.add(joiner);
135 }
136 } else {
137 _secondaryJoiners.add(joiner);
138 }
139 }
140 }
141 }
142
143
144
145
146
147
148
149 public void addRow(Object[] row) throws IOException {
150 if(!enforcing()) {
151 return;
152 }
153 initialize();
154
155 for(Joiner joiner : _secondaryJoiners) {
156 requirePrimaryValues(joiner, row);
157 }
158 }
159
160
161
162
163
164
165
166
167
168 public void updateRow(Object[] oldRow, Object[] newRow) throws IOException {
169 if(!enforcing()) {
170 return;
171 }
172
173 if(!anyUpdates(oldRow, newRow)) {
174
175 return;
176 }
177
178 initialize();
179
180 SharedState ss = _table.getDatabase().getFKEnforcerSharedState();
181
182 if(ss.isUpdating()) {
183
184
185
186 for(Joiner joiner : _secondaryJoiners) {
187 if(anyUpdates(joiner, oldRow, newRow)) {
188 requirePrimaryValues(joiner, newRow);
189 }
190 }
191 }
192
193 ss.pushUpdate();
194 try {
195
196
197
198 for(Joiner joiner : _primaryJoinersChkUp) {
199 if(anyUpdates(joiner, oldRow, newRow)) {
200 requireNoSecondaryValues(joiner, oldRow);
201 }
202 }
203
204
205
206 for(Joiner joiner : _primaryJoinersDoUp) {
207 if(anyUpdates(joiner, oldRow, newRow)) {
208 updateSecondaryValues(joiner, oldRow, newRow);
209 }
210 }
211
212 } finally {
213 ss.popUpdate();
214 }
215 }
216
217
218
219
220
221
222
223 public void deleteRow(Object[] row) throws IOException {
224 if(!enforcing()) {
225 return;
226 }
227 initialize();
228
229
230
231 for(Joiner joiner : _primaryJoinersChkDel) {
232 requireNoSecondaryValues(joiner, row);
233 }
234
235
236
237 for(Joiner joiner : _primaryJoinersDoDel) {
238 joiner.deleteRows(row);
239 }
240
241
242
243 for(Joiner joiner : _primaryJoinersDoNull) {
244 nullSecondaryValues(joiner, row);
245 }
246 }
247
248 private static void requirePrimaryValues(Joiner joiner, Object[] row)
249 throws IOException
250 {
251
252
253 if(!areNull(joiner, row) && !joiner.hasRows(row)) {
254 throw new ConstraintViolationException(
255 "Adding new row " + Arrays.asList(row) + " violates constraint " +
256 joiner.toFKString());
257 }
258 }
259
260 private static void requireNoSecondaryValues(Joiner joiner, Object[] row)
261 throws IOException
262 {
263
264
265 if(joiner.hasRows(row)) {
266 throw new ConstraintViolationException(
267 "Removing old row " + Arrays.asList(row) + " violates constraint " +
268 joiner.toFKString());
269 }
270 }
271
272 private static void updateSecondaryValues(Joiner joiner, Object[] oldFromRow,
273 Object[] newFromRow)
274 throws IOException
275 {
276 IndexCursor toCursor = joiner.getToCursor();
277 List<? extends Index.Column> fromCols = joiner.getColumns();
278 List<? extends Index.Column> toCols = joiner.getToIndex().getColumns();
279 Object[] toRow = new Object[joiner.getToTable().getColumnCount()];
280
281 for(Iterator<Row> iter = joiner.findRows(oldFromRow)
282 .setColumnNames(Collections.<String>emptySet())
283 .iterator(); iter.hasNext(); ) {
284 iter.next();
285
286
287 Arrays.fill(toRow, Column.KEEP_VALUE);
288 for(int i = 0; i < fromCols.size(); ++i) {
289 Object val = fromCols.get(i).getColumn().getRowValue(newFromRow);
290 toCols.get(i).getColumn().setRowValue(toRow, val);
291 }
292
293 toCursor.updateCurrentRow(toRow);
294 }
295 }
296
297 private static void nullSecondaryValues(Joiner joiner, Object[] oldFromRow)
298 throws IOException
299 {
300 IndexCursor toCursor = joiner.getToCursor();
301 List<? extends Index.Column> fromCols = joiner.getColumns();
302 List<? extends Index.Column> toCols = joiner.getToIndex().getColumns();
303 Object[] toRow = new Object[joiner.getToTable().getColumnCount()];
304
305 for(Iterator<Row> iter = joiner.findRows(oldFromRow)
306 .setColumnNames(Collections.<String>emptySet())
307 .iterator(); iter.hasNext(); ) {
308 iter.next();
309
310
311 Arrays.fill(toRow, Column.KEEP_VALUE);
312 for(int i = 0; i < fromCols.size(); ++i) {
313 toCols.get(i).getColumn().setRowValue(toRow, null);
314 }
315
316 toCursor.updateCurrentRow(toRow);
317 }
318 }
319
320 private boolean anyUpdates(Object[] oldRow, Object[] newRow) {
321 for(ColumnImpl col : _cols) {
322 if(!MATCHER.matches(_table, col.getName(),
323 col.getRowValue(oldRow), col.getRowValue(newRow))) {
324 return true;
325 }
326 }
327 return false;
328 }
329
330 private static boolean anyUpdates(Joiner joiner,Object[] oldRow,
331 Object[] newRow)
332 {
333 Table fromTable = joiner.getFromTable();
334 for(Index.Column iCol : joiner.getColumns()) {
335 Column col = iCol.getColumn();
336 if(!MATCHER.matches(fromTable, col.getName(),
337 col.getRowValue(oldRow), col.getRowValue(newRow))) {
338 return true;
339 }
340 }
341 return false;
342 }
343
344 private static boolean areNull(Joiner joiner, Object[] row) {
345 for(Index.Column col : joiner.getColumns()) {
346 if(col.getColumn().getRowValue(row) != null) {
347 return false;
348 }
349 }
350 return true;
351 }
352
353 private boolean enforcing() {
354 return _table.getDatabase().isEnforceForeignKeys();
355 }
356
357 static SharedState initSharedState() {
358 return new SharedState();
359 }
360
361
362
363
364 static final class SharedState
365 {
366
367 private int _updateDepth;
368
369 private SharedState() {
370 }
371
372 public boolean isUpdating() {
373 return (_updateDepth == 0);
374 }
375
376 public void pushUpdate() {
377 ++_updateDepth;
378 }
379
380 public void popUpdate() {
381 --_updateDepth;
382 }
383 }
384 }