DeveelDB  20151217
complete SQL database system, primarly developed for .NET/Mono frameworks
TriggerManager.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 
24 using Deveel.Data.Sql.Objects;
25 using Deveel.Data.Sql.Tables;
27 using Deveel.Data.Types;
28 
29 namespace Deveel.Data.Sql.Triggers {
32  private bool tableModified;
33  private bool cacheValid;
34  private List<Trigger> triggerCache;
35 
36  public TriggerManager(ITransaction transaction) {
37  this.transaction = transaction;
38  triggerCache = new List<Trigger>(24);
39  this.transaction.RegisterOnCommit(OnCommit);
40  }
41 
43  Dispose(false);
44  }
45 
46  public void Dispose() {
47  Dispose(true);
48  GC.SuppressFinalize(this);
49  }
50 
51  private void Dispose(bool disposing) {
52  if (disposing) {
53  if (triggerCache != null)
54  triggerCache.Clear();
55  }
56 
57  triggerCache = null;
58  transaction = null;
59  cacheValid = false;
60  }
61 
63  get { return DbObjectType.Trigger; }
64  }
65 
66  private void OnTableCommit(TableCommitEvent commitEvent) {
67  if (tableModified) {
68  InvalidateTriggerCache();
69  tableModified = false;
70  } else if ((commitEvent.AddedRows != null &&
71  commitEvent.AddedRows.Any()) ||
72  (commitEvent.RemovedRows != null &&
73  commitEvent.RemovedRows.Any())) {
74  InvalidateTriggerCache();
75  }
76  }
77 
78  private void OnCommit(TableCommitInfo commitInfo) {
79  if (tableModified) {
80  InvalidateTriggerCache();
81  tableModified = false;
82  } else if ((commitInfo.AddedRows != null &&
83  commitInfo.AddedRows.Any()) ||
84  (commitInfo.RemovedRows != null &&
85  commitInfo.RemovedRows.Any())) {
86  InvalidateTriggerCache();
87  }
88  }
89 
90  private void BuildTriggerCache() {
91  if (!cacheValid) {
92  var table = transaction.GetTable(SystemSchema.TriggerTableName);
93 
94  var list = new List<Trigger>();
95  foreach (var row in table) {
96  var triggerInfo = FormTrigger(row);
97  list.Add(new Trigger(triggerInfo));
98  }
99 
100  triggerCache = new List<Trigger>(list);
101  cacheValid = true;
102  }
103  }
104 
105  private void InvalidateTriggerCache() {
106  cacheValid = false;
107  }
108 
109  public void Create() {
110  var tableInfo = new TableInfo(SystemSchema.TriggerTableName);
111  tableInfo.AddColumn("schema", PrimitiveTypes.String());
112  tableInfo.AddColumn("name", PrimitiveTypes.String());
113  tableInfo.AddColumn("type", PrimitiveTypes.Integer());
114  tableInfo.AddColumn("on_object", PrimitiveTypes.String());
115  tableInfo.AddColumn("action", PrimitiveTypes.Integer());
116  tableInfo.AddColumn("procedure_name", PrimitiveTypes.String());
117  tableInfo.AddColumn("args", PrimitiveTypes.Binary());
118  transaction.CreateTable(tableInfo);
119  }
120 
121  private ITable FindTrigger(ITable table, string schema, string name) {
122  // Find all the trigger entries with this name
123  var schemaColumn = table.GetResolvedColumnName(0);
124  var nameColumn = table.GetResolvedColumnName(1);
125 
126  using (var session = new SystemSession(transaction, SystemSchema.Name)) {
127  using (var context = session.CreateQuery()) {
128  var t = table.SimpleSelect(context, nameColumn, SqlExpressionType.Equal,
130  return t.ExhaustiveSelect(context,
132  }
133  }
134  }
135 
136  private IEnumerable<TriggerInfo> FindTriggers(ObjectName tableName, TriggerEventType eventType) {
137  var fullTableName = tableName.FullName;
138  var eventTypeCode = (int)eventType;
139 
140  var table = transaction.GetTable(SystemSchema.TriggerTableName);
141  if (table == null)
142  return new TriggerInfo[0];
143 
144  var tableColumn = table.GetResolvedColumnName(3);
145  var eventTypeColumn = table.GetResolvedColumnName(4);
146 
147  ITable result;
148  using (var session = new SystemSession(transaction, SystemSchema.Name)) {
149  using (var context = session.CreateQuery()) {
150  var t = table.SimpleSelect(context, tableColumn, SqlExpressionType.Equal,
151  SqlExpression.Constant(DataObject.String(fullTableName)));
152 
153  result = t.ExhaustiveSelect(context,
154  SqlExpression.Equal(SqlExpression.Reference(eventTypeColumn), SqlExpression.Constant(eventTypeCode)));
155  }
156  }
157 
158  if (result.RowCount == 0)
159  return new TriggerInfo[0];
160 
161  var list = new List<TriggerInfo>();
162 
163  foreach (var row in result) {
164  var triggerInfo = FormTrigger(row);
165 
166  //TODO: get the other information such has the body, the external method or the procedure
167  // if this is a non-callback
168 
169  list.Add(triggerInfo);
170  }
171 
172  return list.AsEnumerable();
173  }
174 
176  var triggerInfo = objInfo as TriggerInfo;
177  if (triggerInfo == null)
178  throw new ArgumentException();
179 
180  CreateTrigger(triggerInfo);
181  }
182 
184  return TriggerExists(objName);
185  }
186 
188  return TriggerExists(objName);
189  }
190 
192  return GetTrigger(objName);
193  }
194 
196  var triggerInfo = objInfo as TriggerInfo;
197  if (triggerInfo == null)
198  throw new ArgumentException();
199 
200  return AlterTrigger(triggerInfo);
201  }
202 
204  return DropTrigger(objName);
205  }
206 
207  public ObjectName ResolveName(ObjectName objName, bool ignoreCase) {
208  throw new NotImplementedException();
209  }
210 
211  [Serializable]
214  Arguments = args;
215  }
216 
217  private TriggerArgument(ObjectData data) {
218  Arguments = data.GetValue<SqlExpression[]>("Arguments");
219  }
220 
221  public SqlExpression[] Arguments { get; private set; }
222 
224  data.SetValue("Arguments", Arguments);
225  }
226  }
227 
228  private static byte[] SerializeArguments(TriggerArgument args) {
229  using (var stream = new MemoryStream()) {
230  using (var writer = new BinaryWriter(stream)) {
231  var serializer = new BinarySerializer();
232  serializer.Serialize(writer, args);
233 
234  writer.Flush();
235 
236  return stream.ToArray();
237  }
238  }
239  }
240 
242  CreateTrigger(triggerInfo);
243  }
244 
245  public void CreateTrigger(TriggerInfo triggerInfo) {
246  if (!transaction.TableExists(SystemSchema.TriggerTableName))
247  return;
248 
249  try {
250  var args = new TriggerArgument(triggerInfo.Arguments.ToArray());
251  var binArgs = SerializeArguments(args);
252 
253  var schema = triggerInfo.TriggerName.ParentName;
254  var name = triggerInfo.TriggerName.Name;
255  var type = (int) triggerInfo.TriggerType;
256  var onTable = triggerInfo.TableName == null ? null : triggerInfo.TableName.FullName;
257  var procedureName = triggerInfo.ProcedureName != null ? triggerInfo.ProcedureName.FullName : null;
258 
259  var action = (int) triggerInfo.EventType;
260 
261  // TODO: if the trigger has a body, create a special procedure and set the name
262 
263  // Insert the entry into the trigger table,
264  var table = transaction.GetMutableTable(SystemSchema.TriggerTableName);
265  var row = table.NewRow();
266  row.SetValue(0, DataObject.String(schema));
267  row.SetValue(1, DataObject.String(name));
268  row.SetValue(2, DataObject.Integer(type));
269  row.SetValue(3, DataObject.String(onTable));
270  row.SetValue(4, DataObject.Integer(action));
271  row.SetValue(5, DataObject.String(procedureName));
272  row.SetValue(6, DataObject.Binary(binArgs));
273  table.AddRow(row);
274 
275  InvalidateTriggerCache();
276 
277  transaction.Registry.RegisterEvent(new ObjectCreatedEvent(triggerInfo.TriggerName, DbObjectType.Trigger));
278 
279  tableModified = true;
280  } catch (Exception) {
281  // TODO: use a specialized exception
282  throw;
283  }
284  }
285 
286  public bool DropTrigger(ObjectName triggerName) {
287  throw new NotImplementedException();
288  }
289 
290  public bool TriggerExists(ObjectName triggerName) {
291  var table = transaction.GetTable(SystemSchema.TriggerTableName);
292  var result = FindTrigger(table, triggerName.ParentName, triggerName.Name);
293  if (result.RowCount == 0)
294  return false;
295 
296  if (result.RowCount > 1)
297  throw new InvalidOperationException(String.Format("More than one trigger found with name '{0}'.", triggerName));
298 
299  return true;
300  }
301 
302  public Trigger GetTrigger(ObjectName triggerName) {
303  var table = transaction.GetTable(SystemSchema.TriggerTableName);
304  var result = FindTrigger(table, triggerName.ParentName, triggerName.Name);
305  if (result.RowCount == 0)
306  return null;
307 
308  if (result.RowCount > 1)
309  throw new InvalidOperationException(String.Format("More than one trigger found with name '{0}'.", triggerName));
310 
311  var triggerInfo = FormTrigger(result.First());
312  return new Trigger(triggerInfo);
313  }
314 
315  private TriggerInfo FormTrigger(Row row) {
316  var schema = row.GetValue(0).Value.ToString();
317  var name = row.GetValue(1).Value.ToString();
318  var triggerName = new ObjectName(new ObjectName(schema), name);
319 
320  var triggerType = (TriggerType)((SqlNumber)row.GetValue(2).Value).ToInt32();
321 
322  // TODO: In case it's a procedural trigger, take the reference to the procedure
323  if (triggerType == TriggerType.Procedure)
324  throw new NotImplementedException();
325 
326  var tableName = ObjectName.Parse(((SqlString) row.GetValue(3).Value).ToString());
327  var eventType = (TriggerEventType) ((SqlNumber) row.GetValue(4).Value).ToInt32();
328  return new TriggerInfo(triggerName, triggerType, eventType, tableName);
329  }
330 
331  public bool AlterTrigger(TriggerInfo triggerInfo) {
332  throw new NotImplementedException();
333  }
334 
335  public IEnumerable<Trigger> FindTriggers(TriggerEventInfo eventInfo) {
336  var triggers = FindTriggers(eventInfo.TableName, eventInfo.EventType);
337  return triggers.Select(x => new Trigger(x));
338  }
339 
340  public void FireTriggers(IQuery context, TableEvent tableEvent) {
341  if (!transaction.TableExists(SystemSchema.TriggerTableName))
342  return;
343 
344  BuildTriggerCache();
345 
346  foreach (var trigger in triggerCache) {
347  if (trigger.CanInvoke(tableEvent))
348  trigger.Invoke(context, tableEvent);
349  }
350  }
351 
353  return new TriggersTableContainer(transaction);
354  }
355 
356  #region TriggersTableContainer
357 
360  : base(transaction, SystemSchema.TriggerTableName) {
361  }
362 
363  public override TableInfo GetTableInfo(int offset) {
364  var triggerName = GetTableName(offset);
365  return CreateTableInfo(triggerName.ParentName, triggerName.Name);
366  }
367 
368  public override string GetTableType(int offset) {
369  return TableTypes.Trigger;
370  }
371 
372  private static TableInfo CreateTableInfo(string schema, string name) {
373  var tableInfo = new TableInfo(new ObjectName(new ObjectName(schema), name));
374 
375  tableInfo.AddColumn("type", PrimitiveTypes.Numeric());
376  tableInfo.AddColumn("on_object", PrimitiveTypes.String());
377  tableInfo.AddColumn("routine_name", PrimitiveTypes.String());
378  tableInfo.AddColumn("param_args", PrimitiveTypes.String());
379  tableInfo.AddColumn("owner", PrimitiveTypes.String());
380 
381  return tableInfo.AsReadOnly();
382  }
383 
384 
385  public override ITable GetTable(int offset) {
386  var table = Transaction.GetTable(SystemSchema.TriggerTableName);
387  var enumerator = table.GetEnumerator();
388  int p = 0;
389  int i;
390  int rowIndex = -1;
391  while (enumerator.MoveNext()) {
392  i = enumerator.Current.RowId.RowNumber;
393  if (p == offset) {
394  rowIndex = i;
395  } else {
396  ++p;
397  }
398  }
399 
400  if (p != offset)
401  throw new ArgumentOutOfRangeException("offset");
402 
403  var schema = table.GetValue(rowIndex, 0).Value.ToString();
404  var name = table.GetValue(rowIndex, 1).Value.ToString();
405 
406  var tableInfo = CreateTableInfo(schema, name);
407 
408  var type = table.GetValue(rowIndex, 2);
409  var tableName = table.GetValue(rowIndex, 3);
410  var routine = table.GetValue(rowIndex, 4);
411  var args = table.GetValue(rowIndex, 5);
412  var owner = table.GetValue(rowIndex, 6);
413 
414  return new TriggerTable(Transaction, tableInfo) {
415  Type = type,
416  TableName = tableName,
417  Routine = routine,
418  Arguments = args,
419  Owner = owner
420  };
421  }
422 
423  #region TriggerTable
424 
427 
428  public TriggerTable(ITransaction transaction, TableInfo tableInfo)
429  : base(transaction.Database.Context) {
430  this.tableInfo = tableInfo;
431  }
432 
433  public override TableInfo TableInfo {
434  get { return tableInfo; }
435  }
436 
437  public DataObject Type { get; set; }
438 
439  public DataObject TableName { get; set; }
440 
441  public DataObject Routine { get; set; }
442 
443  public DataObject Arguments { get; set; }
444 
445  public DataObject Owner { get; set; }
446 
447  public override int RowCount {
448  get { return 1; }
449  }
450 
451  public override DataObject GetValue(long rowNumber, int columnOffset) {
452  if (rowNumber > 0)
453  throw new ArgumentOutOfRangeException("rowNumber");
454 
455  switch (columnOffset) {
456  case 0:
457  return Type;
458  case 1:
459  return TableName;
460  case 2:
461  return Routine;
462  case 3:
463  return Arguments;
464  case 4:
465  return Owner;
466  default:
467  throw new ArgumentOutOfRangeException("columnOffset");
468  }
469  }
470  }
471 
472  #endregion
473  }
474 
475  #endregion
476  }
477 }
Provides some helper functions for resolving and creating SqlType instances that are primitive to the...
static TableInfo CreateTableInfo(string schema, string name)
bool AlterObject(IObjectInfo objInfo)
Modifies an existing object managed, identified by IObjectInfo.FullName component of the given specif...
static DataObject Integer(int value)
Definition: DataObject.cs:576
bool DropObject(ObjectName objName)
Deletes a database object handled by this manager from the system.
void GetData(SerializeData data)
Defines the contract to access the data contained into a table of a database.
Definition: ITable.cs:40
static DataObject Binary(SqlBinary binary)
Definition: DataObject.cs:638
Defines the information about a trigger on a table of the database, such as the event on which is fir...
Definition: TriggerInfo.cs:29
static ObjectName Parse(string s)
Parses the given string into a ObjectName object.
Definition: ObjectName.cs:139
A long string in the system.
The system implementation of a transaction model that handles isolated operations within a database c...
Definition: Transaction.cs:35
TriggerEventType
The different types of high layer trigger events.
bool RealObjectExists(ObjectName objName)
Checks if an object really exists in the system.
DataObject GetValue(int columnOffset)
Gets or the value of a cell of the row at the given offset.
Definition: Row.cs:203
IEnumerable< TriggerInfo > FindTriggers(ObjectName tableName, TriggerEventType eventType)
Trigger GetTrigger(ObjectName triggerName)
TriggerManager(ITransaction transaction)
void OnCommit(TableCommitInfo commitInfo)
ObjectName ProcedureName
Gets or sets the name of a stored procedure to be executed when the trigger is fired.
Definition: TriggerInfo.cs:107
ObjectName ResolveName(ObjectName objName, bool ignoreCase)
Normalizes the input object name using the case sensitivity specified.
Represents a database object, such as a table, a trigger, a type or a column.
Definition: IDbObject.cs:24
An event fired when a database object of the given type is created during the lifetime of a transacti...
static BinaryType Binary(int maxSize)
static byte[] SerializeArguments(TriggerArgument args)
void SetValue(string key, Type type, object value)
The default implementation of a database in a system.
Definition: Database.cs:38
static SqlBinaryExpression Equal(SqlExpression left, SqlExpression right)
ITable FindTrigger(ITable table, string schema, string name)
Describes the name of an object within a database.
Definition: ObjectName.cs:44
const string Trigger
Definition: TableTypes.cs:28
ISqlObject Value
Gets the underlined value that is handled.
Definition: DataObject.cs:84
A single row in a table of a database.
Definition: Row.cs:44
static DataObject String(string s)
Definition: DataObject.cs:592
TriggerEventType EventType
Gets the type of event that happened on the table.
ICollection< SqlExpression > Arguments
Definition: TriggerInfo.cs:113
SqlExpressionType
All the possible type of SqlExpression supported
ObjectName TableName
Gets the fully qualified name of the table where the event happened.
static readonly ObjectName TriggerTableName
TriggerEventType EventType
Gets the modification event on the attached table at which to fire the trigger.
Definition: TriggerInfo.cs:84
A routine (PROCEDURE or FUNCTION) defined in a database, that is a program with defined input paramet...
A user-defined TYPE that holds complex objects in a database column.
override string GetTableType(int offset)
Gets the type of the table at the given offset.
ObjectName TriggerName
Gets the fully qualified name of the trigger.
Definition: TriggerInfo.cs:78
void OnTableCommit(TableCommitEvent commitEvent)
static NumericType Numeric()
void CreateObject(IObjectInfo objInfo)
Create a new object of the ObjectType given the specifications given.
Provides the constant names of the types of tables in a database system.
Definition: TableTypes.cs:24
TriggerType
Enumerates the types of triggers, that can be volatile (like the Callback) or stored in the database...
Definition: TriggerType.cs:22
void RegisterOnCommit(Action< TableCommitInfo > action)
void CreateTrigger(TriggerInfo triggerInfo)
Represents a dynamic object that encapsulates a defined SqlType and a compatible constant ISqlObject ...
Definition: DataObject.cs:35
A container for any system tables that are generated from information inside the database engine...
Provides utilities and properties for handling the SYSTEN schema of a database.
Definition: SystemSchema.cs:37
IEnumerable< Trigger > FindTriggers(TriggerEventInfo eventInfo)
string FullName
Gets the full reference name formatted.
Definition: ObjectName.cs:114
int RowCount
Gets the total number of rows in the table.
Definition: ITable.cs:52
DbObjectType ObjectType
Gets the type of objects managed by this instance.
override DataObject GetValue(long rowNumber, int columnOffset)
Gets a single cell within the table that is located at the given column offset and row...
Exposes the context of an event fired on a table.
Definition: TableEvent.cs:26
bool TriggerExists(ObjectName triggerName)
void FireTriggers(IQuery context, TableEvent tableEvent)
ObjectName TableName
Gets the fully qualified name of the database table on which to attach the trigger.
Definition: TriggerInfo.cs:96
override ITable GetTable(int offset)
Gets the table contained at the given offset within the context.
string Name
Gets the name of the object being referenced.
Definition: ObjectName.cs:108
Represents an event fired at a given modification event (either INSERT, DELETE or UPDATE) at a given ...
Definition: Trigger.cs:38
static SqlReferenceExpression Reference(ObjectName objectName)
IDbObject GetObject(ObjectName objName)
Gets a database object managed by this manager.
bool ObjectExists(ObjectName objName)
Checks if an object identified by the given name is managed by this instance.
Defines the base class for instances that represent SQL expression tree nodes.
Defines the contract for the business managers of database objects of a given type.
const string Name
The name of the system schema that contains tables referring to system information.
static SqlConstantExpression Constant(object value)
The simplest implementation of a transaction.
Definition: ITransaction.cs:30
An object that defines the arguments of an event, used to find triggers associated.
void Create()
Initializes the manager into the underlying system.
DbObjectType
The kind of objects that can be handled by a database system and its managers
Definition: DbObjectType.cs:27
TriggerType TriggerType
Gets the type of trigger.
Definition: TriggerInfo.cs:90
Defines the metadata properties of a table existing within a database.
Definition: TableInfo.cs:41
bool AlterTrigger(TriggerInfo triggerInfo)
void RegisterTrigger(TriggerInfo triggerInfo)
bool DropTrigger(ObjectName triggerName)
override TableInfo GetTableInfo(int offset)
Gets the information of the table at the given offset in this container.