OraStory

Documentation in need of confidence booster

July 5, 2009 · Leave a Comment

Some people attach a lot of weight to what the Oracle documentation says.

Rightly so, most of the time. It is a hugely significant resource.

But there are some documentation bugs / errors.

I was answering a character set posting on the OTN forums and when providing some links to the doco, I noticed this documentational crisis of confidence:


I think these secions that describe encoding are out of date. For example, do Java and Microsoft Windows still use UCS-2? Or do they use UTF-16? I think UNIX uses UTF-32. So I am confused about what is the most current information for these sections.

→ Leave a CommentCategories: 11g · Documentation · charactersets · database

Madness

May 29, 2009 · 5 Comments

Just a quick one…

Had a meeting request today to get an idea of the different authorisation groups that will be required when in the future we move to a model that carries the authorisation ACL with the data.

This is a part of an ongoing architectural initiative to turn a logic-heavy database effectively into a bit bucket.

I know these things are going on all the time everywhere in a quasi-religious battle.
I know I’ve talked about it before as have many others elsewhere.
I’m sure I resolved to let it wash over me and not to get bothered by it or get sucked into battles I can’t win.

But this is madness this aspiration that this database, probably one of the most expensive bits of this tech stack, should be reduced to a bit bucket at some point in the future.

With the middle tier doing the data joining…

The middle tier doing what should be the set operations in the database in some sort of horrific giant nested loop operation…

And the middle tier doing the data security, which should be right there with the data in the database.

It’s a mad world.

And in today’s economic climate, this can’t be what they mean by “doing more for less”.

→ 5 CommentsCategories: architecture · database · oracle

Don’t use One.com

May 20, 2009 · Leave a Comment

This is not an oracle-related post.

Just a warning about using one.com for domain hosting.

Unlike previous domain registration and hosting companies which I have used before, with one.com if you don’t give them 35 days notice before the end of your domain “lease” that you do not wish to renew, then you are obliged to pay for a further 12 months. If you then fail to pay to their schedule they then add late payment charges within a week of the domain renewal timeline with threats to raise late payment charges of 50% of the domain renewal fee per week.

Of course, caveat emptor.
I should have read the alleged terms and conditions.

But if you are looking at the various “deals” on domain registration and hosting, bear this opinion in mind.

→ Leave a CommentCategories: domain registration · hosting · one.com

YAGNI?

May 14, 2009 · 2 Comments

YAGNI, it’s a very common term in development teams these days. Overused even.

However, in today’s more austere and economically challenging times, I have some new acronyms for you.

I’m not really up on popular acronyms so these may already exist in some form or another, but here are some more appropriate sayings that I’m saying/seeing more and more, together especially:

I Do Bloody Well Need It (IBLOWENI – ha ha) … but … NO-one’s Gonna Pay For It (NOGOPAFI).

→ 2 CommentsCategories: development · oracle · yagni

Motivation

February 19, 2009 · 5 Comments

I’ve mentioned before that I’m lucky enough, given my own database-heavy focus, to be currently servicing a client whose applications tend to be database-heavy in terms of logic.

They are moving their applications gradually, yet wholesale and with no exceptions, towards an N-tier architecture throughout the company and their particular interpretation of N-tier means treating the database like a bit bucket (the only place I’ve been where they treat n-tier and database independent as synonymous although I know this is not an uncommon misunderstanding).

Today I had a conversation with one of the .Net developers along the lines of “you must be gutted given your database expertise.”

To which my response was that I was not particularly bothered which tier had the most lines of code – at the end of the day, it’s not a competition.

There are four things which primarily motivate me, or give me the most pleasure, in a professional capacity:

  1. Making slow, inefficient code fly.
  2. *Writing cool satisfying code.
  3. *Good design and good data modelling .
  4. Using databases efficiently.

*Incidentally, when designing and developing, I’m a great believer in the frequent need to not force it but wait for the right way to make itself known (at whatever time of day or night that might be).

So, moving a bunch of business logic (and in general very poorly written database logic) out of the database doesn’t bother me – the location of this logic doesn’t matter as long as it makes sense.

Remodelling and rearchitecting processes out of the database doesn’t bother me as long as it’s done accurately and properly wherever, and data is modelled appropriately.

In fact, the only thing which bothers me is that this rearchitecture is going to eventually involve Hibernate or similar, which will translate a single object save into a hundred individual insert or update statements, no bulk operations, nothing. I don’t like ORM (although I am very fond of OR views or an OR api in the database which can map one object save to as few operations in the database as is necessary).

→ 5 CommentsCategories: oracle

Adventures with VPD I: Result Cache

February 19, 2009 · 1 Comment

Oh, the challenges of implementing VPD on a mature system!

Like partitioning and other major features, implementing VPD on a “mature”* database that has been “evolving”* over many years can be nowhere near as simple as designing such a solution from scratch. (* “Mature” databases have not necessarily “evolved” in a way that you might recommend. Best practice might not apply.)

Anyway, I’ve been intending to write a case study of my current VPD implementation, dealing with some detail of the buy versus build decision of VPD versus OLS and the subsequent challenges and incompatibilities with various features. As usual, that article awaits but I wanted to kick off a mini-series on the various difficulties experienced (and this series might then help the coming together of a single article on the whole experience).

Challenge I
The challenge illustrated here is VPD + Result Cache.

Spoiler: For those who don’t want to read the whole article (it’s a bit long and the formatting which doesn’t help), the two work together but beware the subtle differences in behaviour between the SQL and PL/SQL Result Cache – read the documentation for each.

Quick Background
First a bit of background information which may help put these snippets of info into some sort of context.

  • As mentioned, this is a database that has been around for quite a few years now.
  • There has been a simple requirement to restrict access such that:
    • Users in the US can only see US data.
    • Users in the Rest of the World (ROW) cannot see US data.
  • VPD, OLS and a separate database were the options considered with VPD considered the only practical solution for one reason or another.

Demo
Let’s get into it.

Step 1: First up, script to create users for this demo:

  1. User to own table – APP_DATA
  2. User to own manage VPD security – SECURITY_MGR
  3. User to simulate app user with US visibility
  4. User to simulate app user with ROW visibility


-- Schema to hold some data
CREATE USER APP_DATA
IDENTIFIED BY password
DEFAULT TABLESPACE USERS
TEMPORARY TABLESPACE TEMP
QUOTA UNLIMITED ON USERS
ACCOUNT UNLOCK;

GRANT CREATE SESSION TO app_data;
GRANT CREATE TABLE TO app_data;
GRANT CREATE PROCEDURE TO app_data; 

-- Schema to manage security policies
CREATE USER SECURITY_MGR
IDENTIFIED BY password
DEFAULT TABLESPACE USERS
TEMPORARY TABLESPACE TEMP
QUOTA UNLIMITED ON USERS
ACCOUNT UNLOCK;

GRANT EXEMPT ACCESS POLICY TO security_mgr;
GRANT CREATE SESSION TO security_mgr;
GRANT CREATE TABLE TO security_mgr;
GRANT CREATE ANY CONTEXT TO security_mgr;
GRANT CREATE TRIGGER TO security_mgr;
GRANT ADMINISTER DATABASE TRIGGER TO security_mgr;
GRANT SELECT ANY TABLE TO security_mgr;
GRANT ALTER ANY TABLE TO security_mgr;
GRANT UPDATE ANY TABLE TO security_mgr;
GRANT EXECUTE ON DBMS_RLS TO security_mgr;
GRANT CREATE ANY TRIGGER TO security_mgr;
GRANT ADMINISTER DATABASE TRIGGER TO security_mgr;

-- User to see only US data
CREATE USER APP_USER_US
IDENTIFIED BY password
DEFAULT TABLESPACE USERS
TEMPORARY TABLESPACE TEMP
QUOTA UNLIMITED ON USERS
ACCOUNT UNLOCK;

GRANT CREATE SESSION TO app_user_us;

-- User to see only non-US data
CREATE USER APP_USER_ROW
IDENTIFIED BY password
DEFAULT TABLESPACE USERS
TEMPORARY TABLESPACE TEMP
QUOTA UNLIMITED ON USERS
ACCOUNT UNLOCK;

GRANT CREATE SESSION TO app_user_row;

Step 2: Next up, script to create table and data for this demo. It’s hardly imaginative nor a real world example…



conn app_data/password@dom11g

DROP TABLE cars;

CREATE TABLE cars
(id              NUMBER       NOT NULL
,make            VARCHAR2(48) NOT NULL
,security_label  VARCHAR2(6)  NOT NULL
,CONSTRAINT pk_cars PRIMARY KEY (id));

INSERT INTO app_data.cars
VALUES (1,'Audi','ROW');

INSERT INTO app_data.cars
VALUES (2,'Buick','US');

INSERT INTO app_data.cars
VALUES (3,'BMW','ROW');

INSERT INTO app_data.cars
VALUES (4,'Chrysler','US');

INSERT INTO app_data.cars
VALUES (5,'Citroen','US');

INSERT INTO app_data.cars
VALUES (6,'Dodge','US');

COMMIT;

GRANT SELECT ON cars TO app_user_us, app_user_row;

Step 3: Create vpd policies including

  • Create application context
  • Create package to manage context
  • Create logon trigger to set contexts
  • Create policy function that will apply predicates to policied tables
  • Apply policy to table


conn security_mgr/password@dom11g

CREATE CONTEXT security_ctx USING security_pkg;

CREATE OR REPLACE PACKAGE security_pkg
AS
  --
  PROCEDURE p_initialise_user;
  --
END security_pkg;
/

CREATE OR REPLACE PACKAGE BODY security_pkg
AS
  k_namespace CONSTANT VARCHAR2(12) := 'SECURITY_CTX';
  --
  PROCEDURE p_set_context (
    i_attribute  IN     VARCHAR2,
    i_value      IN     VARCHAR2
  )
  AS
  BEGIN
     --
     dbms_session.set_context(k_namespace,i_attribute,i_value);
     --
  END p_set_context;
  --
  PROCEDURE p_set_us_context
  AS
  BEGIN
     --
     p_set_context('CAN_SEE_US','Y');
     --
  END p_set_us_context;
  --
  PROCEDURE p_set_row_context
  AS
  BEGIN
     --
     p_set_context('CAN_SEE_ROW','Y');
     --
  END p_set_row_context;
  --
  PROCEDURE p_initialise_user
  AS
  BEGIN
     --
     IF USER = 'APP_USER_US'
     THEN
         --
         p_set_us_context;
         --
     ELSIF USER = 'APP_USER_ROW'
     THEN
         --
         p_set_row_context;
         --
     ELSE
         --
         p_set_us_context;
         p_set_row_context;
         --
     END IF;
     --
  END p_initialise_user;
  --
END security_pkg;
/

CREATE OR REPLACE TRIGGER tri_logon_set_rls_security
AFTER LOGON ON DATABASE
BEGIN
   --
   security_pkg.p_initialise_user;
   --
END tri_logon_set_rls_security;
/

CREATE OR REPLACE FUNCTION f_security_policy (
  i_schema                     IN     VARCHAR2 DEFAULT NULL,
  i_object                     IN     VARCHAR2 DEFAULT NULL
)
RETURN VARCHAR2
AS
BEGIN
   --
   RETURN CASE WHEN SYS_CONTEXT('SECURITY_CTX','CAN_SEE_US')  = 'Y'
               AND  SYS_CONTEXT('SECURITY_CTX','CAN_SEE_ROW') = 'Y'
               THEN NULL
               WHEN SYS_CONTEXT('SECURITY_CTX','CAN_SEE_US')  = 'Y'
               THEN ' security_label  = ''US'' '
               WHEN SYS_CONTEXT('SECURITY_CTX','CAN_SEE_ROW') = 'Y'
               THEN ' security_label = ''ROW'' '
          END;
   --
END f_security_policy;
/

begin
  dbms_rls.add_policy
  (object_schema   => 'APP_DATA',
   object_name     => 'CARS',
   policy_name     => 'RLS_CARS',
   function_schema => 'SECURITY_MGR',
   policy_function => 'F_SECURITY_POLICY',
   policy_type     => SYS.DBMS_RLS.SHARED_CONTEXT_SENSITIVE,
   statement_types => 'SELECT');
end;
/

Step 4: Test the VPD policies.
Step 4a: Connecting as APP_USER_US, we should only be able to see data from APP_DATA.CARS which is labelled as US:



conn app_user_us/password@dom11g

app_user_us@dom11g>select * from app_data.cars;

 ID MAKE       SECUR
--- ---------- -----
  2 Buick      US
  4 Chrysler   US
  5 Citroen    US
  6 Dodge      US

Elapsed: 00:00:00.01

Step 4b: Connecting as APP_USER_ROW, we should only be able to see data from APP_DATA.CARS which is labelled as ROW:


conn app_user_row/password@dom11g

app_user_row@dom11g>select * from app_data.cars;

app_user_row@dom11g>select * from app_data.cars;

 ID MAKE       SECUR
--- ---------- -----
  1 Audi       ROW
  3 BMW        ROW

Elapsed: 00:00:00.00
app_user_row@dom11g>

Good stuff. Spotted the mistake? Last time I checked, Citroen was French – should be labelled ROW. I’ll leave it in and update it after we’ve done some result cache queries just to show that that all works.

Step 5: Let’s take a look at the SQL result cache using the result_cache hint.
Step 5a: We’ll go with APP_USER_US first.



conn app_user_us/password@dom11g

app_user_us@dom11g>set autotrace on
app_user_us@dom11g>select /*+ result_cache */ * from app_data.cars;

 ID MAKE       SECUR
--- ---------- -----
  2 Buick      US
  4 Chrysler   US
  5 Citroen    US
  6 Dodge      US

Elapsed: 00:00:00.02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=5 Card=4 Bytes=176)
   1    0   RESULT CACHE OF '25tyax8u9ktwf2g61acmsb5apa'
   2    1     TABLE ACCESS (FULL) OF 'CARS' (TABLE) (Cost=5 Card=4 Bytes=176)

Statistics
----------------------------------------------------------
        121  recursive calls
          0  db block gets
         58  consistent gets
          0  physical reads
          0  redo size
        508  bytes sent via SQL*Net to client
        427  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          4  rows processed

app_user_us@dom11g>select /*+ result_cache */ * from app_data.cars;

 ID MAKE       SECUR
--- ---------- -----
  2 Buick      US
  4 Chrysler   US
  5 Citroen    US
  6 Dodge      US

Elapsed: 00:00:00.02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=5 Card=4 Bytes=176)
   1    0   RESULT CACHE OF '25tyax8u9ktwf2g61acmsb5apa'
   2    1     TABLE ACCESS (FULL) OF 'CARS' (TABLE) (Cost=5 Card=4 Bytes=176)

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          0  consistent gets
          0  physical reads
          0  redo size
        508  bytes sent via SQL*Net to client
        427  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          4  rows processed


I’m happy enough that it’s coming from the cache the second time.

Step 5a: How about APP_USER_US?



conn app_user_row/password@dom11g

app_user_row@dom11g>set autotrace on
app_user_row@dom11g>select /*+ result_cache */ * from app_data.cars;

 ID MAKE       SECUR
--- ---------- -----
  1 Audi       ROW
  3 BMW        ROW

Elapsed: 00:00:00.02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=5 Card=2 Bytes=88)
   1    0   RESULT CACHE OF 'g0ubwn87rvz5s15cj3089zgn3h'
   2    1     TABLE ACCESS (FULL) OF 'CARS' (TABLE) (Cost=5 Card=2 Bytes=88)

Statistics
----------------------------------------------------------
         89  recursive calls
          0  db block gets
         57  consistent gets
          0  physical reads
          0  redo size
        473  bytes sent via SQL*Net to client
        427  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

app_user_row@dom11g>select /*+ result_cache */ * from app_data.cars;

 ID MAKE       SECUR
--- ---------- -----
  1 Audi       ROW
  3 BMW        ROW

Elapsed: 00:00:00.02

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=5 Card=2 Bytes=88)
   1    0   RESULT CACHE OF 'g0ubwn87rvz5s15cj3089zgn3h'
   2    1     TABLE ACCESS (FULL) OF 'CARS' (TABLE) (Cost=5 Card=2 Bytes=88)

Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          0  consistent gets
          0  physical reads
          0  redo size
        473  bytes sent via SQL*Net to client
        427  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          2  rows processed

Step 6: Let’s update that Citroen row – double check what happens.



conn app_data/password@dom11g

app_data@dom11g>update cars set security_label = 'ROW' where id = 5;

1 row updated.

Elapsed: 00:00:00.00
app_data@dom11g>commit;

Commit complete.

Step 7: Back to the visibility users
Step 7a: APP_USER_US



app_user_us@dom11g>select /*+ result_cache */ * from app_data.cars;

 ID MAKE       SECUR
--- ---------- -----
  2 Buick      US
  4 Chrysler   US
  6 Dodge      US

Elapsed: 00:00:00.00
app_user_us@dom11g>

Step 7b: APP_USER_ROW



app_user_row@dom11g>select /*+ result_cache */ * from app_data.cars;

 ID MAKE       SECUR
--- ---------- -----
  1 Audi       ROW
  3 BMW        ROW
  5 Citroen    ROW

Elapsed: 00:00:00.00
app_user_row@dom11g>

Looking more accurate now.

SQL Query Result Cache
So, the SQL Result Cache apparently works fine with VPD.
Let’s review what the documentation says:

Cached results are parameterized with the parameter values if any of the following constructs are used in the query:

Bind variables.

The following SQL functions: dbtimezone, sessiontimezone, userenv/sys_context (with constant variables), uid, and user.

NLS parameters.

Parameterized cache results can be reused if the query is equivalent and the parameter values are the same.

So what of the PL/SQL Function Result Cache?

Step 8: Create a result cache function to demonstrate (this is an artificial and simplified function – it demos what I want to demo, it’s derived from a real-world implementation but I would advocate using the result cache for this purpose)


conn app_data/password@dom11g

CREATE OR REPLACE PACKAGE cars_pkg
AS
--
FUNCTION f_get_make (
i_id IN cars.id%TYPE
)
RETURN cars.make%TYPE;
--
END cars_pkg;
/

CREATE OR REPLACE PACKAGE BODY cars_pkg
AS
--
TYPE t_cars IS TABLE of cars%ROWTYPE INDEX BY PLS_INTEGER;
--
FUNCTION f_get_cars
RETURN t_cars
RESULT_CACHE
RELIES_ON (cars)
AS
--
l_cars t_cars;
l_tmp_cars t_cars;
i PLS_INTEGER;
--
BEGIN
--
SELECT *
BULK COLLECT INTO l_tmp_cars
FROM cars;
--
i := l_tmp_cars.FIRST;
--
WHILE (i IS NOT NULL)
LOOP
--
l_cars(l_tmp_cars(i).id).id := l_tmp_cars(i).id;
l_cars(l_tmp_cars(i).id).make := l_tmp_cars(i).make;
l_cars(l_tmp_cars(i).id).security_label := l_tmp_cars(i).security_label;
--
i := l_tmp_cars.NEXT(i);
--
END LOOP;

--
RETURN l_cars;
--
END f_get_cars;
--
FUNCTION f_get_make (
i_id IN cars.id%TYPE
)
RETURN cars.make%TYPE
AS
--
l_cars t_cars;
--
BEGIN
--
l_cars := f_get_cars;
RETURN l_cars(i_id).make;
--
EXCEPTION
WHEN no_data_found THEN
RETURN NULL;
END f_get_make;
--
END cars_pkg;
/

GRANT EXECUTE ON cars_pkg TO app_user_us, app_user_row;

Step 9: Let’s try it using this.
Step 9a: As APP_USER_US initially


app_user_us@dom11g>var v varchar2(48)
app_user_us@dom11g>set autoprint on
app_user_us@dom11g>select * from app_data.cars;

 ID MAKE       SECUR
--- ---------- -----
  2 Buick      US
  4 Chrysler   US
  6 Dodge      US

Elapsed: 00:00:00.01
app_user_us@dom11g>exec :v := app_data.cars_pkg.f_get_make(1);

PL/SQL procedure successfully completed.

V
---------------

Elapsed: 00:00:00.01
app_user_us@dom11g>exec :v := app_data.cars_pkg.f_get_make(2);

PL/SQL procedure successfully completed.

V
---------------
Buick

Elapsed: 00:00:00.01

As expected, APP_USER_US cannot see the Audi data, can see Buick. Perfect!
Step 9b: Let’s try APP_USER_ROW.



conn app_user_row/password@dom11g

app_user_row@dom11g>var v varchar2(48)
app_user_row@dom11g>set autoprint on
app_user_row@dom11g>select * from app_data.cars;

  ID MAKE       SECUR
---- ---------- -----
   1 Audi       ROW
   3 BMW        ROW
   5 Citroen    ROW

Elapsed: 00:00:00.00
app_user_row@dom11g>exec :v := app_data.cars_pkg.f_get_make(1);

PL/SQL procedure successfully completed.

V
----------

Elapsed: 00:00:00.01
app_user_row@dom11g>exec :v := app_data.cars_pkg.f_get_make(2);

PL/SQL procedure successfully completed.

V
----------
Buick

Elapsed: 00:00:00.01
app_user_row@dom11g>

Wrong! And just what I wanted to demo. By implementing a result function like this, I have bypassed my VPD and rendered results from this function unpredictable depending on who ran the results which were cached.

So, with the PL/SQL function RESULT_CACHE directive you have to be careful, not necessarily anymore so than certain other features but just something to be aware of.

Before I knew the solution, but after I had these results, what happened when I combined VPD with such a Result Cache mis-implementation was not a massive surprise, but I had expected that either an error message would be raised, similiar to Materialized Views or the MERGE statement (forward references to future articles on the challenges of combining VPD with those two), or perhaps that the RESULT_CACHE directive would be ignored.

However the behaviour is documented and the answer is very much documented in the PL/SQL Language Reference:


Making Result-Cached Functions Handle Session-Specific Settings
The PL/SQL function result-caching feature does not automatically handle dependence on session-specific application contexts. If you must cache the results of a function that depends on session-specific application contexts, you must pass the application context to the function as a parameter. You can give the parameter a default value, so that not every user must specify it.

The same advice applies to other session specific settings, e.g. NLS_DATE_FORMAT etc.

Step 10: Make safe the result_cache implementation of APP_DATA.CARS_PKG.


conn app_data/password@dom11g

CREATE OR REPLACE PACKAGE APP_DATA.cars_pkg
AS
  --
  FUNCTION f_get_make_safe (
    i_id          IN  cars.id%TYPE,
    i_can_see_us  IN  VARCHAR2 DEFAULT SYS_CONTEXT('SECURITY_CTX','CAN_SEE_US'),
    i_can_see_row IN  VARCHAR2 DEFAULT SYS_CONTEXT('SECURITY_CTX','CAN_SEE_ROW')
  )
  RETURN cars.make%TYPE;
  --
END cars_pkg;
/

CREATE OR REPLACE PACKAGE BODY APP_DATA.cars_pkg
AS
  --
  TYPE t_cars IS TABLE of cars%ROWTYPE INDEX BY PLS_INTEGER;
  --
  FUNCTION f_get_cars_safe (
    i_can_see_us  IN  VARCHAR2 DEFAULT SYS_CONTEXT('SECURITY_CTX','CAN_SEE_US'),
    i_can_see_row IN  VARCHAR2 DEFAULT SYS_CONTEXT('SECURITY_CTX','CAN_SEE_ROW')
  )
  RETURN t_cars
  RESULT_CACHE
  RELIES_ON (cars)
  AS
    --
    l_cars     t_cars;
    l_tmp_cars t_cars;
    i          PLS_INTEGER;
    --
  BEGIN
     --
     SELECT *
     BULK COLLECT INTO l_tmp_cars
     FROM   cars;
     --
     i := l_tmp_cars.FIRST;
     --
     WHILE (i IS NOT NULL)
     LOOP
         --
         l_cars(l_tmp_cars(i).id).id             := l_tmp_cars(i).id;
         l_cars(l_tmp_cars(i).id).make           := l_tmp_cars(i).make;
         l_cars(l_tmp_cars(i).id).security_label := l_tmp_cars(i).security_label;
         --
         i := l_tmp_cars.NEXT(i);
         --
     END LOOP;

     --
     RETURN l_cars;
     --
  END f_get_cars_safe;
  --
  FUNCTION f_get_make_safe (
    i_id          IN  cars.id%TYPE,
    i_can_see_us  IN  VARCHAR2 DEFAULT SYS_CONTEXT('SECURITY_CTX','CAN_SEE_US'),
    i_can_see_row IN  VARCHAR2 DEFAULT SYS_CONTEXT('SECURITY_CTX','CAN_SEE_ROW')
  )
  RETURN cars.make%TYPE
  AS
    --
    l_cars t_cars;
    --
  BEGIN
     --
     l_cars := f_get_cars_safe;
     RETURN l_cars(i_id).make;
     --
  EXCEPTION
       WHEN no_data_found THEN
            RETURN NULL;
  END f_get_make_safe;
  --
END cars_pkg;
/

Step 11: With this our previous function lookup should work as expected:
Step 11a: As APP_USER_US



conn app_user_us/password@dom11g

app_user_us@dom11g>var v varchar2(48)
app_user_us@dom11g>set autoprint on
app_user_us@dom11g>select * from app_data.cars;

  ID MAKE       SECUR
---- ---------- -----
   2 Buick      US
   4 Chrysler   US
   6 Dodge      US

Elapsed: 00:00:00.01
app_user_us@dom11g>exec :v := app_data.cars_pkg.f_get_make_safe(1);

PL/SQL procedure successfully completed.

V
----------

Elapsed: 00:00:00.01
app_user_us@dom11g>exec :v := app_data.cars_pkg.f_get_make_safe(2);

PL/SQL procedure successfully completed.

V
----------
Buick

Elapsed: 00:00:00.01



Step 11a: As APP_USER_ROW



conn app_user_row/password@dom11g

app_user_row@dom11g>var v varchar2(48)
app_user_row@dom11g>set autoprint on
app_user_row@dom11g>select * from app_data.cars;

  ID MAKE       SECUR
---- ---------- -----
   1 Audi       ROW
   3 BMW        ROW
   5 Citroen    ROW

Elapsed: 00:00:00.01
app_user_row@dom11g>exec :v := app_data.cars_pkg.f_get_make_safe(1);

PL/SQL procedure successfully completed.

V
----------
Audi

Elapsed: 00:00:00.01
app_user_row@dom11g>exec :v := app_data.cars_pkg.f_get_make_safe(2);

PL/SQL procedure successfully completed.

V
----------

Elapsed: 00:00:00.01
app_user_row@dom11g>

Problem fixed.

Moral
The moral of the story is simple in this case – RTFM.
This time everything I needed was there in the docs. I hadn’t picked up on it fully when I implemented the result cache procs when VPD wasn’t in the picture and I didn’t review the information up front when looking at the VPD solution.
Caveat Developer.

Documentation

→ 1 CommentCategories: 11.1.0.7 · oracle · result cache · vpd

Beware the compound trigger

January 20, 2009 · 1 Comment

In 11g, Oracle introduced a new feature in the compound trigger.

Through many years and versions, the solution to the mutating table / ora-04091 problem was often a clumsy combination of packaged global, before statement trigger, after row trigger and after statement trigger.

However, in 11g, the introduction of the compound trigger provided a much more compact solution, where you could declare your global, your statement level logic and any row level code in a single trigger.

Examples from the Oracle documentation:

Trouble is, under certain simple circumstances, they don’t work.

Take this illustration abstracted from a proposed VPD labelling solution on 11.1.0.6.

The illustration is a little artificial because all distractions have been removed but it’s taken from a real world example.

Essentially, three updates via a package cause an ORA-07445 but three direct update statements do not. Take a look:


dominic@DOM11G>prompt ---------- cleanup previous creates
---------- cleanup previous creates
dominic@DOM11G>drop table compound_trigger_test_tab1;

Table dropped.

Elapsed: 00:00:01.02
dominic@DOM11G>drop package compound_trigger_test_pkg;

Package dropped.

Elapsed: 00:00:00.00
dominic@DOM11G>
dominic@DOM11G>prompt ---------- create table
---------- create table
dominic@DOM11G>
dominic@DOM11G>create table compound_trigger_test_tab1
  2  (col1 number
  3  ,col2 number);

Table created.

Elapsed: 00:00:00.00
dominic@DOM11G>
dominic@DOM11G>prompt ---------- insert data
---------- insert data
dominic@DOM11G>
dominic@DOM11G>insert into compound_trigger_test_tab1 values (1,1);

1 row created.

Elapsed: 00:00:00.00
dominic@DOM11G>
dominic@DOM11G>
dominic@DOM11G>prompt ---------- create compound trigger
---------- create compound trigger
dominic@DOM11G>
dominic@DOM11G>CREATE OR REPLACE TRIGGER compound_trigger_test_tri1
  2  FOR INSERT OR UPDATE ON compound_trigger_test_tab1
  3  REFERENCING NEW AS NEW OLD AS OLD
  4  COMPOUND TRIGGER
  5   --
  6   TYPE r_ctt IS RECORD
  7   (col1    NUMBER
  8   ,col2    NUMBER);
  9   --
 10   TYPE t_ctt IS TABLE OF r_ctt
 11   INDEX BY PLS_INTEGER;
 12   --
 13   vt_ctt t_ctt;
 14   --
 15  BEFORE STATEMENT
 16  IS
 17  BEGIN
 18     --
 19     DBMS_OUTPUT.PUT_LINE('compound_trigger_test_tri1::before statement');
 20     NULL;
 21     --
 22  END BEFORE STATEMENT;
 23  --
 24  BEFORE EACH ROW
 25  IS
 26  BEGIN
 27      --
 28      DBMS_OUTPUT.PUT_LINE('compound_trigger_test_tri1::before row');
 29      NULL;
 30      --
 31  END BEFORE EACH ROW;
 32  --
 33  AFTER EACH ROW
 34  IS
 35  BEGIN
 36     --
 37     DBMS_OUTPUT.PUT_LINE('compound_trigger_test_tri1::after row');
 38     NULL;
 39     --
 40  END AFTER EACH ROW;
 41  --
 42  AFTER STATEMENT
 43  IS
 44    i PLS_INTEGER;
 45  BEGIN
 46     --
 47     DBMS_OUTPUT.PUT_LINE('compound_trigger_test_tri1::after statement');
 48     i := vt_ctt.FIRST;
 49     --
 50     WHILE (i IS NOT NULL)
 51     LOOP
 52         --
 53         NULL;
 54         --
 55         i := vt_ctt.NEXT(i);
 56         --
 57     END LOOP;
 58     --
 59  END AFTER STATEMENT;
 60  --
 61  END compound_trigger_test_tri1;
 62  /

Trigger created.

Elapsed: 00:00:00.00
dominic@DOM11G>
dominic@DOM11G>show errors
No errors.
dominic@DOM11G>
dominic@DOM11G>
dominic@DOM11G>prompt ---------- create package header
---------- create package header
dominic@DOM11G>
dominic@DOM11G>CREATE OR REPLACE PACKAGE compound_trigger_test_pkg
  2  AS
  3    --
  4    PROCEDURE p_update_tab1 (
  5      i_col1                     in     compound_trigger_test_tab1.col1%TYPE
  6    );
  7    --
  8  END;
  9  /

Package created.

Elapsed: 00:00:00.00
dominic@DOM11G>
dominic@DOM11G>
dominic@DOM11G>prompt ---------- create package body
---------- create package body
dominic@DOM11G>
dominic@DOM11G>CREATE OR REPLACE PACKAGE BODY compound_trigger_test_pkg
  2  AS
  3    --
  4    PROCEDURE p_update_tab1 (
  5      i_col1                     in     compound_trigger_test_tab1.col1%TYPE
  6    )
  7    AS
  8    BEGIN
  9       --
 10       DBMS_OUTPUT.PUT_LINE('compound_trigger_test_pkg.p_update_tab1');
 11       UPDATE compound_trigger_test_tab1
 12       SET    col2 = col2
 13       WHERE  col1 = i_col1;
 14       --
 15    END p_update_tab1;
 16  END;
 17  /

Package body created.

Elapsed: 00:00:00.00
dominic@DOM11G>
dominic@DOM11G>
dominic@DOM11G>prompt ---------- run update via package test 1 - should be ok
---------- run update via package test 1 - should be ok
dominic@DOM11G>exec compound_trigger_test_pkg.p_update_tab1(1);

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
dominic@DOM11G>
dominic@DOM11G>prompt ---------- run update via package 2 - should be ok
---------- run update via package 2 - should be ok
dominic@DOM11G>exec compound_trigger_test_pkg.p_update_tab1(2);

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
dominic@DOM11G>
dominic@DOM11G>prompt ---------- run update via package 3 - should go bang
---------- run update via package 3 - should go bang
dominic@DOM11G>exec compound_trigger_test_pkg.p_update_tab1(3);
BEGIN compound_trigger_test_pkg.p_update_tab1(3); END;

*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

Elapsed: 00:00:00.05
dominic@DOM11G>conn dominic@dom11g
Enter password: *******
Connected.
dominic@DOM11G>UPDATE compound_trigger_test_tab1
  2  SET    col2 = col2
  3  WHERE  col1 = 1;

1 row updated.

Elapsed: 00:00:00.00
dominic@DOM11G>
dominic@DOM11G>UPDATE compound_trigger_test_tab1
  2  SET    col2 = col2
  3  WHERE  col1 = 2;

0 rows updated.

Elapsed: 00:00:00.00
dominic@DOM11G>
dominic@DOM11G>UPDATE compound_trigger_test_tab1
  2  SET    col2 = col2
  3  WHERE  col1 = 3;

0 rows updated.

Elapsed: 00:00:00.00
dominic@DOM11G>

Alert log and trace file reveal the cause to be an ORA-07445:


Exception [type: SIGSEGV, Address not mapped to object] [ADDR:0x448] [PC:0x1B6A3B8, kkxtexe()+988]
Errors in file /app/oracle/diag/rdbms/dom11g/DOM11G/trace/dom11G_ora_4860.trc  (incident=845912):
ORA-07445: exception encountered: core dump [kkxtexe()+988] [SIGSEGV] [ADDR:0x448] [PC:0x1B6A3B8] [Address not mapped to object] []
ORA-21779: duration not active

So, for the moment, I would not go near them.
I’ve got an SR raised (it’s all I seem to doing these days is find bugs in 11.1.0.6 (more on the incompatibility between VPD and Materialized Views another time) and 11.1.0.7)
It’s back to multiple row and statement level triggers.

→ 1 CommentCategories: 11g · compound trigger · ora-03113 · ora-07445 · trigger

11.1.0.7 Follow up

January 12, 2009 · 3 Comments

As a follow-up to Friday’s post about some local issues with the 11.1.0.7 patch, I’ve distilled an isolated piece of SQL to demonstrate the ORA-03113.

Demo test case looks like this:


DOM@11g>l
    WITH subq_goes_bang_data AS
         (SELECT 'CST1' database_name,'-6' ro_id,'\\xxxxx\uts_dev\SplitterToolSystemTestRO1' ros_path
          FROM   dual)
    ,    subq_db_name   AS
         (SELECT DECODE (INSTR (global_name, '.WORLD'),
                         0,global_name,
                         SUBSTR (global_name,1,INSTR (global_name,'.WORLD') - 1)
                        ) database_name
          FROM   global_name)
   ,    subq_goes_bang AS
       (
        SELECT rrc.ro_id,
               rrc.ros_path
        FROM   subq_goes_bang_data rrc
        WHERE rrc.database_name = (SELECT database_name FROM subq_db_name)
        UNION ALL
        SELECT rrc.ro_id,
               rrc.ros_path
        FROM   subq_goes_bang_data rrc
        WHERE  rrc.database_name = 'CST1'
        AND    NOT EXISTS(SELECT NULL
                          FROM subq_goes_bang_data rrc1
                          WHERE rrc1.database_name =(SELECT database_name FROM subq_db_name)
                          AND rrc1.ro_id = rrc.ro_id)
        AND    'CSP1' <>(SELECT database_name FROM subq_db_name)
   )
   SELECT  '<?xml version="1.0" encoding="UTF-16"?>'
           || XMLELEMENT ("LocalConfiguration",XMLFOREST (a.ros_path AS "RootFilePath"))
   FROM    subq_goes_bang a
  WHERE   a.ro_id = -6

DOM@11g>/
WITH subq_goes_bang_data AS
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel

Elapsed: 00:00:00.05
DOM@11g>

Comment out the XML bits and bobs and just select the column – no problem.
Comment out the UNION ALL in subq_goes_bang – no problem.

Explain plan looks like this:


DOM@11g>explain plan for
  2  WITH subq_goes_bang_data AS
  3       (SELECT 'CST1' database_name,'-6' ro_id,'\\xxxxx\uts_dev\SplitterToolSystemTestRO1' ros_path
  4        FROM   dual)
  5  ,    subq_db_name   AS
  6       (SELECT DECODE (INSTR (global_name, '.WORLD'),
  7                       0,global_name,
  8                       SUBSTR (global_name,1,INSTR (global_name,'.WORLD') - 1)
  9                      ) database_name
 10        FROM   global_name)
 11  ,    subq_goes_bang AS
 12      (
 13       SELECT rrc.ro_id,
 14              rrc.ros_path
 15       FROM   subq_goes_bang_data rrc
 16       WHERE rrc.database_name = (SELECT database_name FROM subq_db_name)
 17       UNION ALL
 18       SELECT rrc.ro_id,
 19              rrc.ros_path
 20       FROM   subq_goes_bang_data rrc
 21       WHERE  rrc.database_name = 'CST1'
 22       AND    NOT EXISTS(SELECT NULL
 23                         FROM subq_goes_bang_data rrc1
 24                         WHERE rrc1.database_name =(SELECT database_name FROM subq_db_name)
 25                         AND rrc1.ro_id = rrc.ro_id)
 26       AND    'CSP1' <> (SELECT database_name FROM subq_db_name)
 27  )
 28  SELECT  '<?xml version="1.0" encoding="UTF-16"?>'
 29          || XMLELEMENT ("LocalConfiguration",XMLFOREST (a.ros_path AS "RootFilePath"))
 30  FROM    subq_goes_bang a
 31  WHERE   a.ro_id = -6;

Explained.

Elapsed: 00:00:00.00
DOM@11g>
DOM@11g>select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 4143783385

----------------------------------------------------------------------------------------------------
| Id  | Operation                  | Name                        | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |                             |     2 |    86 |    16   (7)| 00:00:01 |
|   1 |  TEMP TABLE TRANSFORMATION |                             |       |       |            |          |
|   2 |   LOAD AS SELECT           |                             |       |       |            |          |
|   3 |    INDEX FULL SCAN         | SYS_IOT_TOP_57625           |     1 |       |     1   (0)| 00:00:01 |
|   4 |   LOAD AS SELECT           |                             |       |       |            |          |
|*  5 |    TABLE ACCESS FULL       | PROPS$                      |     1 |    27 |     2   (0)| 00:00:01 |
|   6 |   VIEW                     |                             |     2 |    86 |    13   (8)| 00:00:01 |
|   7 |    UNION-ALL               |                             |       |       |            |          |
|*  8 |     VIEW                   |                             |     1 |    53 |     2   (0)| 00:00:01 |
|   9 |      TABLE ACCESS FULL     | SYS_TEMP_0FD9D6643_B0B39986 |     1 |    53 |     2   (0)| 00:00
|  10 |      VIEW                  |                             |     1 |  2002 |     2   (0)| 00:00:01 |
|  11 |       TABLE ACCESS FULL    | SYS_TEMP_0FD9D6644_B0B39986 |     1 |    27 |     2   (0)| 00:0
|* 12 |     FILTER                 |                             |       |       |            |          |
|* 13 |      HASH JOIN ANTI        |                             |     1 |    57 |     7  (15)| 00:00:01 |
|* 14 |       VIEW                 |                             |     1 |    53 |     2   (0)| 00:00:01 |
|  15 |        TABLE ACCESS FULL   | SYS_TEMP_0FD9D6643_B0B39986 |     1 |    53 |     2   (0)| 00:0
|  16 |       VIEW                 | VW_SQ_1                     |     1 |     4 |     4   (0)| 00:00:01 |
|* 17 |        VIEW                |                             |     1 |    10 |     2   (0)| 00:00:01 |
|  18 |         TABLE ACCESS FULL  | SYS_TEMP_0FD9D6643_B0B39986 |     1 |    53 |     2   (0)| 00:00:01 |
|  19 |         VIEW               |                             |     1 |  2002 |     2   (0)| 00:00:01 |
|  20 |          TABLE ACCESS FULL | SYS_TEMP_0FD9D6644_B0B39986 |     1 |    27 |     2   (0)| 00:00:01 |
|  21 |      VIEW                  |                             |     1 |  2002 |     2   (0)| 00:00:01 |
|  22 |       TABLE ACCESS FULL    | SYS_TEMP_0FD9D6644_B0B39986 |     1 |    27 |     2   (0)| 00:0
----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   5 - filter("NAME"='GLOBAL_DB_NAME')
   8 - filter(TO_NUMBER("RRC"."RO_ID")=(-6) AND "RRC"."DATABASE_NAME"= (SELECT "DATABASE_NAME"
              FROM  (SELECT /*+ CACHE_TEMP_TABLE ("T1") */ "C0" "DATABASE_NAME" FROM
              "SYS"."SYS_TEMP_0FD9D6644_B0B39986" "T1") "SUBQ_DB_NAME"))
  12 - filter( (SELECT "DATABASE_NAME" FROM  (SELECT /*+ CACHE_TEMP_TABLE ("T1") */ "C0"
              "DATABASE_NAME" FROM "SYS"."SYS_TEMP_0FD9D6644_B0B39986" "T1") "SUBQ_DB_NAME")<>'CSP1')
  13 - access("ITEM_0"="RRC"."RO_ID")
  14 - filter("RRC"."DATABASE_NAME"='CST1' AND TO_NUMBER("RRC"."RO_ID")=(-6))
  17 - filter("RRC1"."DATABASE_NAME"= (SELECT "DATABASE_NAME" FROM  (SELECT /*+ CACHE_TEMP_TABLE
              ("T1") */ "C0" "DATABASE_NAME" FROM "SYS"."SYS_TEMP_0FD9D6644_B0B39986" "T1") "SUBQ_DB_NAME")

43 rows selected.

Elapsed: 00:00:00.01
DOM@11g>

So, to Oracle support with some trace files.

But I’d be interested if anyone else with an 11.1.0.7 environment can run the above statement.

→ 3 CommentsCategories: 11.1.0.7 · 11g · ora-03113 · oracle

11.1.0.7 – our survey says…

January 9, 2009 · 4 Comments

Uh-uh (Family fortunes anyone? No? Don’t blame you)

Un-bloody-believable.

First day testing the 11.1.0.7 patch in preparation for an OLS implementation (for which there are various significant performance fixes in 10.2.0.4 and therefore should be in 11.1.0.7 rather than the current production version of 11.1.0.6), and one of the very first SQL statements that the application issues on startup goes bang.

End of file on communication channel.

T’rific…..

Ok, ok, I don’t know for sure yet that it’s 100% definitely the patch.

But it’s going to be. I’ve reduced the suspect SQL statement down and down and it’s gotta be.

What a way to finish the week.

I’d shout at it if I thought it would help.

Damn it.

→ 4 CommentsCategories: 11g · oracle

ORM – like we’ve always said

November 26, 2008 · 5 Comments

Brian Oliver’s post (http://brianoliver.wordpress.com/2008/11/26/terracotta-chooses-oracle-technology-for-high-availability-and-performance – now removed for some reason) about Terracota has reminded that I meant to blog about this product last week.

It’s a long established behaviour that we database specialists do not have the fondest of spots for ORM tools such as Hibernate, the inefficient way in which they deal with the database and the horrendous SQL that they sometimes generate. (Some of my previous can be found here)

Last week I came across this article, encouragingly ;-) titled “Hibernate without Database Bottlenecks”.

The fact that products such as Terracotta exist and are becoming more popular is finally sort of proof of what we’ve always said – that the row-by-row processing of Hibernate doesn’t work very well in the database (where it’s important to be thinking in sets).

‘Nuff said.

P.S. If that article requires registration, then to summarise:


One of the most prevalent application architectures today is that of a stateless application that maps object data into the database to be stored in relational format, and Hibernate is the most popular way to perform this object-relational mapping. Applications are designed this way for two reasons. First, scalability at the database server is a known and tunable quantity. Second, availability of the database is much closer to “five nines” than that of the application server. Despite these reasons, the burden that shared Java state places on the database and on the application developer is very high. While Hibernate lessens the developer’s workload in having to interface to a database, Terracotta lessens Hibernate’s need to depend on the database for availability and scalability in the first place. The marriage of Terracotta and Hibernate simplifies application development and greatly improves application performance.

To cut a long story short, there’s an in-memory database for Hibernate to hammer instead. Can people seriously think that that is the solution?

→ 5 CommentsCategories: hibernate · oracle · orm