DeveelDB  20151217
complete SQL database system, primarly developed for .NET/Mono frameworks
TransactionWork.cs
Go to the documentation of this file.
1 //
2 // Copyright 2010-2015 Deveel
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 using System;
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using System.Linq;
21 
22 using Deveel.Data;
23 using Deveel.Data.Index;
24 using Deveel.Data.Sql;
25 using Deveel.Data.Sql.Tables;
26 
27 namespace Deveel.Data.Transactions {
29  internal TableSourceComposite Composite { get; private set; }
30 
31  internal Transaction Transaction { get; private set; }
32 
33  public IEnumerable<ITableSource> SelectedFromTables { get; private set; }
34 
35  internal TransactionWork(TableSourceComposite composite, Transaction transaction, IEnumerable<ITableSource> selectedFromTables, IEnumerable<IMutableTable> touchedTables, TransactionRegistry journal) {
36  Composite = composite;
37  Transaction = transaction;
38  SelectedFromTables = selectedFromTables;
39 
40  // Get individual journals for updates made to tables in this
41  // transaction.
42  // The list TableEventRegistry
43 
44  ChangedTables = touchedTables.Select(t => t.EventRegistry).Where(tableJournal => tableJournal.EventCount > 0);
45 
46  // The list of tables created by this journal.
47  CreatedTables = journal.TablesCreated;
48  // Ths list of tables dropped by this journal.
49  DroppedTables = journal.TablesDropped;
50  // The list of tables that constraints were alter by this journal
51  ConstraintAlteredTables = journal.TablesConstraintAltered;
52 
53  // Get the list of all database objects that were created in the
54  // transaction.
55  ObjectsCreated = transaction.Registry.ObjectsCreated;
56  // Get the list of all database objects that were dropped in the
57  // transaction.
58  ObjectsDropped = transaction.Registry.ObjectsDropped;
59 
60  CommitId = transaction.CommitId;
61  }
62 
63  public IEnumerable<int> CreatedTables { get; private set; }
64 
65  public IEnumerable<int> DroppedTables { get; private set; }
66 
67  public IEnumerable<int> ConstraintAlteredTables { get; private set; }
68 
69  public IEnumerable<TableEventRegistry> ChangedTables { get; private set; }
70 
71  public IEnumerable<ObjectName> ObjectsCreated { get; private set; }
72 
73  public IEnumerable<ObjectName> ObjectsDropped { get; private set; }
74 
75  public bool Done { get; private set; }
76 
77  public long CommitId { get; private set; }
78 
79  public bool HasChanges {
80  get { return CreatedTables.Any() || DroppedTables.Any() || ConstraintAlteredTables.Any() || ChangedTables.Any(); }
81  }
82 
90  private static bool CommitTableListContains(IEnumerable<CommitTableInfo> list, TableSource master) {
91  return list.Any(info => info.Master.Equals(master));
92  }
93 
94  private void AssertNoDirtySelect() {
95  // We only perform this check if transaction error on dirty selects
96  // are enabled.
97  if (Transaction.ErrorOnDirtySelect()) {
98  // For each table that this transaction selected from, if there are
99  // any committed changes then generate a transaction error.
100  foreach (TableSource selectedTable in SelectedFromTables) {
101  // Find all committed journals equal to or greater than this
102  // transaction's commit_id.
103  var journalsSince = selectedTable.FindChangesSinceCmmit(CommitId);
104  if (journalsSince.Any()) {
105  // Yes, there are changes so generate transaction error and
106  // rollback.
107  throw new TransactionException(
109  "Concurrent Serializable Transaction Conflict(4): " +
110  "Select from table that has committed changes: " +
111  selectedTable.TableName);
112  }
113  }
114  }
115  }
116 
117  internal void CheckConflicts(IEnumerable<TransactionObjectState> namespaceJournals) {
118  AssertNoDirtySelect();
119 
120  // Check there isn't a namespace clash with database objects.
121  // We need to create a list of all create and drop activity in the
122  // Composite from when the transaction started.
123  var allDroppedObs = new List<ObjectName>();
124  var allCreatedObs = new List<ObjectName>();
125  foreach (var nsJournal in namespaceJournals) {
126  if (nsJournal.CommitId >= CommitId) {
127  allDroppedObs.AddRange(nsJournal.DroppedObjects);
128  allCreatedObs.AddRange(nsJournal.CreatedObjects);
129  }
130  }
131 
132  // The list of all dropped objects since this transaction
133  // began.
134  bool conflict5 = false;
135  object conflictName = null;
136  string conflictDesc = "";
137  foreach (ObjectName droppedOb in allDroppedObs) {
138  if (ObjectsDropped.Contains(droppedOb)) {
139  conflict5 = true;
140  conflictName = droppedOb;
141  conflictDesc = "Drop Clash";
142  }
143  }
144  // The list of all created objects since this transaction
145  // began.
146  foreach (ObjectName createdOb in allCreatedObs) {
147  if (ObjectsCreated.Contains(createdOb)) {
148  conflict5 = true;
149  conflictName = createdOb;
150  conflictDesc = "Create Clash";
151  }
152  }
153  if (conflict5) {
154  // Namespace conflict...
155  throw new TransactionException(
157  "Concurrent Serializable Transaction Conflict(5): " +
158  "Namespace conflict: " + conflictName + " " +
159  conflictDesc);
160  }
161 
162  // For each journal,
163  foreach (TableEventRegistry changeJournal in ChangedTables) {
164  // The table the change was made to.
165  int tableId = changeJournal.TableId;
166  // Get the master table with this table id.
167  TableSource master = Composite.GetTableSource(tableId);
168 
169  // True if the state contains a committed resource with the given name
170  bool committedResource = Composite.ContainsVisibleResource(tableId);
171 
172  // Check this table is still in the committed tables list.
173  if (!CreatedTables.Contains(tableId) && !committedResource) {
174  // This table is no longer a committed table, so rollback
175  throw new TransactionException(
177  "Concurrent Serializable Transaction Conflict(2): " +
178  "Table altered/dropped: " + master.TableName);
179  }
180 
181  // Since this journal was created, check to see if any changes to the
182  // tables have been committed since.
183  // This will return all journals on the table with the same commit_id
184  // or greater.
185  var journalsSince = master.FindChangesSinceCmmit(CommitId);
186 
187  // For each journal, determine if there's any clashes.
188  foreach (TableEventRegistry tableJournal in journalsSince) {
189  // This will thrown an exception if a commit classes.
190  changeJournal.TestCommitClash(master.TableInfo, tableJournal);
191  }
192  }
193 
194  // Look at the transaction journal, if a table is dropped that has
195  // journal entries since the last commit then we have an exception
196  // case.
197  foreach (int tableId in DroppedTables) {
198  // Get the master table with this table id.
199  TableSource master = Composite.GetTableSource(tableId);
200  // Any journal entries made to this dropped table?
201  if (master.FindChangesSinceCmmit(CommitId).Any()) {
202  // Oops, yes, rollback!
203  throw new TransactionException(
205  "Concurrent Serializable Transaction Conflict(3): " +
206  "Dropped table has modifications: " + master.TableName);
207  }
208  }
209  }
210 
212  // Create a normalized list of TableSource of all tables that
213  // were either changed (and not dropped), and created (and not dropped).
214  // This list represents all tables that are either new or changed in
215  // this transaction.
216 
217  var normalizedChangedTables = new List<CommitTableInfo>(8);
218 
219  // Add all tables that were changed and not dropped in this transaction.
220 
221  normalizedChangedTables.AddRange(
222  ChangedTables.Select(tableJournal => new { tableJournal, tableId = tableJournal.TableId })
223  .Where(t => !DroppedTables.Contains(t.tableId))
224  .Select(t => new { t, masterTable = Composite.GetTableSource(t.tableId) })
225  .Select(t => new CommitTableInfo {
226  Master = t.masterTable,
227  Journal = t.t.tableJournal,
228  ChangesSinceCommit = t.masterTable.FindChangesSinceCmmit(CommitId).ToArray()
229  }));
230 
231  // Add all tables that were created and not dropped in this transaction.
232  foreach (var tableId in CreatedTables) {
233  // If this table is not dropped in this transaction then this is a
234  // new table in this transaction.
235  if (!DroppedTables.Contains(tableId)) {
236  TableSource masterTable = Composite.GetTableSource(tableId);
237  if (!CommitTableListContains(normalizedChangedTables, masterTable)) {
238 
239  // This is for entries that are created but modified (no journal).
240  var tableInfo = new CommitTableInfo {
241  Master = masterTable
242  };
243 
244  normalizedChangedTables.Add(tableInfo);
245  }
246  }
247  }
248 
249  return normalizedChangedTables.ToArray();
250  }
251 
253  // Create a normalized list of TableSource of all tables that
254  // were dropped (and not created) in this transaction. This list
255  // represents tables that will be dropped if the transaction
256  // successfully commits.
257 
258  var normalizedDroppedTables = new List<TableSource>(8);
259  foreach (var tableId in DroppedTables) {
260  // Was this dropped table also created? If it was created in this
261  // transaction then we don't care about it.
262  if (!CreatedTables.Contains(tableId)) {
263  TableSource masterTable = Composite.GetTableSource(tableId);
264  normalizedDroppedTables.Add(masterTable);
265  }
266  }
267 
268  return normalizedDroppedTables.ToArray();
269  }
270 
271  private ITable[] FindChangedTables(ITransaction checkTransaction, CommitTableInfo[] normalizedChangedTables) {
272  var changedTableSource = new ITable[normalizedChangedTables.Length];
273 
274  // Set up the above arrays
275  for (int i = 0; i < normalizedChangedTables.Length; ++i) {
276  // Get the information for this changed table
277  CommitTableInfo tableInfo = normalizedChangedTables[i];
278 
279  // Get the master table that changed from the normalized list.
280  TableSource master = tableInfo.Master;
281  // Did this table change since the transaction started?
282  TableEventRegistry[] allTableChanges = tableInfo.ChangesSinceCommit;
283 
284  if (allTableChanges == null || allTableChanges.Length == 0) {
285  // No changes so we can pick the correct IIndexSet from the current
286  // transaction.
287 
288  // Get the state of the changed tables from the Transaction
289  var mtable = Transaction.GetMutableTable(master.TableName);
290  // Get the current index set of the changed table
291  tableInfo.IndexSet = Transaction.GetIndexSetForTable(master);
292  // Flush all index changes in the table
293  mtable.FlushIndexes();
294 
295  // Set the 'check_transaction' object with the latest version of the
296  // table.
297  checkTransaction.UpdateVisibleTable(tableInfo.Master, tableInfo.IndexSet);
298  } else {
299  // There were changes so we need to merge the changes with the
300  // current view of the table.
301 
302  // It's not immediately obvious how this merge update works, but
303  // basically what happens is we WriteByte the table journal with all the
304  // changes into a new IMutableTableDataSource of the current
305  // committed state, and then we flush all the changes into the
306  // index and then update the 'check_transaction' with this change.
307 
308  // Create the IMutableTableDataSource with the changes from this
309  // journal.
310  var mtable = master.CreateTableAtCommit(checkTransaction, tableInfo.Journal);
311  // Get the current index set of the changed table
312  tableInfo.IndexSet = checkTransaction.GetIndexSetForTable(master);
313  // Flush all index changes in the table
314  mtable.FlushIndexes();
315 
316  // Dispose the table
317  mtable.Dispose();
318  }
319 
320  // And now refresh the 'changedTableSource' entry
321  changedTableSource[i] = checkTransaction.GetTable(master.TableName);
322  }
323 
324  return changedTableSource;
325  }
326 
327  private void FireChangeEvents(ITransaction checkTransaction, CommitTableInfo[] normalizedChangedTables, Action<TableCommitInfo> commitActions) {
328  if (commitActions == null)
329  return;
330 
331  foreach (var tableInfo in normalizedChangedTables) {
332  // Get the journal that details the change to the table.
333  TableEventRegistry changeJournal = tableInfo.Journal;
334  if (changeJournal != null) {
335  // Get the table name
336  var tableName = tableInfo.Master.TableName;
337  commitActions(new TableCommitInfo(checkTransaction.CommitId, tableName, tableInfo.NormalizedAddedRows,
338  tableInfo.NormalizedAddedRows));
339  }
340  }
341  }
342 
343  private void CheckConstraintViolations(ITransaction checkTransaction, CommitTableInfo[] normalizedChangedTables, ITable[] changedTableSource) {
344  // Any tables that the constraints were altered for we need to check
345  // if any rows in the table violate the new constraints.
346  foreach (var tableId in ConstraintAlteredTables) {
347  // We need to check there are no constraint violations for all the
348  // rows in the table.
349  for (int n = 0; n < normalizedChangedTables.Length; ++n) {
350  CommitTableInfo tableInfo = normalizedChangedTables[n];
351  if (tableInfo.Master.TableId == tableId) {
352  checkTransaction.CheckAddConstraintViolations(changedTableSource[n], ConstraintDeferrability.InitiallyDeferred);
353  }
354  }
355  }
356 
357  // For each changed table we must determine the rows that
358  // were deleted and perform the remove constraint checks on the
359  // deleted rows. Note that this happens after the records are
360  // removed from the index.
361 
362  // For each changed table,
363  for (int i = 0; i < normalizedChangedTables.Length; ++i) {
364  CommitTableInfo tableInfo = normalizedChangedTables[i];
365  // Get the journal that details the change to the table.
366  TableEventRegistry changeJournal = tableInfo.Journal;
367  if (changeJournal != null) {
368  // Find the normalized deleted rows.
369  int[] normalizedRemovedRows = changeJournal.RemovedRows.ToArray();
370  // Check removing any of the data doesn't cause a constraint
371  // violation.
372  checkTransaction.CheckRemoveConstraintViolations(changedTableSource[i], normalizedRemovedRows, ConstraintDeferrability.InitiallyDeferred);
373 
374  // Find the normalized added rows.
375  int[] normalizedAddedRows = changeJournal.AddedRows.ToArray();
376  // Check adding any of the data doesn't cause a constraint
377  // violation.
378  checkTransaction.CheckAddConstraintViolations(changedTableSource[i], normalizedAddedRows, ConstraintDeferrability.InitiallyDeferred);
379 
380  // Set up the list of added and removed rows
381  tableInfo.NormalizedAddedRows = normalizedAddedRows;
382  tableInfo.NormalizedRemovedRows = normalizedRemovedRows;
383 
384  }
385  }
386  }
387 
388  internal IEnumerable<TableSource> Commit(IList<TransactionObjectState> nameSpaceJournals, Action<TableCommitInfo> commitActions) {
389  var changedTablesList = new List<TableSource>();
390 
391  // This is a transaction that will represent the view of the database
392  // at the end of the commit
393  ITransaction checkTransaction = null;
394 
395  bool entriesCommitted = false;
396 
397  try {
398  // ---- Commit check stage ----
399  CheckConflicts(nameSpaceJournals);
400 
401  // Tests passed so go on to commit,
402 
403  // ---- Commit stage ----
404 
405  var normalizedChangedTables = GetNormalizedChangedTables();
406  var normalizedDroppedTables = GetNormalizedDroppedTables();
407 
408  // We now need to create a ITransaction object that we
409  // use to send to the triggering mechanism. This
410  // object represents a very specific view of the
411  // transaction. This view contains the latest version of changed
412  // tables in this transaction. It also contains any tables that have
413  // been created by this transaction and does not contain any tables
414  // that have been dropped. Any tables that have not been touched by
415  // this transaction are shown in their current committed state.
416  // To summarize - this view is the current view of the database plus
417  // any modifications made by the transaction that is being committed.
418 
419  // How this works - All changed tables are merged with the current
420  // committed table. All created tables are added into check_transaction
421  // and all dropped tables are removed from check_transaction. If
422  // there were no other changes to a table between the time the
423  // transaction was created and now, the view of the table in the
424  // transaction is used, otherwise the latest changes are merged.
425 
426  // Note that this view will be the view that the database will
427  // ultimately become if this transaction successfully commits. Also,
428  // you should appreciate that this view is NOT exactly the same as
429  // the current trasaction view because any changes that have been
430  // committed by concurrent transactions will be reflected in this view.
431 
432  // Create a new transaction of the database which will represent the
433  // committed view if this commit is successful.
434  checkTransaction = Composite.CreateTransaction(IsolationLevel.Serializable);
435 
436  // Overwrite this view with tables from this transaction that have
437  // changed or have been added or dropped.
438 
439  // (Note that order here is important). First drop any tables from
440  // this view.
441  foreach (TableSource masterTable in normalizedDroppedTables) {
442  // Drop this table in the current view
443  checkTransaction.RemoveVisibleTable(masterTable);
444  }
445 
446  // Now add any changed tables to the view.
447 
448  // Represents view of the changed tables
449  var changedTableSource = FindChangedTables(checkTransaction, normalizedChangedTables);
450 
451  // The 'checkTransaction' now represents the view the database will be
452  // if the commit succeeds. We Lock 'checkTransaction' so it is
453  // Read-only (the view is immutable).
454  checkTransaction.ReadOnly(true);
455 
456  CheckConstraintViolations(checkTransaction, normalizedChangedTables, changedTableSource);
457 
458  // Deferred trigger events.
459  FireChangeEvents(checkTransaction, normalizedChangedTables, commitActions);
460 
461  // NOTE: This isn't as fail safe as it could be. We really need to
462  // do the commit in two phases. The first writes updated indices to
463  // the index files. The second updates the header pointer for the
464  // respective table. Perhaps we can make the header update
465  // procedure just one file Write.
466 
467  // Finally, at this point all constraint checks have passed and the
468  // changes are ready to finally be committed as permanent changes
469  // to the Composite. All that needs to be done is to commit our
470  // IIndexSet indices for each changed table as final.
471  // ISSUE: Should we separate the 'committing of indexes' changes and
472  // 'committing of delete/add flags' to make the FS more robust?
473  // It would be more robust if all indexes are committed in one go,
474  // then all table flag data.
475 
476  // Set flag to indicate we have committed entries.
477  entriesCommitted = true;
478 
479  // For each change to each table,
480  foreach (CommitTableInfo tableInfo in normalizedChangedTables) {
481  // Get the journal that details the change to the table.
482  TableEventRegistry changeJournal = tableInfo.Journal;
483  if (changeJournal != null) {
484  // Get the master table with this table id.
485  TableSource master = tableInfo.Master;
486  // Commit the changes to the table.
487  // We use 'this.commit_id' which is the current commit level we are
488  // at.
489  master.CommitTransactionChange(Composite.CurrentCommitId, changeJournal, tableInfo.IndexSet);
490  // Add to 'changed_tables_list'
491  changedTablesList.Add(master);
492  }
493  }
494 
495  // Only do this if we've created or dropped tables.
496  if (CreatedTables.Any() || DroppedTables.Any()) {
497  // Update the committed tables in the Composite state.
498  // This will update and synchronize the headers in this Composite.
499  Composite.CommitToTables(CreatedTables, DroppedTables);
500  }
501 
502  // Update the namespace clash list
503  if (ObjectsCreated.Any() || ObjectsDropped.Any()) {
504  nameSpaceJournals.Add(new TransactionObjectState(CommitId, ObjectsCreated, ObjectsDropped));
505  }
506  } finally {
507  try {
508  // If entries_committed == false it means we didn't get to a point
509  // where any changed tables were committed. Attempt to rollback the
510  // changes in this transaction if they haven't been committed yet.
511  if (entriesCommitted == false) {
512  // For each change to each table,
513  foreach (TableEventRegistry changeJournal in ChangedTables) {
514  // The table the changes were made to.
515  int tableId = changeJournal.TableId;
516  // Get the master table with this table id.
517  TableSource master = Composite.GetTableSource(tableId);
518  // Commit the rollback on the table.
519  master.RollbackTransactionChange(changeJournal);
520  }
521 
522  // TODO: Notify the system we're rolling back
523  }
524  } finally {
525  try {
526  // Dispose the 'checkTransaction'
527  if (checkTransaction != null) {
528  checkTransaction.Dispose();
529  Composite.CloseTransaction(checkTransaction);
530  }
531  // Always ensure a transaction close, even if we have an exception.
532  // Notify the Composite that this transaction has closed.
533  Composite.CloseTransaction(Transaction);
534  } catch (Exception) {
535  // TODO: notify the error
536  } finally {
537  Done = true;
538  }
539  }
540  }
541 
542  return changedTablesList.ToArray();
543  }
544 
549  private sealed class CommitTableInfo {
550  // The master table
552  // The immutable index set
554  // The journal describing the changes to this table by this
555  // transaction.
557  // A list of journals describing changes since this transaction
558  // started.
560  // Break down of changes to the table
561  // Normalized list of row ids that were added
562  public int[] NormalizedAddedRows;
563  // Normalized list of row ids that were removed
564  public int[] NormalizedRemovedRows;
565  }
566  }
567 }
static bool CommitTableListContains(IEnumerable< CommitTableInfo > list, TableSource master)
Returns true if the given List of CommitTableInfo objects contains an entry for the given master tabl...
A static container class for information collected about a table during the commit cycle...
Defines the contract to access the data contained into a table of a database.
Definition: ITable.cs:40
void FireChangeEvents(ITransaction checkTransaction, CommitTableInfo[] normalizedChangedTables, Action< TableCommitInfo > commitActions)
The system implementation of a transaction model that handles isolated operations within a database c...
Definition: Transaction.cs:35
IEnumerable< TableEventRegistry > FindChangesSinceCmmit(long commitId)
Describes the name of an object within a database.
Definition: ObjectName.cs:44
ConstraintDeferrability
The type of deferrance of a constraint.
IEnumerable< TableSource > Commit(IList< TransactionObjectState > nameSpaceJournals, Action< TableCommitInfo > commitActions)
IMutableTable CreateTableAtCommit(ITransaction transaction)
Definition: TableSource.cs:688
void RollbackTransactionChange(TableEventRegistry registry)
void TestCommitClash(TableInfo tableInfo, TableEventRegistry journal)
TransactionWork(TableSourceComposite composite, Transaction transaction, IEnumerable< ITableSource > selectedFromTables, IEnumerable< IMutableTable > touchedTables, TransactionRegistry journal)
An object that access to a set of indexes.
Definition: IIndexSet.cs:27
ITable[] FindChangedTables(ITransaction checkTransaction, CommitTableInfo[] normalizedChangedTables)
void CheckConflicts(IEnumerable< TransactionObjectState > namespaceJournals)
The simplest implementation of a transaction.
Definition: ITransaction.cs:30
void CommitTransactionChange(int commitId, TableEventRegistry change, IIndexSet indexSet)
Definition: TableSource.cs:696
void CheckConstraintViolations(ITransaction checkTransaction, CommitTableInfo[] normalizedChangedTables, ITable[] changedTableSource)