Archie poked around in his company’s billing system. This was a mistake, because that system is implemented largely in PL/SQL, and only a developer made from the sternest stuff can deal with PL/SQL.
Like most PL/SQL applications, the Oracle database is the closest thing they have to version control. Each developer makes changes in the live dev environment and then hopes for the best. They don’t use TOAD or SQLPlus or any “normal” tool for making these changes- they have to use an in-house developed GUI, because that GUI tracks their changes and writes rows into a database called “VersionDB”. When they finish a patch and want to release the changes to their customer sites, they send a copy of the VersionDB and let a simple script apply all of those changes.
It works about as well as you’d expect.
Now, it’s important to note that Oracle defaults to using ANSI NULLs, which means
NULL != NULL
. I note this, because it means if you have an old version of a row, and a new version of a row, you can’t simply determine if they’re different by using code in the form:
IF oldValue != newValue THEN
/* do some stuff */
END IF;
If both oldValue and newValue are NULL, this code will mistakenly think that they’ve been changed. The obvious solution then, is to throw some NULL guards:
IF NOT oldValue IS NULL AND oldValue != newValue THEN
/* do some stuff */
END IF;
The problem with this is that the intent of the code is no longer clear. So our next instinct is to build some sort of function to handle this. That’s not a bad instinct, but it can lead to some very bad results…
--**********************
-- Changed() - Public
-- Indicates if the value has been changed.
--
procedure Changed (
a_OldValue varchar2,
a_NewValue varchar2,
a_Changed in out boolean
) is
begin
a_Changed := a_OldValue is null and a_NewValue is not null or
a_NewValue is null and a_OldValue is not null or
a_OldValue != a_NewValue;
end Changed;
--
--**********************
-- Changed() - Public
-- Indicates if the value has been changed.
--
procedure Changed (
a_OldValue number,
a_NewValue number,
a_Changed in out boolean
) is
begin
a_Changed := a_OldValue is null and a_NewValue is not null or
a_NewValue is null and a_OldValue is not null or
a_OldValue != a_NewValue;
end Changed;
--
--**********************
-- Changed() - Public
-- Indicates if the value has been changed.
--
procedure Changed (
a_OldValue date,
a_NewValue date,
a_Changed in out boolean
) is
begin
a_Changed := a_OldValue is null and a_NewValue is not null or
a_NewValue is null and a_OldValue is not null or
a_OldValue != a_NewValue;
end Changed;
--
--**********************
-- Changed() - Public
-- Indicates if the value has been changed.
--
procedure Changed (
a_OldValue timestamp with time zone,
a_NewValue timestamp with time zone,
a_Changed in out boolean
) is
begin
a_Changed := a_OldValue is null and a_NewValue is not null or
a_NewValue is null and a_OldValue is not null or
a_OldValue != a_NewValue;
end Changed;
--
--**********************
-- Changed() - Public
-- Indicates if the value has been changed.
--
function Changed (
a_OldValue varchar2,
a_NewValue varchar2
) return boolean is
t_Changed boolean;
begin
Changed(a_OldValue, a_NewValue, t_Changed);
--
return t_Changed;
end Changed;
--
--**********************
-- Changed() - Public
-- Indicates if the value has been changed.
--
function Changed (
a_OldValue number,
a_NewValue number
) return boolean is
t_Changed boolean;
begin
Changed(a_OldValue, a_NewValue, t_Changed);
--
return t_Changed;
end Changed;
--
--**********************
-- Changed() - Public
-- Indicates if the value has been changed.
--
function Changed (
a_OldValue date,
a_NewValue date
) return boolean is
t_Changed boolean;
begin
Changed(a_OldValue, a_NewValue, t_Changed);
--
return t_Changed;
end Changed;
--
--**********************
-- Changed() - Public
-- Indicates if the value has been changed.
--
function Changed (
a_OldValue timestamp with time zone,
a_NewValue timestamp with time zone
) return boolean is
t_Changed boolean;
begin
Changed(a_OldValue, a_NewValue, t_Changed);
--
return t_Changed;
end Changed;
I particularly like the utility of having both PROCEDURE versions with output parameters and FUNCTION versions that wrap them. The real kicker is that, with all of that code, the Changed functions are only called about a dozen times in the program.