Posted on

Editioning Implementation – Converting tables to Editioning Views

OK, ready to rename all your tables and break everything at once?
Good, let’s do this. Remember the SQL you used back in the planning stage to show the base table and editioning view names? We are going to build on that with the SQL below to generate the DDL commands to convert all the tables to EV’s. Remember this will invalidate a lot of objects, so give yourself time to revalidate.
SELECT ‘alter table ‘ || owner || ‘.’ || ev_name || ‘ rename to ‘ || new_table_name || ‘;’ ||
chr(10) || ‘create editioning view ‘ || owner || ‘.’ || ev_name || ‘ as select * from ‘ ||
owner || ‘.’ || new_table_name || ‘;’
FROM (SELECT owner, table_name ev_name, table_name || ‘_’ new_table_name FROM dba_tables
WHERE owner IN (SELECT owner FROM editioned_users_obj_cnt) AND length(table_name) < 30
UNION
SELECT owner, table_name ev_name, substr(table_name, 1, 29) || ‘_’ new_table_name FROM dba_tables
WHERE owner IN (SELECT owner FROM editioned_users_obj_cnt) AND length(table_name) = 30);
Pretty slick. This will save so much work, you can even expand on this to filter out any tables that are already converted. How? The DBA_EDITIONING_VIEWS view shows you the existing EV’s along with their base tables. I simple minus can find any tables that do not have an EV. Remember, a table can only have 1 EV and the EV will always be in the same schema as the base table. So, this SQL will show all of the tables in schemas you are intending to edition that do not yet have an EV.
SELECT owner, table_name FROM dba_tables WHERE owner IN (SELECT owner FROM editioned_users_obj_cnt)
MINUS
SELECT owner, table_name FROM dba_editioning_views;
Note: once you have turned on editioning for the schemas involved, it would be more accurate to select from dba_users where the editioning flag is turned on, but I am developing this process without making changes to the db, so you can generate and save the DDL scripts from your prod system and then take them to your dev system for execution and testing.
Posted on

Editioning Implementation – Save all triggers on the tables to be editioned

If you perform this before you rename any tables to replace them with editioning views, you will save yourself a lot of time editing source code. When  table is renamed, any triggers remain on the table. If you extract the source code after the rename it will have the new table name. Since we plan to drop the triggers and recreate them in the editioning views, we want to save the source code before the rename and apply it after creating the EV’s. Since the EV has the same name as the original table (so we don’t have to edit the name in all the dependent objects), running the saved trigger source will create the triggers correctly on the EV.
First to answer a question that is sure to occur to you. Why do we want to move the trigger to the EV, will it not work on the renamed base table?
It will continue to work if left on the base table, and it can still be editioned, and there are some use cases where it makes sense to put it here, but my reasoning for moving it is to enforce consistency of access and coding. If a developer is being retrained to use the EV’s instead of the underlying table, won’t they naturally use the EV to build their triggers on? Any triggers on the base table will not appear when viewing triggers on the EV and if you end up with some of both, confusion is the sure outcome.
That said, the EV’s hide certain things by design. For example, if you “alter” a column by adding a new column and making the EV hide that swap by displaying the new column with the old column name, how can a trigger see the old and new columns? It can’t. There are special triggers, called cross-edition triggers to handle this case, or you could create the trigger on the underlying table, that does have both columns visible. Just be careful when doing this, since your trigger is essentially hidden from developers used to using only the EV’s. My recommendation is to actively discourage triggers on base tables and monitor the appearance of any such triggers.
These commands, if put into a SQL script and run from sqlplus, will save a copy of the current source for all triggers on tables in the schemas you are going to edition. I would do this just before the table renames, but it’s safe to run now for testing. If you have all your trigger code easily deployable from the original codebase repository, you don’t really need this, but won’t you feel just a little better knowing it is saved off into a file?

SET ECHO OFF
SET FEED OFF
SET HEAD OFF
SET LINES 2047
SET PAGES 0
SET LONGCHUNKSIZE 2047
SET LONG 200000
SET TRIMSPOOL ON
EXECUTE DBMS_METADATA.SET_TRANSFORM_PARAM(DBMS_METADATA.SESSION_TRANSFORM,’SQLTERMINATOR’,TRUE);
COLUMN source_text WORD_WRAPPED
SPOOL trigger_ddl.sql
SELECT dbms_metadata.get_ddl(‘TRIGGER’, trigger_name, owner) source_text FROM dba_triggers
WHERE owner IN (SELECT owner FROM dheltzel.editioned_users_obj_cnt WHERE object_type = ‘TRIGGER’)
AND base_object_type = ‘TABLE’;
SPOOL OFF
EXIT

Posted on

Editioning Implementation – Enable schemas for editioning

The SQL below will write the DDL to enable the schemas for editioning. You are probably  not ready to execute these alter statements yet because here is what will happen:
  1. Most of the non-system objects in your database will become invalid – allow time to revalidate everything
  2. Some objects, with dependencies on the objects you just placed under EBR control, will fail to revalidate. If you did your homework with the prerequisite cleanups well, there won;t be many surprises here, but do allocate some time to deal with the unexpected.
  3. From now on, whenever you create a new user, you will need to be aware of their intended editioning status. The default is to not enable editions, likely because this can’t be undone, you can always enable editions on a user later, but you can disable editions. If you want to create a new user with editioning enabled, you must add the “ENABLE EDITIONS” clause to the create statement.
SELECT DISTINCT owner, editions_enabled, ‘alter user ‘ || username || ‘ enable editions force;’ cmd
  FROM editioned_users_obj_cnt  JOIN dba_users ON (username = owner) WHERE editions_enabled = ‘N’;
When you do feel ready to turn this on in any database, run the SQL and inspect it carefully (because it can’t be undone). You may want to do a backup and/or set a restore point, out of an abundance of caution.
After you execute the created DDL statements, re-run the SQL to see if they are all enabled. You should get no rows in the final run, except for any that you manually removed from the results the first time.
Posted on

Editioning Implementation – Replace Public Synonyms

Identify the public synonyms that will become invalid when you enable editioning for the selected schemas. Remember the rule here is that no non-editioned object can depend on an editioned object. The special Oracle users, like SYS and PUBLIC, can never be editioned, so by extension none of their objects can depend on anything in the schemas that you are editioning.
SELECT synonym_name, table_owner, table_name FROM dba_synonyms WHERE owner = ‘PUBLIC’ AND table_owner IN (SELECT owner FROM editioned_users_obj_cnt);

Now that you’ve found them what do you do? It would be nice if you could ascertain that some or all are not in use and just drop them. Doing this for objects that reside in the database is possible.
First, you should identify any dependencies on these public synonyms you need to drop and make them explicit references. This SQL will show the objects that need to be edited:
SELECT DISTINCT t.owner,t.name,t.type object_type,s.owner synonym_owner,s.synonym_name,s.table_owner || ‘.’ || s.table_name real_object
  FROM dba_dependencies t JOIN dba_synonyms s ON (s.owner = t.referenced_owner AND s.synonym_name = t.referenced_name AND t.referenced_type = ‘SYNONYM’)
 WHERE s.owner = ‘PUBLIC’ AND s.table_owner IN (SELECT owner FROM editioned_users_obj_cnt);
Fix those dependencies and check by running the SQL again and assure that it is clean.
For external connections, you cannot query a dependencies view. You can drop the synonyms on a test system and fix what breaks (either fixing the code to not need a synonym or create the private synonym),or you can simply create all the private synonyms that might possibly be used. The first way is obviously cleaner and better, but time consuming and more dangerous if you can’t test everything.
If you decide to take the approach of creating every possibly needed private synonym, this SQL will help you to do that. If you can all a filter to further restrict the grantee to a list of actual users that login on behalf of your app, even better. Be warned that running the resulting DDL will cause a lot of object invalidations, so schedule downtime appropriately.
SELECT ‘create or replace synonym ‘ || grantee || ‘.’ || table_name || ‘ for ‘ || owner || ‘.’ || table_name || ‘;’
  FROM ((SELECT r.grantee, p.owner, p.table_name FROM dba_role_privs r JOIN dba_tab_privs p ON (p.grantee = r.granted_role)
         UNION
         SELECT grantee, owner, table_name FROM dba_tab_privs)
        MINUS SELECT owner, table_owner, table_name FROM dba_synonyms) a
 WHERE (owner, table_name) IN
  (SELECT table_owner, table_name FROM dba_synonyms WHERE owner = ‘PUBLIC’ AND table_owner IN (SELECT owner FROM editioned_users_obj_cnt));
Posted on

Editioning Implementation – Create Editions

We are now going to create the editions that you decided on in the planning stage:
create edition RED as child of ora$base;
comment on edition RED is ‘This is the red edition’;
grant use on edition RED to PUBLIC;
create edition BLACK as child of RED;
comment on edition BLACK is ‘This is the black edition’;
grant use on edition BLACK to PUBLIC;
Now you can see your shiny new editions:
SELECT e.*, c.comments FROM dba_editions e JOIN dba_edition_comments c ON (c.edition_name = e.edition_name);
You should see 3 editions, all marked as usable.
Check on the grants with this SQL, each edition should have a grant to public.
SELECT * FROM dba_tab_privs WHERE privilege = ‘USE’;
Now you are ready to set the default edition to the first of your newly created editions.
First, check the current default edition:
SELECT property_value FROM database_properties WHERE property_name = ‘DEFAULT_EDITION’;
It should return ORA$BASE
Now change the default edition to RED:
ALTER SESSION SET DDL_LOCK_TIMEOUT=30;
ALTER DATABASE DEFAULT EDITION=RED;
 Check the default edition again:
SELECT property_value FROM database_properties WHERE property_name = ‘DEFAULT_EDITION’;
This time it should return ‘RED’
 As a final check, exit your sqlplus session and relogin. You should now be in the RED edition, check that with:
SELECT sys_context(‘USERENV’, ‘CURRENT_EDITION_NAME’) FROM dual;
 Incidentally, after you are fully switched over to the new editions, you can revoke use on ORA$BASE from public to retire the default edition. Don’t do that yet, you might want to switch back.
Posted on

Editioning Implementation – Planning

How many editions ?
The answer here is 2. Or 3 if you count ORA$BASE, which I will be ignoring in these discussions, so 2.
What schemas will contain editioned objects ?
Create this view in your private account to help you analyze the existing schemas. You will probably need an explicit grant to dba_objects, or use all_objects instead.
CREATE OR REPLACE VIEW editioned_users_obj_cnt AS
SELECT owner, object_type, COUNT(*) obj_count FROM dba_objects o
 WHERE owner NOT LIKE ‘%SYS%’
   AND owner NOT IN (‘PUBLIC’, ‘XDB’, ‘DBSNMP’, ‘ORACLE_OCM’, ‘ORDDATA’, ‘ORDPLUGINS’, ‘OUTLN’, ‘SCOTT’, ‘DIP’, ‘OPS$ORACLE’, ‘ORACLE’)
   AND object_type IN (‘FUNCTION’, ‘PACKAGE’, ‘PROCEDURE’, ‘TABLE’, ‘TRIGGER’, ‘TYPE’, ‘VIEW’)
 GROUP BY owner, object_type ORDER BY owner, object_type;
Once you have that, query and investigate whether you want/need all of them to be editioned. If in doubt, the answer is probably yes.
This query will show all the schemas you should probably edition:
SELECT DISTINCT owner, editions_enabled FROM editioned_users_obj_cnt JOIN dba_users ON (username = owner);
Naming conventions
editions – pick 2 names, I don’t think they should reflect an order. Colors work well, let’s say “RED” and “BLACK”
tables vs editioning views – We will append an underscore to each table name. If that makes the table name too long, we will change the final char to an underscore. Any remaining name conflicts will be resolved manually. This SQL will generate the new table names, it calls the old table name the “EV _NAME” (Editioning View):
SELECT owner, table_name ev_name, table_name || ‘_’ new_table_name FROM dba_tables
 WHERE owner IN (SELECT owner FROM editioned_users_obj_cnt) AND length(table_name) < 30
UNION
SELECT owner, table_name ev_name, substr(table_name, 1, 29) || ‘_’ new_table_name FROM dba_tables
 WHERE owner IN (SELECT owner FROM editioned_users_obj_cnt) AND length(table_name) = 30;
This SQL will show any namespace collisions with the new tables names that were generated. If it returns no rows, there are no issues.
SELECT owner, substr(table_name, 1, 29) || ‘_’ new_table_name, COUNT(*)  FROM dba_tables
 WHERE owner IN (SELECT owner FROM editioned_users_obj_cnt) AND length(table_name) = 30
 GROUP BY owner, substr(table_name, 1, 29) || ‘_’ HAVING COUNT(*) > 1;
Will you use cross edition triggers
For now, no. This can be revisited later if needed, but it removes a lot of extra training and confusion to say no at this point.
Handling DML changes to live systems
All DML done during deploy windows must be backward compatible with the former (live while we are releasing) code. Violations of this must be noted and a custom plan made for dealing with 2 code versions running against the same data. If aware of this pitfall, developers can usually code around this.
Cleanup tasks
We will drop or otherwise cleanup any invalid objects. This can be handled well ahead of time, with some monitoring in place to keep it clean.

Wherever possible, we will find code dependencies on synonyms and make the reference explicit. This SQL identifies dependencies on synonyms inside the database:

SELECT DISTINCT t.owner,t.name,t.type object_type,s.owner synonym_owner,s.synonym_name,s.table_owner || ‘.’ || s.table_name real_object
  FROM dba_dependencies t JOIN dba_synonyms s ON

    (s.owner = t.referenced_owner AND s.synonym_name = t.referenced_name AND t.referenced_type = ‘SYNONYM’)
 WHERE t.owner NOT LIKE ‘%SYS%’ AND t.owner NOT IN (‘PUBLIC’, ‘DBSNMP’, ‘ORACLE_OCM’, ‘XDB’, ‘ORDPLUGINS’, ‘CSMIG’) AND
       t.referenced_owner NOT IN (‘PUBLIC’) AND s.db_link IS NULL AND t.type NOT IN (‘SYNONYM’)
 ORDER BY t.owner, t.type, t.name;

Posted on

Editioning – Implementation Overview

This is the overall  plan for implementing editioning in a database.
  1. Planning steps
  2. Create Editions
  3. Replace Public Synonyms
  4. Enable schemas for editioning
  5. Save all triggers on the tables to be editioned
  6. Converting tables to Editioning Views
  7. Moving Triggers
  8. Stabilizes all editioned objects across editions
  9. Switch the default to one of the new edtions
  10. Implement no-downtime code deploys

Planning steps

How many editions ?
What schemas will contain editioned objects ?
Naming conventions

  • editions
  • tables vs editioning views – example would be to append an underscore to each table name

Will you use cross edition triggers ?
Handling DML changes to live systems
Cleanup tasks

  • drop any invalid objects (or make them valid if you need to keep them)
  • (optional) find code dependencies on synonyms and make the reference explicit if possible

Create Editions
Script this and test it thoroughly in your dev database
Be certain to build them in the same order and with the same names in all your databases
Don’t worry about instantiating anything, no users are enabled for editions yet, so nothing will edition

Replace Public Synonyms
Only need to drop the public synonyms that point to objects that belong to the schemas to be editioned (as determined in the planning step above)
Script a method of creating private synonyms for all schemas that can access these objects. Create those private synonyms before dropping the public ones.
Recompile any invalid objects – these go invalid when you create the private synonyms that override the publicones
Finally, drop the public synonyms and recheck for invalid objects

Enable schemas for editioning
Using the list from your planning, alter the users to turn on their editioned flag
You can query dba_users to confirm they are all enabled for editioning
Recompile everything. Here is where having no invalid objects to start makes this faster and cleaner. If something won’t recompile, fix it now.

Save all triggers on the tables to be editioned
Make sure you can recreate the trigger exactly as they are now, before you rename any tables.
After the table rename, the triggers will point to the new names, that is why you must grab the source now, or ensure it is otherwise available for creating the triggers from scratch

Converting tables to Editioning Views
You will need to rename each table where you wish to use editioning views. This is normally all the tables owned by the editioned schemas.
If the existing table name is the max number of characters, you need to modify it in some way if you wish to append an underscore
Script this also (seeing a theme here?), where for each table you rename it and create an EV as select * from the (changed) tablename
Again recompile everything – doing the recompiles in between help you debug issues with this, once you have the scripts proven, you might skip the interim compiles to save time on your production implementation, Depending on how good you feel about it

Moving Triggers
First, you must drop all the triggers on the underlying tables
Run the trigger create SQL you saved or otherwise made available, before the renames
Verify the triggers all match what they were before. They will still point to the same “table”, though that is now an editioning view

Stabilizes all editioned objects across editions
Starting in the lowest child edition, compile each editioned object into that edition, checking for invalid objects and fixing as needed
Repeat for the other editions
Run queries to compare counts of the various editions

Switch the default to one of the new edtions
Alter the default edition and any cluster ready services to point to one of the new editoins
Test with new connections to the DB

Implement no-downtime code deploys
Script this for safety and convenience
It is critical that you check the current edition and ensure you never deploy to the default edition

Want more details for these tasks? I am composing detailed instructions and some actual code for future posts.

I also reserve the right to come back to edit and add to this as I go through my next implementation. This was all written from memory and nothing jogs ones memory like repeating the process a second time.

Posted on

Editioning – Releasing code transparently

We left off with you in a confirmed correct edition in a sqlplus session. At this point you run your regular deploy scripts and re-compile any invalid objects. Now you are ready to test the new release.
Since you deployed to the non-default edition, all your live sessions are still running the same as before, you are the only session that sees the new code. In order to test, you want some other sessions to use this non-default edition. You will need to decide how you want to control this, but I will assume you will use cluster-ready services. To do that, create two new services, one for each edition and set the edition for each service with the -t option to srvctl. Then change the service name your app uses to connect to the one that uses the non-default edition. Then test as long as you wish, you are still in this isolated environment and your regular connections are still unaffected by all of your deploy and testing operations.
When you are finally ready to “go live”, simply change the default edition in the database. There are, of course, considerations outside the database, in upgrading other code and making sure it matches the code in the edition that you are connecting to. Give some thought to how the various parts work together when devising your deploy procedures.
Rollback is pretty obvious here, you simple switch the edition back and you are where you were before the deploy started. It doesn’t get much simpler than that!
Posted on

Editioning – Deploy code to an inactive edition

Last time I discussed controlling which edition a session connects with. This is crucial when actually deploying code. If you  deploy code to an edition with active sessions, you can disrupt those sessions (which negates the point of editioning), or you may find them blocking your code deploy with locks. To avoid this, may sure that no sessions are using the edition you intend to deploy to, and then make very sure you are in the intended edition before starting to deploy code.
I’m assuming that you will be deploying with scripts run by sqlplus, and that the live sessions are using the default edition. These are the steps you should take before deploying any code:
  1. Determine the correct edition. If you are using the two edition method, simply find the default edition and pick the other one, hereafter known as the “deploy edition”.
  2. Connect as the deploy edition using one of the methods outlined in the previous post.
  3. Verify that the current edition is the deploy edition, and that it is not the default edition.
  4. Verify that no other sessions that use the code you are deploying are using the deploy edition.
You will want to script all of this, not have to run the checks manually, but it’s good to understand how to run the checks for occasions when they fail, so you know how to fix the issues.
Determine the deploy edition
# Get the current default edition
DEF_EDITION=`sqlplus -s / as sysdba <<!
set head off
set feed off
set pages 0
select property_value from database_properties where property_name = ‘DEFAULT_EDITION’;
!`
# now switch the edition name
[ ${DEF_EDITION} = “BLACK” ] && DEPLOY_EDITION=WHITE || DEPLOY_EDITION=BLACK
Connect with the deploy edition
ORA_EDITION=${DEPLOY_EDITION}
sqlplus / as sysdba
– or –
sqlplus / as sysdba edition=${DEPLOY_EDITION}
Verify that we are ok to deploy to the current edition
SELECT sys_context(‘USERENV’, ‘CURRENT_EDITION_NAME’) “Current Edition” FROM dual;
WHENEVER SQLERROR EXIT
SET SERVEROUT ON SIZE UNLIMITED
DECLARE
e_halt_processing EXCEPTION;
v_edition varchar2(35);
  v_sess_cnt pls_integer;
BEGIN
SELECT sys_context(‘USERENV’, ‘CURRENT_EDITION_NAME’) INTO v_edition FROM dual;
— check that we are in the correct edition
IF v_edition <> ‘${DEPLOY_EDITION}’ THEN
DBMS_OUTPUT.PUT_LINE(‘Deploy halted – edition not set properly!!’);
RAISE e_halt_processing;
END IF;
— check that it’s not the default edition
  SELECT property_value INTO v_edition FROM database_properties WHERE property_name = ‘DEFAULT_EDITION’;
IF v_edition = ‘${DEPLOY_EDITION}’ THEN
DBMS_OUTPUT.PUT_LINE(‘Deploy halted – this is the default edition !!’);
RAISE e_halt_processing;
END IF;
— check that no app sessions are using the edition we are deploying to
SELECT COUNT(*) INTO v_sess_cnt FROM sys.gv$session s LEFT OUTER JOIN sys.dba_objects o ON (s.session_edition_id = o.object_id)
 WHERE username IN (‘APP1′,’APP2′,’APP3’)  AND s.status <> ‘KILLED’
 AND o.object_name = ‘${DEPLOY_EDITION}’;
  IF v_sess_cnt > 0 THEN
     DBMS_OUTPUT.PUT_LINE(‘Deploy halted – sessions are using this edition !!’);
     RAISE e_halt_processing;
  END IF;
END;
/
WHENEVER SQLERROR CONTINUE
Posted on

Editioning – Controlling the edition in use

You have your editions, and you can tell which edition each session is currently using, now how do you control that?

First we’ll look at sessions that you control. If you, or code under your control, initiates the edition, you can control the edition with an ALTER SESSION command, like this:

alter session set edition=MYEDITION;

There are shortcuts to this. When using sqlplus, you can set the ORA_EDITION env var or pass the edition on the command line like this:

sqlplus / as sysdba edition=MYEDITION

Note that although the SYS user cannot have editions enabled, it is still true that every login session has a current edition, since it may access objects owned by users that do have editions enabled.

Some other tools may allow you to set an edition for a database connection. This is really all just making the alter sessions command automatic.

You can change the edition in a session as often as you wish, but you cannot be in a place where changing the edition would make the current context invalid. For example, you may not change the edition inside PL/SQL because then you might be executing code from the old edition in the context of the new edition. I know you were thinking of login triggers to change the edition, but you can forget that.

So what about sessions that you don’t control in any way? If they are already running, you cannot change their edition. Think about the havoc that would wreck if it was allowed!

You can however influence the edition that they connect with. I say influence because the session can override your setting with an alter session of their own. The main way you do this is by changing the default edition for the entire database. Think of the default edition as the stable, steady state between code deploys. With no other intervention, all sessions connect as this edition and everything is consistent.

What if you want only certain sessions to connect with the newly deployed code in a non-default edition? The ideal way is to have those sessions make the alter session command, but if you don’t want to do that you have one final option, cluster-ready services.

The -t option to the srvctl modify service command will allow you to specify the edition for that service. Be careful in implementing this, it doesn’t always work reliably, but it’s the best option for controlling the connections of general apps. For one thing, please resist the temptation to use the dbms_services package to change the edition. It has all the options needed and will change the edition in the DBA_SERVICES view, but it will not change it in CRS correctly. Another bug is that if you try to unset the edition with the srvctl command (with -r “”), that will remove it from CRS (as shown by srvctl config), but leave it in the data dictionary, so then you must use the services package as a second step to truly clear it back to use the default edition.

My recommendation is that you not alter the edition of a service to effect this change, but create separate services for each edition and change the service name in the connect details the session will use. Editing the tnsnames.ora on the webserver for example.