Developer: Python
  DOWNLOAD
Oracle Database XE
Python
cx_Oracle
  TAGS
python, scripting, xeAll

Case Study: From Forms to Python


by Magnus Lagerkvist

How one Oracle Forms shop fled client/server for wider Python horizons

Published February 2008

Starting with Oracle 6 and Forms 3.0, we have been an Oracle shop, and had come to critically rely on Oracle Forms for our data entry and query needs. Our environment consists of a group of about 10 clerks and management users using a population of approximately 50 Oracle Forms to query a 2GB OLTP database. Over the years, we kept current with database and Forms upgrades, finally ending up with fairly standard 6i forms, deployed in the classic client-server mode: a full set of forms deployed to each user's desktop PC.

Because of the de-support of client-server mode in post-6i versions of Forms, we had a decision to make in regard to our body of work. But this wasn't entirely unwelcome, for over the years, we had come to recognize several shortcomings with the client/server deployment; we actually welcomed the chance to improve but were concerned about disrupting our now smoothly functioning and well understood operations.

We saw were three specific problems with client/server mode:

  • Each client code update meant an IT tech had to physically visit each user's workstation to update their local copy of Forms. We made this "sneaker-net" approach more palatable by locating the master copy of our forms on a shared Windows directory, but getting time from each user to do the upgrade was awkward.
  • Incomplete updates meant we had to manage two versions of the client code in use simultaneously for a time, which complicated bug fixes.
  • We occasionally encountered different behavior in the same form running on Win95 and WinXP, which complicated development.

Furthermore, as Web-based applications became more common in other aspects of the business, it was increasingly attractive to integrate our Forms applications to ease training and improve productivity. So we were quite certain that some sort of Web conversion was necessary for our forms. Of course, many other organizations faced these same sorts of challenges, so it was time to examine our options.

Possible Solutions

Each of the following options initially seemed overwhelming and we struggled with how to proceed:

Move to a New Version of Forms. We were dissatisfied with this option because to the additional complexity and GUI-heavy end result of this sort of conversion. Yes, it would have been the easiest in terms of letting us keep most of our old code, but there still would have been some rewriting necessary to deal with operations that didn't map from client/server mode to Web deployment. Also, we would have had to set up a separate middle tier application server and address the cost and administrative complexity going forward. We recognized the users of our forms were far more concerned with data-entry speed and basic functionality than UI niceties, so we ended up ruling out Forms 9i and successors.

Use a New 4GL Tool. This was quickly rejected due to cost and learning curve concerns. We would be far better off with Forms 9i or 10g than starting over with a brand new tool (e.g. Zope, Twisted, Zango, TurboGears).

Perform Automatic Conversion. Early on, this was the way we thought we would most like. But as we explored different alternatives, we became disenchanted with the complexity and less-than-perfect conversions that would probably result. The technology behind several of these products was impressive, but over time, we came to appreciate how difficult it would be to cleanly generate an equivalent form without a lot of manual work.

By default, each Oracle Form comes with an impressive amount of default functionality provided by the framework, and even if much of it isn't used in practice, the conversion tools have no way of "knowing" what is important to each customer and thus have to map each construct, used or not. This leads to complex, and probably tough-to-maintain, generated output forms.

Further, some of these conversion products seek to retain the forms architecture in the new language, so training of new staff is complicated - engineers have to be trained on Forms and the output language, even if Forms isn't really in active use in the group any longer. Forms coding becomes a sort of "Latin" one has to know to do one's job!

The Manual Rewrite Option. We started (and ended) with this option. It had the advantage of giving us a lot of flexibility in how to proceed, but had several disadvantages. A very large danger was the project scope: because of an Oracle Forms' declarative (i.e. 4GL) architecture, the interaction across the elements of the code can be tough to reproduce in a 3GL language. An engineer could easily loose sight of the big picture and spend weeks converting a single Form, making the cost of the conversion very high. Previous experience doing software migrations taught us to beware of projects with lots of moving parts or "big-bang" delivery schedules, so we started to look for ways to reduce the overall scope of the project.

Factoring Out Application Tier Code

Thinking about the architecture of typical forms, we recalled some early Oracle Application Server materials that pointed out that programs could be functionally visualized as a combination of code in the database, application, and presentation tiers. Of these three groups, the database interaction and application tier code (e.g. Oracle Forms Program Units) looked to be the major stumbling blocks in a conversion. We also recalled an Oracle Forms advertisement from several years ago that showed the dragging and dropping of PL/SQL code from the Form to the database and the reverse.

After some reflection, we decided to pursue a near term goal of moving our application tier code out of the forms and into a PL/SQL database packages while continuing to evaluate alternatives for the final UI. This meant the creation of packages for each group of forms, and the movement of program units into procedures in one of these new packages. As this was done, we updated each Form Program Unit to call the procedure in its new location. Specifically, we created one-line program units with a call to the package.procedure. This was fast and effective (and transparent to the users) in almost every case. (Note: in some forms there were still a few program units devoted to UI functions, which we properly did not attempt to push into database packages).

So we lost nothing by doing this, and gained several advantages:

  • It was self-contained so we were able to work on this in parallel with the evaluation of the different migration alternatives.
  • Code re-use and refactoring across forms was achieved with modest effort.
  • We ended up with much smaller Forms, both easier to deal with as Forms and much easier to convert in the future. (The irreducible remainder of code that remained in the Form was largely UI-related.)
  • We were able to add unit-test procedures to the packages to increase code quality. (Testing the same code as a Program Unit in a form would be much harder.)
  • Debugging was easier because of the clean separation of logic.
  • We could much sooner detect schema problems via package invalidation, as opposed to latent runtime errors lurking in forms. Because the database can see the code that was formerly "hiding" in program units, any definition problems are brought to the fore immediately when a change is made that breaks them. This heads off bug reports, or at least makes them much quicker to fix.
  • We were able to take advantage of some 10g features to re-implement certain functionality (e.g. external tables).

One question that may be in many people's minds is: "Why use PL/SQL in the database instead of Java/Python/etc. in the application server?" We considered this option, but it was too risky for us. Our code was already PL/SQL, and we were confident it would work identically in the server. (Indeed this was the basis for that print advertisement for Oracle Forms several years ago). Because we were greatly interested in reducing the scope of the changes we were making (to reduce project risk), once we had the new packages compiled and Oracle Forms updated, we were able to certify our database tier and much of our application tier before we had even picked our final implementation methodology. After the intermediate update was pushed into production and worked perfectly, we had greatly reduced our workload and risk for the remainder of the migration.

If we had attempted to immediately rewrite the code, we would have faced the challenge of interfacing it to the existing forms, and making it perfect enough to replace the existing functionality. With changes of that magnitude, bugs are unavoidable. We just didn't want to deal with the extra bugs that would come from this strategy, as we needed to stay in our user's good graces for the later migration work.

That said, as we went about designing the PL/SQL packages to contain the migrating code, we were careful to logically separate the database tier from the application tier, and so ended up with separate groups of packages of each type. Of course, physically, all this PL/SQL code was resident in the same database PL/SQL engine, but logically they were distinct and the calling patterns we established between the modules respected this. At some vague later date, those application tier PL/SQL packages would be the first candidates for rewriting in Python for use in the web server, should we want to pursue that strategy.

Product Stack

After months of considering alternatives and doing small pilot projects, we decided on the following components for our project. One very important detail about this stack: its total cost today is $0. Certainly, it's possible to spend more, but without explicitly trying to keep costs down, we were pleasantly surprised to find our preferred selections ended up costing the least.

Linux. Linux is a stable and inexpensive platform. It has been much more trouble-free than Windows machines in the same environment. In preparation for this article, I looked at a Suse Linux machine running an Oracle database: the uptime of the machine was 768 days, or more than two years. We later shifted development to Ubuntu 6.06 (Dapper Drake) and Ubuntu 7.04 (Feisty Fawn) and both have since also worked extremely well for us. Highly recommended. (Note however that Ubuntu is currently supported only by Oracle Database Express Edition.)

Apache Web Server. Certainly the default Web server choice, but one we were happy to make to reduce risk. We found it works well with Linux, had much documentation, and met our needs without complication. Our usage of the Web server to date has been very generic, so some other product could be easily substituted. A full Python solution here would be philosophically very attractive, but so far can't make a strong business case to do the work to swap out Apache.

Firefox. Firefox worked well for us during development. We of course did some QA work from Internet Explorer to make sure we hadn't been bitten by some of its famed incompatibilities. We did encounter a few of these midway through the project and worked around them changing the code slightly. We also later investigated Opera as a lighter weight alternative to Firefox and that has been equally acceptable.

Python. We considered both Java and Python for our implementation language. Early on, it looked as if Java would win because many of the automated Form conversion technologies were were exploring generated output in Java, so the path of least resistance would be to carry on with Java for any related tasks and new development. Well, this was a problem because we just didn't like Java, with its large IDEs, fussy syntax, and windy, hair-splitting library calls.

Casting about for a better solution, we noticed programming pundits like Bruce Eckels saying very positive things about Python. For us, it was love at first sight. Python's interactivity, clear, charming syntax, and extensive library support made us confident we had the right platform to do almost any sort of application coding. In particular, Python's late binding behavior (aka "duck typing") really helped us map some of the Oracle Forms declarative constructs to Python classes. Highly recommended as well.

CGI. There are several Python Web platforms we briefly considered. But given our still-developing proficiency with Python, we were leery of our ability to select the right one, and reluctant to climb the formidable learning curve. While we can probably be accused of a case of "not-invented-here" syndrome, we also strongly wanted to retain full control of our project, so we made the conservative choice to use CGI. This will no doubt be controversial among some readers of this article.

We selected CGI because it was well proven technology and we found plenty of documentation to assist us in getting our prototypes working. We recognized that scalability would be lacking, but for our limited population of users, this was not even remotely a problem. We also recognized that once the system was working well, we could safely explore fast CGI techniques (e.g. mod-python) to speed up the system and reduce system load.

HTMLgen. This is a Python library that performs dynamic generation of HTML. As one observer commented, HTMLgen is the package you would write yourself if you had the time to design one. It's very good, and we've grown to love it. While the documentation could be better, studying the sample code is a workable replacement. Once you see how HTMLgen pages are defined, the code is quite compact and readily sharable between programs. HTMLgen is extremely modular so you will quickly see ways to generate portions of web pages that can be recycled to provide uniform look and feel across your applications. We did several months of low-key experiments to test major constructs we knew we would need to map our forms, and then built Python modules to implement those. Those underlying modules keep improving as we learn more things, but the calling application code doesn't typically change much.

IDE. We didn't use one. Being the old-school programmers that we are, we relied on Ubuntu's GNOME virtual desktops and standard tools to keep up productive. We just set up file browsers and shells in the appropriate locations in the file tree and we were good to go. Yes, we even used vi (okay, gvim). We did regularly use pychecker to detect problems and made sure every program passed before being released. Pychecker was a great help, but because it does static checks it misses certain dynamic situations. For example, if a variable is added to an object at runtime, Pychecker will complain it is missing when it looks at the code statically.

Oracle Database XE. Mid-way though development we changed from Enterprise Edition to the XE platform and were very impressed. XE running with all the components mentioned here easily fit on a development laptop with only 512 MB of memory, with room to spare. Also, aside from the documented differences from Standard and Enterprise Edition, we found no incompatibilities to spoil our experience. Highly recommended.

cx_Oracle. This is the component that provides connectivity from Python to Oracle, by implementing Python's DB API spec. While there are several modules of this sort, cx_Oracle is today overwhelmingly the preferred choice. It uses OCI so performance and feature support are very good. Running within Apache worked well once we sorted out environmental issues, namely how to specify the several environment variables Oracle requires. This was a challenge because Apache is started by root, which doesn't normally have the shell variables ORACLE_HOME, ORACLE_SID, etc. We solved this through explicit settings in the Apache configuration files, but this does create a small maintenance problem if the settings ever change.

Forms Conversion Methodology

Database Access. After a bit of experimentation, we settled on an approach that wrapped each Oracle table with a Python class implementing the basic CRUD (Create, Read, Update and Delete) routines. These all subclassed a SQL class built on cx_Oracle that provided core routines for these operations. Once queried from Oracle, we stored the data in Python dictionaries, with the column name as the hash key. This facilitated processing for display and updates and made for elegant code.

We also provided functionality to handle database log-in. Since we were running in CGI mode, we had to reconnect to the database whenever a form action required. We handled this by storing a single cookie in the user's browser to retain login information after it was validated by the server from our initial login program.

We also had routines to invoke Oracle package procedures and functions. These were critical to the project because they were the bridge from our CGI/Python world back to the stored procedures we had earlier moved from the Oracle Forms into the database. So where we originally had a button or "hot key" in an Oracle Form, we were able to create an action button in a CGI form and end up calling the very same back-end procedure. Our users loved the fact that the functionality was identical between the old and new forms, and our debugging was minimal.

There are of course some Python modules out there that will generate some of this code automatically, but in defense of the do-it-yourself approach we embraced here, the critical core SQL class file ended up being all of 600 lines long, with some of that being comments and unit-test code. The individual table files that subclass the SQL class added perhaps another 250 lines, more or less depending on complexity. Most of these lines were simple column data and unit-test code. The final form-level programs were only 20-300 lines long, depending again on complexity.

Learning Curve. We approached this as an R&D effort, starting with the smallest forms first, and repeatedly modifying them as we figured out better techniques. This had the welcome effect of actually shrinking our forms over time, and making them run faster. While keeping in mind Knuth's maxim that "Premature optimization is the root of all evil", we did keep one eye on efficiency, because we were conscious of all the processing that was happening to prepare each screen, especially compared to the Oracle Forms situation where a dedicated Runform instance is handling a compiled Oracle Form with opportunities for local caching of database data. In contrast, because Python is interpreted, with each new screen, we were loading and compiling several substantial Python modules, in addition to whatever SQL calls were needed to make to gather the latest data to display. Nevertheless, the maturity of all our platform choices showed itself and we detected no notable delays in wait time at the browser level.

Methodology. To actually create each form, we analyzed the flow that was occurring in the original Oracle form. Luckily for us, our forms were either simple query forms, or else they progressed linearly or hierarchically through a series of tabs. We had earlier eschewed relying much on Oracle Forms ability to move randomly between blocks, because this made training and documentation hard. Once the navigation was understood it was simply a matter of using HTMLgen to create matching HTML tables (for simple display) and CGI forms (for input or modification needs). All actions were reduced to discrete HTML "action" buttons tying back to either a procedure in a database package or a Python method in the form or a support module.

To promote maximum code re-use, we wrote a button-handling class to handle the CGI form actions. We worked up a dictionary structure for each form that contained the valid buttons that could be received, and used this both for form generation and for analyzing the reply. We classed each button as one of several types: (i.e. call database PL/SQL, perform a record print, call Python code, etc). The button handler parsed the incoming CGI form and performed the relevant action. In essence, it performed a lot of the subset of Runform work we needed to map our forms, and it took up fewer than 200 lines of code.

Output-only forms were of course much easier. Their interaction was mostly canned and usually amounted to call a stored procedure to write the eventual output on the database server. For input forms, things were more challenging. In particular we had trouble with input forms that needed custom local code. We solved these cases by subclassing the button-handling class to extend it with the custom code needed. The trick then was to get the button handler to know how to call this code. We accomplished this by updating the button dictionary at run time with the address of these new routines.

HTMLgen comes with several "document" styles, which serve as frameworks of varying complexity which you can decorate with your particular content. Development is quick if you can use the standard HTMLgen document types. Larger projects will probably want to develop their own, with appropriate graphical headers, navigation buttons, etc. Once you have nailed down these decisions and built several forms, it takes just minutes to derive new small, single screen form.

Example

Here's a complete example of how this all looks in practice:

Here is the code behind the example:

#!/usr/bin/env python

# This is demo code for a single page form that provides both user input, output and database actions.
# Forms such as this are typically executed multiple times:
# First, on initial invocation (to display current status)
# Then, one more time in reponse to any user actions.
# The user can navigate away from this screen at any time by selecting a new form from the menu
# available in a separate frame.


import HTMLgen

# This is our login class which contains a program to accept user login credentials and verify against
# the Oracle database. It also has code to manipulate a cookie we use to preserve credentials across
# form executions.
import Login

# We have factored out the repetitive elements of building up these forms into a small, but heavily used module
import ButtonHandler

version_dt_tx = '2008-01-01'

# Here we define a dictionary of actions the form will be able to handle.
# Element 0 is the key to the tuple
# Element 1 is a label for the button
# Element 2 is a keyword telling the form what code handler to use when the button is pressed
# Element 3 is the name of the handler code (e.g. database stored procedure name or custom python code)
button_db = {
'bound_dates': ('bound_dates', 'Bound Dates', 'update_bound', 'close'),
'database_action_1': ('database_action_1', 'DB Action 1', 'synch', 'database_action_1'),
'database_action_2': ('database_action_2', 'DB Action 2', 'asynch', 'database_action_2')}

# Each screen is provided by a single high-level proceedure
def showMainForm(msg_tx):
# HTMLgen uses template documents. Here we select a simple one we will build upon.
doc = HTMLgen.SimpleDocument(title = 'Simple Oracle Database Action Triggered from CGI',
cgi = 1,
bgcolor = Login.form_colors['user1_update'])
title = HTMLgen.Heading(3, 'Section Header' , align='center')
doc.append(title)
form = HTMLgen.Form('demo1.py')

# We typically organize the form into stanzas or sections. HTMLgen doesn't
# require this, but we found it easiest to map each Oracle Forms block into an
# section set of by HTML <HR> statements.

# This is one section of the screen. It can contain any valid HTML you like.
# Here just call our shared code to build up an editable date range
# and ask it to include the two named buttons.
part1 = handler.prepareSection('Demo Section',
'Adjust this date range to enclose the period of interest.',
'bound_dates', 'database_action_1', 'database_action_2')

# Typically, multiple parts are built but here we show just one to save space
lst = HTMLgen.OrderedList([part1])
form.append(lst)

# The document needs a formal submit button, which we use to provide the ubiquitous cancel option
form.submit = HTMLgen.Input(type='submit', name='cancel', value='Cancel')
doc.append(form)
doc.append(HTMLgen.HR())
doc.append(HTMLgen.P())
doc.append(HTMLgen.Text('Message: %s' % msg_tx))
print doc

if __name__ == '__main__':
# Here we ask that our login cookie be retrieved from the browser and the session re-established
# If the user hasn't authenticated, the this statement will fail and provide an HTML failure message
login = Login.Login('reconnect')

# Here we invoke our common code, providing the login credentials,
# button dictionary and optional Oracle package information
handler = ButtonHandler.ButtonHandler(login, button_db, 'user1', 'otn_demo')

# This looks for a CGI form action and attempts to match it against the button dictionary.
# If it matches, the action is performed
msg_tx = handler.processForm()

# We always display our single page form, updating the message line with each CGI round-trip.
showMainForm(msg_tx)

 

Terminology Problem. On a semi-joking level, we struggled with how to refer to our new Python versions of Oracle Forms. Unfortunately "Web form" was already taken, at least informally, by Oracle Forms itself. And of course Python has "forms" in the CGI context, so danger of semantic confusion loomed there as well. We ended up formally calling them "Python CGI Forms" and informally as "Web forms".

Problematic Constructs. As we studied our current forms, we recognized several usages that wouldn't readily move to a Web-based model, and some rethinking was required in dealing with unsupported constructs.

Menus. Oracle Forms has the concept of menu modules, where you can define the relationship between Forms and Reports and have these items inserted into the Runform Windows menu. We handled this functionality by employing HTML frames, with the leftmost side of the screen devoted to a menu frame. Within this frame, a yet another short HTMLgen Python program called "menu.py" is run to provide a list of hyperlinks to the rest of the Python CGI Forms. The called programs run in the right-hand frame and come and go without disturbing the menu.

While we haven't gotten around to doing this yet, because we know who the user is via the cookie set during login, it would be a simple matter to tailor the menu list by querying the database for the appropriate programs for a particular user.

Long Running Jobs. One aspect of Forms that we really struggled with was what to do with long-running jobs. While most procedures were essentially instantaneous, we had certain important extract routines that took a variable amount of time, anywhere from several seconds to several minutes on occasion. When using Oracle Forms, our users didn't really mind, and were used to starting these jobs normally via a button press and doing something else for the duration of the run until the Windows hourglass indicated the routine was finished.

In a CGI environment however, this wasn't viable – if the routine wasn't instantaneous in all cases, we were in danger of eventually timing out in the web server. So to handle this, we added a Python function to our SQL class to call the desired procedure via the DBMS_JOBS facility. This let the same routine execute either synchronously or asynchronously, simply by adjusting the class method we called in our SQL class. To keep the user informed, for synchronous calls, we passed back a success/failure message, and for asynchronous calls, we passed back the job number in a message indicating background execution. To let users monitor these operations, we also provided a form to display current user jobs. This was a simple 60 line program with a meta tag specifying AutoRefresh every three seconds.

File Upload/Download. We also had several examples of file upload and download to deal with. Users almost daily obtained new data files, placed them in a local directory and used a button on an Oracle Form to load them into the database. They would reverse this process when they wished to create a data extract, for a mailing, for example.

We were able to re-engineer this process by setting up a Samba share from working directories on the database server that were mapped to their individual Windows Explorers. We defined these working directories to the Oracle server, and then we used the External Table feature to read the data files, all in response to the same button they were used to from earlier training. Output worked similarly, but we used the UTL_FILE to perform the file system writes in this case.

Local Printing. To solve this, first asked users to indicate which screens they needed to print. These were typically data-rich summary screens. So we created a custom Python routine for each of these screens, all formated and optimized for character-mode printing. We added this to the class defined for the relevant database table. When users click on an on-screen button, this Python routine executes within Apache and creates its output on the database server, with a unique name tying it back to the user. The user then is able to load the print file locally from the Samba share (via notepad or equivalent) and print it to any printer Windows is aware of. This approach worked well, and in fact solved a problem we had with GUI mode screen-shots – as screen resolutions creep ever upward, we had complaints about the time, paper and ink consumed with printing screen shots. Oftentimes the desired screen region wasn't even visible in the printed output. So this character mode solution solved all that, since we could optimize the layout for the known size of the paper available to us.

Reporting. Reporting was a challenge in this approach. Previously, we had done a lot of our reporting through SQL*Plus on the client. We looked for some open-source alternatives, but didn't find anything mature enough to be comfortable with. So we ended up creating some Python routines to address common situations in our reports and rewrote them as individual Python scripts. This wasn't the best solution we could have imagined, but it did work well, once we had worked out how to map Sql*Plus's declarative format to procedural Python. A bonus to this approach that we plan to implement in the future is to move the driving SQL statements out of the report and into a separate DB module, where we can write unit-tests to verify functionality.

Costs

Looking back, we learned quite a bit from this project.

Time Spent. As with most software projects, this one took much more time than we thought at the outset.

Phase

Time (engineer-years)

Move middle tier PL/SQL code from 6.0 Forms to database

0.25


R & D effort to evaluate alternative strategies

0.25


Learn product stack (Python, Apache2, HTMLgen, cx_Oracle)

0.25


Develop Python database and form modules (including unittests)

0.50


Form translation (50 forms equivalent) and testing

1.0

Total

2.25

Performance Change. We retained the same server through this conversion, so we could compare before and after. In one hand we were potentially worsening the situation in moving code from the client to the server, as well as running our web server on the database platform. But we were very satisfied with the overall performance; OLTP operations occurred as fast as the screen could be redrawn. Investigating why this was so, we realized we were adding CPU load to the database machine, which is typically limited by IO -- specifically, the full-table-scan operations that come from various reporting operations. Essentially, we were adding load which consumed the underutilized CPU resource, and removed some network latency, so speed was comparable or even better than the client/server situation.

Maintenance Costs. Maintenance costs dropped, or rather, we were able to accomplish more with the same resources. Since our fixes and testing were centralized, we recovered time formally spent deploying fixes and wrestling with MS Windows. All this time and more was spent in building unittests for the code. This, along with good design, is the key to good Python code – extensive, thorough unittests to anchor code to business reality. We still need to do more in this area, but we love the reassurance that comes from passing unittests after some code changes.

Another aspect that we found valuable with our small staff was the ability to support an old and new version of a form in the menus. Instead of forcing users to cut over to a new or trial webform, we could put the new version in the menu with a slightly different name, and allow users to try them out to give us feedback. This sort of operation took only minutes to do or undo and made us more popular with our users.

Conclusion

Overall, we were very happy with how this project turned out. Certainly, not every business would agree with our selection of functionality over flash, but it was the right call for the small business paying the bills for this project. We were particularly pleased that we were able to marry Python with Oracle Database 10g, and look forward to further refining the system to take advantage of new 11g features. With Python's delightful syntax and extensive library support, combined with 10g's extensive database feature set, it goes a long way toward making programming fun again. Our Forms live on. Now we just have to figure out what to call them.


Magnus Lagerkvist (a pseudonym) has been coding professionally since 1983. He first learned about databases when armed with fresh copies of K&R "C" and a book on algorithms; he sucessfully replicated the key functionality of a minicomputer file manager on 386 UNIX. He has been a user of the Oracle Database since late version 5, when it came on floppy disks and lifting the complete manual set didn't qualify one for a spot on an Olympic weight lifting team.
E-mail this page
Printer View Printer View
Oracle Is The Information Company About Oracle | Oracle RSS Feeds | Careers | Contact Us | Site Maps | Legal Notices | Terms of Use | Privacy