Oracle Forms 6i with Oracle Cloud Infrastructure

Everybody wants to work with the latest tools, the latest feautures. But the world of enterprise IT isn’t like that.

My customer started to move all of his Oracle Databases to the Oracle Cloud Infrastructure (OCI). But one of these databases had an Oracle Forms C/S frontend .
We are talking Oracle Forms 6i client/server on Windows, 100% generated with Oracle Designer.
We could rebuild/redesign the old application to Oracle Apex, but that would be an entire project by itself.
But first we investigated if it was possible to connect this software from 1995 to the 21th century.

This is the road I traveled:

Step 1: Disable SQL*Net

First I tried making a connection with SQL*Plus from the same Oracle-home as Forms to the new database:

ORA-12660: Encryption or crypto-checksumming parameters incompatible

A standard OCI Database installation enables secure SQL*Net. So we have to disable that:

we are not using an Autonomous database, so we still have a “server” we can connect to in OCI.
On this database database server secure SQL*Net is enabled by

/u01/app/oracle/product/****/dbhome_1/network/admin/sqlnet.ora

SQLNET.ENCRYPTION_SERVER=REQUIRED
SQLNET.CRYPTO_CHECKSUM_SERVER=REQUIRED
SQLNET.ENCRYPTION_TYPES_SERVER=(AES256,AES192,AES128)
SQLNET.CRYPTO_CHECKSUM_TYPES_SERVER=(SHA1)
SQLNET.ENCRYPTION_CLIENT=REQUIRED
SQLNET.CRYPTO_CHECKSUM_CLIENT=REQUIRED
SQLNET.ENCRYPTION_TYPES_CLIENT=(AES256,AES192,AES128)
SQLNET.CRYPTO_CHECKSUM_TYPES_CLIENT=(SHA1)

This was removed.

Step 2: Correcting the characterset of the database

Testing, again with SQL*Plus, the executable crashed big time with a General Protection Fault.
[Sorry, dutch only]

Gebeurtenisnaam van probleem: APPCRASH
Naam van de toepassing: PLUS80.exe
Versie van toepassing: 0.0.0.0
Tijdstempel van toepassing: 35aa8e2f
Naam van foutmodule: ORA805.dll
Versie van foutmodule: 0.0.0.0
Tijdstempel van foutmodule: 3b1f22b0
Uitzonderingscode: c0000005
Uitzonderingsmarge: 000b4f04
Versie van besturingssysteem: 6.1.7601.2.1.0.256.48

Sqlnet.log

Fatal NI connect error 12203, connecting to:
(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=BEQ)(PROGRAM=oracle80)(ARGV0=oracle80ORCL)(ARGS='(DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))’)))(CONNECT_DATA=(SID=ORCL)(CID=(PROGRAM=***\ifrun60.EXE)(HOST=*****)(USER=****))))

VERSION INFORMATION:
TNS for 32-bit Windows: Version 8.0.6.0.0 – Production
Oracle Bequeath NT Protocol Adapter for 32-bit Windows: Version 8.0.6.0.0 – Production
Time: 01-FEB-19 09:29:12
Tracing not turned on.
Tns error struct:
nr err code: 12203
TNS-12203: TNS:unable to connect to destination
ns main err code: 12560
TNS-12560: TNS:protocol adapter error
ns secondary err code: 0
nt main err code: 530
TNS-00530: Protocol adapter error
nt secondary err code: 0
nt OS err code: 0

After some research I found out that the characterset of the database was the culprit, so we provisioned a cloud database with the same characterset as the old on-premises database

WE8ISO8859P1

Step 3: Allowed versie of the database

To connect with an older client to database with a higher version, you have to allow this in the sqlnet.ora on the serverside. So we did:

SQLNET.ALLOWED_LOGON_VERSION_SERVER = 8

Testing again with SQL*Plus became funky:

SQL*Plus: Release 8.0.5.0.0 – Production on Fri Feb 1 14:49:43 2019

(c) Copyright 1999 Oracle Corporation. All rights reserved.

Enter user-name: *****
ERROR:
ORA-03137: malformed TTC packet from client rejected: [kpoal8Check-5] [0]
[0x000000000] [0x000000000] [0] [0] [2080] []

First I ignored this “notification”, but later I found that IFRUN60.EXE got the same message and stopped. So I had to address the problem.

It seems that in database version 12.2 some extra checks where build in SQL*Net (Bug 26092744) causing that older clients can’t connect any more to the database:
the on-premises database was 12.1, the Cloud 12.2.

So we provisioned a 12.1 database with the correct characterset with secure sqlnet disabled in OCI

SQL*Plus stopped complaining and we were able to connect with Oracle Forms.

We are now in production with this configuration, and the responstime within the screens are beter than using the on-premises database.
We stille have some issues of course:

  • We have to end-of-life the Forms application.
  • 12.1 will be desupported by Oracle
  • WE8ISO8859P1 is missing some vital characters ( euro sign).

Our migration pad will be to migrate to Oracle Apex on a 19.2+ database version with UTF8 as characterset with secure SQL*Net.

But that will be another story.

Changing the status of an Oracle Apex application with pl/sql

I needed this. I needed this badly. And for once the Oracle Apex forum gave me the answer for my need..

create or replace procedure set_application_status
  ( p_application in apex_applications.application_id%type
  , p_status      in varchar2
  )
is
begin
  if p_status not in ( 'AVAILABLE'        , 'AVAILABLE_W_EDIT_LINK', 'DEVELOPER_ONLY'
                     , 'RESTRICTED_ACCESS', 'UNAVAILABLE'          , 'UNAVAILABLE_PLSQL'
	             , 'UNAVAILABLE_URL')
  then
    raise_application_error(-20000, 'Status '||p_status||' is not supported');
  end if;
  --
  for r_i in ( select app.workspace_id
               ,      app.application_id
               from   apex_applications app
               where  app.application_id = p_application
	     )
  loop
    wwv_flow_api.set_security_group_id
      ( p_security_group_id => r_i.workspace_id
      );
    wwv_flow_api.set_flow_status
      ( p_flow_id     => r_i.application_id
      , p_flow_status => p_status
      );
    commit;
  end loop;
end;

Using a serviceworker with Oracle APEX

After a question from a colleague of mine about caching JavaScript, css, images ed. in APEX I started to look at the new way : service workers.
With service workers we have the opportunity to manage caching programmatically with JavaScript.

I’m not going to tell about service workers. There are a lot of people who know more about it and have excellent posts on blogs and YouTube.
This post is more about how I implemented a service worker in a website I developed.

First, in the template of the LOGIN page I added a script section with the code:

function printState(state) {
  console.log(state);
}
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/m2b_service_worker.js', {
    scope : './'
  }).then(function (registration) {
    var serviceWorker;
    if (registration.installing) {
      serviceWorker = registration.installing;
      printState('installing');
    } else if (registration.waiting) {
      serviceWorker = registration.waiting;
      printState('waiting');
    } else if (registration.active) {
      serviceWorker = registration.active;
      printState('active');
    }
    if (serviceWorker) {
      printState(serviceWorker.state);
      serviceWorker.addEventListener('statechange', function (e) {
        printState(e.target.state);
      });
    }
  }).catch (function (error) {
    printState(error);
  });
}

With this script I registered the service worker file m2b_service_worker.js. Notice that the printState function is just some overhead for debugging.
This seems all to simple, but it has one small pitfall: scoping.

As an APEX developer I wanted to upload the script as static file in the framework. However, when you do that the maximum scope of the caching would be something like domain/pls/dad/workspace/r/….
That’s no good. I also want to cache static files like domain/i/apex.min.css and those files are out of the mentioned scope. Uploading it as a static file will result in a console.log message:

DOMException: Failed to register a ServiceWorker: The path of the provided scope ('/i/') is not 
under the max scope allowed ('/pls/apex/workspace/r/****'). Adjust the scope, move the Service 
Worker script, or use the Service-Worker-Allowed HTTP header to allow the scope.

Luckily I use a reverse proxy in front of our ORDS so I was able to install the script at the root of the reverse proxy and register it at root /, such that all requests to the domain could be cached if necessary.

The Service Worker:

var
VERSIE = "2",
FILES = [
  '/i/myimage.gif',
  'offline.html'
],
CACHENAME = 'omy-cache-' + VERSIE,
EXTENTIES = ['gif', 'jpg', 'ico', 'css', 'js', 'png'];

self.addEventListener('install', function (event) {
  event.waitUntil(caches.open(CACHENAME).then(function (cache) {
      return cache.addAll(FILES);
    }));
1});

self.addEventListener('activate', function (event) {
  return event.waitUntil(caches.keys().then(function (keys) {
      return Promise.all(keys.map(function (k) {
          if (k != CACHENAME && k.indexOf('omy-cache-') == 0) {
            return caches.delete (k);
          } else {
            return Promise.resolve();
          }
        }));
    }));
});

self.addEventListener('fetch', function (event) {
  var isGet = event.request.method;
  
  event.respondWith(
    caches.match(event.request)
    .then(function (response) {
    // Cache hit - return the response from the cached version
      if (response) {
        return response;
      }

    // Not in cache - return the result from the live server
      return fetch(event.request)
          .then(function (response) {
        var 
      shouldCache = false,
      reqWithoutQuery = event.request.url.split("?")[0],
          ext = reqWithoutQuery.split(".").pop();
      
    if (EXTENTIES.indexOf(ext) >= 0 ) {
      shouldCache = true;
    }
        if (shouldCache) {
      //before we return the response from the server
      //we cache the response for the next time
          return caches.open(CACHENAME).then(function (cache) {
            cache.put(event.request, response.clone());
            return response;
          });
        } else {
          return response;
        }
      }).catch(function(error){
      // Is I understand it, fetch throws an exception when offline
      // but a valid HTTP response, e.g. 404, will go tho then(), not to catch()
      return caches.open(CACHENAME).then(function(cache){return cache.match('offline.html');});
    });
    }));
});

With APEX you don’t want to cache all GET requests. E.g. all GETs from \f are dynamic, dependent from session state. Your application will behave not as expected when you’ll cache \f.
I only want to cache the components that are truly static. Hence the array with exceptions.
I also want to show a static file [offline.hml] when the user has no internet connected, to increase user experience. This static file (and its image) is added to the cache on installation of the service worker.

The meat of the worker is the fetch event. When the request is found in the cache, the cached response is returned. When the request is unknown in the cache, the request is fetched from the server and when the requested item is within the array, it is cached for the next cycle.

When you look in Developer Tools > Application > Cache Storage you will notice your new cache with all the static files that were cached.

To emulate an offline connection, just set the checkbox “offline” in Network and hit F5. This should serve the mentioned offline.html from cache.

Component view in Apex 5.1

So I’m a dinosaur. I don’t care if I’m not one of the cool kids. I like component view, I started with component view, I’m comfortable in component view and I get the “flow” of component view.
Of course I’m using page designer, but when things get “buggy”, I switch in dinosaur-modus.

And than there is Apex 5.1….
Where is my component view?

First, there is Two pane mode
1_switch_to_two_pane
For me that is a big step in the right direction. I’m not always in the position to ask for a 24″ monitor for development. So, less is more in this case.
And look
2_two_pane_layout
Component view!

But it’s not the same. This Component View imposter doesn’t give me the original page for e.g. a region. It jumps to the right section of the page-designer.
Not fair! That’s cheating! …. given that it is also very handy and perhaps my new MO.

But look in the upper right corner.
3_goto_preferences
Here you can set some developer specific preferences, like

4_enable_component

This will change the original panel to
4_legacy_button

Legacy, deprecated, whatever. It’s still there.

Like I said, I will be using Two Pane Mode as default with component view as my prefered way of developing (it could be nice if I could set this somewhere in my preferences),
but it’s comforting to know I can still switch “back”.

ORA-00600: with arguments: [12811], [154970]

We encountered this bug on Windows 12.1.0.2.0 Enterprise Edition.

When dropping a table and you get a ORA-00 600 with arguments: [12811] and [154970], you will have to check your application for the following sequence of events:

– You created the table with an identity column GENERATED BY DEFAULT ON NULL AS IDENTITY
– you have code that tries to alter the value the sequence by an alter table statement in dynamic sql.

When the alter table results in an error, the sequence is dropped and you are stuck with a corrupted database.

Don’t do this in a production database, as we have šŸ˜¦

CREATE TABLE edwin_test2
   (  ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY 
      MINVALUE 1 MAXVALUE 9999999999999999999999999999 
      INCREMENT BY 1 START WITH 179193 CACHE 20 NOORDER  
      NOCYCLE  CONSTRAINT edw_con2 NOT NULL ENABLE, 
      CREATION_DATE date, 
      CONSTRAINT edwin_test_pk2 PRIMARY KEY (ID)
      USING INDEX  ENABLE
   ) NO INMEMORY ;

declare
  l_max_id number:= 10;
begin
   insert into edwin_test2 
     ( creation_date
     ) 
   values 
     ( sysdate
     );
   commit;
   -- 
   l_max_id := -1;  -- force the error
   --
   -- Let's blow things up...
   execute immediate 
   'alter table edwin_test2  modify id number '
   ||'generated by default on null as identity '
   ||'( start with ' ||l_max_id || ' )';
end;
/

drop table edwin_test2;

You can check if you’re have this corruption by the following script:

declare
  cursor c_seq
    ( cp_name in varchar2
    )
  is
    select 'exists' bla
    from   dba_sequences seq
    where  seq.sequence_name = cp_name
  ;
  r_seq c_seq%rowtype;
begin
  for r_i in 
    ( select col.table_name
      ,      col.data_default as seq
      from   dba_tab_columns col
      where  col.identity_column = 'YES'
      and    not exists ( select 1
                          from   dba_views vw
                          where  vw.view_name = col.table_name
                        )
    )
  loop
    r_i.seq := substr( r_i.seq, instr(r_i.seq,'.')+2);
    r_i.seq := substr( r_i.seq, 1, instr(r_i.seq,'"')-1);   
    --      
    r_seq:= null;
    open c_seq(r_i.seq);
    fetch c_seq into r_seq;
    close c_seq;
    --- 
    dbms_output.put_line
      ( rpad(r_i.table_name,40,' ')
      ||r_i.seq
      ||' '
      ||nvl(r_seq.bla, ' ------ohoh!')
      );
  end loop;
end;               

To handle this corruption, just open a SR with Oracle. They should be able to help you.

No, recreating the sequence by hand doesn’t work. I tried. The sys.seq.FLAGS column hasĀ a value of 8 for a hand-created sequence and 40 for an identity column; I see no problem in selecting from the datadictionairy, but updating it is even for me a big NO.

 

ora-01403 in wwv_flow_api.import_begin

Two new databases, two fresh new apex installations. Yeah! One for development, one for test. Let’s create a new Workspace X in every environments.
Everything is ready, let the development begin!

After a while the first iteration of the application was ready and we imported it in the the second environment and everything failed miserably.

The site exploded in an error page giving the dreaded ORA-01403 after the first statement in the install script WWV_FLOW_API.IMPORT_BEGIN.

But after running the script in SQLPlus with spooling enabled I got the following :

ORA-02291: Integriteitsbeperking (APEX_050000.WWV_FLOWS_FK) is geschonden – bovenliggende sleutel
is niet gevonden.
ORA-06512: in “APEX_050000.WWV_FLOW_API”, regel 2750
ORA-06512: in regel 2

That’s dutch for an integrity constraint violation, parent key not found

Digging into dba_constraints it gave me that the workspace was not found.

The provisioning_company_id of wwv_flow_companies for workspace X had a different value than development!

Note to myself: when deploying into a new environment, don’t create the workspaces by hand, but import it from a source installation.

Note to the APEX development team: it wouldn’t kill you when you stop the process with a simple message like ‘Workspace-id XXXXXX not found’ instead of “I didn’t find it” without givingĀ the “it” some meaning.

Node.js scripts for Oracle Cloud Storage Service

Working with Oracle Cloud Storage Service I noticed that it’s not really customer-ready (in my humble opinion).

e.g. Creation of a storage container is not yet supported from the dashboard. You’ll have to create a container using a magical Java library or a REST-API using Curl.

But we are on Windows.

So we don’t have Curl.

And I refused to install Cygwin just for this purpose.

However, node.js is installed in our Windows environment, so I created a small repository of node.js scripts to handle some of the basics of the Oracle Cloud Storage Service.

For everybody who is interested : https://github.com/emoracle/OracleCloudStorage