Is it possible to have database wide constants?
What I want is to define a constant like:
UPDATE_CONSTANT = 1
INSERT_CONSTANT = 2
DELETE_CONSTANT = 3
and then use it in for example a trigger like:
CREATE TRIGGER AD_PRJ_PROJECTS FOR PRJ_PROJECT
ACTIVE AFTER DELETE
POSITION 1
AS
BEGIN
EXECUTE PROCEDURE SP_ADD_HISTORY 'PRJ_PROJECT', DELETE_CONSTANT;
END;
You could use a generator:
SET GENERATOR DELETE_CONSTANT TO 3;
...
EXECUTE PROCEDURE SP_ADD_HISTORY 'PRJ_PROJECT', GEN_ID(DELETE_CONSTANT, 0);
Update: yes, using a generator for this purpose is dangerous, as they can be changed.
However, in FireBird 3.0 Alpha 1 this risk can be eliminated using access rights: Grants access on generators.
I don't think there is an easy way for declaring constants.
I could be done by creating you own DLL for user defined function, and lmake a function for each constant.
I Think the Idea using generators as "global" constants is briliant.
But you can make a "local constant" to make your code a bit more readable:
CREATE TRIGGER AD_PRJ_PROJECTS FOR PRJ_PROJECT
ACTIVE AFTER DELETE
POSITION 1
AS
DECLARE VARIABLE DELETE_CONSTANT INTEGER;
BEGIN
DELETE_CONSTANT = 1;
EXECUTE PROCEDURE SP_ADD_HISTORY 'PRJ_PROJECT', DELETE_CONSTANT;
END;
Use a single row table with triggers that prevent insertion and deletion from it. Having to read from it certainly does not makes code clearer but it helps to implement such "constants". Also remove write permissions from everybody but sysdba
You can implement some simple preprocesor of yours scripts that converts constants to values..
triggers.presql
#DELETE_CONSTANT = 1
CREATE TRIGGER AD_PRJ_PROJECTS FOR PRJ_PROJECT
ACTIVE AFTER DELETE
POSITION 1
BEGIN
EXECUTE PROCEDURE SP_ADD_HISTORY 'PRJ_PROJECT', DELETE_CONSTANT;
END;
triggers.sql
CREATE TRIGGER AD_PRJ_PROJECTS FOR PRJ_PROJECT
ACTIVE AFTER DELETE
POSITION 1
BEGIN
EXECUTE PROCEDURE SP_ADD_HISTORY 'PRJ_PROJECT', 1;
END;
Related
I'm trying to create this trigger in my PostgresSql environment:
CREATE TRIGGER MYTRIGGER
BEFORE INSERT
ON MYTABLE
FOR EACH ROW
BEGIN
IF( LENGTH( :NEW.VAL ) > 10 )
THEN
RAISE_APPLICATION_ERROR( -20003,
'Cannot exceed 10 chars' );
END IF;
IF :NEW.FQN_ID IS NULL THEN
:NEW.FQN_ID :=
CASE :NEW.SUBTYPECODE
WHEN NULL THEN 'A:'
WHEN 0 THEN 'B:'
WHEN 1 THEN 'C:'
WHEN 2 THEN 'D:'
ELSE 'Z:' || :NEW.SUBTYPECODE || '::'
--END || :NEW.OBJECTID;
END || STRUCTURE_FQNID_SEQ.NEXTVAL;
END IF;
END;
But I get this error:
ERROR: syntax error at or near "BEGIN"
LINE 5: BEGIN
^
SQL state: 42601
Character: 79
I think I'm missing something but I can't get it.
Any suggestion would be greatly appreciated.
Thank you.
Here are my notes about triggers in several DBMSs: https://github.com/iwis/SQL-notes. I marked the differences between the DBMSs in orange. I think that the notes are quite complete, so you don't have to read about triggers in Postgres documentation.
I see the following changes that need to done in your example:
Change Oracle :NEW to Postgres NEW.
Instead of a BEGIN ... END block, write EXECUTE FUNCTION my_trigger_function();, where my_trigger_function is a function that needs to be created like in the example given by a_horse_with_no_name.
This function should return NEW in your case - the reason is described here.
If a more complicated code is fired by a trigger, then you also need to understand the differences between PL/SQL and PL/pgSQL languages. These languages are quite similar, though there are some differences. Your code is simple so the differences are small. It's probably enough to:
Write Postgres RETURNS in the function definition instead of Oracle RETURN.
Write $$ before BEGIN, and $$ LANGUAGE plpgsql; after END.
Write Postgres RAISE 'Cannot exceed 10 chars'; instead of Oracle RAISE_APPLICATION_ERROR(-20003, 'Cannot exceed 10 chars');.
I don't know if sequences work in the same way in PostgreSQL - I haven't read about them yet.
Let me know if my notes are understandable - I'm not sure about it because they are super compact so you need to decipher the markings used by me.
I just noticed that I could alter my stored procedure code with a misspelled user defined function in it.
I noticed that at 1st time I execute the SP.
Is there any way to get a compile error when an SP include an invalid user-defined function name in it?
At compile time? No.
You can, however, use some of SQL's dependency objects (if using MS SQL) to find problems just after deployment, or as part of your beta testing. Aaron Bertran has a pretty nice article rounding up the options, depending upon the version of SQL Server.
Here is an example using SQL Server 2008 sys object called sql_expression_dependencies
CREATE FUNCTION dbo.scalarTest
(
#input1 INT,
#input2 INT
)
RETURNS INT
AS
BEGIN
-- Declare the return variable here
DECLARE #ResultVar int
-- Add the T-SQL statements to compute the return value here
SELECT #ResultVar = #input1 * #input2
-- Return the result of the function
RETURN #ResultVar
END
GO
--Fn Works!
SELECT dbo.ScalarTest(2,2)
GO
CREATE PROCEDURE dbo.procTest
AS
BEGIN
SELECT TOP 1 dbo.scalarTest(3, 3) as procResult
FROM sys.objects
END
GO
--Sproc Works!
EXEC dbo.procTest
GO
--Remove a dependency needed by our sproc
DROP FUNCTION dbo.scalarTest
GO
--Does anything have a broken dependency? YES
SELECT OBJECT_NAME(referencing_id) AS referencing_entity_name,
referenced_entity_name, *
FROM sys.sql_expression_dependencies
WHERE referenced_id IS NULL --dependency is missing
GO
--Does it work? No
EXEC dbo.procTest
GO
I have jsut started to use tSQLt and is about to test a trigger. I call the FakeTable procedure and do my test but the trigger is not executed. If don't use FakeTable the trigger is executed. That seems to be really bad and I canät find any info that there is any method to readded them.
Then I thought the triggers are removed by FakeTable but I can recreate them after the call and did the following code in my test:
DECLARE #createTrigger NVARCHAR(MAX);
SELECT #createTrigger = OBJECT_DEFINITION(OBJECT_ID('MoveDataFromAToB'))
EXEC tSQLt.FakeTable 'dbo.A';
EXEC(#createTrigger);
I got the following error: "There is already an object named 'MoveDataFromAToB' in the database.{MoveDataFromAToB,14} (There was also a ROLLBACK ERROR --> The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.{Private_RunTest,60})"
Anyone that have any experience with tSQLt and know anyworkaround for this problem?
There is an ApplyTrigger method on the tSQLt backlog, but it is not finished yet. For now you should be able to use this code in your test:
DECLARE #createTrigger NVARCHAR(MAX);
SELECT #createTrigger = OBJECT_DEFINITION(OBJECT_ID('MoveDataFromAToB'));
DROP TRIGGER MoveDataFromAToB;
EXEC tSQLt.FakeTable 'dbo.A';
EXEC(#createTrigger);
You need to drop the existing trigger as FakeTable does not drop the original table. It just renames it, which leaves the old trigger intact; hence the name collision you did see.
The rollback that tSQLt executes at the end of every test will put the dropped trigger back in place (unless you are doing something really bad in your code). If you are worried about that use sp_rename instead of drop on the trigger.
I would put all this into a helper stored procedure within the test class and call it from the tests that need it. That way, once we have a better solution implemented in tSQLt you will have to change only one place in your code.
Thanks Sebastian for your answer. It really helped me too :)
I made a stored proc for the code you gave. I will use this until the 'ApplyTrigger' function is available:
CREATE PROCEDURE [tSQLt].[FakeTableWithTrigger]
#TableName NVARCHAR(MAX),
#TriggerName NVARCHAR(MAX),
#SchemaName NVARCHAR(MAX) = NULL, --parameter preserved for backward compatibility. Do not use. Will be removed soon.
#Identity BIT = NULL,
#ComputedColumns BIT = NULL,
#Defaults BIT = NULL
AS
BEGIN
DECLARE #createTrigger NVARCHAR(MAX);
SELECT #createTrigger = OBJECT_DEFINITION(OBJECT_ID(#TriggerName));
EXEC('DROP TRIGGER ' + #TriggerName);
EXEC tSQLt.FakeTable #TableName, #SchemaName, #Identity, #ComputedColumns, #Defaults;
EXEC(#createTrigger);
END
An example of it's useage in a test is:
exec tSQLt.FakeTableWithTrigger 'dbo.MyTable', 'MyTable_SyncTrigger', #Identity = 1
I have a package which contains more than 2000 lines. My question is can I create the packages dynamically by using execute immediate?
You'll need to use the associative array DBMS_SQL interface assuming that "2000 lines" equates to more than 32k worth of text. That means that you'll need to load the DDL into multiple elements of an associative array before passing that to the DBMS_SQL.PARSE method. Something like this works
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_sql dbms_sql.varchar2a;
3 c integer;
4 begin
5 l_sql(1) := 'CREATE OR REPLACE PACKAGE pkg_dynamic ';
6 l_sql(2) := 'AS ';
7 l_sql(3) := ' PROCEDURE my_proc;';
8 l_sql(4) := 'END;';
9 c := dbms_sql.open_cursor;
10 dbms_sql.parse( c, l_sql, 1, 4, true, dbms_sql.native );
11* end;
SQL> /
PL/SQL procedure successfully completed.
SQL> desc pkg_dynamic;
PROCEDURE MY_PROC
But I would strongly question why you are trying to use dynamic SQL to create packages in the first place. It doesn't generally make sense to write code that turns around and generates more code. You wouldn't, for example, generally want to write a Java application that turned around and wrote and compiled another Java application that someone would then run.
When creating a stored procedure, does the BEGIN/END block serve and purpose?
eg,
CREATE PROCEDURE SPNAME
AS
SELECT * FROM TABLE
vs.
CREATE PROCEDURE SPNAME
AS
BEGIN
SELECT * FROM TABLE
END
As indicated in the CREATE PROCEDURE documentation, the BEGIN/END is optional:
{ [ BEGIN ] sql_statement [;] [ ...n ]
[ END ] }
One or more Transact-SQL
statements comprising the body of the
procedure. You can use the optional
BEGIN and END keywords to enclose the
statements. For information, see the
Best Practices, General Remarks, and
Limitations and Restrictions sections
that follow.
As a matter of personal preference, I always include them.
To me, it is just an unnecessary extra indent level. However, if you make it a BEGIN TRY - END TRY with a BEGIN CATCH - END CATCH then it adds a real purpose.
The latter is just a good style of programming and following general standards.They don't differ in anything as far as I know.
I personally think it makes sense and improves readability to have a BEGIN and END around every logical chunk of code (even in stored procedures case).
There is no real pourpose in the case. But your code will be readable and clean if you use BEGIN/END blocks.
The BEGIN and END have no bearing for a stored procedure, however they are required for a Function.
Okay:
CREATE PROC dbo.sproc(#v varchar(10))
AS
RETURN -1
Not okay:
CREATE FUNCTION dbo.fun(#v varchar(10))
RETURNS INT
AS
RETURN 1