In my previous post, I talked about how Oracle SQL Developer Data Modeler (SDDM) is extendable with scripts written in languages that support JSR-223. One of those languages is Groovy ( http://www.groovy-lang.org/ ) and I showed how to add Groovy to the JVM classpath used by SDDM. You might want to visit thatpostto see how. The reason that I needed to write a script was that I wanted to easily add Journal Tables and the triggers for updating them to my database DDL. SDDM actually comes with a script for doing this, but the script writes the triggers in PL/SQL for Oracle databases. As my readers know, the database I was designing was a Microsoft SQL Server database, and the triggers needed to be written in Transact-SQL (T-SQL).
About Journal TablesSo, first of all, you might ask, is what is a Journal Table? A Journal Table is a table that captures an before or after image of every change to a row in the table that is being monitored, usually with a date/time to show when the change occurred. The idea is that we be able to reconstruct a history of changes to the data in a table. Because of the overhead, you probably don't want a journal table behind all of your tables, but when you need to track who did it, when was it done, and what exactly was changed, a journal table can be a good solution. It would also help to recover from changes that shouldn't have been done. There are other solutions like Oracle's flashback query capabilities, but some databases can't do that, and some tables require a little more precise control. By the way, it is often necessary to set permissions on journal tables more stringently than permissions on the tables being journaled, so that hackers can't cover their tracks.
CREATE TABLE my_table (
my_id INTEGER,
my_char_data VARCHAR(30)
);
CREATE TABLE my_table_jn (
my_id INTEGER,
my_char_data VARCHAR(30),
operation VARCHAR(10),
date_changed DATETIME
);
If I do (on September 10):
INSERT INTO my_table (my_id, my_char_data)
VALUES (1,'Example 1');
The after INSERT trigger should do:
INSERT INTO my_table_jn ( my_id, my_char_data, operation, date_changed)
VALUES (1, 'Example1','INSERT', CONVERT(datetime,'09/10/2018',101));
If I do (on September 12):
UPDATE my_table SET my_char_data = 'Example2'
WHERE my_id = 1 ;
The after UPDATE trigger should do:
INSERT INTO my_table_jn ( my_id, my_char_data, operation, date_changed)
VALUES (1, 'Example2','UPDATE', CONVERT(datetime, '09/12/2018' ,101) );
If I do (on September 14):
DELETE my_table
WHERE my_id = 1 ;
The after DELETE trigger should do:
INSERT INTO my_table_jn ( my_id, my_char_data, operation, date_changed)
VALUES (1, 'Example2','DELETE', CONVERT(datetime, '09/14/2018' ,101) );
The data for DELETE is actually a before image, since after the delete there is no data. By the way, triggers participate in the underlying transaction, so if the change to my_table is rolled back, so will the INSERT into my_table_jn.
The ScriptAs I said before, SDDM includes a script for adding code for Journal Tables to your DDL. Though I couldn't use Oracle's script as written, it served as an excellent starting point for my version. It also shows how you get access to the underlying SDDM data. Here is the first part of the script:
/*
Writes CREATE commands for Journal Table and Triggers for SQL Server.
variable ddlStatementsList should be used to return the list with DDL statements
that are created by script - as shown below:
ddlStatementsList.add(new java.lang.String(ddl));
other available variables:
- model - relational model instance
- pModel - physical model instance
- table - the table in relational model
- tableProxy - table definition in physical model
*/
Since the original script is written in javascript, and mine is written in Groovy, I needed to change the syntax to Groovy, but much is the same or similar, including comments. Notice that SDDM hands you access points to the SDDM data - listed in the comments above. But it doesn't tell you how to write to SDDM's log. Fortunately, Dave Schleis provided the following code:
// get a handle to the application object
def app = oracle.dbtools.crest.swingui.ApplicationView
app.log("Creating DDL for Journal table for ${table.name}");
"ddl" is a variable to hold the code to be added to the ddl being exported for the table to be journaled. In the original, this was a string variable, but strings in Groovy are immutable. When you do "ddl = ddl + 'a string'" you are really creating a new string object. So I changed it to a StringBuilder, which in Groovy and Java is an object to which you can append more data, without the waste of discarding old strings and creating new ones.
StringBuilder ddl;
String lname;
//journal table name suffix
jnTabSuf = "_jn";
// trigger name suffix
jnAISuf = "_jn_ai";
jnAUSuf = "_jn_au";
jnADSuf = "_jn_ad";
prompt = model.appView.settings.includePromptInDDL;
useSchema = model.appView.settings.isIncludeSchemaInDDL();
if(model.storageDesign.open){
if(useSchema){
lname = tableProxy.longName;
}else{
lname = tableProxy.name;
}
}else{
if(useSchema){
lname = table.longName;
}else{
lname = table.name;
}
}
Here you will see a major advantage of using Groovy for your DDL Transformation (and other)scripts. Groovy has a GString type, similar to strings in Java and Javascript, but you can embed variables in your GStrings. In other languages you would have to concatenate strings. This is a great space and time saver when the script is really code that writes code.
if(prompt){
ddl = new StringBuilder("PRINT 'Creating Journal Table for ${lname};'\n");
}else{
ddl = new StringBuilder("");
}
app.log("Creating Journal Table DDL.");
Most of the rest of the code is appending strings (GStrings) to the ddl variable. Groovy overloads the "append()" method of StringBuilder to the "<<" operator, once again saving me a little time and space. Also notice that I'm using the triple quoted string in this section of code, which lets me use actual line feeds in place of the "\n" line feed character. I didn't do this throughout, because I didn't want to fool with the