1 /*
2 # PostgreSQL Database Modeler (pgModeler)
3 #
4 # Copyright 2006-2020 - Raphael Araújo e Silva <raphael@pgmodeler.io>
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation version 3.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # The complete text of GPLv3 is at LICENSE file on source code root directory.
16 # Also, you can get the complete GNU General Public License at <http://www.gnu.org/licenses/>
17 */
18 
19 #include "trigger.h"
20 
Trigger()21 Trigger::Trigger()
22 {
23 	unsigned i;
24 	EventType tipos[4]={EventType::OnInsert, EventType::OnDelete,
25 						EventType::OnUpdate, EventType::OnTruncate};
26 
27 	function=nullptr;
28 	is_exec_per_row=is_constraint=is_deferrable=false;
29 	obj_type=ObjectType::Trigger;
30 	referenced_table=nullptr;
31 
32 	for(i=0; i < 4; i++)
33 		events[tipos[i]]=false;
34 
35 	attributes[Attributes::Arguments]="";
36 	attributes[Attributes::Events]="";
37 	attributes[Attributes::TriggerFunc]="";
38 	attributes[Attributes::Table]="";
39 	attributes[Attributes::Columns]="";
40 	attributes[Attributes::FiringType]="";
41 	attributes[Attributes::PerRow]="";
42 	attributes[Attributes::InsEvent]="";
43 	attributes[Attributes::DelEvent]="";
44 	attributes[Attributes::UpdEvent]="";
45 	attributes[Attributes::TruncEvent]="";
46 	attributes[Attributes::Condition]="";
47 	attributes[Attributes::RefTable]="";
48 	attributes[Attributes::DeferType]="";
49 	attributes[Attributes::Deferrable]="";
50 	attributes[Attributes::DeclInTable]="";
51 	attributes[Attributes::Constraint]="";
52 	attributes[Attributes::OldTableName]="";
53 	attributes[Attributes::NewTableName]="";
54 }
55 
addArgument(const QString & arg)56 void Trigger::addArgument(const QString &arg)
57 {
58 	arguments.push_back(arg);
59 }
60 
setArgumentAttribute(unsigned def_type)61 void Trigger::setArgumentAttribute(unsigned def_type)
62 {
63 	QString str_args;
64 	unsigned i, count;
65 
66 	count=arguments.size();
67 	for(i=0; i < count; i++)
68 	{
69 		if(def_type==SchemaParser::SqlDefinition)
70 			str_args+=QString("'") + arguments[i] + QString("'");
71 		else
72 			str_args+=arguments[i];
73 
74 		if(i < (count-1)) str_args+=QString(",");
75 	}
76 
77 	attributes[Attributes::Arguments]=str_args;
78 }
79 
setFiringType(FiringType firing_type)80 void Trigger::setFiringType(FiringType firing_type)
81 {
82 	setCodeInvalidated(this->firing_type != firing_type);
83 	this->firing_type=firing_type;
84 }
85 
setEvent(EventType event,bool value)86 void Trigger::setEvent(EventType event, bool value)
87 {
88 	if(event==EventType::OnSelect)
89 		throw Exception(ErrorCode::RefInvalidTriggerEvent,__PRETTY_FUNCTION__,__FILE__,__LINE__);
90 
91 	setCodeInvalidated(events[event] != value);
92 	events[event]=value;
93 }
94 
setFunction(Function * func)95 void Trigger::setFunction(Function *func)
96 {
97 	//Case the function is null an error is raised
98 	if(!func)
99 		throw Exception(Exception::getErrorMessage(ErrorCode::AsgNotAllocatedFunction)
100 						.arg(this->getName())
101 						.arg(BaseObject::getTypeName(ObjectType::Trigger)),
102 						ErrorCode::AsgNotAllocatedFunction,__PRETTY_FUNCTION__,__FILE__,__LINE__);
103 	else
104 	{
105 		//Case the function doesn't returns 'trigger' it cannot be used with the trigger thus raise an error
106 		if(func->getReturnType()!=QString("trigger"))
107 			throw Exception(Exception::getErrorMessage(ErrorCode::AsgInvalidTriggerFunction).arg(QString("trigger")),__PRETTY_FUNCTION__,__FILE__,__LINE__);
108 		//Case the function has some parameters raise an error
109 		else if(func->getParameterCount()!=0)
110 			throw Exception(Exception::getErrorMessage(ErrorCode::AsgFunctionInvalidParamCount)
111 							.arg(this->getName())
112 							.arg(BaseObject::getTypeName(ObjectType::Trigger)),
113 							ErrorCode::AsgFunctionInvalidParamCount,__PRETTY_FUNCTION__,__FILE__,__LINE__);
114 
115 		setCodeInvalidated(function != func);
116 		this->function=func;
117 	}
118 }
119 
setCondition(const QString & cond)120 void Trigger::setCondition(const QString &cond)
121 {
122 	setCodeInvalidated(condition != cond);
123 	this->condition=cond;
124 }
125 
addColumn(Column * column)126 void Trigger::addColumn(Column *column)
127 {
128 	if(!column)
129 		throw Exception(Exception::getErrorMessage(ErrorCode::AsgNotAllocatedColumn)
130 						.arg(this->getName(true))
131 						.arg(this->getTypeName()),
132 						ErrorCode::AsgNotAllocatedColumn,__PRETTY_FUNCTION__,__FILE__,__LINE__);
133 	else if(!column->getParentTable())
134 		throw Exception(Exception::getErrorMessage(ErrorCode::AsgColumnNoParent)
135 						.arg(this->getName(true))
136 						.arg(this->getTypeName()),
137 						ErrorCode::AsgNotAllocatedColumn,__PRETTY_FUNCTION__,__FILE__,__LINE__);
138 	else if(this->getParentTable() &&
139 			column->getParentTable() != this->getParentTable())
140 		throw Exception(Exception::getErrorMessage(ErrorCode::AsgInvalidColumnTrigger)
141 						.arg(column->getName(true))
142 						.arg(this->getName(true)),
143 						ErrorCode::AsgInvalidColumnTrigger,__PRETTY_FUNCTION__,__FILE__,__LINE__);
144 
145 	upd_columns.push_back(column);
146 	setCodeInvalidated(true);
147 }
148 
editArgument(unsigned arg_idx,const QString & new_arg)149 void Trigger::editArgument(unsigned arg_idx, const QString &new_arg)
150 {
151 	//Raises an error if the argument index is invalid (out of bound)
152 	if(arg_idx>=arguments.size())
153 		throw Exception(ErrorCode::RefArgumentInvalidIndex,__PRETTY_FUNCTION__,__FILE__,__LINE__);
154 
155 	vector<QString>::iterator itr;
156 
157 	itr=arguments.begin()+arg_idx;
158 	(*itr)=new_arg;
159 
160 	setCodeInvalidated(true);
161 }
162 
setExecutePerRow(bool value)163 void Trigger::setExecutePerRow(bool value)
164 {
165 	setCodeInvalidated(is_exec_per_row != value);
166 	is_exec_per_row=value;
167 }
168 
isExecuteOnEvent(EventType event)169 bool Trigger::isExecuteOnEvent(EventType event)
170 {
171 	if(event==EventType::OnSelect)
172 		throw Exception(ErrorCode::RefInvalidTriggerEvent,__PRETTY_FUNCTION__,__FILE__,__LINE__);
173 
174 	return events.at(!event);
175 }
176 
isExecutePerRow()177 bool Trigger::isExecutePerRow()
178 {
179 	return is_exec_per_row;
180 }
181 
getArgument(unsigned arg_idx)182 QString Trigger::getArgument(unsigned arg_idx)
183 {
184 	//Raises an error if the argument index is invalid (out of bound)
185 	if(arg_idx>=arguments.size())
186 		throw Exception(ErrorCode::RefArgumentInvalidIndex,__PRETTY_FUNCTION__,__FILE__,__LINE__);
187 
188 	return arguments[arg_idx];
189 }
190 
getColumn(unsigned col_idx)191 Column *Trigger::getColumn(unsigned col_idx)
192 {
193 	//Raises an error if the column index is invalid (out of bound)
194 	if(col_idx>=upd_columns.size())
195 		throw Exception(ErrorCode::RefColumnInvalidIndex,__PRETTY_FUNCTION__,__FILE__,__LINE__);
196 
197 	return upd_columns[col_idx];
198 }
199 
getArgumentCount()200 unsigned Trigger::getArgumentCount()
201 {
202 	return arguments.size();
203 }
204 
getColumnCount()205 unsigned Trigger::getColumnCount()
206 {
207 	return upd_columns.size();
208 }
209 
getFunction()210 Function *Trigger::getFunction()
211 {
212 	return function;
213 }
214 
getCondition()215 QString Trigger::getCondition()
216 {
217 	return condition;
218 }
219 
getFiringType()220 FiringType Trigger::getFiringType()
221 {
222 	return firing_type;
223 }
224 
removeArgument(unsigned arg_idx)225 void Trigger::removeArgument(unsigned arg_idx)
226 {
227 	//Raises an error if the argument index is invalid (out of bound)
228 	if(arg_idx>=arguments.size())
229 		throw Exception(ErrorCode::RefArgumentInvalidIndex,__PRETTY_FUNCTION__,__FILE__,__LINE__);
230 
231 	vector<QString>::iterator itr;
232 	itr=arguments.begin()+arg_idx;
233 	arguments.erase(itr);
234 	setCodeInvalidated(true);
235 }
236 
removeArguments()237 void Trigger::removeArguments()
238 {
239 	arguments.clear();
240 	setCodeInvalidated(true);
241 }
242 
removeColumns()243 void Trigger::removeColumns()
244 {
245 	upd_columns.clear();
246 	setCodeInvalidated(true);
247 }
248 
setReferecendTable(BaseTable * ref_table)249 void Trigger::setReferecendTable(BaseTable *ref_table)
250 {
251 	//If the referenced table isn't valid raises an error
252 	if(ref_table && ref_table->getObjectType()!=ObjectType::Table)
253 		throw Exception(ErrorCode::AsgObjectInvalidType,__PRETTY_FUNCTION__,__FILE__,__LINE__);
254 
255 	setCodeInvalidated(referenced_table != ref_table);
256 	this->referenced_table=ref_table;
257 }
258 
setDeferralType(DeferralType type)259 void Trigger::setDeferralType(DeferralType type)
260 {
261 	setCodeInvalidated(deferral_type != type);
262 	deferral_type=type;
263 }
264 
setDeferrable(bool value)265 void Trigger::setDeferrable(bool value)
266 {
267 	setCodeInvalidated(is_deferrable != value);
268 	is_deferrable=value;
269 }
270 
getReferencedTable()271 BaseTable *Trigger::getReferencedTable()
272 {
273 	return referenced_table;
274 }
275 
getDeferralType()276 DeferralType Trigger::getDeferralType()
277 {
278 	return deferral_type;
279 }
280 
isDeferrable()281 bool Trigger::isDeferrable()
282 {
283 	return is_deferrable;
284 }
285 
setConstraint(bool value)286 void Trigger::setConstraint(bool value)
287 {
288 	setCodeInvalidated(is_constraint != value);
289 	is_constraint=value;
290 }
291 
setTransitionTableName(unsigned tab_idx,const QString & name)292 void Trigger::setTransitionTableName(unsigned tab_idx, const QString &name)
293 {
294 	if(tab_idx > NewTableName)
295 		throw Exception(ErrorCode::RefElementInvalidIndex,__PRETTY_FUNCTION__,__FILE__,__LINE__);
296 
297 	setCodeInvalidated(transition_tabs_names[tab_idx] != name);
298 	transition_tabs_names[tab_idx] = name;
299 }
300 
getTransitionTableName(unsigned tab_idx)301 QString Trigger::getTransitionTableName(unsigned tab_idx)
302 {
303 	if(tab_idx > NewTableName)
304 		throw Exception(ErrorCode::RefElementInvalidIndex,__PRETTY_FUNCTION__,__FILE__,__LINE__);
305 
306 	return transition_tabs_names[tab_idx];
307 }
308 
isConstraint()309 bool Trigger::isConstraint()
310 {
311 	return is_constraint;
312 }
313 
isReferRelationshipAddedColumn()314 bool Trigger::isReferRelationshipAddedColumn()
315 {
316 	vector<Column *>::iterator itr, itr_end;
317 	Column *col=nullptr;
318 	bool found=false;
319 
320 	itr=upd_columns.begin();
321 	itr_end=upd_columns.end();
322 
323 	while(itr!=itr_end && !found)
324 	{
325 		col=(*itr);
326 		found=col->isAddedByRelationship();
327 		itr++;
328 	}
329 
330 	return found;
331 }
332 
getRelationshipAddedColumns()333 vector<Column *> Trigger::getRelationshipAddedColumns()
334 {
335 	vector<Column *> cols;
336 
337 	for(auto &col : upd_columns)
338 	{
339 		if(col->isAddedByRelationship())
340 			cols.push_back(col);
341 	}
342 
343 	return cols;
344 }
345 
setBasicAttributes(unsigned def_type)346 void Trigger::setBasicAttributes(unsigned def_type)
347 {
348 	QString str_aux,
349 			attribs[4]={Attributes::InsEvent, Attributes::DelEvent,
350 						Attributes::TruncEvent, Attributes::UpdEvent },
351 			sql_event[4]={"INSERT OR ", "DELETE OR ", "TRUNCATE OR ", "UPDATE   "};
352 	unsigned count, i, i1, event_types[4]={EventType::OnInsert, EventType::OnDelete,
353 										   EventType::OnTruncate, EventType::OnUpdate};
354 
355 
356 	setArgumentAttribute(def_type);
357 
358 	for(i=0; i < 4; i++)
359 	{
360 		if(events.at(event_types[i]))
361 		{
362 			str_aux+=sql_event[i];
363 			attributes[attribs[i]]=Attributes::True;
364 
365 			if(event_types[i]==EventType::OnUpdate)
366 			{
367 				count=upd_columns.size();
368 				attributes[Attributes::Columns]="";
369 
370 				for(i1=0; i1 < count; i1++)
371 				{
372 					attributes[Attributes::Columns]+=upd_columns.at(i1)->getName(true);
373 					if(i1 < count-1)
374 						attributes[Attributes::Columns]+=QString(",");
375 				}
376 			}
377 		}
378 	}
379 
380 	if(!str_aux.isEmpty()) str_aux.remove(str_aux.size()-3,3);
381 
382 	if(def_type==SchemaParser::SqlDefinition && !attributes[Attributes::Columns].isEmpty())
383 		str_aux+=QString(" OF ") + attributes[Attributes::Columns];
384 
385 	attributes[Attributes::Events]=str_aux;
386 
387 	if(function)
388 	{
389 		if(def_type==SchemaParser::SqlDefinition)
390 			attributes[Attributes::TriggerFunc]=function->getName(true);
391 		else
392 			attributes[Attributes::TriggerFunc]=function->getCodeDefinition(def_type, true);
393 	}
394 }
395 
getCodeDefinition(unsigned def_type)396 QString Trigger::getCodeDefinition(unsigned def_type)
397 {
398 	QString code_def=getCachedCode(def_type, false);
399 	if(!code_def.isEmpty()) return code_def;
400 
401 	setBasicAttributes(def_type);
402 
403 	/* Case the trigger doesn't referece some column added by relationship it will be declared
404 		inside the parent table construction by the use of 'decl-in-table' schema attribute */
405 	if(!isReferRelationshipAddedColumn())
406 		attributes[Attributes::DeclInTable]=Attributes::True;
407 
408 	if(getParentTable())
409 		attributes[Attributes::Table]=getParentTable()->getName(true);
410 
411 	attributes[Attributes::Constraint]=(is_constraint ? Attributes::True : "");
412 	attributes[Attributes::FiringType]=(~firing_type);
413 
414 	//** Constraint trigger MUST execute per row **
415 	attributes[Attributes::PerRow]=((is_exec_per_row && !is_constraint) || is_constraint ? Attributes::True : "");
416 
417 	attributes[Attributes::Condition]=condition;
418 
419 	if(referenced_table)
420 		attributes[Attributes::RefTable]=referenced_table->getName(true);
421 
422 	attributes[Attributes::Deferrable]=(is_deferrable ? Attributes::True : "");
423 	attributes[Attributes::DeferType]=(~deferral_type);
424 
425 	if(def_type == SchemaParser::XmlDefinition)
426 	{
427 		attributes[Attributes::OldTableName]=transition_tabs_names[OldTableName];
428 		attributes[Attributes::NewTableName]=transition_tabs_names[NewTableName];
429 	}
430 	else
431 	{
432 		attributes[Attributes::OldTableName]=BaseObject::formatName(transition_tabs_names[OldTableName]);
433 		attributes[Attributes::NewTableName]=BaseObject::formatName(transition_tabs_names[NewTableName]);
434 	}
435 
436 	return BaseObject::__getCodeDefinition(def_type);
437 }
438 
validateTrigger()439 void Trigger::validateTrigger()
440 {
441 	if(getParentTable())
442 	{
443 		ObjectType parent_type=getParentTable()->getObjectType();
444 
445 		if(!is_constraint)
446 		{
447 			//The INSTEAD OF mode cannot be used on triggers that belongs to tables! This is available only for view triggers
448 			if(firing_type==FiringType::InsteadOf && parent_type != ObjectType::View)
449 				throw Exception(ErrorCode::InvTableTriggerInsteadOfFiring,__PRETTY_FUNCTION__,__FILE__,__LINE__);
450 
451 			//The INSTEAD OF mode cannot be used on view triggers that executes for each statement
452 			else if(firing_type==FiringType::InsteadOf && parent_type==ObjectType::View && !is_exec_per_row)
453 				throw Exception(ErrorCode::InvUsageInsteadOfOnTrigger,__PRETTY_FUNCTION__,__FILE__,__LINE__);
454 
455 			//A trigger cannot make reference to columns when using INSTEAD OF mode and UPDATE event
456 			else if(firing_type==FiringType::InsteadOf && events[EventType::OnUpdate] && !upd_columns.empty())
457 				throw Exception(ErrorCode::InvUsageInsteadOfUpdateTrigger,__PRETTY_FUNCTION__,__FILE__,__LINE__);
458 
459 			//The TRUNCATE event can only be used when the trigger executes for each statement and belongs to a table
460 			else if(events[EventType::OnTruncate] && (is_exec_per_row || parent_type==ObjectType::View))
461 				throw Exception(ErrorCode::InvUsageTruncateOnTrigger,__PRETTY_FUNCTION__,__FILE__,__LINE__);
462 
463 			//A view trigger cannot be AFTER/BEFORE when it executes for each row
464 			else if(parent_type==ObjectType::View && is_exec_per_row && (firing_type==FiringType::After || firing_type==FiringType::Before))
465 				throw Exception(ErrorCode::InvUsageAfterBeforeViewTrigger,__PRETTY_FUNCTION__,__FILE__,__LINE__);
466 
467 			//Only constraint triggers can be deferrable or reference another table
468 			else if(referenced_table || is_deferrable)
469 				throw Exception(ErrorCode::InvUseConstraintTriggerAttribs,__PRETTY_FUNCTION__,__FILE__,__LINE__);
470 		}
471 		//Constraint triggers can only be executed on AFTER events and for each row
472 		else
473 		{
474 			if(firing_type!=FiringType::After && !is_exec_per_row)
475 				throw Exception(ErrorCode::InvConstrTriggerNotAfterRow,__PRETTY_FUNCTION__,__FILE__,__LINE__);
476 		}
477 	}
478 }
479 
getSignature(bool format)480 QString Trigger::getSignature(bool format)
481 {
482 	if(!getParentTable())
483 		return BaseObject::getSignature(format);
484 
485 	return (QString("%1 ON %2").arg(this->getName(format)).arg(getParentTable()->getSignature(true)));
486 }
487