Home » SQL & PL/SQL » SQL & PL/SQL » ORA-04091: table is mutating ... how to? (Oracle 10.2)
ORA-04091: table is mutating ... how to? [message #390771] Mon, 09 March 2009 12:45 Go to next message
amnogueira
Messages: 3
Registered: March 2009
Location: São Paulo
Junior Member
create table temp.xpto (from_nr number(3) not null, to_nr number(3) not null)
/
create or replace trigger temp.xpto_1_tg
   before insert or update
   on temp.xpto
   for each row
declare
   p_l_aux pls_integer;
   pragma autonomous_transaction;
begin
   select count(1)
     into p_l_aux
     from temp.xpto
    where :new.from_nr between from_nr and to_nr
      and rowid <> :new.rowid
      and rownum = 1;

   if p_l_aux > 0 then
      raise_application_error(-20001, 'No intersection allowed!');
   end if;
end;
/
Let's put some records in there.
insert into temp.xpto
     values (1, 10)
/
commit
/
insert into temp.xpto
     values (2, 11)
/
commit
/
Great it works! Now let's vary a little bit...
truncate table temp.xpto
/
insert into temp.xpto
     values (1, 10)
/
insert into temp.xpto
     values (2, 11)
/
commit
/

Well, not that good since the second insert is intersecting with the first. Let's try and block it also...
create or replace trigger temp.xpto_2_tg
   before insert or update
   on temp.xpto
   for each row
declare
   p_l_aux pls_integer;
begin
   select count(1)
     into p_l_aux
     from temp.xpto
    where :new.from_nr between from_nr and to_nr
      and rowid <> :new.rowid
      and rownum = 1;

   if p_l_aux > 0 then
      raise_application_error(-20001, 'No intersection allowed!');
   end if;
end;
/

Another test and...
truncate table temp.xpto
/
insert into temp.xpto
     values (1, 10)
/
insert into temp.xpto
     values (2, 11)
/
commit
/

Last insert statement results in:
ORA-20001: No intersection allowed! ORA-06512: at "TEMP.XPTO_2_TG", line 12 ORA-04088: error during execution of trigger 'TEMP.XPTO_2_TG'

That´s great, I´m preventing intersection, but let's try something else...
update temp.xpto
   set from_nr = 2
 where from_nr = 1
/
commit
/

Saddly last update statement results in:
ORA-04091: table TEMP.XPTO is mutating, trigger/function may not see it ORA-06512: at "TEMP.XPTO_2_TG", line 4 ORA-04088: error during execution of trigger 'TEMP.XPTO_2_TG'


Is there an effective way to block modifications which will result in data intersection?

[Updated on: Mon, 09 March 2009 13:08] by Moderator

Report message to a moderator

Re: ORA-04091: table is mutating ... how to? [message #390773 is a reply to message #390771] Mon, 09 March 2009 12:57 Go to previous messageGo to next message
cookiemonster
Messages: 12422
Registered: September 2008
Location: Rainy Manchester
Senior Member
Use a stored procedure to do the insert/update and do all the checking in that.

You aren't going to be able to do this with triggers.
Re: ORA-04091: table is mutating ... how to? [message #390775 is a reply to message #390771] Mon, 09 March 2009 13:15 Go to previous messageGo to next message
Michel Cadot
Messages: 64152
Registered: March 2007
Location: Nanterre, France, http://...
Senior Member
Account Moderator
Using "pragma autonomous_transaction" in a trigger to prevent from mutating error is obviously not understanding what a transaction is.

To do what you want, you have to lock the table or all interested rows (select for update) to prevent from other modifying the rows when you try to update one.

Regards
Michel
Re: ORA-04091: table is mutating ... how to? [message #390777 is a reply to message #390771] Mon, 09 March 2009 13:44 Go to previous messageGo to next message
amnogueira
Messages: 3
Registered: March 2009
Location: São Paulo
Junior Member
Thank you folks. I never thought I'd be that promptly replied. Smile I really appreciate.
I will go with the stored procedure approach.
Re: ORA-04091: table is mutating ... how to? [message #390858 is a reply to message #390773] Tue, 10 March 2009 01:43 Go to previous messageGo to next message
Frank
Messages: 7880
Registered: March 2000
Senior Member
cookiemonster wrote on Mon, 09 March 2009 18:57
Use a stored procedure to do the insert/update and do all the checking in that.

You aren't going to be able to do this with triggers.

Why can't this be done with triggers?
You can use the standard pattern to circumvent mutating table triggers to do this. Headstart has done so for years.
Re: ORA-04091: table is mutating ... how to? [message #390905 is a reply to message #390771] Tue, 10 March 2009 04:21 Go to previous messageGo to next message
cookiemonster
Messages: 12422
Registered: September 2008
Location: Rainy Manchester
Senior Member
I believed you couldn't issue a lock table command from a trigger.
Seems I was wrong.

Ok, you can do this from a trigger, but you need to understand mutating table to do so.
I personally would rather use a stored proc just to keep all the code in one place so it's easy to understand what's going on.
Re: ORA-04091: table is mutating ... how to? [message #390922 is a reply to message #390858] Tue, 10 March 2009 05:43 Go to previous messageGo to next message
JRowbottom
Messages: 5933
Registered: June 2006
Location: Sunny North Yorkshire, ho...
Senior Member
I agree with Frank.
I reckon that this should do the trick:
create table test_138(from_val  number, to_val number); 

create or replace package pkg_Test_138 as
  procedure store_initial;
  
  function  check_overlap   (p_from  in  number
                            ,p_to    in  number) return boolean;
end;
/

create or replace package body pkg_Test_138 as
  type ty_r_range is record (v_from number
                            ,v_to   number);
                         
  type ty_t_range is table of ty_r_range index by binary_integer;
  
  t_range      ty_t_range;
  
  procedure store_initial is
  
  begin
    select from_val,to_val
    bulk collect into t_range
    from  test_138
    order by from_val,to_val;
    
    dbms_output.put_line('SI - '||t_range.count);
    
  end store_initial;
  
  function  check_overlap   (p_from  in  number
                            ,p_to    in  number) return boolean is
    r_range   ty_r_range;
  begin
    if t_range.count > 0 then
      for idx in t_range.first .. t_range.last loop
        dbms_output.put_line('CO - '||idx||':'||t_range(idx).v_from||':'||t_range(idx).v_to);
        if p_from >= t_range(idx).v_from 
        and p_from <= t_range(idx).v_to then
          return true;
        end if;
      end loop;
    end if;
    
    r_range.v_from := p_from;
    r_range.v_to   := p_to;
    t_range(nvl(t_range.last,0)+1) := r_range;
    return false;
  end check_overlap;
  
end;
/

create or replace trigger test_138_bui before insert or update on test_138
begin
  pkg_test_138.store_initial;
end;
/

create or replace trigger test_138_bui before insert or update on test_138 for each row
begin

  if pkg_test_138.check_overlap(:new.from_val,:new.to_val) then
    raise_application_error(-20001,:new.from_val||' Overlaps an existing range');
  end if;
end;
/


insert into test_138 values (1,10);

insert into test_138 values (2,11);

insert into test_138 values (11,15);

update test_138 set from_val = 5 where from_val = 11;
Re: ORA-04091: table is mutating ... how to? [message #390928 is a reply to message #390922] Tue, 10 March 2009 06:20 Go to previous messageGo to next message
JRowbottom
Messages: 5933
Registered: June 2006
Location: Sunny North Yorkshire, ho...
Senior Member
Mmm - multi user functionality - that's what my solution doesn't have.
Re: ORA-04091: table is mutating ... how to? [message #390933 is a reply to message #390928] Tue, 10 March 2009 06:35 Go to previous messageGo to next message
Frank
Messages: 7880
Registered: March 2000
Senior Member
JRowbottom wrote on Tue, 10 March 2009 12:20
Mmm - multi user functionality - that's what my solution doesn't have.

Query the table in the after statement trigger instead of storing the records in the before statement trigger.
Re: ORA-04091: table is mutating ... how to? [message #390935 is a reply to message #390933] Tue, 10 March 2009 06:39 Go to previous messageGo to next message
JRowbottom
Messages: 5933
Registered: June 2006
Location: Sunny North Yorkshire, ho...
Senior Member
Surely that would still miss records that other users had inserted but not committed that would intersect with the records I've just inserted ?

I think I agree with Cookiemonster - we would need to guarantee that no-one else was changing the data when we did this.
Re: ORA-04091: table is mutating ... how to? [message #390940 is a reply to message #390771] Tue, 10 March 2009 06:51 Go to previous messageGo to next message
amnogueira
Messages: 3
Registered: March 2009
Location: São Paulo
Junior Member
JRowbottom, I really appreciated your solution but my real application goes way beyond my example. So much that loading all my table data to further searches for overlaps would cost a lot to the database. I even tried to retrieve the statement where clause that triggered it so I could limit the loaded dataset but I didn't get any luck on that.

My final solution was to create a package where all data must go through to get to the table. Not creative at all but functional.

Thank you very much anyway.
Re: ORA-04091: table is mutating ... how to? [message #390946 is a reply to message #390935] Tue, 10 March 2009 07:14 Go to previous messageGo to next message
Frank
Messages: 7880
Registered: March 2000
Senior Member
JRowbottom wrote on Tue, 10 March 2009 12:39
Surely that would still miss records that other users had inserted but not committed that would intersect with the records I've just inserted ?

I think I agree with Cookiemonster - we would need to guarantee that no-one else was changing the data when we did this.

Whether you would check before insert/update (the procedure approach) or after insert/update (the trigger approach), you will need a table lock to be 100% sure in the end.
Re: ORA-04091: table is mutating ... how to? [message #390955 is a reply to message #390771] Tue, 10 March 2009 08:05 Go to previous messageGo to next message
cookiemonster
Messages: 12422
Registered: September 2008
Location: Rainy Manchester
Senior Member
Right.

I started doing this reply after seeing JRowbottoms initial post.
As you can probably guess it took me a while and things have moved on.
Which is to say you've already spotted the multi user issue.
I still think my post is worth posting (I refuse to waste the time and effort Razz )
and I want to get on with other stuff so I'm not rewriting it.
So just bare in mind I'm restating some stuff that's already been said:



That's missing a lock table.
In one session it's fine:

SQL> insert into test_138 values (1,10);

1 row created.

SQL> 
SQL> insert into test_138 values (2,11);
insert into test_138 values (2,11)
            *
ERROR at line 1:
ORA-20001: 2 Overlaps an existing range
ORA-06512: at "LIVE.TEST_138_BUI", line 4
ORA-04088: error during execution of trigger 'LIVE.TEST_138_BUI'



In two sessions however...
Session 1:
SQL> insert into test_138 values (1,10);

1 row created.

Session 2:
SQL> insert into test_138 values (2,11);

1 row created.

SQL> commit;

Commit complete.

Session 1:
SQL> commit;

Commit complete.

SQL> select * from test_138;

  FROM_VAL     TO_VAL
---------- ----------
         2         11
         1         10


You're also missing code to clear the array each time.
I had to recreate the package to run the 2nd test.
It was telling me there were clashes even though I'd deleted the contents of the table.
Actually - on closer inspection you've named the triggers the same. The array is only populated because check_overlap does it.

If we add the missing delete command and a lock table to the before statement trigger, then I think it works:
create table test_138(from_val  number, to_val number); 

create or replace package pkg_Test_138 as
  procedure store_initial;
  
  function  check_overlap   (p_from  in  number
                            ,p_to    in  number) return boolean;
end;
/

create or replace package body pkg_Test_138 as
  type ty_r_range is record (v_from number
                            ,v_to   number);
                         
  type ty_t_range is table of ty_r_range index by binary_integer;
  
  t_range      ty_t_range;
  
  procedure store_initial is
  
  begin
    
    t_range.delete;
    
    select from_val,to_val
    bulk collect into t_range
    from  test_138
    order by from_val,to_val;
    
    dbms_output.put_line('SI - '||t_range.count);
    
  end store_initial;
  
  function  check_overlap   (p_from  in  number
                            ,p_to    in  number) return boolean is
    r_range   ty_r_range;
  begin
    if t_range.count > 0 then
      for idx in t_range.first .. t_range.last loop
        dbms_output.put_line('CO - '||idx||':'||t_range(idx).v_from||':'||t_range(idx).v_to);
        if p_from >= t_range(idx).v_from 
        and p_from <= t_range(idx).v_to then
          return true;
        end if;
      end loop;
    end if;
    
    r_range.v_from := p_from;
    r_range.v_to   := p_to;
    t_range(nvl(t_range.last,0)+1) := r_range;
    return false;
  end check_overlap;
  
end;
/

create or replace trigger test_138_bui before insert or update on test_138
begin
  execute immediate 'lock table test_138 in exclusive mode';
  pkg_test_138.store_initial;
end;
/

create or replace trigger test_138_buir before insert or update on test_138 for each row
begin

  if pkg_test_138.check_overlap(:new.from_val,:new.to_val) then
    raise_application_error(-20001,:new.from_val||' Overlaps an existing range');
  end if;
end;
/


insert into test_138 values (1,10);

insert into test_138 values (2,11);

insert into test_138 values (11,15);

update test_138 set from_val = 5 where from_val = 11;



If we redo the two session test:

Session 1:
SQL> insert into test_138 values (1,10);

1 row created.

Session 2:
SQL> insert into test_138 values (2,11);

Session 2 is hanging at this point.
Session 1:
SQL> commit;

Commit complete.


Session 2:
insert into test_138 values (2,11)
            *
ERROR at line 1:
ORA-20001: 2 Overlaps an existing range
ORA-06512: at "LIVE.TEST_138_BUIR", line 4
ORA-04088: error during execution of trigger 'LIVE.TEST_138_BUIR'


So that's that problem fixed.

Further testing shows that my last statement was rather optimistic.
Data is rejected or allowed depending on the order of insertion:

SQL> insert into test_138 values (1,10);

1 row created.

SQL> 
SQL> insert into test_138 values (2,11);
insert into test_138 values (2,11)
            *
ERROR at line 1:
ORA-20001: 2 Overlaps an existing range
ORA-06512: at "LIVE.TEST_138_BUIR", line 4
ORA-04088: error during execution of trigger 'LIVE.TEST_138_BUIR'


That errors as expected.
If we reverse the order however:
SQL> rollback;

Rollback complete.

SQL> insert into test_138 values (2,11);

1 row created.

SQL> insert into test_138 values (1,10);

1 row created.

SQL> commit;

Commit complete.

SQL> select * from test_138;

  FROM_VAL     TO_VAL
---------- ----------
         2         11
         1         10
         


Since the OP wants to disallow all intersects we need to change check_overlap to the following:

  FUNCTION  check_overlap   (p_from  IN  NUMBER
                            ,p_to    IN  NUMBER) RETURN BOOLEAN IS
    r_range   ty_r_range;
    
  BEGIN
  
    IF t_range.count > 0 THEN

      FOR idx IN t_range.first .. t_range.last LOOP
        dbms_output.put_line('CO - '||idx||':'||t_range(idx).v_from||':'||t_range(idx).v_to);

        IF p_from BETWEEN t_range(idx).v_from AND t_range(idx).v_to 
        OR p_to BETWEEN t_range(idx).v_from AND t_range(idx).v_to 
        OR t_range(idx).v_from BETWEEN p_from AND p_to
        OR t_range(idx).v_to BETWEEN p_from AND p_to THEN
          RETURN TRUE;
        END IF;
      END LOOP;
      
    END IF;

    r_range.v_from := p_from;
    r_range.v_to   := p_to;
    t_range(nvl(t_range.last,0)+1) := r_range;
    RETURN FALSE;
  END check_overlap;


So lets test that:
SQL> insert into test_138 values (2,11);

1 row created.

SQL> insert into test_138 values (1,10);
insert into test_138 values (1,10)
            *
ERROR at line 1:
ORA-20001: 1 Overlaps an existing range
ORA-06512: at "LIVE.TEST_138_BUIR", line 4
ORA-04088: error during execution of trigger 'LIVE.TEST_138_BUIR'


SQL> insert into test_138 values (3,10);
insert into test_138 values (3,10)
            *
ERROR at line 1:
ORA-20001: 3 Overlaps an existing range
ORA-06512: at "LIVE.TEST_138_BUIR", line 4
ORA-04088: error during execution of trigger 'LIVE.TEST_138_BUIR'


SQL> insert into test_138 values (2,11);
insert into test_138 values (2,11)
*
ERROR at line 1:
ORA-20001: 2 Overlaps an existing range
ORA-06512: at "LIVE.TEST_138_BUIR", line 4
ORA-04088: error during execution of trigger 'LIVE.TEST_138_BUIR'


SQL> insert into test_138 values (1,12);
insert into test_138 values (1,12)
            *
ERROR at line 1:
ORA-20001: 1 Overlaps an existing range
ORA-06512: at "LIVE.TEST_138_BUIR", line 4
ORA-04088: error during execution of trigger 'LIVE.TEST_138_BUIR'


SQL> insert into test_138 values (12,13);

1 row created.

SQL> commit;

Commit complete.

SQL> select * from test_138;

  FROM_VAL     TO_VAL
---------- ----------
         2         11
        12         13
        


So far so good.
Lets try an update:
SQL> UPDATE test_138 SET from_val = 10 
  2  WHERE from_val = 12;
UPDATE test_138 SET from_val = 10
       *
ERROR at line 1:
ORA-20001: 10 Overlaps an existing range
ORA-06512: at "LIVE.TEST_138_BUIR", line 4
ORA-04088: error during execution of trigger 'LIVE.TEST_138_BUIR'


That works. Lets try another:

SQL> UPDATE test_138 SET from_val = 3
  2  WHERE from_val = 2;
UPDATE test_138 SET from_val = 3
       *
ERROR at line 1:
ORA-20001: 3 Overlaps an existing range
ORA-06512: at "LIVE.TEST_138_BUIR", line 4
ORA-04088: error during execution of trigger 'LIVE.TEST_138_BUIR'


That should be allowed.
It failed because we're checking the row to be updated against itself.
Simplest thing to do is check the rowid.

create or replace package pkg_Test_138 as
  procedure store_initial;

  function  check_overlap   (p_from  in  number
                            ,p_to    in  number
                            ,p_rowid IN  ROWID) return boolean;
end;
/

create or replace package body pkg_Test_138 as
  type ty_r_range is record (v_from number
                            ,v_to   NUMBER
                            ,v_rowid ROWID);

  type ty_t_range is table of ty_r_range index by binary_integer;

  t_range      ty_t_range;

  procedure store_initial is

  begin

    t_range.delete;

    select from_val,to_val, ROWID
    bulk collect into t_range
    from  test_138
    order by from_val,to_val;

    dbms_output.put_line('SI - '||t_range.count);

  end store_initial;

  FUNCTION  check_overlap   (p_from  IN  NUMBER
                            ,p_to    IN  NUMBER
                            ,p_rowid IN  ROWID) RETURN BOOLEAN IS
    r_range   ty_r_range;
    
  BEGIN
  
    IF t_range.count > 0 THEN

      FOR idx IN t_range.first .. t_range.last LOOP
        dbms_output.put_line('CO - '||idx||':'||t_range(idx).v_from||':'||t_range(idx).v_to);

        IF p_rowid != t_range(idx).v_rowid THEN
        
          IF p_from BETWEEN t_range(idx).v_from AND t_range(idx).v_to 
          OR p_to BETWEEN t_range(idx).v_from AND t_range(idx).v_to 
          OR t_range(idx).v_from BETWEEN p_from AND p_to
          OR t_range(idx).v_to BETWEEN p_from AND p_to THEN
            RETURN TRUE;
          END IF;
          
        END IF;
        
      END LOOP;
      
    END IF;

    r_range.v_from := p_from;
    r_range.v_to   := p_to;
    t_range(nvl(t_range.last,0)+1) := r_range;
    RETURN FALSE;
    
  END check_overlap;

end;
/

create or replace trigger test_138_buir before insert or update on test_138 for each row
begin

  if pkg_test_138.check_overlap(:new.from_val,:new.to_val, :new.rowid) then
    raise_application_error(-20001,:new.from_val||' Overlaps an existing range');
  end if;
end;
/


Let's try that last one again:


SQL> select * from test_138;

  FROM_VAL     TO_VAL
---------- ----------
         2         11
        12         13

SQL> UPDATE test_138 SET from_val = 3
  2  WHERE from_val = 2;

1 row updated.

SQL> commit;

Commit complete.

SQL> select * from test_138;

  FROM_VAL     TO_VAL
---------- ----------
         3         11
        12         13
        


That's what we wanted.

Lets see if the other update still behaves:
SQL> update test_138 set from_val = 11
  2  where from_val = 12;
update test_138 set from_val = 11
       *
ERROR at line 1:
ORA-20001: 11 Overlaps an existing range
ORA-06512: at "LIVE.TEST_138_BUIR", line 4
ORA-04088: error during execution of trigger 'LIVE.TEST_138_BUIR'


Cool.
Lets check an insert:
SQL> insert into test_138 values (1,4);
insert into test_138 values (1,4)
            *
ERROR at line 1:
ORA-20001: 1 Overlaps an existing range
ORA-06512: at "LIVE.TEST_138_BUIR", line 4
ORA-04088: error during execution of trigger 'LIVE.TEST_138_BUIR'


SQL> insert into test_138 values (2,14);
insert into test_138 values (2,14)
            *
ERROR at line 1:
ORA-20001: 2 Overlaps an existing range
ORA-06512: at "LIVE.TEST_138_BUIR", line 4
ORA-04088: error during execution of trigger 'LIVE.TEST_138_BUIR'


SQL> insert into test_138 values (14,15);

1 row created.

SQL> commit;

Commit complete.

SQL> select * from test_138;

  FROM_VAL     TO_VAL
---------- ----------
         3         11
        14         15
        12         13
        


Halleujah!
I think we might have cracked it.
emphasis on think.
When you get right down enforcing integrity between rows on the same table is one of the most awkward things you can do in oracle.
Using triggers to do it just makes the solution more obscure.

Final test code is:


create table test_138(from_val  number, to_val number); 

create or replace package pkg_Test_138 as
  procedure store_initial;
  
  function  check_overlap   (p_from  in  number
                            ,p_to    in  number) return boolean;
end;
/

create or replace package pkg_Test_138 as
  procedure store_initial;

  function  check_overlap   (p_from  in  number
                            ,p_to    in  number
                            ,p_rowid IN  ROWID) return boolean;
end;
/

create or replace package body pkg_Test_138 as
  type ty_r_range is record (v_from number
                            ,v_to   NUMBER
                            ,v_rowid ROWID);

  type ty_t_range is table of ty_r_range index by binary_integer;

  t_range      ty_t_range;

  procedure store_initial is

  begin

    t_range.delete;

    select from_val,to_val, ROWID
    bulk collect into t_range
    from  test_138
    order by from_val,to_val;

    dbms_output.put_line('SI - '||t_range.count);

  end store_initial;

  FUNCTION  check_overlap   (p_from  IN  NUMBER
                            ,p_to    IN  NUMBER
                            ,p_rowid IN  ROWID) RETURN BOOLEAN IS
    r_range   ty_r_range;
    
  BEGIN
  
    IF t_range.count > 0 THEN

      FOR idx IN t_range.first .. t_range.last LOOP
        dbms_output.put_line('CO - '||idx||':'||t_range(idx).v_from||':'||t_range(idx).v_to);

        IF p_rowid != t_range(idx).v_rowid THEN
        
          IF p_from BETWEEN t_range(idx).v_from AND t_range(idx).v_to 
          OR p_to BETWEEN t_range(idx).v_from AND t_range(idx).v_to 
          OR t_range(idx).v_from BETWEEN p_from AND p_to
          OR t_range(idx).v_to BETWEEN p_from AND p_to THEN
            RETURN TRUE;
          END IF;
          
        END IF;
        
      END LOOP;
      
    END IF;

    r_range.v_from := p_from;
    r_range.v_to   := p_to;
    t_range(nvl(t_range.last,0)+1) := r_range;
    RETURN FALSE;
    
  END check_overlap;

end;
/

create or replace trigger test_138_bui before insert or update on test_138
begin
  execute immediate 'lock table test_138 in exclusive mode';
  pkg_test_138.store_initial;
end;
/

create or replace trigger test_138_buir before insert or update on test_138 for each row
begin

  if pkg_test_138.check_overlap(:new.from_val,:new.to_val, :new.rowid) then
    raise_application_error(-20001,:new.from_val||' Overlaps an existing range');
  end if;
end;
/

Re: ORA-04091: table is mutating ... how to? [message #390956 is a reply to message #390771] Tue, 10 March 2009 08:07 Go to previous message
cookiemonster
Messages: 12422
Registered: September 2008
Location: Rainy Manchester
Senior Member
Jeez that's a long post Confused
Guess I just wanted to see if it really could be done with triggers.
Previous Topic: procs calling function
Next Topic: Serialize and Stream Data
Goto Forum:
  


Current Time: Sat Dec 10 06:42:50 CST 2016

Total time taken to generate the page: 0.29417 seconds