DeveelDB  20151217
complete SQL database system, primarly developed for .NET/Mono frameworks
TableSourceComposite.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.IO;
20 using System.Linq;
21 
22 using Deveel.Data.Index;
23 using Deveel.Data.Sql;
24 using Deveel.Data.Sql.Tables;
25 using Deveel.Data.Store;
27 
28 namespace Deveel.Data {
30  private readonly object commitLock = new object();
31  private Dictionary<int, TableSource> tableSources;
32 
33  private List<TransactionObjectState> objectStates;
34 
36  private IStore lobStore;
37  private IStore stateStore;
38 
39  private const string StateStorePostfix = "_sf";
40 
41  public const string ObjectStoreName = "lob_store";
42 
43  public TableSourceComposite(Database database) {
44  Database = database;
45 
46  tempStoreSystem = new InMemoryStorageSystem();
47  objectStates = new List<TransactionObjectState>();
48 
49  StateStoreName = String.Format("{0}{1}", database.Name, StateStorePostfix);
50 
51  Setup();
52  }
53 
55  Dispose(false);
56  }
57 
58  public Database Database { get; private set; }
59 
61  get { return Database.Context; }
62  }
63 
64  private IStoreSystem StoreSystem {
65  get { return DatabaseContext.StoreSystem; }
66  }
67 
68  public int CurrentCommitId { get; private set; }
69 
70  private bool IsReadOnly {
71  get { return Database.Context.ReadOnly(); }
72  }
73 
74  private bool IsClosed {
75  get { return tableSources == null; }
76  }
77 
78  private TableStateStore StateStore { get; set; }
79 
80  private string StateStoreName { get; set; }
81 
82  private IObjectStore LargeObjectStore { get; set; }
83 
84  private void ReadVisibleTables() {
85  lock (commitLock) {
86  var tables = StateStore.GetVisibleList();
87 
88  // For each visible table
89  foreach (var resource in tables) {
90  var tableId = resource.TableId;
91  var sourceName = resource.SourceName;
92 
93  // TODO: add a table source type?
94 
95  // Load the master table from the resource information
96  var source = LoadTableSource(tableId, sourceName);
97 
98  if (source == null)
99  throw new InvalidOperationException(String.Format("Table {0} was not found.", sourceName));
100 
101  source.Open();
102 
103  tableSources.Add(tableId, source);
104  }
105  }
106  }
107 
108  private void ReadDroppedTables() {
109  lock (commitLock) {
110  // The list of all dropped tables from the state file
111  var tables = StateStore.GetDeleteList();
112 
113  // For each visible table
114  foreach (var resource in tables) {
115  int tableId =resource.TableId;
116  string tableName = resource.SourceName;
117 
118  // Load the master table from the resource information
119  var source = LoadTableSource(tableId, tableName);
120 
121  // File wasn't found so remove from the delete resources
122  if (source == null) {
123  StateStore.RemoveDeleteResource(tableName);
124  } else {
125  source.Open();
126 
127  // Add the table to the table list
128  tableSources.Add(tableId, source);
129  }
130  }
131 
132  StateStore.Flush();
133  }
134  }
135 
136  private TableSource LoadTableSource(int tableId, string tableName) {
137  var source = new TableSource(this, StoreSystem, LargeObjectStore, tableId, tableName);
138  if (!source.Exists())
139  return null;
140 
141  return source;
142  }
143 
144  private void MarkUncommitted(int tableId) {
145  var masterTable = GetTableSource(tableId);
146  StateStore.AddDeleteResource(new TableStateStore.TableState(tableId, masterTable.SourceName));
147  }
148 
149  //public ITransaction CreateTransaction(TransactionIsolation isolation) {
150  // var thisCommittedTables = new List<TableSource>();
151 
152  // // Don't let a commit happen while we are looking at this.
153  // lock (commitLock) {
154  // long thisCommitId = CurrentCommitId;
155  // var committedTableList = StateStore.GetVisibleList();
156  // thisCommittedTables.AddRange(committedTableList.Select(resource => GetTableSource(resource.TableId)));
157 
158  // // Create a set of IIndexSet for all the tables in this transaction.
159  // var indexInfo = (thisCommittedTables.Select(mtable => mtable.CreateIndexSet())).ToList();
160 
161  // // Create the transaction and record it in the open transactions list.
162  // var t = new Transaction(this, thisCommitId, isolation, thisCommittedTables, indexInfo);
163  // openTransactions.AddTransaction(t);
164  // return t;
165  // }
166 
167  //}
168 
169  private void Dispose(bool disposing) {
170  if (disposing) {
171  if (!IsClosed)
172  Close();
173 
174  if (lobStore != null)
175  lobStore.Dispose();
176  if (stateStore != null)
177  stateStore.Dispose();
178 
179  if (tempStoreSystem != null)
180  tempStoreSystem.Dispose();
181 
182  tempStoreSystem = null;
183  lobStore = null;
184  }
185  }
186 
187  public void Dispose() {
188  Dispose(true);
189  GC.SuppressFinalize(this);
190  }
191 
192  private void Setup() {
193  lock (this) {
194  CurrentCommitId = 0;
195  tableSources = new Dictionary<int, TableSource>();
196  }
197  }
198 
199  private void InitObjectStore() {
200  // Does the file already exist?
201  bool blobStoreExists = StoreSystem.StoreExists(ObjectStoreName);
202  // If the blob store doesn't exist and we are read_only, we can't do
203  // anything further so simply return.
204  if (!blobStoreExists && IsReadOnly) {
205  return;
206  }
207 
208  // The blob store,
209  if (blobStoreExists) {
210  lobStore = StoreSystem.OpenStore(ObjectStoreName);
211  } else {
212  lobStore = StoreSystem.CreateStore(ObjectStoreName);
213  }
214 
215  try {
216  lobStore.Lock();
217 
218  // TODO: have multiple BLOB stores
219  LargeObjectStore = new ObjectStore(0, lobStore);
220 
221  // Get the 64 byte fixed area
222  var fixedArea = lobStore.GetArea(-1, false);
223  // If the blob store didn't exist then we need to create it here,
224  if (!blobStoreExists) {
225  long headerP = LargeObjectStore.Create();
226  fixedArea.WriteInt8(headerP);
227  fixedArea.Flush();
228  } else {
229  // Otherwise we need to initialize the blob store
230  long headerP = fixedArea.ReadInt8();
231  LargeObjectStore.Open(headerP);
232  }
233  } finally {
234  lobStore.Unlock();
235  }
236  }
237 
238  private void CleanUp() {
239  lock (commitLock) {
240  if (IsClosed)
241  return;
242 
243  // If no open transactions on the database, then clean up.
245  var deleteList = StateStore.GetDeleteList().ToArray();
246  if (deleteList.Length > 0) {
247  int dropCount = 0;
248 
249  for (int i = deleteList.Length - 1; i >= 0; --i) {
250  var tableName = deleteList[i].SourceName;
251  CloseTable(tableName, true);
252  }
253 
254  for (int i = deleteList.Length - 1; i >= 0; --i) {
255  string tableName = deleteList[i].SourceName;
256  bool dropped = CloseAndDropTable(tableName);
257  // If we managed to drop the table, remove from the list.
258  if (dropped) {
259  StateStore.RemoveDeleteResource(tableName);
260  ++dropCount;
261  }
262  }
263 
264  // If we dropped a table, commit an update to the conglomerate state.
265  if (dropCount > 0)
266  StateStore.Flush();
267  }
268  }
269  }
270  }
271 
272  private bool CloseAndDropTable(string tableFileName) {
273  // Find the table with this file name.
274  int? tableId = null;
275  foreach (var source in tableSources.Values) {
276  if (source.StoreIdentity.Equals(tableFileName)) {
277  if (source.IsRootLocked)
278  return false;
279 
280  if (!source.Drop())
281  return false;
282 
283  tableId = source.TableId;
284  }
285  }
286 
287  if (tableId != null)
288  tableSources.Remove(tableId.Value);
289 
290  return false;
291  }
292 
293  private void CloseTable(string sourceName, bool pendingDrop) {
294  // Find the table with this file name.
295  foreach (var source in tableSources.Values) {
296  if (source.SourceName.Equals(sourceName)) {
297  if (source.IsRootLocked)
298  break;
299 
300  source.Close(pendingDrop);
301  break;
302  }
303  }
304  }
305 
306  public bool Exists() {
307  return StoreSystem.StoreExists(StateStoreName);
308  }
309 
310  public void Open() {
311  if (!Exists())
312  throw new IOException("Table composite does not exist");
313 
314  // Check the file Lock
315  if (!IsReadOnly) {
316  // Obtain the Lock (generate error if this is not possible)
317  StoreSystem.Lock(StateStoreName);
318  }
319 
320  // Open the state store
321  stateStore = StoreSystem.OpenStore(StateStoreName);
322  StateStore = new TableStateStore(stateStore);
323 
324  // Get the fixed 64 byte area.
325  var fixedArea = stateStore.GetArea(-1);
326  long headP = fixedArea.ReadInt8();
327  StateStore.Open(headP);
328 
329  Setup();
330 
331  InitObjectStore();
332 
333  ReadVisibleTables();
334  ReadDroppedTables();
335 
336  CleanUp();
337  }
338 
339  public void Create() {
340  MinimalCreate();
341 
342  // Initialize the conglomerate system tables.
343  InitSystemSchema();
344 
345  // Commit the state
346  StateStore.Flush();
347  }
348 
349  private void InitSystemSchema() {
350  using (var transaction = Database.CreateSafeTransaction(IsolationLevel.Serializable)) {
351  try {
352  SystemSchema.Setup(transaction);
353  transaction.Commit();
354  } catch (Exception ex) {
355  throw new InvalidOperationException("Transaction Exception initializing tables.", ex);
356  }
357  }
358  }
359 
360  internal void MinimalCreate() {
361  if (Exists())
362  throw new IOException("Composite already exists");
363 
364  // Lock the store system (generates an IOException if exclusive Lock
365  // can not be made).
366  if (!IsReadOnly) {
367  StoreSystem.Lock(StateStoreName);
368  }
369 
370  // Create/Open the state store
371  stateStore = StoreSystem.CreateStore(StateStoreName);
372  try {
373  stateStore.Lock();
374 
375  StateStore = new TableStateStore(stateStore);
376  long headP = StateStore.Create();
377  // Get the fixed area
378  var fixedArea = stateStore.GetArea(-1);
379  fixedArea.WriteInt8(headP);
380  fixedArea.Flush();
381  } finally {
382  stateStore.Unlock();
383  }
384 
385  Setup();
386 
387  // Init the conglomerate blob store
388  InitObjectStore();
389 
390  // Create the system table (but don't initialize)
391  CreateSystemSchema();
392  }
393 
394  private void CreateSystemSchema() {
395  // Create the transaction
396  ITransaction transaction = null;
397 
398  try {
399  transaction = Database.CreateSafeTransaction(IsolationLevel.Serializable);
400  transaction.CreateSystemSchema();
401 
402  // Commit and close the transaction.
403  transaction.Commit();
404  transaction = null;
405  } catch (TransactionException e) {
406  throw new InvalidOperationException("Transaction Exception creating composite.", e);
407  } finally {
408  if (transaction != null)
409  transaction.Rollback();
410  }
411  }
412 
413  public void Close() {
414  lock (commitLock) {
415  CleanUp();
416 
417  StoreSystem.SetCheckPoint();
418 
419  // Go through and close all the committed tables.
420  foreach (var source in tableSources.Values) {
421  source.Close(false);
422  }
423 
424  StateStore.Flush();
425  StoreSystem.CloseStore(stateStore);
426 
427  tableSources = null;
428  }
429 
430  // Release the storage system
431  StoreSystem.Unlock(StateStoreName);
432 
433  if (LargeObjectStore != null)
434  StoreSystem.CloseStore(lobStore);
435  }
436 
437  public void Delete() {
438  lock (commitLock) {
439  // We possibly have things to clean up.
440  CleanUp();
441 
442  // Go through and delete and close all the committed tables.
443  foreach (var source in tableSources.Values)
444  source.Drop();
445 
446  // Delete the state file
447  StateStore.Flush();
448  StoreSystem.CloseStore(stateStore);
449  StoreSystem.DeleteStore(stateStore);
450 
451  // Delete the blob store
452  if (LargeObjectStore != null) {
453  StoreSystem.CloseStore(lobStore);
454  StoreSystem.DeleteStore(lobStore);
455  }
456 
457  tableSources = null;
458  }
459 
460  // Release the storage system.
461  StoreSystem.Unlock(StateStoreName);
462  }
463 
465  return CreateTableSource(tableInfo, temporary);
466  }
467 
468  internal TableSource CreateTableSource(TableInfo tableInfo, bool temporary) {
469  lock (commitLock) {
470  try {
471  int tableId = NextTableId();
472 
473  // Create the object.
474  var storeSystem = StoreSystem;
475  if (temporary)
476  storeSystem = tempStoreSystem;
477 
478  var source = new TableSource(this, storeSystem, LargeObjectStore, tableId, tableInfo.TableName.FullName);
479  source.Create(tableInfo);
480 
481  tableSources.Add(tableId, source);
482 
483  if (!temporary) {
484  MarkUncommitted(tableId);
485 
486  StateStore.Flush();
487  }
488 
489  // And return it.
490  return source;
491  } catch (IOException e) {
492  throw new InvalidOperationException(String.Format("Unable to create source for table '{0}'.", tableInfo.TableName), e);
493  }
494  }
495  }
496 
497  internal TableSource GetTableSource(int tableId) {
498  lock (commitLock) {
499  if (tableSources == null)
500  return null;
501 
502  TableSource source;
503  if (!tableSources.TryGetValue(tableId, out source))
504  throw new ObjectNotFoundException(
505  String.Format("Could not find any source for table with id {0} in this composite.", tableId));
506 
507  return source;
508  }
509  }
510 
511  public int NextTableId() {
512  return StateStore.NextTableId();
513  }
514 
515  private void OnCommitModification(ObjectName objName, IEnumerable<int> addedRows, IEnumerable<int> removedRows) {
516 
517  }
518 
519  internal void Commit(Transaction transaction, IList<ITableSource> visibleTables,
520  IEnumerable<ITableSource> selectedFromTables,
521  IEnumerable<IMutableTable> touchedTables, TransactionRegistry journal, Action<TableCommitInfo> commitActions) {
522 
523  var state = new TransactionWork(this, transaction, selectedFromTables, touchedTables, journal);
524 
525  // Exit early if nothing changed (this is a Read-only transaction)
526  if (!state.HasChanges) {
527  CloseTransaction(state.Transaction);
528  return;
529  }
530 
531  lock (commitLock) {
532  var changedTablesList = state.Commit(objectStates, commitActions);
533 
534  // Flush the journals up to the minimum commit id for all the tables
535  // that this transaction changed.
537  foreach (var master in changedTablesList) {
538  master.MergeChanges(minCommitId);
539  }
540  int nsjsz = objectStates.Count;
541  for (int i = nsjsz - 1; i >= 0; --i) {
542  var namespaceJournal = objectStates[i];
543  // Remove if the commit id for the journal is less than the minimum
544  // commit id
545  if (namespaceJournal.CommitId < minCommitId) {
546  objectStates.RemoveAt(i);
547  }
548  }
549 
550  // Set a check point in the store system. This means that the
551  // persistance state is now stable.
552  StoreSystem.SetCheckPoint();
553  }
554  }
555 
556  internal void Rollback(Transaction transaction, IList<IMutableTable> touchedTables, TransactionRegistry journal) {
557  // Go through the journal. Any rows added should be marked as deleted
558  // in the respective master table.
559 
560  // Get individual journals for updates made to tables in this
561  // transaction.
562  // The list MasterTableJournal
563  var journalList = new List<TableEventRegistry>();
564  for (int i = 0; i < touchedTables.Count; ++i) {
565  var tableJournal = touchedTables[i].EventRegistry;
566  if (tableJournal.EventCount > 0) // Check the journal has entries.
567  journalList.Add(tableJournal);
568  }
569 
570  var changedTables = journalList.ToArray();
571 
572  lock (commitLock) {
573  try {
574  // For each change to each table,
575  foreach (var changeJournal in changedTables) {
576  // The table the changes were made to.
577  int tableId = changeJournal.TableId;
578  // Get the master table with this table id.
579  var master = GetTableSource(tableId);
580  // Commit the rollback on the table.
581  master.RollbackTransactionChange(changeJournal);
582  }
583  } finally {
584  // Notify the conglomerate that this transaction has closed.
585  CloseTransaction(transaction);
586  }
587  }
588  }
589 
591  return CopySourceTable((TableSource) tableSource, indexSet);
592  }
593 
594  internal TableSource CopySourceTable(TableSource tableSource, IIndexSet indexSet) {
595  lock (commitLock) {
596  try {
597  // The unique id that identifies this table,
598  int tableId = NextTableId();
599  var sourceName = tableSource.SourceName;
600 
601  // Create the object.
602  var masterTable = new TableSource(this, StoreSystem, LargeObjectStore, tableId, sourceName);
603 
604  masterTable.CopyFrom(tableId, tableSource, indexSet);
605 
606  // Add to the list of all tables.
607  tableSources.Add(tableId, masterTable);
608 
609  // Add this to the list of deleted tables,
610  MarkUncommitted(tableId);
611 
612  // Commit this
613  StateStore.Flush();
614 
615  // And return it.
616  return masterTable;
617  } catch (IOException e) {
618  throw new Exception(String.Format("Unable to copy source table '{0}' because of an error.", tableSource.TableInfo.TableName), e);
619  }
620  }
621  }
622 
624  var thisCommittedTables = new List<TableSource>();
625 
626  // Don't let a commit happen while we are looking at this.
627  lock (commitLock) {
628  int thisCommitId = CurrentCommitId;
629  var committedTableList = StateStore.GetVisibleList();
630  thisCommittedTables.AddRange(committedTableList.Select(resource => GetTableSource(resource.TableId)));
631 
632  // Create a set of IIndexSet for all the tables in this transaction.
633  var indexInfo = (thisCommittedTables.Select(mtable => mtable.CreateIndexSet())).ToList();
634 
635  // Create a context for the transaction to handle the isolated storage of variables and services
637 
638  // Create the transaction and record it in the open transactions list.
639  return new Transaction(context, Database, thisCommitId, isolation, thisCommittedTables, indexInfo);
640  }
641  }
642 
643  private Action<TableCommitInfo> tableCommitCallback;
644 
645  internal void RegisterOnCommit(Action<TableCommitInfo> action) {
646  if (tableCommitCallback == null) {
647  tableCommitCallback = action;
648  } else {
649  tableCommitCallback = (Action<TableCommitInfo>) Delegate.Combine(tableCommitCallback, action);
650  }
651  }
652 
653  internal void UnregisterOnCommit(Action<TableCommitInfo> action) {
654  tableCommitCallback = Delegate.Remove(tableCommitCallback, action) as Action<TableCommitInfo>;
655  }
656 
657  internal void CloseTransaction(ITransaction transaction) {
658  bool lastTransaction;
659  // Closing must happen under a commit Lock.
660  lock (commitLock) {
662  // Increment the commit id.
663  ++CurrentCommitId;
664  // Was that the last transaction?
665  lastTransaction = Database.TransactionFactory.OpenTransactions.Count == 0;
666  }
667 
668  // If last transaction then schedule a clean up event.
669  if (lastTransaction) {
670  try {
671  CleanUp();
672  } catch (IOException) {
673  // TODO: Register the error ...
674  }
675  }
676  }
677 
678  internal void CommitToTables(IEnumerable<int> createdTables, IEnumerable<int> droppedTables) {
679  // Add created tables to the committed tables list.
680  foreach (int createdTable in createdTables) {
681  // For all created tables, add to the visible list and remove from the
682  // delete list in the state store.
683  var t = GetTableSource(createdTable);
684  var resource = new TableStateStore.TableState(t.TableId, t.SourceName);
685  StateStore.AddVisibleResource(resource);
686  StateStore.RemoveDeleteResource(resource.SourceName);
687  }
688 
689  // Remove dropped tables from the committed tables list.
690  foreach (int droppedTable in droppedTables) {
691  // For all dropped tables, add to the delete list and remove from the
692  // visible list in the state store.
693  var t = GetTableSource(droppedTable);
694  var resource = new TableStateStore.TableState(t.TableId, t.SourceName);
695  StateStore.AddDeleteResource(resource);
696  StateStore.RemoveVisibleResource(resource.SourceName);
697  }
698 
699  try {
700  StateStore.Flush();
701  } catch (IOException e) {
702  throw new InvalidOperationException("IO Error: " + e.Message, e);
703  }
704  }
705 
706  internal bool ContainsVisibleResource(int resourceId) {
707  return StateStore.ContainsVisibleResource(resourceId);
708  }
709  }
710 }
void Commit(Transaction transaction, IList< ITableSource > visibleTables, IEnumerable< ITableSource > selectedFromTables, IEnumerable< IMutableTable > touchedTables, TransactionRegistry journal, Action< TableCommitInfo > commitActions)
TableSource GetTableSource(int tableId)
static void Setup(ITransaction transaction)
void CloseTable(string sourceName, bool pendingDrop)
The context of a single database within a system.
The system implementation of a transaction model that handles isolated operations within a database c...
Definition: Transaction.cs:35
Defines the contract for stores that handle lrge objects within a database system.
Definition: IObjectStore.cs:35
Dictionary< int, TableSource > tableSources
Action< TableCommitInfo > tableCommitCallback
List< TransactionObjectState > objectStates
void Lock()
This method is called before the start of a sequence of Write commands between consistant states of s...
An object that creates and manages the IStore objects that the database engine uses to represent itse...
Definition: IStoreSystem.cs:31
void CommitToTables(IEnumerable< int > createdTables, IEnumerable< int > droppedTables)
The default implementation of a database in a system.
Definition: Database.cs:38
ITransaction CreateTransaction(IsolationLevel isolation)
TableSource LoadTableSource(int tableId, string tableName)
Describes the name of an object within a database.
Definition: ObjectName.cs:44
IContext IEventSource. Context
Definition: Database.cs:95
ITableSource CreateTableSource(TableInfo tableInfo, bool temporary)
ObjectName TableName
Gets the fully qualified name of the table that is ensured to be unique within the system...
Definition: TableInfo.cs:97
void UnregisterOnCommit(Action< TableCommitInfo > action)
bool CloseAndDropTable(string tableFileName)
void OnCommitModification(ObjectName objName, IEnumerable< int > addedRows, IEnumerable< int > removedRows)
void Rollback(Transaction transaction, IList< IMutableTable > touchedTables, TransactionRegistry journal)
void Rollback()
Rollback any write operations done during the lifetime of this transaction and invalidates it...
void RegisterOnCommit(Action< TableCommitInfo > action)
ITransactionFactory TransactionFactory
Gets an object that is used to create new transactions to this database
Definition: Database.cs:89
Provides utilities and properties for handling the SYSTEN schema of a database.
Definition: SystemSchema.cs:37
string FullName
Gets the full reference name formatted.
Definition: ObjectName.cs:114
An object that access to a set of indexes.
Definition: IIndexSet.cs:27
void Commit()
Commits all write operation done during the lifetime of this transaction and invalidates it...
void Unlock()
This method is called after the end of a sequence of Write commands between consistant states of some...
void CloseTransaction(ITransaction transaction)
ITableSource CopySourceTable(ITableSource tableSource, IIndexSet indexSet)
IArea GetArea(long id, bool readOnly)
Returns an object that allows for the contents of an area (represented by the id parameter) to be Re...
TableSource CreateTableSource(TableInfo tableInfo, bool temporary)
ITransactionContext IDatabaseContext. CreateTransactionContext()
Creates a context to handle services and variables in the scope of a transaction. ...
The simplest implementation of a transaction.
Definition: ITransaction.cs:30
Defines the metadata properties of a table existing within a database.
Definition: TableInfo.cs:41
string Name
Gets the database name, as configured in the parent context.
Definition: Database.cs:77
A store is a resource where areas can be allocated and freed to store information (a memory allocator...
Definition: IStore.cs:56
bool ContainsVisibleResource(int resourceId)
TransactionCollection OpenTransactions
Gets the collection of currently open transactions.
TableSource CopySourceTable(TableSource tableSource, IIndexSet indexSet)