EBR – Part 5: Explicit Actualization of Dependent Objects

This is part 5 of a post series about EBR.
In part 1 we created the baseline model and code – a table (PEOPLE) and two packages (PEOPLE_DL and APP_MGR).
In part 2 we saw that even a simple change – a package body compilation – can be dangerous in a busy system.
In part 3 we learned about editions and how they can be used for solving the problems described in part 2, so changes can be applied in both online and safe way. I discussed enabling, creating and using editions; session edition; and editioned objects.
In part 4 we discussed another challenge of online upgrades – invalidation of dependent objects.
In this part I’ll demonstrate everything I wrote about in the previous post.

Visit the index page for all the parts of the series

Creating a New Edition

We want to change the PEOPLE_DL spec, and as in any online upgrade, we start by creating a new edition. Let’s create edition V2 and grant use on it to DEMO_EBR:

-- connected as system
ORA$BASE> create edition v2;

Edition created.

ORA$BASE> grant use on edition v2 to demo_ebr;

Grant succeeded.

Invalidation of Dependent Objects

Before doing the online upgrade properly (i.e., using the new edition), let’s see what happens in a non-EBR environment.
First let’s see the objects in our schema and the dependencies between them before the upgrade: Continue reading “EBR – Part 5: Explicit Actualization of Dependent Objects”

EBR – Part 4: Invalidation and Actualization of Dependent Objects

This is part 4 of a post series about EBR.
In part 1 we created the baseline model and code – a table (PEOPLE) and two packages (PEOPLE_DL and APP_MGR).
In part 2 we saw that even a simple change – a package body compilation – can be dangerous in a busy system.
In part 3 we learned about editions and how they can be used for solving the problems described in part 2, so changes can be applied in both online and safe way. I discussed enabling, creating and using editions; session edition; and editioned objects.
In this part we’ll see more challenges that online upgrades bring – this time when changing a package spec.

Visit the index page for all the parts of the series

Invalidation

This time we need to change the PEOPLE_DL package spec. There are no table changes, and of course, as we speak about EBR, the upgrade from the previous version to the new one should be online.

An online upgrade means that the application users should be able to continue working uninterruptedly. The code objects that they use should remain valid and available at any time.

In addition to the challenges raised from the first use case – changing PEOPLE_DL package body – the current use case introduces another challenge: invalidation. Changing the PEOPLE_DL package spec will cause its dependent objects become invalid.
Even if the change does not cause any compilation errors in the dependent objects, they become invalid.
Actually, even if we just recompile an object, without making any changes in it, its dependent objects still become invalid.
Now, it’s true that in these cases revalidation will occur automatically as soon as the invalid object is used, but in an online upgrade scenario this is usually unacceptable.

Moreover, many times we need to change multiple interrelated objects. For example, changing the API of some procedure in one package, and consequently changing it in the package body and changing the calls to that procedure from another package. In this case we cannot avoid having broken objects – invalid and with compilation errors – during the upgrade, and this is obviously cannot be really considered an online upgrade. Continue reading “EBR – Part 4: Invalidation and Actualization of Dependent Objects”

EBR – Part 3: Editions and Editioned Objects

This is part 3 of a post series about EBR.
In part 1 we created the baseline model and code – a table (PEOPLE) and two packages (PEOPLE_DL and APP_MGR).
In part 2 we saw that even a simple change – such as a package body compilation – can be dangerous in a busy system.
In this post we’ll see how EBR solves the problems described in the previous post, so such a change can be applied in both online and safe way.

Visit the index page for all the parts of the series

Overview

The basic concept of EBR is that while the clients are connected to the database and use the objects – say the package pkg from the previous post – we can create another instance of pkg – a copy of pkg – and make all the necessary changes in this new instance.
We create this new pkg in the same schema, so now we actually have two packages named pkg in one schema. But each session sees only one of these packages – the clients continue seeing the old instance of pkg, and we the developers currently see the new instance of pkg.
When we are happy with the new implementation, we can expose it, so clients that will open new connections to the database from now on, will see the new pkg.
We achieve this by editions. Imagine that our schema is divided into multiple separate areas. At any point in time, each session sees one and only one area.
These separate areas are called editions, and we can keep basically any code object there – procedures, functions, packages, triggers, views, and more.
We can’t have two objects named pkg in the same edition, but we can in two separate editions.

Editions

Editions are database-level objects Continue reading “EBR – Part 3: Editions and Editioned Objects”

EBR – Part 2: Locking, Blocking and ORA-04068

This is part 2 of a post series about EBR.
In part 1 we created the baseline model and code – a table (PEOPLE) and two packages (PEOPLE_DL and APP_MGR).
In this post we’ll start handling the first type of change request: changing a package body.

Visit the index page for all the parts of the series

The Task

We need to change the implementation of the PEOPLE_DL package; i.e. we need to change the package body.
There are no API changes (the package spec is not changed) and no table changes.
And of course, as we speak about EBR, the upgrade from the base version to the new one should be online.

The Problems

Locking and Blocking

An online upgrade means Continue reading “EBR – Part 2: Locking, Blocking and ORA-04068”

EBR – Part 1: Overview and Setup

This is a link to an index page for all the parts of the series

I have been using EBR in a real production system for more than 4 years now.
EBR – an acronym for Edition-Based Redefinition – is a powerful and unique feature (or, more precisely, a set of features) that enables patching and upgrading live Oracle-based applications in an online fashion, with zero downtime.

As an Oracle Developer and DBA I find EBR one of the most important tools in my toolkit, and I take advantage of every opportunity to let other Oracle developers and DBAs know about it.

I love EBR because:

  • It enables us to do any change to active production systems – from a small bug fix to a major version upgrade – in an online fashion, using hot rollover: we expose a new version (a new edition) while users are still using the old version, and different users may use two (or even more) versions at the same time.
  • We perform the upgrade in the privacy of a new unexposed edition, which is a huge benefit, in my opinion, because it eliminates the regular pressure of completing the upgrade as soon as possible to reduce downtime, and the stress of not making any harm by mistake to a live system.
  • We can do the upgrade at any time, not necessarily at off-peak hours.
  • And EBR is supported in all the editions (since Oracle 11.2), including standard edition, and requires no special license.

I’m starting today a series of posts about Continue reading “EBR – Part 1: Overview and Setup”

OTN Appreciation Day: Edition-Based Redefinition

Here’s my contribution to the OTN Appreciation Day.

Edition-Based Redefinition (EBR) is a really great feature, or more accurately a set of features, that was introduced in Oracle 11.2 and allows for online application upgrades using hot rollover. If you know me then you know that I talk (ok, preach) about it a lot, and from a long and successful experience.

It is impossible to learn EBR in 5 minutes, but perhaps you’ll understand why I like EBR from this 5-minute introduction-concise-summary video, and hopefully you’ll be intrigued to learn more:

Misconceptions about (Regular) Views Revealed when Presenting Editioning Views – Part 2

In a previous post I wrote about one misconception about views that is revealed when I talk about Editioning Views in my EBR (Edition-Based Redefinition) presentations.

This post is about another misconception.

In the part of the presentation in which I “preach” to cover every table with an Editioning View and to replace every reference to tables in the code with reference to the corresponding Editioning Views, I usually get the following question from the audience: “but what about DML?”.

Everybody knows that you can SELECT from a view, but there is a misconception that you cannot perform INSERT, UPDATE, MERGE or DELETE statements directly on a view.

So the truth is that (assuming you have been granted the necessary privileges) you can perform DML statements on every view (although it may require some additional work sometimes), except if the view is defined with the WITH READ ONLY clause.

The high-level general rule is that if Oracle can transform the statement to work on actual tables (the view’s base tables), in a consistent and deterministic way, without too much trouble, then we don’t need to do anything else – the view is inherently updatable and the DML statement will succeed. In the rest of the cases, although the view is not inherently updatable, we can “teach” it how to react to DML statements – by defining INSTEAD OF triggers.

Upon view creation Oracle analyzes for each of the view’s columns if it’s inherently updatable, and stores this information in the USER/ALL/DBA_UPDATABLE_COLUMNS dictionary views.

Let’s see some examples.

> create table t (
      x number,
      y date,
      z varchar2(100)
  );

Table created.

> create view v as
   select x, y, z as an_alias_for_z from t where x>10 order by y;

View created.

> select column_name,updatable,insertable,deletable
   from user_updatable_columns
  where table_name='V';

COLUMN_NAME                    UPDATABLE INSERTABL DELETABLE
------------------------------ --------- --------- ---------
X                              YES       YES       YES
Y                              YES       YES       YES
AN_ALIAS_FOR_Z                 YES       YES       YES

3 rows selected.

> insert into v (x,y,an_alias_for_z) values (1,sysdate,'a');

1 row created. 

> select * from t;

         X Y                   Z
---------- ------------------- ----------
         1 20/06/2016 06:05:12 a

1 row selected.

If the view’s top-level query contains, for example, the DISTINCT operator, then the view is not inherently updatable.

> create or replace view v as
   select distinct x, y, z from t;

View created.

> select column_name,updatable,insertable,deletable
    from user_updatable_columns
   where table_name='V';

COLUMN_NAME                    UPDATABLE INSERTABL DELETABLE
------------------------------ --------- --------- ---------
X                              NO        NO        NO
Y                              NO        NO        NO
Z                              NO        NO        NO

3 rows selected.

> insert into v (x, y, z) values (2,sysdate,'b');
insert into v (x, y, z) values (2,sysdate,'b')
            *
ERROR at line 1:
ORA-01732: data manipulation operation not legal on this view

If a column of the view is defined as an expression, then this column is not inherently updatable, but other “simple” columns are.

> create or replace view v as
    select x, y, upper(z) upper_z from t;

View created.

> select column_name,updatable,insertable,deletable
    from user_updatable_columns
   where table_name='V';

COLUMN_NAME                    UPDATABLE INSERTABL DELETABLE
------------------------------ --------- --------- ---------
X                              YES       YES       YES
Y                              YES       YES       YES
UPPER_Z                        NO        NO        NO

3 rows selected.

> insert into v (x, y, upper_z) values (3,sysdate,'C');
insert into v (x, y, upper_z) values (3,sysdate,'C')
                     *
ERROR at line 1:
ORA-01733: virtual column not allowed here

> insert into v (x, y) values (3,sysdate);

1 row created.

> update v set upper_z='C' where x=3;
update v set upper_z='C' where x=3
             *
ERROR at line 1:
ORA-01733: virtual column not allowed here


> update v set y=sysdate+1;

2 rows updated.

> select * from t;

         X Y                   Z
---------- ------------------- --------------
         3 21/06/2016 06:07:08
         1 21/06/2016 06:07:08 a

2 rows selected.

Even columns of a join view – a view with more than one base table in its FROM clause – may be inherently updatable (under some restrictions).

> create table p (
    id integer primary key,
    name varchar2(100)
  );

Table created.

> create table c (
    id integer primary key,
    p_id integer references p(id),
    details varchar2(100)
  );

Table created.

> insert into p values (1,'Parent 1');

1 row created.

> insert into p values (2,'Parent 2');

1 row created.

> create or replace view v as
    select c.id,c.p_id,c.details,p.name parent_name
      from p,c
     where p.id = c.p_id;

View created.

> select column_name,updatable,insertable,deletable
    from user_updatable_columns
   where table_name='V';

COLUMN_NAME                    UPDATABLE INSERTABL DELETABLE
------------------------------ --------- --------- ---------
ID                             YES       YES       YES
P_ID                           YES       YES       YES
DETAILS                        YES       YES       YES
PARENT_NAME                    NO        NO        NO

4 rows selected.

> insert into v(id,p_id,details,parent_name) values (101,1,'Child 1 of parent 1','Parent 1');
insert into v(id,p_id,details,parent_name) values (101,1,'Child 1 of parent 1','Parent 1')
                              *
ERROR at line 1:
ORA-01776: cannot modify more than one base table through a join view


> insert into v(id,p_id,details) values (101,1,'Child 1 of parent 1');

1 row created.

> update v set parent_name='Parent 8' where id=101;
update v set parent_name='Parent 8' where id=101
             *
ERROR at line 1:
ORA-01779: cannot modify a column which maps to a non key-preserved table


> update v set details=upper(details);

1 row updated.

> select * from v;

        ID       P_ID DETAILS                  PARENT_NAME
---------- ---------- ------------------------ ---------------------
       101          1 CHILD 1 OF PARENT 1      Parent 1

1 row selected.

These are just a few examples. For all the details please see the documentation.

And what about Editioning Views?

Editioning Views are deliberately very limited, because they were designed to allow using them in the code instead of using the base tables.

An Editioning View may contain only the SELECT and FROM clauses, the FROM clause refers to a single table, the SELECT list may contain only columns and aliases (no expressions).

Therefore, by definition, Editioning Views are always inherently updatable.

 

Misconceptions about (Regular) Views Revealed when Presenting Editioning Views

Sometimes when you present an advanced feature, questions from the audience reveal misconceptions about basic features.

It happens to me almost every time I talk about Edition-Based Redefinition. I present Editioning Views, and then I get questions that reveal misunderstandings about views in general.

One such misunderstanding is regarding what is kept in the view definition.

When we create a view as “select * from table”, the * is expanded to actual columns during the view creation. The * is not kept as part of the view definition.

For example:

> create table people (
>    person_id integer not null,
>    first_name varchar2(20),
>    last_name varchar2(20)
> );

Table created.

> create or replace view people_v as select * from people;

View created.

> select text from user_views where view_name='PEOPLE_V';

TEXT
--------------------------------------------------------------------------------
select "PERSON_ID","FIRST_NAME","LAST_NAME" from people

> select column_id,column_name,nullable,data_type 
>   from user_tab_columns 
>  where table_name='PEOPLE_V'
>  order by column_id;

 COLUMN_ID COLUMN_NAME          NUL DATA_TYPE
---------- -------------------- --- ----------
         1 PERSON_ID            N   NUMBER
         2 FIRST_NAME           Y   VARCHAR2
         3 LAST_NAME            Y   VARCHAR2

If we add a new column to the PEOPLE table, the PEOPLE_V view will not be affected (as the column list in its definition is not “*”).

> alter table people add (eye_color varchar2(10));

Table altered.

> select text from user_views where view_name='PEOPLE_V';

TEXT
--------------------------------------------------------------------------------
select "PERSON_ID","FIRST_NAME","LAST_NAME" from people

> select column_id,column_name,nullable,data_type 
>   from user_tab_columns 
>  where table_name='PEOPLE_V' 
>  order by column_id;

COLUMN_ID COLUMN_NAME          NUL DATA_TYPE
---------- -------------------- --- ----------
1 PERSON_ID            N   NUMBER
2 FIRST_NAME           Y   VARCHAR2
3 LAST_NAME            Y   VARCHAR2

There is one case though where the * is not expanded during the view creation – if we (forcibly) create a view with invalid SELECT statement:

> create or replace force view people_v as 
>   select * from people
>    where non_exiting_column is not null;

Warning: View created with compilation errors.

> select text from user_views where view_name='PEOPLE_V';

TEXT
------------------------------------------------------------
select * from people where non_exiting_column is not null

> select * from people_v;

select * from people_v
*
ERROR at line 1:
ORA-04063: view "DEMO.PEOPLE_V" has errors

But as soon as the view becomes valid, the * is expanded and the actual column list is stored in the view definition:

> create or replace force view people_v as 
>   select * from people 
>    where last_name is not null;

View created.

> select text from user_views where view_name='PEOPLE_V';

TEXT
------------------------------------------------------------
select "PERSON_ID","FIRST_NAME","LAST_NAME","EYE_COLOR" from
people where last_name is not null

In the next post I’ll discuss another misconception about views.

Implementing Arc Relationships with Virtual Columns

Virtual columns were added in Oracle 11g Release 1. We can implement the same concept using views, but one of the advantages that I see in virtual columns is that we can define foreign key constraints on them. Well, we can define foreign key constraints on views as well, but only in DISABLE NOVALIDATE mode, which makes them a decoration rather than a data integrity protector. So to be more precise, we can define enabled foreign key constraints on virtual columns.

This post is about using virtual columns for implementing arc relationships.

In the following ERD, the arc represents the rule “each Entity Address must be owned by either a Person or a Company”:
arc
There are three common ways to implement this logical data model.

Multiple Tables

We can split the ENTITY_ADDRESS entity into two tables – say, PERSON_ADDRESSES and COMPANY_ADDRESSES. Disadvantages of this solution are that we need to maintain two (or more) tables and the code parts that manipulate them, although they are very similar. In addition, if later on we need to add another entity to the relationship (for example, Store, in addition to Person and Company) we will have to add another join table – STORE_ADDRESSES – and the relevant code.

A Single Table with Dedicated Columns

We can implement the ENTITY_ADDRESS entity as a single table – say, ENTITY_ADDRESSES – with a dedicated column for each referenced entity – PERSON_ID and COMPANY_ID (in addition to ADDRESS_ID) – and a check constraint to make sure one and only one of the columns PERSON_ID and COMPANY_ID is not null. The disadvantage of this solution is that we still need to maintain the dedicated columns and the code parts that manipulate them, although they are very similar. And if we need to add another entity to the relationship we will have to add another column to the table and to add the relevant code to manipulate the new column.

A Generic Table

We can implement the ENTITY_ADDRESS entity as a single table – say, ENTITY_ADDRESSES – with a generic column for the referenced entity and a column that stores the entity type to which the generic column references – e.g., ADDRESS_ID, OWNER_TYPE_ID, OWNER_ID. The disadvantage of this solution is that we cannot define foreign key constraints from ENTITY_ADDRESSES to the PEOPLE and COMPANIES tables.

Using Virtual Columns

I’d like to suggest another solution, that, using virtual columns, overcomes the above disadvantages.
This solution is based on the generic table one, but we’ll add also a virtual column for each referenced entity. The purpose of the virtual columns is just for enforcing the referential integrity, and the application should not be aware of them. Therefore, if another entity should be added afterwards, we will add another virtual column, but we will have no code changes to make.

Here it is:

> create table people (
>   id integer not null primary key,
>   first_name varchar2(30) not null,
>   last_name varchar2(30) not null
> );

Table created.

> create table companies (
>   id integer not null primary key,
>   name varchar2(100) not null,
>   number_of_employees integer,
>   description varchar2(1000)
> );

Table created.

> create table addresses (
>   id integer not null primary key,
>   street varchar2(30),
>   house_number varchar2(10),
>   city varchar2(30),
>   country varchar2(30)
> );

Table created.
> create table entity_addresses (
>   address_id integer not null references addresses,
>   owner_type_id char(1) not null check (owner_type_id in ('P','C')),
>   owner_id integer not null,
>   --
>   primary key (owner_id,owner_type_id,address_id),
>   --
>   person_id generated always as (decode(owner_type_id,'P',owner_id)) virtual
>     constraint address_fk_people references people,
>   company_id generated always as (decode(owner_type_id,'C',owner_id)) virtual
>     constraint address_fk_companies references companies
> );

Table created. 
> insert into people (id,first_name,last_name) values (1,'John','Doe');

1 row created.

> insert into companies (id,name) values (101,'DB Oriented');

1 row created.

> insert into addresses (id,street,city,country) values (1,'Dekel','Zurit','Israel');

1 row created.

> insert into entity_addresses (address_id,owner_type_id,owner_id) 
> values (1,'P',1);

1 row created.

> insert into entity_addresses (address_id,owner_type_id,owner_id) 
> values (1,'P',101);
insert into entity_addresses (address_id,owner_type_id,owner_id) values (1,'P',101)
*
ERROR at line 1:
ORA-02291: integrity constraint (DEMO5.ADDRESS_FK_PEOPLE) violated - parent key not found


> insert into entity_addresses (address_id,owner_type_id,owner_id) 
> values (1,'C',1);
insert into entity_addresses (address_id,owner_type_id,owner_id) values (1,'C',1)
*
ERROR at line 1:
ORA-02291: integrity constraint (DEMO5.ADDRESS_FK_COMPANIES) violated - parent key not found


> insert into entity_addresses (address_id,owner_type_id,owner_id) 
> values (1,'C',101);

1 row created.

Hiding the Virtual Columns

Since the virtual columns in this solution are just for enforcing the referential integrity, I prefer to hide them, so adding new entities to the arc (or removing entities from it) will be transparent to the application.

Invisible Columns

In Oracle 12c we can do it by making these columns invisible.

> alter table entity_addresses modify (
>   person_id invisible,
>   company_id invisible
> );

Table altered.

> select * from entity_addresses;

ADDRESS_ID OWN   OWNER_ID
---------- --- ----------
         1 P            1
         1 C          101

Editioning Views

Being an Edition-Based Redefinition (EBR) evangelist, my application code never refers to tables. Each table is covered by an editioning view, and the application code refers only to views. This practice enables to hide the virtual columns very easily, simply by not including them in the editioning view:

> create editioning view entity_addresses_v as
>   select address_id,
>          owner_type_id,
>          owner_id
>   from entity_addresses;

View created.

Unfortunately, EBR is not widely used 🙁
You can, however, use the same idea in non-EBR environment, by creating a regular view rather than an editioning view, and refer to this view from the application code.