Oracle PL/SQL Best Practices [1st ed.] 0596001215, 9780596001216

In this book, Steven Feuerstein, widely recognized as one of the world's experts on the Oracle PL/SQL language, dis

274 117 1MB

English Pages 198 Year 2001

Report DMCA / Copyright

DOWNLOAD PDF FILE

Recommend Papers

Oracle PL/SQL Best Practices [1st ed.]
 0596001215, 9780596001216

  • 0 0 0
  • Like this paper and download? You can publish your own PDF file online for free in a few minutes! Sign Up
File loading please wait...
Citation preview

Term Fly Presents

http://www.flyheart.com

Oracle PL/SQL Best Practices By St even Feuerst ein Publisher

: O'Reilly

Pub Dat e

: April 2001

I SBN

: 0- 596-00121-5

Pages

: 202

Or a cle PL/ SQL Be st Pr a ct ice s is a con cise , e a sy- t o- u se su m m a r y of be st pr a ct ice s in t h e pr ogr a m de ve lopm e n t pr oce ss. I t cov e r s codin g st yle , w r it in g SQL in PL/ SQL, da t a st r u ct u r e s, con t r ol st r u ct u r e s, e x ce pt ion h a n dlin g, pr ogr a m a n d pa ck a ge con st r u ct ion , a n d bu ilt - in pa ck a ge s. Com ple m e n t a r y code e x a m ple s a r e a va ila ble on t h e O'Re illy w e b sit e . I n clu de s a pu ll- ou t qu ick - r e fe r e n ce ca r d.

Pr e fa ce When I first st art ed w rit ing about t he Oracle PL/ SQL language back in 1994, t he only sources of inform at ion were t he product docum ent at ion ( such as it was) and t he occasional paper and present at ion at Oracle User Group ev ent s. Today, t here are at least a dozen books t hat focus exclusively on PL/ SQL, num erous product s t hat help you w rit e PL/ SQL code ( int egrat ed dev elopm ent environm ent s, knowledge bases, et c.) , t raining classes, and web sit es. And t he com m unit y of PL/ SQL developers cont inues t o grow in size and m at urit y, even wit h t he advent of Java. Access t o inform at ion about PL/ SQL is no longer t he challenge. I t can, on t he ot her hand, be difficult t o m ake sense of all t he new feat ures, t he num erous resources, t he choices for t ools, and so on. When it com es t o w rit ing a program or an ent ire applicat ion, developers have, ov er and over again, expressed t he desire for advice. They ask: • • • • •

How should I form at m y code? What nam ing convent ions, if any, should I use? How can I writ e m y packages so t hat t hey can be m ore easily m aint ained? What is t he best way t o query inform at ion from t he dat abase? How can I get all t he developers on m y t eam t o handle errors t he sam e way?

So m any quest ions, so m uch burning desire t o writ e code w ell, and so few resources available t o help us do t hat . So I decided t o writ e a book t hat offers a concent rat ed set of " best pract ices" for t he Oracle PL/ SQL language. The obj ect ive of t his book is t o provide concret e,

1

im m ediat ely applicable, quickly locat ed advice t hat will assist you in writ ing code t hat is readable, m aint ainable, and efficient . You will undoubt edly find recom m endat ions in t his book t hat also appear in som e of m y ot her books; I hope you will not be offended by t his repet it ion. I t 's sim ply im possible t o offer in a single book ev eryt hing t hat can and should be writ t en about t he Oracle PL/ SQL language. While I plan t o reinforce t hese best pract ices in t he appropriat e places in m y ot her t ext s, I believe t hat we will all benefit from also having t hem available in one concise book, a book designed, beginning t o end, t o give you quick access t o m y recom m endat ions for ex cellent PL/ SQL coding t echniques.

St r u ct u r e of Th is Book Oracle PL / SQL Best Pract ices is com posed of nine chapt ers and one appendix. Each chapt er cont ains a set of best pract ices for a part icular area of funct ionalit y in t he PL/ SQL language. For each best pract ice, I 've provided as m any of t he following elem ent s as are applicable: Tit le A single sent ence t hat describes t he best pract ice and provides an ident ifier for it in t he form XXX- nn ( where XXX is t he t ype of best pract ice—for exam ple, EXC for except ion handling —and nn is t he sequent ial num ber wit hin t his set of best pract ices) ; see Sect ion P.4 for how t o use t his ident ifier online. I have, whenev er possible, sought t o m ak e t his t it le st and on it s own. I n ot her words, you should be able t o glance at it and underst and it s im pact on how you writ e code. This way, aft er y ou've read t he ent ire best pract ice, you can use Appendix A t o inst ant ly rem ind you of best pract ices as you w rit e y our code. Descript ion A lengt hier explanat ion of t he best pract ice. I t 's sim ply not possible t o cov er all t he nuances in a single sent ence! Exam ple We learn best from exam ples, so j ust about ev ery best pract ice illust rat es, t hrough code and/ or anecdot e, t he value of t he best pract ice. Whenev er it m akes sense, I put t he exam ple code in a file t hat you can use ( or learn from ) in your own program m ing environm ent . You'll find t hese files on t he O'Reilly web sit e ( see Sect ion P.4 lat er in t his Preface) . Benefit s Why should you bot her wit h t his best pract ice? How crucial is it for you t o follow t his part icular recom m endat ion? This sect ion offers a quick review of t he m ain benefit s you w ill see by following t he best pract ice. Challenges

2

Term Fly Presents

http://www.flyheart.com

Wouldn't it be great if w e lived in a world in which following a best pract ice was all- around easier t han t he " quick and dirt y" approach? That is, unfort unat ely, not always t he case. This elem ent warns y ou about t he challenges, or drawback s, y ou m ight face as you im plem ent t he best pract ice. Resources I n t he world of t he I nt ernet , everyt hing is connect ed; no program m er st ands alone! This sect ion recom m ends resources, ranging from books t o URLs t o files cont aining code, t hat you can use t o help you successfully follow t his best pract ice. Where filenam es are shown in t his sect ion, t hey refer t o files available on, or referenced by, t he O'Reilly web sit e. Here are brief descript ions of t he chapt ers and appendix: Chapt er 1 st eps back from specific program m ing recom m endat ions. I t offers advice about how t o im prov e t he overall process by w hich you writ e code. Chapt er 2 offers a series of suggest ions on how t o form at and organize your code so t hat it is m ore readable and, t herefore, m ore m aint ainable. Chapt er 3 t akes a close look at how y ou ought t o declare and m anage dat a wit hin your PL/ SQL program s. Chapt er 4 is a " back t o basics" chapt er t hat t alks about t he best way t o writ e I F st at em ent s, loops, and ev en t he GOTO st at em ent ! Sure, t hese aren't t erribly com plicat ed const ruct s, but t here are st ill right and wrong ways t o work wit h t hem . Chapt er 5 cov ers anot her crit ical aspect of robust applicat ion dev elopm ent : ex cept ion handling, or what t o do when t hings go wrong. Chapt er 6 focuses on t he m ost crucial aspect of PL/ SQL dev elopm ent : how y ou should writ e t he SQL st at em ent s in your program s. Chapt er 7 offers advice on how best t o build your procedures, funct ions, and t riggers—t he program unit s t hat cont ain your business logic. I t also includes best pract ices for param et er const ruct ion. Chapt er 8 st eps back from individual program unit s t o present recom m endat ions for packages, t he building blocks of any well- designed PL/ SQL- based applicat ion. Chapt er 9 focuses on how t o t ak e best advant age of a few of t he m ost oft en used of t he packages provided t o us by Oracle Corporat ion. Appendix A com piles t he best pract ice t it les across all t he chapt ers int o a concise resource. Once y ou hav e st udied t he individual best pract ices, y ou can use t his appendix as a checklist , t o be review ed before you begin coding a new program or applicat ion.

H ow t o Use Th is Book

3

My prim ary goal in writ ing t his book was t o creat e a resource t hat w ould m ake a concret e, not iceable difference in t he qualit y of t he PL/ SQL code y ou w rit e. To accom plish t his, t he book needs t o be useful and usable not j ust for general st udy, but also for day- t o- day , program - t o- program t asks. I t also needs t o be concise and t o t he point . A 1,000- page t ext on best pract ices w ould be ov erwhelm ing, int im idat ing, and hard t o use. The result is t his relat ively brief ( I consider any publicat ion under 200 pages a m aj or personal accom plishm ent ! ) , highly st ruct ured book . I recom m end t hat you approach Oracle PL/ SQL Best Pract ices as follows: 1. Read Sect ion P.3. Som e of t he best pract ices in t his book—whole chapt ers, in fact —will have a m uch higher im pact t han ot hers on t he qualit y and efficiency of your code. I f you find t hat your current pract ices ( or t hose of y our organizat ion) are far from t he m ark, t hen y ou w ill have ident ified your priorit ies for init ial st udy. 2. Skip t o Appendix A and peruse t he best pract ice t it les from each chapt er. I f you hav e been program m ing for any lengt h of t im e, you will probably find yourself t hinking: " Yes, I do t hat ," and " Uh- huh, we'v e got t hat one cov ered." Great ! I w ould st ill encourage y ou t o read what I 've got t o say on t hose t opics, as y ou m ight be able t o deepen y our knowledge or learn new t echniques. I n any case, a quick review of t he appendix will allow you t o ident ify areas t hat are new t o y ou, or perhaps st rike a chord, as in " Oh m y gosh, t hat program I wrot e last week does ex act ly what St even say s t o av oid. Bet t er check t hat out ! " 3. Dive int o individual chapt ers or best pract ices wit hin chapt ers. Read a best pract ice, wrest le wit h it , if necessary, t o m ake sure t hat y ou really, t ruly agree wit h it . And t hen apply t hat best pract ice. This isn't an academ ic ex ercise. You will only t ruly absorb t he lesson if you apply it t o your code—if you hav e a problem or program t hat can be im prov ed by t he best pract ice. I f y ou are new t o program m ing or new t o PL/ SQL, y ou will cert ainly also benefit great ly from a cov er- t o- cov er reading of t he t ex t . I n t his case, don't t ry t o fully absorb and t est out ev ery best pract ice. I nst ead, read and t hink about t he best pract ices wit hout t he pressure of applying each one. When you are done, t ry t o pict ure t he best pract ices as a whole, reinforcing t he following t hem es: • •

I want t o w rit e code t hat I —and ot hers—can easily underst and and change as needed. The w orld is t erribly com plex, so I should st rive t o k eep m y code sim ple. I can t hen m eet t hat com plexit y t hrough carefully designed int eract ion bet ween elem ent s of m y code.

Then y ou will be ready t o go back t o individual chapt ers and deepen your underst anding of individual best pract ices. The ot her crucial way t o t ake advant age of t his book is t o use t he code provided on t he com panion web sit e. See Sect ion P.4 for det ailed inform at ion on t he soft ware t hat will help you bring your best pract ices t o life.

N ot All Be st Pr act ice s Ar e Cr e a t e d Equ al

4

Term Fly Presents

http://www.flyheart.com

This book cont ains about 120 dist inct recom m endat ions. I could have included m any, m any m ore. I n fact , I filled up a Rej ect s docum ent as I wrot e t he book. Following t he prov en, " t op- down" approach, I first cam e up w it h a list of best pract ices in each area of t he language. Then I w ent t hrough each area, filling in t he descript ions, exam ples, and so on. As I did t his, I encount ered num erous " best pract ices" t hat surely w ere t he right way t o do t hings. The realit y, how ev er, is t hat few people would ever bot her t o rem em ber and follow t hem , and if t hey did bot her, it would not m ake a significant difference in t heir code. I had realized, you see, t hat not all best pract ices are creat ed equal. Som e are m uch, m uch m ore im port ant t han ot hers. And som e are j ust bet t er left out of t he book, so t hat readers aren't dist ract ed by " clut t er." I hope t hat t he result —t his book—has an im m ediat e and last ing im pact . But ev en am ong t he best pract ices I didn't rej ect , som e st and out as being especially im port ant —so I 'v e decided t o award t hese best pract ices t he following prizes: Grand Prize SQL- 00: Est ablish and follow clear rules for how t o w rit e SQL in your applicat ion. ( See Chapt er 6.) First Prize MOD- 01: Encapsulat e and nam e business rules and form ulas behind funct ion headers. ( See Chapt er 7 .) Second Prize: Tw o Winners EXC- 00: Set guidelines for applicat ion- wide error handling before you st art coding. ( See Chapt er 5.) PKG- 02: Provide w ell- defined int erfaces t o business dat a and funct ional m anipulat ion using packages. ( See Chapt er 8 .) Third Prize: Four Winners MOD- 03: Lim it execut ion sect ion sizes t o a single page using m odularizat ion. ( See Chapt er 7.) DAT- 15: Expose package globals using " get and set " m odules. ( See Chapt er 3.) DEV- 03: Walk t hrough each ot her's code. ( See Chapt er 1.) STYL- 09: Com m ent t ersely wit h value- added inform at ion. ( See Chapt er 2 .) I f y ou follow each of t hese " best of t he best " pract ices, you will end up wit h applicat ions t hat are t he j oy and envy of developers every where!

Abou t t h e Code

5

The best way t o learn how t o w rit e good code is by analyzing and following exam ples. Alm ost ev ery best pract ice offered in t his book includes a code exam ple, bot h in t he t ext and in downloadable form from t he Oracle PL/ SQL Best Pract ices sit e, av ailable t hrough t he O'Reilly & Associat es sit e at : ht t p: / / www.oreilly.com / cat alog/ orbest prac To locat e t he code for y our best pract ice, sim ply ent er t he best pract ice ident ifier, such as BI P- 1 0 , in t he search field. You will t hen be direct ed t o t he associat ed code. You can also browse t he sit e by t opic area. You can ev en offer y our own insight s about PL/ SQL best pract ices, so I encourage you t o visit and cont ribut e. As a rule, I will follow m y own best pract ices in t hese exam ples ( unless t he point of t he code is t o dem onst rat e a " bad pract ice" ! ) . So, for exam ple, y ou will rarely see m e using DBMS_OUTPUT.PUT_LI NE, ev en t hough t his " show m e" capabilit y is needed in m any program s. As I m ent ion in [ BI P- 01: Av oid using t he DBMS_OUTPUT.PUT_LI NE procedure direct ly.] , you should avoid calling t his procedure direct ly; inst ead, build or use an encapsulat ion over DBMS_OUTPUT.PUT_LI NE. So rat her t han seeing code like t his: DBMS_OUTPUT.PUT_LINE (l_date_published); you will inst ead encount er a call t o t he " pl" or " put line" procedure: pl (l_date_published); I also m ak e m any references t o PL/ Vision packages. PL/ Vision is a code library, consist ing of m ore t han 60 packages t hat offer 1,000- plus procedures and funct ions t o perform a m y riad of useful t asks in PL/ SQL applicat ions. I have deposit ed m uch of what I have learned in t he last five y ears about PL/ SQL int o PL/ Vision, so I nat urally ret urn t o it for exam ples. Any package m ent ioned in t his book whose nam e st art s wit h " PLV" is a PL/ Vision package. A com plet ely free, " lit e" version of PL/ Vision is available from t he PL/ SQL Pipeline Archives at : ht t p: / / www.rev ealnet .com / Pipelines/ PLSQL/ archives.ht m Select t he " Rev ealNet Act ive PL/ SQL Knowledge Base" from t he list . ( You m ight also like t o download and t ry out t he ot her code y ou'll find t here.) A com m ercial version of PL/ Vision ( wit h m ore packages and funct ionalit y t han t he lit e version) is current ly available inside t he Rev ealNet Act ive PL/ SQL Knowledge Base ( ht t p: / / www.rev ealnet .com / ) . Whenev er possible, t he code I provide for t he book can be used t o generat e best pract ice- based code and as prebuilt , generalized com ponent s in your applicat ions, code t hat y ou can use w it hout having t o m ake any m odificat ions. The code exam ples offer program s t hat y ou can use t o bot h generat e and direct ly im plem ent t hose best pract ices. I n som e cases, t he program s are rat her sim ple

6

Term Fly Presents

http://www.flyheart.com

" prot ot ypes" ; t hey w ork as adv ert ised, but y ou will probably want t o m ake som e changes before you put t hem int o product ion applicat ions. And you should m ost cert ainly t est every single program y ou use from Oracle PL / SQL Best Pract ices ! I have run som e t est s, and m y w onderful t echnical reviewers have also ex ercised t he code. I n t he end, how ev er, if t he code goes int o your applicat ion, you are responsible for m aking sure t hat it m eet s y our needs.

Ot h e r Re sou r ces This book is int ended t o com plem ent num erous ot her resources for PL/ SQL dev elopers. I t is, t o m y knowledge, t he first collect ion of best pract ices specifically for t he Oracle PL/ SQL language. On t he ot her hand, it doesn't st and on it s own as a com prehensive resource, eit her for PL/ SQL, in part icular, or for Oracle applicat ion dev elopm ent , in general. What follows is by no m eans an exhaust ive list of resources for dev elopers. However, I find t hat a 15- page bibliography is m ore int im idat ing t han it is helpful. So I offer t his short list of t he resources t hat I hav e recent ly found m ost useful and int erest ing: Code Com plet e by St ev en McConnell ( Microsoft Press) A classic t ext , t his " pract ical handbook of soft w are crit icism " should be on t he book shelf of every developer or at least in your t eam 's library. Chock- full of pract ical advice for const ruct ing code, it show s exam ples in m any languages, including Ada, which is enough like PL/ SQL t o m ake learning from McConnell a breeze. Don't st art coding wit hout it ! The web sit e for St ev en McConnell's consult ing pract ice, ht t p: / / www.const rux.com / , is also packed wit h lot s of good advice. Refact oring by Mart in Fowler ( Addison Wesley) According t o t his book, " refact oring is t he process of changing a soft ware syst em in such a way t hat it doesn't alt er t he ex t ernal of t he code, y et im proves it s int ernal st ruct ure." Sound great , or what ? This ex cellent book uses Java as it s exam ple language, but t he w rit ing is clear and t he Java st raight forward. There is m uch t o apply here t o PL/ SQL program m ing. Ext rem e Program m ing Explained, by Kent Beck ( Addison Wesley) This book is a highly readable and concise int roduct ion t o Ext rem e Program m ing ( XP) , a light weight soft ware dev elopm ent m et hodology. Visit ht t p: / / www.xprogram m ing.com / or ht t p: / / www.ext rem eprogram m ing.org/ for a glim pse int o t he world of t his int erest ing approach t o dev elopm ent . And t hen, of course, t here is m y own oeuvre, t he Oracle PL/ SQL Series from O'Reilly & Associat es, which includes: Oracle PL/ SQL Program m ing, wit h Bill Pribyl

7

The com plet e language reference for Oracle PL/ SQL. Oracle PL/ SQL Program m ing: Guide t o Oracle8i Feat ures A com panion volum e describing t he Oracle8i addit ions t o t he PL/ SQL language. Oracle PL/ SQL Dev eloper's Workbook, wit h Andrew Odewahn A workbook cont aining problem s ( and accom panying solut ions) t hat will t est your knowledge of Oracle PL/ SQL language feat ures. Oracle Built - in Packages, wit h Charles Dy e and John Beresniewicz A com plet e reference t o t he m any built - in packages provided by Oracle Corporat ion. Advanced Oracle PL/ SQL Program m ing wit h Packages A descript ion of how t o writ e your own PL/ SQL packages, including a large num ber of packages you can use in your own program s. Oracle PL/ SQL Language Pocket Reference, wit h Bill Pribyl and Chip Dawes A quick reference t o t he PL/ SQL language synt ax. Oracle PL/ SQL Built - ins Pocket Reference, wit h John Beresniewicz and Chip Dawes A quick reference t o t he calls t o t he Oracle built - in funct ions and packages.

Con ven t ion s Use d in Th is Book The following t ypographical convent ions are used in t his book: I t alic I ndicat es filenam es, direct ory nam es, and URLs. I t 's also used for em phasis and for t he first use of a t echnical t erm . Bold Used when referring, by num ber, t o a best pract ice described in t his book ( e.g., [ BI P- 04: Handle expect ed and nam ed except ions when perform ing file I / O.] ) . Const ant widt h I ndicat es exam ples and t o show t he cont ent s of files and t he out put of com m ands.

8

Term Fly Presents

http://www.flyheart.com

Constant width bold I ndicat es code ent ered by a user ( e.g., via SQL* Plus) or t o highlight code lines being discussed. UPPERCASE I n code exam ples, indicat es PL/ SQL k eyw ords. lowercase I n code exam ples, indicat es user- defined it em s ( e.g., variables) .

This icon designat es a not e, which is an im port ant aside t o t he nearby t ext . For exam ple, I 'll use t his icon when suggest ing t he use of an alt ernat ive feat ure. This icon designat es a warning relat ing t o t he nearby t ext . For exam ple, I 'll use t his icon when a part icular feat ure m ight affect perform ance or preclude use of som e ot her feat ure.

Com m e n t s an d Qu est ion s Please address com m ent s and quest ions concerning t his book t o t he publisher: O'Reilly & Associat es, I nc. 101 Morris St reet Sebast opol, CA 95472 ( 800) 998- 9938 ( in t he Unit ed St at es or Canada) ( 707) 829- 0515 ( int ernat ional/ local) ( 707) 829- 0104 ( fax) There is a w eb page for t his book, which list s errat a, exam ples, or any addit ional inform at ion. You can access t his page at : ht t p: / / www.oreilly.com / cat alog/ orbest prac To com m ent or ask t echnical quest ions about t his book, send em ail t o: bookquest [email protected] For m ore inform at ion about books, conferences, soft ware, Resource Cent ers, and t he O'Reilly Net work , see t he O'Reilly web sit e at : ht t p: / / www.oreilly.com /

Ack n ow ledgm en t s 9

Thanks go, first of all, t o m y edit or of six years at O'Reilly & Associat es, Deborah Russell. She got m e off t he dim e on t his proj ect and helped m e t urn it around in record t im e ( I st art ed doing serious w rit ing on t his book in Oct ober 2000 and finished it up in January 2001) . I t was, once again, a real pleasure working wit h you, Debby! Thanks as w ell t o t he ot her O'Reilly people who t urned t his book int o a finished product : Mary Anne Weeks May o, t he product ion edit or; Ellie Volckhausen, who designed t he cov er; and Caroline Senay, t he edit orial assist ant who helped in m any ways t hroughout t he proj ect . Many out st anding Oracle developers and DBAs cont ribut ed t heir t im e and expert ise, t hrough t echnical review, code sam ples, or w rit ing. My deep- felt grat it ude goes out t o: John Beresniewicz, Rohan Bishop, Dick Bolz, Dan Clam age, Bill Caulkins, Dan Condon- Jones, Fawwad- uz- Zafar Siddiqi, Gerard Hart gers, Edwin van Hat t em , Dwayne King, Darryl Hurley, Giovanni Jaram illo, Vadim Loev ski, Pav el Luzanov, Mat t hew MacFarland, Jeffrey Meens, Jam es " Padders" Padfield, Rakesh Pat el, Bill Pribyl, Andre Vergison ( t he brains behind PL/ Form at t er) , and Solom on Yakobson. This book benefit ed, in part icular, from a rew orking of best pract ice t it les by John Beresniewicz, close readings of m any chapt ers by Dan Clam age ( whose ex cellent com m ent s on cert ain best pract ices I 've included as sidebars in t he t ext ) , and t he cont ribut ion of t rigger best pract ices by Darryl Hurley. Oracle PL/ SQL Best Pract ices is a m uch im prov ed t ext as a result of all of your assist ance, m y friends. Any errors, on t he hand, are ent irely m y fault and responsibilit y. I would also like t o t hank m y wife, Veva, for volunt eering t o pick up Eli from Jordan's house so t hat I could st ay behind and writ e t hese acknowledgm ent s ( oh, and also for adding layer upon layer of m eaning and happiness t o m y life) .

Ch a pt e r 1 . Th e D e ve lopm e n t Pr oce ss To do y our j ob w ell, you need t o be aware of, and t o follow, bot h " lit t le" best pract ices—very focused t ips on a part icular coding t echnique—and " big" best pract ices. This chapt er offers som e suggest ions on t he big pict ure: how t o w rit e your code as part of a high- qualit y dev elopm ent process. My obj ect ive isn't t o " sell" you on any part icular dev elopm ent m et hodology ( t hough I m ust adm it t hat I am m ost at t ract ed t o so- called " light weight " m et hodologies such as Ext rem e Program m ing and SCRUM) . [ 1] I nst ead, I 'll rem ind you of basic processes you should follow wit hin any big- pict ure m et hodology. [ 1] This chapt er cont ains num erous references t o Ext rem e Program m ing resources. For m ore inform at ion about SCRUM, " a process for em pirically m anaging product developm ent and im proving t eam product ivit y," visit ht t p: / / www.cont rolchaos.com / . Not e t hat SCRUM isn't an acronym , but a reference t o t he " scrum " in t he sport of rugby, a m et aphor for t he daily m eet ings t hat are t he core of t he SCRUM m et hodology.

10

Term Fly Presents

http://www.flyheart.com

I n ot her w ords, if you ( or your m et hodology) don't follow som e form of t he best pract ices in t his chapt er, y ou are less likely t o produce high- qualit y, successful soft ware. I don't ( wit h perhaps a single except ion) suggest a specific pat h or t ool. You j ust need t o m ak e sure you've got t hese bases cov ered.

D EV- 0 1 : Se t st an da r ds an d gu ide lin e s be fore w r it in g an y code .

These st andards and guidelines would, if I had m y way , include m any or all of t he best pract ices described in t his book. Of course, you need t o m ake your own decisions about what is m ost im port ant and pract ical in your own part icular environm ent . Key areas of dev elopm ent for which you should proact ively set st andards are: •

Select ion of dev elopm ent t ools : You should no longer be relying on SQL* Plus t o com pile, execut e, and t est code; on a basic edit or like Not epad t o w rit e t he code; or on EXPLAI N PLAN t o analyze applicat ion perform ance. Soft ware com panies offer a m ult it ude of t ools ( wit h a wide range of funct ionalit y and price) t hat will help dram at ically im prove y our dev elopm ent environm ent . Decide on t he t ools t o be used by all m em bers of t he developm ent group.



How SQL is writ t en in PL/ SQL code : The SQL in your applicat ion can be t he Achilles' heel of y our code base. I f you aren't careful about how y ou place SQL st at em ent s in your PL/ SQL code, y ou'll end up wit h applicat ions t hat are difficult t o opt im ize, debug, and m anage ov er t im e.



An except ion handling archit ect ure : Users hav e a hard t im e underst anding how t o use an applicat ion correct ly, and developers hav e an ev en harder t im e debugging and fixing an applicat ion if errors are handled inconsist ent ly ( or not at all) . The best way t o im plem ent applicat ion- wide, consist ent error handling is t o use a st andardized package according t o specific guidelines.



Processes for code review and t est ing : There are som e basic t enet s of program m ing t hat m ust not be ignored. You should never put code int o product ion wit hout having it reviewed by one or m ore ot her developers, and wit hout perform ing exhaust ive t est ing. Ast onishingly, m any ( if not m ost ) PL/ SQL developm ent shops hav e neit her st andard, m andat ory code reviews nor a st rict t est ing regim en.

Best pract ices t hroughout t his chapt er and t he rest of t he book address t hese crucial aspect s of soft ware dev elopm ent . You will also find m any relevant exam ples t hroughout t he book.

Be n e fit s By set t ing clear st andards and guidelines for at least t he areas j ust list ed ( t ools, SQL, error handling, and code review and t est ing) , y ou ensure a foundat ion t hat will allow you t o be product ive and t o produce code of reasonable qualit y.

11

Ch a lle n ge s The deadline pressures of m ost applicat ions m it igat e against t aking t he t im e up front t o est ablish st andards, ev en t hough we all know t hat such st andards are likely t o save t im e down t he line.

D EV- 0 2 : Ask for h e lp a ft er 3 0 m in u t es on a pr oblem .

Following t his sim ple piece of advice will probably have m ore im pact on your code t han anyt hing else in t his book! How m any t im es hav e y ou st ared at t he screen for hours, t rying t his and t hat vain at t em pt t o fix a problem in your code? Finally, exhaust ed and desperat e, call over y our cubicle wall: " Hey, Melinda, could you com e ov er here and look t his?" When Melinda reaches your cube she sees in an inst ant what you, aft er st ill could not see. Gosh, it 's like m agic!

in a you at hours,

Except it 's not m agic and it 's not m yst erious at all. Rem em ber: hum ans writ e soft ware, so an underst anding of hum an psychology is crucial t o set t ing up processes t hat encourage qualit y soft ware. We hum ans ( especially t he m ales of t he species) like t o get t hings right , like t o solve our own problem s, and do not like t o adm it t hat we don't know what is going on. Consequent ly, we t end t o want t o hide our ignorance and difficult ies. This t endency leads t o m any wast ed hours, high levels of frust rat ion, and, usually, nast y, spaghet t i code. Team leaders and developm ent m anagers need t o cult ivat e an environm ent in which we are encouraged t o adm it what we do not know, and ask for help earlier rat her t han lat er. I gnorance isn't a problem unless it is hidden from view. And by asking for help, you validat e t he knowledge and experience of ot hers, building t he ov erall selfest eem and confidence of t he t eam . There is a good chance t hat if you spend 30 m inut es fruit lessly analyzing your code, t wo m ore hours will not get y ou any furt her along t o a solut ion. I nst ead, get in t he habit of sharing your difficult y wit h a cow orker ( preferably an assigned " buddy," so t he line of com m unicat ion bet w een t he t w o of y ou is officially acknowledged and doesn't represent in any way acknowledgem ent of a failure) .

Ex a m ple Program m ers are a proud and noble people. We don't like t o ask for help; we like t o bury our noses in our screen and creat e. So t he biggest challenge t o get t ing people t o ask for help is t o change behaviors. Here are som e suggest ions: •

12

The t eam leader m ust set t he exam ple. When I have t he privilege t o m anage a t eam of dev elopers, I go out of m y way t o ask each and ev ery person on t hat t eam for help on one issue or anot her. I f y ou are a coach t o ot her t eam s of dev elopers, ident ify t he program m er who is respect ed by all ot hers for her

Term Fly Presents

http://www.flyheart.com

expert ise. Then convince her t o seek out t he advice of ot hers. Once t he leader ( form al or inform al) shows t hat it is OK t o adm it ignorance, ev ery one else will gladly j oin in. •

Post rem inders in work areas, perhaps ev en individual cubicles, such as " STUCK? ASK FOR HELP" and " I T'S OK NOT TO KNOW EVERYTHI NG." We need t o be rem inded about t hings t hat don't com e nat urally t o us.

Be n e fit s Problem s in code are ident ified and solved m ore rapidly. Fewer hours are wast ed in a fut ile hunt for bugs. Knowledge about t he applicat ion and about t he underlying soft ware t echnology is shared m ore evenly across t he dev elopm ent t eam .

Ch a lle n ge s The m ain challenge t o successful im plem ent at ion of t his best pract ice is psychological: don't be afraid t o adm it you don't know som et hing or are having t rouble figuring som et hing out .

Re sou r ce s Peopleware : Product iv e Proj ect s and Team s, by Tom DeMarco and Tim ot hy List er ( Dorset House) . This is a fant ast ic book t hat com bines deep experience in proj ect m anagem ent wit h hum or and com m on sense.

D EV- 0 3 : W alk t h r ou gh e a ch ot h e r's code .

Soft ware is w rit t en t o be ex ecut ed by a m achine. These m achines are very, very fast , but t hey aren't t erribly sm art . They sim ply do what t hey are t old, following t he inst ruct ions of t he soft w are we writ e, as w ell as t he m any ot her layers of soft ware t hat cont rol t he CPU, st orage, m em ory, et c. I t is ext rem ely im port ant , t herefore, t hat we m ake sure t he code we writ e does t he right t hing. Our com put ers can't t ell us if we m issed t he m ark ( " garbage in, garbage out " or, unfort unat ely, " garbage in, gospel out " ) . The usual way we validat e code is by running t hat code and checking t he out com es ( well, act ually, in m ost cases we have our users run t he code and let us know about failures) . Such t est s are, of course, crucial and m ust be m ade. But t hey aren't enough. I t is cert ainly possible t hat our t est s aren't com prehensive and leave errors undet ect ed. I t is also conceivable t hat t he way in which our code was w rit t en produces t he correct result s in very undesirable ways. For inst ance, t he code m ight work " by accident " ( t wo errors cancel t hem selves out ) .

13

A crucial com plem ent t o form al t est ing of code is a form alized process of code review or walk- t hrough. Code review involves having ot her dev elopers act ually read and review your source code. This review process can t ake m any different form s, including: •

The buddy syst em : Each program m er is assigned anot her program m er t o be ready at any t im e t o look at his buddy's code and t o offer feedback.



Form al code walkt hroughs : On a regular basis ( and cert ainly as a " gat e" before any program m oves t o product ion st at us) , a developer present s or " walks t hrough" her code before a group of program m ers.



Pair program m ing : No one codes alone! Whenev er you writ e soft ware, y ou do it in pairs, where one person handles t he t act ical work ( t hinks about t he specific code t o be writ t en and does t he t yping) , while t he second person t akes t he st rat egic role ( keeps an ey e on t he ov erall archit ect ure, looks out for possible bugs, and generally crit iques—always const ruct ively) . Pair program m ing is an int egral part of Ext rem e Program m ing.

Be n e fit s Ov erall qualit y of code increases dram at ically. The archit ect ure of t he applicat ion t ends t o be sounder, and t he num ber of bugs in product ion code goes w ay down. A furt her advant age is t hat of st aff educat ion —not j ust awareness of t he proj ect , but also an increase in t echnological proficiency due t o t he synergist ic effect of w orking t oget her.

Ch a lle n ge s The dev elopm ent m anager or t eam leader m ust t ake t he init iat ive t o set up t he code review process and m ust give dev elopers t he t im e ( and t raining) t o do it right . Also, code review seem s t o be t he first casualt y of deadline crunch. Furt her, a new PL/ SQL proj ect m ight not have t he language expert ise available on t he t eam t o do com plet e, m eaningful walkt hroughs.

Re sou r ce s 1. Handbook of Walkt hroughs, I nspect ions, and Technical Reviews, by Daniel Freedm an and Gerald M. Weinberg ( Dorset House) . Now in it s t hird edit ion, t his book uses a quest ion- and- answer form at t o show you exact ly how t o im plem ent review s for all sort s of product and soft ware dev elopm ent . 2. Ext rem e Program m ing Explained, by Kent Beck ( Addison Wesley) . The first book on Ext rem e Program m ing offers m any insight s int o pair program m ing. 3. Ext rem e Program m ing I nst alled, by Ron Jeffries, Ann Anderson, and Chet Hendrickson ( Addison Wesley) . Focuses on how t o im plem ent Ext rem e Program m ing in your environm ent .

D EV- 0 4 : Valida t e st an da r ds a gain st sou r ce code in t h e da t a ba se .

14

Term Fly Presents

http://www.flyheart.com

This book is chock- full of recom m endat ions, st andards, guidelines, and so on. The usual im m ediat e, visceral response t o all of t hese shoulds is: how can I possibly rem em ber t hem ? And how can I m ak e sure t hat any of our developers act ually follow t hrough on t heir " shoulds?" PL/ SQL offers one big advant age in t his area: all source code is st ored in t he dat abase and is m ade available t hrough dat a dict ionary views ( ALL_SOURCE, USER_SOURCE, DBA_SOURCE) . Oracle also m aint ains addit ional inform at ion about code, such as dependencies, in ot her views. You can—and should—fairly easily validat e at least som e of t he st andards t hat y ou set by running queries against t hese views. Here are som e t hings y ou can do wit h t his inform at ion: •

Set up a w eekly j ob ( via DBMS_ JOB) t o ident ify any program s t hat hav e changed, hav e been creat ed, or have been rem ov ed in t he past w eek. Publish t his inform at ion as HTML on an int ranet so dev elopers can, at any t im e, be aware of t hese changes. This approach can im prov e reuse wit hin your organizat ion, for exam ple.



Provide queries, preferably organized wit hin program s in a package, t hat dev elopers can run ( or, again, can be run as scheduled, weekly j obs) t o check t o see how well t heir code com plies wit h st andards.

Execut ing, as well as writ ing, queries against dat a dict ionary views ( part icular ly t he dependency- relat ed views) can be t im e- consum ing. Be pat ient ! Ex a m ple Suppose we have agreed t hat individual developers should never call RAI SE_APPLI CATI ON_ERROR direct ly. I nst ead t hey should call t he raise procedure of t he st andard error- handling package ( see [ EXC- 04: Use your own raise procedure in place of explicit calls t o RAI SE_APPLI CATI ON_ERROR.] ) . Here is a sim ple query t hat ident ifies all t hose program unit s ( and lines of code) t hat cont ain t his " off lim it s" built - in: SELECT FROM WHERE ORDER

name, line || ' - ' || text code ALL_SOURCE UPPER (text) LIKE '%RAISE_APPLICATION_ERROR%' BY name, line;

This answers a com m on quest ion: " does m y code hav e X in it ?" Rat her t han ex ecut ing t hese st andalone queries ov er and ov er again, you will find it wort hwhile t o encapsulat e such a query inside a pack aged int erface, such as t his " st andards validat ion" package:

15

CREATE OR REPLACE PACKAGE valstd IS PROCEDURE progwith (str IN VARCHAR2); PROCEDURE pw_rae; END valstd; / You can now call valst d.pw_rae t o show all t he " program s wit h" RAI SE_APPLI CATI ON_ERROR ( as you can easily see from t he valst d package body) . You can also call valst d.progwit h t o search for ot her st rings. I f, t herefore, You've a st andard t hat dev elopers should nev er hard- code - 20,000 error num bers, issue t his com m and: SQL> exec valstd.progwith ('-20') and view what is likely t o be a superset of all such inst ances. Anot her kind of st andard t hat m ight be set wit hin an organizat ion is t hat applicat ion code should never reference a t able or view direct ly but inst ead always go t hrough an encapsulat ion package ( [ SQL- 01: Qualify PL/ SQL variables wit h t heir scope nam es when referenced inside SQL st at em ent s. ] ) . Here is a query t hat ident ifies all program unit s t hat violat e t his rule: SELECT owner || '.' || name refs_table, referenced_owner || '.' || referenced_name table_referenced FROM all_dependencies WHERE owner LIKE UPPER ('&1') AND TYPE IN ('PACKAGE', 'PACKAGE BODY', 'PROCEDURE', 'FUNCTION') AND referenced_type IN ('TABLE', 'VIEW') ORDER BY owner, name, referenced_owner, referenced_name;

Be n e fit s You don't have t o rely solely on " m anual" walkt hroughs of code t o validat e com pliance wit h group st andards. Code analysis and code " m ining" ( ext ract ing inform at ion from / about source code) can be aut om at ed and t ight ly int egrat ed int o t he developm ent process.

Ch a lle n ge s You need t o design and build t he analysis code and t hen int egrat e t hese check s int o your ongoing dev elopm ent effort .

Re sou r ce s

16

Term Fly Presents

http://www.flyheart.com

1. reft abs.sql : Query ident ifying direct references t o t ables and view s. 2. valst d.pkg : Sim ple prot ot ype package offering an int erface t o ident ify t he presence of unwant ed t ext in source code.

D EV- 0 5 : Gen e r at e code w h en e ve r possible an d a ppr opr ia t e. Life is short —and way t oo m uch of it is consum ed by t im e spent in front of a com put er screen, m oving digit s wit h varying accuracy ov er t he keyboard. Seem s t o m e t hat w e should be aggressive about finding ways t o build our applicat ions wit h an absolut e m inim um of t im e and effort while st ill producing qualit y goods. A k ey com ponent of such a st rat egy is code generat ion: rat her t han writ e t he code yourself, you let som e ot her piece of soft w are w rit e t he code for you. Code generat ion is part icularly useful when you have defined st andards you want ev ery one t o follow. You can t ry t o get dev elopers t o conform t o t hose st andards wit h a " st ick" approach: follow t he st andards, or else! But a m ore effect ive w ay t o get t he oft en anarchist ic, or at least highly individualist ic, program m er t o follow st andards is t o m ak e it easier t o follow t han not follow t hose guidelines. See Exam ples for specific dem onst rat ions of t his " carrot " approach. I n addit ion t o helping t o im plem ent st andards, code generat ion com es in handy when you hav e t o writ e code t hat is repet it ive in st ruct ure ( i.e., it can be expressed generally by a pat t ern) . For exam ple, t he kind of code y ou w rit e t o det erm ine if t here is at least one row in a t able for a given prim ary key is t he sam e regardless of t he t able ( and prim ary key) . Wouldn't it be nice t o be able t o call a procedure t hat queries t he t able st ruct ure and key from t he dat a dict ionary and generat es t he funct ion? How do you generat e code? You can pick from one of t hese t hree opt ions: •

Writ e y our own cust om query or program t o m eet specific needs. Exam ples st eps you t hrough a sim ple dem onst rat ion of how t o go about t his.



Use a com m ercial t ool t hat focuses on code generat ion. Resources offers a list of known code- generat ion t ools for PL/ SQL dev elopers.



Run relat ively const rained, funct ionally specific generat ion ut ilit ies t hat ot hers have w rit t en ( noncom m ercial, freeware) . Resources offers a list of generat ion ut ilit ies available on t he Oracle PL/ SQL Best Pract ices w eb sit e.

Ex a m ple s Let 's explore t hese t hree opt ions for generat ion. First , we hav e t he classic " SQL generat ing SQL." Suppose t hat I want t o drop all t he t ables in m y schem a. There is no " drop all" com m and. I nst ead, I t hrow t oget her a

17

query against USER_TABLES whose out put is, in fact , a series of DROP st at em ent s, and t hen ex ecut e t hat out put as a spooled file in SQL* Plus: SET PAGESIZE 0 SET FEEDBACK OFF SELECT 'DROP TABLE ' || table_name || ';' FROM user_tables WHERE table_name LIKE UPPER ('&1%') SPOOL drop.cmd / SPOOL OFF @drop.cmd Now, let 's m ov e on t o PL/ SQL- based generat ion. My t eam is about t o st art a largescale developm ent effort . We will need t o perform ret rievals of ent ire rows of dat a for m any different t ables, based on t heir various ( but single) prim ary key colum ns. I want t o do t his in a way t hat conform s t o all of our organizat ion's st andards ( except ion handling wit h logging, use, and encapsulat ion of t he im plicit query t hat offers best perform ance, et c.) . Rat her t han w rit e a m em o t o t his effect , I build a procedure: CREATE OR REPLACE PROCEDURE genlookup (tab IN VARCHAR2, col IN VARCHAR2) IS l_ltab VARCHAR2 (100) := LOWER (tab); l_lcol VARCHAR2 (100) := LOWER (col); BEGIN pl ('CREATE OR REPLACE FUNCTION ' || l_ltab || '_row_for ('); pl (' ' || l_lcol || '_in IN ' || l_ltab || '.' || l_lcol || '%TYPE)'); pl (' RETURN ' || l_ltab || '%ROWTYPE'); pl ('IS'); pl (' retval ' || l_ltab || '%ROWTYPE;'); pl ('BEGIN'); pl (' SELECT * INTO retval'); pl (' FROM ' || l_ltab); pl (' WHERE ' || l_lcol || ' = ' || l_lcol || '_in;'); pl (' RETURN retval;'); pl ('EXCEPTION'); pl (' WHEN NO_DATA_FOUND THEN'); pl (' RETURN NULL;'); pl (' WHEN OTHERS THEN'); pl (' err.log;'); pl ('END ' || l_ltab || '_row_for;'); pl ('/'); END; / And I can t hen use t his procedure as follows: SQL> exec genlookup ('book', 'isbn') CREATE OR REPLACE FUNCTION book_row_for ( isbn_in IN book.isbn%TYPE) RETURN book%ROWTYPE IS

18

Term Fly Presents

http://www.flyheart.com

retval book%ROWTYPE; BEGIN SELECT * INTO retval FROM book WHERE isbn = isbn_in; RETURN retval; EXCEPTION WHEN NO_DATA_FOUND THEN RETURN NULL; WHEN OTHERS THEN err.log; END book_row_for; / You can get m uch m ore sophist icat ed in your generat ion effort s; y ou can, for exam ple, look up t he prim ary key colum n( s) in t he ALL_CONS_COLUMNS dat a dict ionary view, inst ead of having t o specify t he WHERE clause colum n. You hav e t o decide for yourself where t o draw t he line: do y ou really need t hat flexibilit y or does it j ust look like lot s of fun t o build?

Be n e fit s You can build your applicat ions fast er; ut ilit ies can generat e soft ware lot s fast er t han you can t ype it . You will im prove t he qualit y of your applicat ion code: assum ing t hat your generat or program has been w ell- designed and t est ed, it will generat e bug- free code wit h each use. As y our underlying dat a st ruct ures change, y ou can regenerat e program unit s t hat work wit h t hose dat a st ruct ures. Much less t im e is spent upgrading exist ing code.

Ch a lle n ge s Building anyt hing but t he m ost crude generat ors involves a lev el of abst ract ion and com plexit y higher t han t he usual t ask t ackled by m ost developers.

Re sou r ce s Com m e r cia l Code - Ge n e r a t ion Tools 1. ht t p: / / www.oracle.com / : Oracle Designer from Oracle Corporat ion generat es code in a variet y of languages. 2. ht t p: / / www.rev ealnet .com / : RevealNet 's PL/ Generat or generat es com prehensive encapsulat ion packages for t ables and views. 3. PLVgen: RevealNet 's Act ive PL/ SQL Knowledge Base offers PLVgen, a package t hat generat es funct ions, procedures, cursor FOR loops and ot her code elem ent s. Visit t he PL/ SQL Pipeline archives as described in t he Preface. 4. Most CASE/ designer t ools offer som e lev el of code generat ion. Visit t he web sit es of Quest , Com put er Associat es, Precise, BMC, Em barcadero, and so on t o check out t heir respect ive product s.

19

Code - Ge n e r a t ion Ut ilit ie s 1. genlookup.pro : Generat es a lookup procedure t hat ret urns a row in a t able. 2. m sginfo.pkg : Generat es a package wit h definit ions for all applicat ion- specific except ions. 3. genm ods.pkg : Generat es st andard form at t ed funct ions.

D EV- 0 6 : Se t u p a n d u se form a l u n it t e st in g pr oce du r e s.

A unit t est is a t est a developer creat es t o ensure t hat his or her " unit ," usually a single program , w orks properly. A unit t est is very different from a sy st em or funct ional t est ; t hese lat t er t ypes of t est s are orient ed t o applicat ion feat ures or ov erall t est ing of t he sy st em . You can't properly or effect ively perform a sy st em t est unt il you know t hat t he individual program s behave as expect ed. So, of course, you w ould t herefore expect t hat program m ers do lot s of unit t est ing and have a correspondingly high level of confidence in t heir program s. Ah, if only t hat were t he case! The realit y is t hat program m ers generally perform an inadequat e num ber of inadequat e t est s and figure t hat if t he users don't find a bug, t here is no bug. Why does t his happen? Let m e count t he ways: The psychology of success and failure We are so focused on get t ing our code t o w ork correct ly t hat we generally shy away from bad news, from ev en want ing t o t ake t he chance of get t ing bad news. Bet t er t o do som e cursory t est ing, confirm t hat it seem s t o be w orking OK, and t hen wait for ot hers t o find bugs, if t here are any ( as if t here w ere any doubt ) . Deadline pressures Hey, it 's I nt ernet t im e! Tim e t o m ark et det erm ines all. We need ev eryt hing yest erday, so let 's be j ust like Microsoft and Net scape: release pre- bet a soft ware as product ion and let our users t est / suffer t hrough our applicat ions. Managem ent 's lack of underst anding I T m anagem ent is not orious for not really underst anding t he soft ware dev elopm ent process. I f we aren't given t he t im e and aut horit y t o w rit e ( writ e, t hat is, in t he broadest sense, including t est ing, docum ent at ion, refinem ent , et c.) our own code properly, w e will always end up wit h buggy j unk t hat no one w ant s t o adm it ownership of. Overhead of set t ing up and running t est s I f it 's a big deal t o writ e and run t est s, t hey w on't get done. I don't hav e t im e, and t here is always som et hing else t o work on. One consequence of t his point

20

Term Fly Presents

http://www.flyheart.com

is t hat m ore and m ore of t he t est ing is handed over t o t he QA depart m ent , if t here is one. That t ransfer of responsibilit y is, on t he one hand, posit ive. Professional qualit y assurance professionals can have a t rem endous im pact on applicat ion qualit y. Yet we dev elopers m ust t ak e and ex ercise responsibilit y for unit t est ing our own code, ot herwise, t he t est ing/ QA process is m uch m ore frust rat ing and ext ended. Ego I wrot e it ; t herefore it work s t he way I int ended it t o work. The bot t om line is t hat our code alm ost universally needs m ore t est ing. And t he best way t o do unit t est ing is wit h a form al procedure built around soft ware t hat m akes t est ing as easy and as aut om at ed as possible. I can't help wit h deadline pressures, and m y abilit y t o im prove your m anager's underst anding of t he need t o t ake m ore t im e t o t est is lim it ed. I can, on t he ot her hand, offer y ou a " fram ew ork" —a set of processes and code elem ent s—t hat can great ly im prove your abilit y t o perform high qualit y unit t est ing. I n t he spring of 2000, I st udied Ext rem e Program m ing ( ht t p: / / www.xprogram m ing.com / ) and it s associat ed concept s on unit t est ing ( m ost widely used in it s Java int erpret at ion, t he open source JUnit ) . I t hen adapt ed t hese ideas t o t he world of PL/ SQL, creat ing ut PLSQL, t he unit t est ing fram ew ork for Oracle PL/ SQL. By t he t im e t his book is published, t here m ay be ot her unit t est ing facilit ies available for PL/ SQL. As a st art ing point for exploring t he im plem ent at ion of form al unit t est s for y our code, how ev er, I encourage you t o visit ht t p: / / oracle.oreilly.com / ut plsql.

Ex a m ple Wit h ut PLSQL, y ou build a t est package for y our st andalone or packaged program s. You t hen ask ut PLSQL t o run t he t est s in your t est package, and display t he result s. When y ou use ut PLSQL, you don't hav e t o analyze t he result s and det erm ine whet her y our t est s succeeded or failed; t he ut ilit y aut om at ically figures t hat out for you. Suppose, for exam ple, t hat I hav e built a sim ple alt ernat ive t o t he SUBSTR funct ion called bet wnst r: it ret urns t he subst ring found bet ween t he specified st art and end locat ions in t he st ring. Here it is: CREATE OR REPLACE FUNCTION betwnstr ( string_in IN VARCHAR2, start_in IN INTEGER, end_in IN INTEGER ) RETURN VARCHAR2 IS BEGIN RETURN ( SUBSTR ( string_in,

21

start_in, end_in - start_in + 1 ) ); END betwnstr; / To t est t his funct ion, I want t o pass in a variet y of input s, as shown in t his t able: St r i ng

St ar t

End

Expect ed Resul t

abcdefg 3 ( posit ive num ber)

5 ( bigger posit ive num ber)

cde

abcdefg NULL

Any

NULL

abcdefg Any

NULL

NULL

abcdefg 5

2 ( end sm aller t han st art

NULL

abcdefg 3

200 ( end larger t han st ring lengt h)

cdefg

From t his t able ( which, of course, doesn't y et cov er all t he variat ions needed for a com prehensive t est ) , I build t est cases for each ent ry in m y t est package's unit t est procedure: CREATE OR REPLACE PACKAGE BODY ut_betwnstr IS PROCEDURE ut_betwnstr IS BEGIN utassert.eq ('Typical valid usage', betwnstr (string_in => 'abcdefg', start_in => 3, end_in => 5), 'cde'); utassert.isnull ('NULL start', betwnstr (string_in=> 'abcdefg', start_in => NULL, end_in => 5)); utassert.isnull ('NULL end', betwnstr (string_in=> 'abcdefg', start_in => 2, end_in => NULL)); utassert.isnull ('End smaller than start', betwnstr (string_in => 'abcdefg', start_in => 5, end_in => 2)); utassert.eq ('End larger than string length', betwnstr (string_in=> 'abcdefg', start_in => 3, end_in => 200), 'cdefg'); END ut_betwnstr; END ut_betwnstr; / I call t he ut Assert procedures so t hat t he result s of m y t est s ( m y " assert ions" t hat such and such is t rue) can be logged aut om at ically wit h ut PLSQL.

22

Term Fly Presents

http://www.flyheart.com

Then I can run t he t est and view t he result s. Here is a run t hat ident ifies no errors: SQL> exec utplsql.test ('betwnstr') . > SSSS U U CCC CCC > S S U U C C C C > S U U C C C C > S U U C C > SSSS U U C C > S U U C C > S U U C C C C > S S U U C C C C > SSSS UUU CCC CCC . SUCCESS: "betwnstr"

EEEEEEE SSSS SSSS E S S S S E S S E S S EEEE SSSS SSSS E S S E S S E S S S S EEEEEEE SSSS SSSS

And here is t he out put shown when problem s arise: SQL> exec utplsql.test ('betwnstr') . > FFFFFFF AA III L U U RRRRR EEEEEEE > F A A I L U U R R E > F A A I L U U R R E > F A A I L U U R R E > FFFF A A I L U U RRRRRR EEEE > F AAAAAAAA I L U U R R E > F A A I L U U R R E > F A A I L U U R R E > F A A III LLLLLLL UUU R R EEEEEEE . FAILURE: "betwnstr" . UT_BETWNSTR: Typical valid usage; expected "cde", got "cd" UT_BETWNSTR: IS NULL: NULL start UT_BETWNSTR: IS NULL: End smaller than start

Be n e fit s You develop applicat ions fast er, wit h a higher degree of confidence and wit h fewer bugs. I t is m uch easier for ot her dev elopers t o m aint ain and enhance your code, because aft er t hey m ak e a change, t hey can run t he full suit e of t est s and confirm t hat t he program st ill passes all t est s.

Ch a lle n ge s The only challenge t o perform ing com prehensive unit t est ing is you! You know y ou have t o t est y our code, and you hav e t o t est it repeat edly. So t ake t he t im e t o define your t est s wit hin a t est package, and use a t est ing facilit y t o run y our t est s for you. Enlist t he help of ot her dev elopers in your organizat ion t o review y our unit t est cases and build ot hers. Did you m iss anyt hing? I s y our t est accurat e? I t is oft en difficult for

23

t he person who wrot e ( or is about t o w rit e) t he code t o be obj ect ive about it . You'll find m ore about t his t opic in [ DEV- 07: Get independent t est ers for funct ional signoff.] .

Re sou r ce s 1. ht t p: / / oracle.oreilly.com / ut plsql : To download ut PLSQL and t o obt ain m ore inform at ion about it s approach t o unit t est ing. 2. ht t p: / / www.xprogram m ing.com / : For m ore general inform at ion about Ext rem e Program m ing's approach t o and underlying principles for unit t est ing. 3. ht t p: / / www.ext rem eprogram m ing.org/ : For a wonderfully accessible, webbased int roduct ion t o Ex t rem e Program m ing.

D EV- 0 7 : Ge t in de pen den t t e st e rs for fu n ct ion a l sign - off.

I ndividual developers should and m ust be responsible for defining and execut ing unit t est s on t he program s t hey w rit e ( see [ DEV- 06: Set up and use form al unit t est ing procedures.] ) . Dev elopers should not , on t he ot her hand, be responsible for overall funct ional t est ing of t heir applicat ions. There are sev eral reasons for t his: •

We don't own t he requirem ent s. We don't decide when and if t he sy st em works properly. Our users or cust om ers have t his responsibilit y. They need t o be int im at ely connect ed wit h, and drive, t he funct ional t est s.



Whenev er w e t est our code, w e follow t he " pat hways t o success" wit hout ev er knowing it . I n ot her w ords, t he m indset we had when we w rot e t he code is t he sam e m indset w e have when t est ing t he code. Ot her people, ot her ey es, need t o run t he soft ware in com plet e ignorance of t hose pat hway s. I t is no wonder t hat unit t est ing was so successful and yet int egrat ion t est ing has such problem s.

To im prove t he qualit y of code t hat is handed over t o cust om ers for t est ing, your t eam leader or dev elopm ent m anager should: •

Work wit h t he cust om er t o define t he set of t est s t hat m ust be run successfully before an applicat ion is considered t o be ready for product ion.



Est ablish a dist inct t est ing group—eit her a devot ed Qualit y Assurance organizat ion or sim ply a bunch of dev elopers w ho haven't writ e any of t he soft ware t o be t est ed.

This ext ra layer of t est ing, based on t he cust om er's own requirem ent s and perform ed before t he handoff t o cust om ers for t heir " sign off" t est , will great ly im prove code qualit y and cust om er confidence in t he dev elopm ent t eam .

Ex a m ple

24

Term Fly Presents

http://www.flyheart.com

I spend several days building a really slick applicat ion in Oracle Dev eloper ( or Visual Basic or Java or...) . I t allows users t o m anage dat a in a few different t ables, request report s, and so on. I t hen devot e m ost of a day t o running t he applicat ion t hrough it s paces. I click here, click t here, ent er good dat a, ent er bad dat a, find a bunch of bugs, fix t hem , and finally hand it over t o m y m ain cust om er, Johanna. I feel confident in m y applicat ion. I can no longer break it . I m agine how crushed I feel ( and I bet y ou can im agine it , because undoubt edly t he sam e t hing has happened t o you) when Johanna sit s down in front of t he com put er, st art s up t he applicat ion, and in no m ore t han t hree clicks of t he m ouse causes an error window t o pop up on t he screen. The look she sends m y way ( " Why are you wast ing m y t im e?" ) will st ay wit h m e for y ears. There is no way for m e t o convince Johanna t hat I really, t ruly did spend hours t est ing t he applicat ion. Why should she believe such a t hing? She is t hen left t o believe I am a t ot ally incom pet ent t est er.

Be n e fit s Qualit y of code handed t o users for t est ing is higher, which m eans t he end result m ov ed t o product ion is of correspondingly higher qualit y. Cust om er confidence in t he dev elopm ent organizat ion rem ains high. This confidence—and t he respect t hat com es wit h it —m akes it easier for developers t o negot iat e wit h cust om ers over t he t im e- versus- qualit y dilem m a so m any of us face in soft ware developm ent .

Ch a lle n ge s Many sm all developm ent groups can't afford ( i.e., can't convince m anagem ent t o spend t he m oney) t o st aff a separat e QA organizat ion. At a m inim um , you m ust m ake sure t hat cust om ers hav e defined a clear set of t est s. Then dist ribut e t he funct ional t est ing load t o t he dev elopers so t hat t hey do not t est t heir own code.

Re sou r ce s ht t p: / / www.well.com / ~ vision/ sqa.ht m l : A gat hering place for references relat ed t o t he t heory and pract ice of Soft ware Qualit y Assurance. This sit e is grow ing t o include inform at ion on St andards and Developm ent Procedures, Product Evaluat ion and Process Monit oring, Configurat ion Managem ent Monit oring, t he role of SQA in t he Product Developm ent Cycle, and Aut om at ed Test ing Tools.

Ch a pt e r 2 . Codin g St yle a n d Con ve n t ion s Soft ware dev elopers are a v ery privileged bunch. We don't have t o work in dangerous environm ent s, and our j obs aren't physically t axing ( t hough carpal t unnel syndrom e is always a t hreat ) . We are paid t o t hink about t hings, and t hen t o writ e down our t hought s in t he form of code. This code is t hen used and m aint ained by ot hers, som et im es for decades.

25

Given t his sit uat ion, I believe w e all have a responsibilit y t o writ e code t hat can be easily underst ood and m aint ained ( and, c'm on, let 's adm it our secret desires, adm ired) by developers who follow in our foot st eps.

St eve McConnell's ht t p: / / www.const rux.com / sit e, along wit h his book, Code Com plet e ( Microsoft Press) , offers checklist s on coding st yle, nam ing convent ions and rules, and m odule definit ions.

STYL- 0 1 : Adopt a con sist en t , r ea da ble form a t t h a t is e a sy t o m a in t a in .

Your code should have a " signat ure," a st yle t hat is consist ent ( all your program s look t he sam e) , readable ( anyone can pick up y our code and m ake sense of it ) , and m aint ainable ( a m inor change in t he code shouldn't require 15 m inut es of reform at t ing) . I deally, ev eryone in your organizat ion would adopt a sim ilar st yle, so t hat ev eryone can easily underst and everyone else's code. This can be t ricky, as program m ers som et im es t ak e a dogm at ic approach t o such issues as size of indent at ion and use of whit espace. You hav e t w o opt ions regarding coding st yle: •

Find or w rit e a set of guidelines, and t hen t ry as hard as y ou can t o follow ( and get your group t o follow) t hose guidelines. See Resources for a sam ple docum ent .



Use a t ool t o aut om at ically form at y our code for y ou. The dom inant code form at t er for PL/ SQL is current ly PL/ Form at t er from Rev ealNet ( see Resources) . This product is not only available st andalone, but is also int egrat ed int o m any popular int egrat ed developm ent environm ent s ( I DEs) .

I st rongly recom m end t hat you use PL/ Form at t er or som e ot her " pret t y print " t ool. I t is quit e liberat ing t o writ e code wit hout any concern what soev er for how it looks: I focus com plet ely on t he logical flow and t hen press a but t on a m om ent lat er t o t urn it int o readable, at t ract ive code.

Ex a m ple Here is a package specificat ion t hat has som e clear problem s: all uppercase, no indent at ion, no whit espace: CREATE OR REPLACE PACKAGE OVERDUE_PKG IS PROCEDURE SET_DAILY_FINE (FINE_IN IN NUMBER); FUNCTION DAILY_FINE RETURN NUMBER; FUNCTION DAYS_OVERDUE (ISBN_IN IN BOOK.ISBN%TYPE)RETURN INTEGER;

26

Term Fly Presents

http://www.flyheart.com

FUNCTION FINE (ISBN_IN IN BOOK.ISBN%TYPE) RETURN INTEGER; END OVERDUE_PKG; / I ran it t hrough PL/ Form at t er and cam e up wit h t his: CREATE OR REPLACE PACKAGE overdue_pkg IS PROCEDURE set_daily_fine (fine_in IN NUMBER); FUNCTION daily_fine RETURN NUMBER; FUNCTION days_overdue ( isbn_in IN book.isbn%TYPE) RETURN INTEGER; FUNCTION fine (isbn_in IN book.isbn%TYPE) RETURN INTEGER; END overdue_pkg; / Which of t hese specificat ions would you prefer t o read and m aint ain?

D éj à vu Code I wrot e and enact ed a PL/ SQL Coding St andard at a form er client 's. Aft er t wo years t here as a consult ant , I m oved on t o ot her assignm ent s. A year lat er, I ret ur ned t o t he previous client . I was t asked wit h m aint aining a part icular package. Looking at it , I got a st range sense of déj à vu; t he code looked like som et hing I would have writ t en, but I could not rem em ber having writ t en it . Since it was laid out according t o t he prescribed st andard, it was easy t o locat e sect ions and m ake t he needed changes. I checked t he docum ent header t o discover who wrot e it , w hich t urned out t o be anot her fellow t here. I asked him about it , and he said t hat he sim ply followed t he st andard. He liked how so m any packages were all consist ent ly organized, m aking it a breeze t o read and m aint ain t hem . —Dan Clam age Be n e fit s Code can be m ore effect ively reviewed, m aint ained, and enhanced if it 's w ellform at t ed and form at t ed consist ent ly wit h t he rest of your developm ent t eam .

27

Ch a lle n ge s I t 's hard t o enforce a coding st yle am ong program m ers, who can be fiercely libert arian. I t t ak es t im e t o produce a com prehensive st yle docum ent for PL/ SQL.

Re sou r ce s 1. Recom m endat ions for coding st yle from Chapt er 3 of Oracle PL/ SQL Program m ing, available online at ht t p: / / www.oreilly.com / cat alog/ oraclep2/ . 2. ht t p: / / www.rev ealnet .com / product s/ form at t er/ form at t er.ht m : For inform at ion about PL/ Form at t er.

STYL- 0 2 : Adopt logica l, con sist en t n am in g con ven t ion s for m odu le s a n d da t a st r u ct u r e s.

Adopt and prom ot e st andard way s t o define nam es of program elem ent s. Choose a level of " form alit y" of nam ing convent ions based on y our needs. I f, for exam ple, y ou have a t eam of t wo dev elopers w orking on a sm all code base, y ou can probably get away wit h nam ing conv ent ions t hat don't go far bey ond " use m eaningful nam es." I f you are building a m assive applicat ion involving dozens of developers, y ou probably need t o define m ore com prehensive rules. Here are som e general recom m endat ions for convent ions:

28



I dent ify t he scope of a variable in it s nam e. A global variable can be prefaced wit h g_ , for exam ple.



Use a prefix or suffix t o ident ify t he t ypes of st ruct ures being defined. Consider, for exam ple, declarat ions of TYPEs: of collect ions, obj ect s, records, ref cursors, et c. A st andard approach t o declaring such a st ruct ure is _t. Types are quit e different from variables; you should be able t o ident ify t he difference w it h a glance.



Use t he sam e case conv ent ion for user- defined t ypes as t he st andard dat at ypes in order t o help t hem st and out . Dat at ypes ( built - in or user- defined) should follow a different casing rule from variables ( such as all uppercase for t ypes, lowercase for variables) .



Use a readable form at for your nam es. Since PL/ SQL isn't case- sensit ive, t he " cam el not at ion" ( as in m inBalanceRequired) , for exam ple, is probably not a good choice for const ruct ing nam es. I nst ead, use separat ors such as _ ( underscore) t o im prov e readabilit y ( as in m in_balance_required) . While nam es can be as long as 30 charact ers, keep t hem short , as well as readable.

Term Fly Presents •

http://www.flyheart.com

Organize like it em s t oget her. For exam ple, declare record variables t oget her in t he sam e sect ion. Declare all const ant s t oget her in anot her sect ion, separat ed from t he prev ious sect ion by whit espace.

I t isn't possible t o provide a com prehensive list of nam ing conv ent ions in t his book. The part icular conv ent ions you choose, furt herm ore, aren't nearly as im port ant as t he fact t hat you set som e st andard for nam ing conv ent ions. See Resources for downloadable st yle guides.

Ex a m ple Here is a block of code t hat reflect s no st andardizat ion of nam ing convent ions: CREATE OR REPLACE PROCEDURE showborrowedbooks ( date_borrowed IN DATE) IS date_returned DATE := SYSDATE; mindaysborrowed INTEGER := 10; TYPE book_borrowed IS RECORD ( dateborrowed DATE, daysborrowed INTEGER, isbn book.isbn%TYPE, datedue DATE); borrowedbook book_borrowed; CURSOR allborrowed IS SELECT * FROM borrowed_book WHERE returned = 'N'; BEGIN IF dateborrowed < datereturned THEN FOR rec IN allborrowed LOOP borrowedbook:= rec; IF borrowedbook.daysborrowed > mindaysborrowed THEN pl (borrowedbook.isbn); END IF; END LOOP; END IF; END showborrowedbooks; Here's t hat sam e block of code based on st andards. I use underscores in nam es; suffixes on param et ers, records, and cursors; prefixes t o show scope ( l_ for local) and t ype ( c_ for const ant ) . Com pare carefully t he following it em nam es wit h t hose in t he previous exam ple: CREATE OR REPLACE PROCEDURE show_borrowed_books ( date_borrowed_in IN DATE) IS c_date_returned CONSTANT DATE := SYSDATE;

29

l_min_days_borrowed INTEGER := 10; TYPE book_borrowed_rt IS RECORD ( date_borrowed DATE, days_borrowed INTEGER, isbn book.isbn%TYPE, date_due DATE); borrowed_book_rec book_borrowed_rt; CURSOR all_borrowed_cur IS SELECT * FROM borrowed_book WHERE returned = 'N'; BEGIN IF date_borrowed_in < c_date_returned THEN FOR book_rec IN all_borrowed_cur LOOP borrowed_book_rec := book_rec; IF borrowed_book_rec.days_borrowed > l_min_days_borrowed THEN pl (borrowed_book_rec.isbn); END IF; END LOOP; END IF; END show_borrowed_books; Now it 's possible t o look at any individual part of show_borrow ed_books and m ake sense of t he different kinds of st ruct ures m anipulat ed by t he program .

Be n e fit s By set t ing st andards, y ou don't hav e t o const ant ly worry about how t o writ e your code. Concent rat e on t he im port ant st uff: t he business logic. Dev elopers com e up t o speed m ore quickly on t he code base as t hey t ransfer int o new groups; t hey also t ransfer t heir knowledge and product ivit y from one proj ect t o anot her.

Ch a lle n ge s Define nam ing st andards t hat are appropriat e t o y our proj ect ( not overly rigid for t he size of t he t eam and com plexit y of t he applicat ion) . Get dev eloper buy- in for t he conv ent ions. One way t o achieve t his is t o get t he dev elopers t hem selves t o set t hose convent ions. Check for com pliance wit h conv ent ions ( alt hough t his is difficult t o do) . You can build script s in PL/ SQL and SQL t o analyze source code for conform ance wit h som e rules ( e.g., " Don't use fixed- lengt h CHAR declarat ions." ) . Current ly, a com prehensive

30

Term Fly Presents

http://www.flyheart.com

review m ust be perform ed m anually; I hope t hat t ools will becom e available in t he next sev eral y ears.

Re sou r ce s 1. See St ev e McConnell's ht t p: / / www.const rux .com / sit e and his Code Com plet e book for nam ing conv ent ion suggest ions. 2. st andards.doc : An unfinished draft of som e nam ing and coding st andards for PL/ SQL developers; be sure t o review and edit t his docum ent before using in your organizat ion. 3. st andards.zip : An HTML- driven com prehensive guide t o a set of nam ing conv ent ions for PL/ SQL code ( court esy of Mat t hew MacFarland) .

STYL- 0 3 : St an da r dize m odu le an d pr ogr am h e a de r s.

While you should generally keep com m ent s t o a m inim um in your code ( see [ STYL09: Com m ent t ersely wit h value- added inform at ion. ] ) , it 's ext rem ely im port ant t o creat e and keep current a st andard header for all program s. This header should cont ain, at a m inim um , t he following elem ent s: •

Version, aut hor, and copyright inform at ion : What is t he version of t he code? Who w rot e t he program , who owns t he program , et c.



Access inform at ion : Where is t he program st ored? On disk in a file? Wit hin t he dat abase under a cert ain schem a?



Overview : What does t his program do?



Dependencies : What does t his program need t o hav e defined, or hav e access t o, in order t o run properly?



Algorit hm s : Are any algorit hm s of special not e used in t he program ? I f so, specify t hem and/ or supply a m ore det ailed descript ion of t he t heory of operat ion ( if t here is one) .



Scope : What applicat ion m odule( s) was t he program writ t en for ( if it 's not a generic library- t ype of program ) ? Frequent ly, packages are back end com ponent s of a syst em wit h a com plex front end. For exam ple, a set of packages m ight com prise t he Pay roll subsyst em .



Modificat ion hist ory : What m odificat ions have been m ade t o t he program ? I nclude a line ent ry for each change t o t he program , showing who, when, and what . Put t he ent ries in dat e- descending order, so t hat t he m ost recent change is at t he t op.



Except ions : What errors m ight be raised by t he program ?

31

You are best off defining t his header aft er t he I S or AS k eyw ord in your program definit ion. For exam ple: CREATE OR REPLACE PROCEDURE my_procedure IS /* ... header text */ When y ou put t he header inside t he program definit ion, t hat header is also st ored in t he USER_SOURCE dat a dict ionary view, m aking it accessible t o analysis.

Ex a m ple Here's a st andard header form at t hat follows an XML- like synt ax ( see Resources for a package t hat helps you leverage t his st andardized form at in your dev elopm ent process) : /* 1.0.5 stdhdr.pkg Steven Feuerstein API to standard headers in code Steven Feuerstein, 2000

Rather than simply document a standard header for programs, this package offers a package-based API so that you can easily extract information stored in the header.

ALL_SOURCE data dictionary view

None Modification History Date By ---------- -------- 06/30/2000 SEF 06/07/2000 SEF

*/

Modification ------------------------------Change to XML-compatible syntax Program created

Be n e fit s You can, at a glance, grasp all t he adm inist rat ive aspect s of t he program . An accurat e m odificat ion hist ory m ak es it easier t o m aint ain t he code.

32

Term Fly Presents

http://www.flyheart.com

The various sect ions can be parsed and st ored anywhere as ongoing docum ent at ion ( showing t he changes t he program underw ent ) . This benefit , com bined wit h capt uring t he last DDL t im est am p, m ak es for good QA of t he product ion dat abase.

Ch a lle n ge s I f t he header isn't k ept up t o dat e, it 's worse t han useless: it 's m isleading. Most im port ant ly, dev elopers m ust updat e t he m odificat ion hist ory wit h every change. I f t he code m odificat ions point back t o t he version com m ent in t he header, t hat 's ev en bet t er.

Re sou r ce s st dhdr.pkg : A prot ot ype " st andard header" pack age t hat generat es a st andard header ( wit h an XML- st yle form at ) and offers program s t o query such headers from st ored code.

STYL- 0 4 : Tag m odu le EN D st a t e m en t s w it h m odu le n am es.

Ev ery program ( indeed, ev ery block of code; see [ STYL- 06: Self- docum ent using block and loop labels. ] ) has an END st at em ent . You can, and should, append t he nam e of t he program t o t he end st at em ent : CREATE OR REPLACE PACKAGE BODY IS PROCEDURE (...) IS BEGIN ... END ; PROCEDURE (...) IS BEGIN ... END ; END ;

Ex a m ple My package consist s of 243 procedures and funct ions, st ret ching t o ov er 5,000 lines. Wit hout END labels, I could easily be confront ed wit h code like t his: END LOOP; END; END; Yikes! Wouldn't it be so m uch bet t er if m y code had inst ead been w rit t en like t his:

33

END LOOP yearly_analysis; END best_seller_review; END book_usage_pkg;

Be n e fit s This is m erely good form for st andalone program s. For packaged procedures and funct ions wit h code t hat goes on for hundreds or t housands of lines, howev er, nam ed ENDs are crucial t o im proving t he readabilit y of t hat package.

STYL- 0 5 : N am e pr oce du r e s w it h ve r b ph r a ses a n d fu n ct ion s w it h n ou n ph r a ses.

We build procedures t o j oin t oget her ( and run) a series of logically relat ed execut able st at em ent s. The nam e of t he procedure should reflect what t hose st at em ent s do, and should be in t he form of a v erb phrase, as in: PROCEDURE calculate_totals (...); PROCEDURE display_favorite_flavors (...); A funct ion execut es one or m ore st at em ent s wit h t he express int ent of ret urning a value. The nam e of a funct ion should describe what is being ret urned and be in t he form of a noun phrase, as in: FUNCTION total_salary (...) RETURN NUMBER; FUNCTION book_title (...) RETURN VARCHAR2; You m ight also consider st andardizing elem ent s of your procedures' v erb phrases; st andard prefixes can indicat e t he t ype of operat ion. Here are som e exam ples: ins_ I nsert s som et hing get _ Select s som et hing del_ Delet es som et hing upd_ Updat es som et hing chk_

34

Term Fly Presents

http://www.flyheart.com

Validat es som et hing

Ex a m ple The following t able shows som e bad nam es for procedures and funct ions: Name

PROCEDURE total_salary

What ' s Wr ong?

What is t he procedure doing wit h t ot al salary? Displaying it ? Calculat ing it ?

Bet t er Name

display_total_salary

Well, of course, you're calculat ing FUNCTION total_salary calculate_total_salary t he t ot al salary—and ret urning it as w ell. FUNCTION get_total_salary

What else does a funct ion do but get and ret urn t hings? Use of t he total_salary get_ prefix is unnecessary; t he funct ion usage in code m akes t his clear.

Be n e fit s The m ore accurat ely a nam e reflect s t he purpose and usage of a program , t he easier it is t o underst and code t hat uses t hat program .

Ch a lle n ge s Enum erat e t he kinds of verb and noun phrases you m ight use repeat edly, and st andardize a set of prefixes for t hem .

STYL- 0 6 : Self- docu m en t u sin g block a n d loop la bels.

While PL/ SQL labels ( ident ifiers wit hin double angle bracket s, such as ) are m ost oft en associat ed wit h GOTOs and are t herefore disdained, t hey can be a big help in im proving t he readabilit y of code. Use a label direct ly in front of loops and nest ed anonym ous blocks: •

To nam e t hat port ion of code and t hereby self- docum ent what it 's doing



So y ou can repeat t hat nam e wit h t he END st at em ent of t hat block or loop

This recom m endat ion is especially im port ant w hen you hav e m ult iple nest ings of loops ( and possibly inconsist ent indent at ion) , as in t he following: LOOP

35

WHILE LOOP

END LOOP; END LOOP;

Ex a m ple I use labels for a block and t wo nest ed loops, and t hen apply t hem in t he appropriat e END st at em ent s. I can now easily see which loop and block is ending, no m at t er how badly m y code is indent ed! CREATE OR REPLACE PROCEDURE display_book_usage IS BEGIN

DECLARE CURSOR yearly_analysis_cur IS SELECT ...; CURSOR monthly_analysis_cur IS SELECT ...; BEGIN

FOR book_rec IN yearly_analysis_cur (2000) LOOP

FOR month_rec IN monthly_analysis_cur ( yearly_analysis_cur%rowcount) LOOP ... lots of month-related code ... END LOOP monthly_analysis; ... lots of year-related code ... END LOOP yearly_analysis; END best_seller_review; END display_book_usage;

Be n e fit s I f y ou use labels, it 's m uch easier t o read y our code, especially if it cont ains loops and nest ed blocks t hat have long bodies ( i.e., t he loop st art s on page 2 and ends on page 7, wit h t hree ot her loops inside t hat out er loop) .

STYL- 0 7 : Ex press com plex ex pr e ssion s u n a m bigu ou sly u sin g pa r en t h e se s.

The rules of operat or precedence follow t he com m only accept ed precedence of algebraic operat ors. The st rong t yping approach of PL/ SQL, [ 1] com bined wit h t he com m on precedence rules, m ak e m any parent heses unnecessary. When an uncom m on com binat ion of operat ors occurs, howev er, it m ay be helpful t o add parent heses ev en when t he precedence rules apply.

36

Term Fly Presents

http://www.flyheart.com

[ 1]

I n a st rongly t yped program m ing language, you m ust declare each t ype of dat a st ruct ure before you can work wit h it . And when you declare it , you specify it s t ype and, opt ionally, an init ial or default value. Cert ain operat ions are allowed only wit h cert ain t ypes.

The rules of evaluat ion do specify left - t o- right evaluat ion for operat ors t hat have t he sam e precedence lev el. How ev er, t his is t he m ost com m only ov erlook ed rule of evaluat ion when checking expressions for correct ness. Many developers apply a consist ent rule for im prov ed readabilit y in t his area: always use parent heses around ev ery Boolean expression, including I F, ELSI F, and WHI LE st at em ent s, as w ell as v ariable assignm ent s, regardless of t he sim plicit y of t he expressions. So, rat her t han: IF cust_rec.min_balance < 1000 THEN ... you inst ead writ e: IF ( cust_rec.min_balance < 1000 ) THEN ...

Ex a m ple You m ight not want a st andard t hat requires y ou t o always use parent heses, but in som e sit uat ions, parent heses are all but required for readabilit y. Consider t he following expression: 5 + Y**3 MOD 10 The PL/ SQL com piler will not be t he least bit confused by t his st at em ent ; it will apply it s unam biguous rules and com e up wit h an answer. Dev elopers, how ev er, m ay not have such an easy t im e of it . You are bet t er off writ ing t hat sam e line of code as follows: 5 + ((Y ** 3) MOD 10)

Be n e fit s Ev eryone, including t he aut hor of t he code, can m ore easily underst and t he logic and int ent ( which is crucial for m aint enance) of com plex expressions.

STYL- 0 8 : Use ve r t ica l code align m en t t o e m ph asize ve r t ica l r ela t ion sh ips.

A com m on code form at t ing t echnique is vert ical alignm ent . Here is an exam ple in a SQL WHERE clause: WHERE AND AND AND

COM.company_id COM.company_type_cd TYP.company_type_cd COM.region_cd

= = = =

SAL.company_id TYP.company_type_cd CFG.company_type_cd REG.region_cd

37

AND REG.status

= RST.status;

You should use v ert ical alignm ent only when t he elem ent s t hat are lined up vert ically have a relat ionship wit h each ot her t hat y ou want t o express. I n t he WHERE clause shown here, however, t here is no relat ionship bet ween t he right sides of t he various expressions. The relat ionship is bet ween t he left and right sides of each individual expression. This is, t herefore, a m isuse of vert ical alignm ent .

Ex a m ple Dev elopers oft en ( and j ust ifiably) use vert ical alignm ent wit h program param et er list s, as in: PROCEDURE maximize_profits ( advertising_budget IN bribery_budget IN OUT merge_and_purge_on IN obscene_bonus OUT

NUMBER, NUMBER, DATE := SYSDATE, NUMBER);

Vert ical alignm ent allows y ou t o easily see t he different param et er m odes and dat at ypes. Vert ical alignm ent is also handy when declaring m any const ant s, as in: CREATE OR REPLACE PACKAGE genAPI IS c_table CONSTANT CHAR(5) c_column CONSTANT CHAR(6) c_genpky CONSTANT CHAR(6) c_genpkyonly CONSTANT CHAR(10) c_sequence CONSTANT CHAR(7) c_pkygenproc CONSTANT CHAR(10) c_pkygenfunc CONSTANT CHAR(10) c_usingxmn CONSTANT CHAR(8) c_fromod2k CONSTANT CHAR(8)

:= := := := := := := := :=

'TABLE'; 'COLUMN'; 'GENPKY'; 'GENPKYONLY'; 'SEQNAME'; 'PKYGENPROC'; 'PKYGENFUNC'; 'USINGXMN'; 'FROMOD2K';

I n t his case, I want t o be able t o scan t he list of values t o m ak e sure t hey are unique. I can also easily com pare lengt hs of st rings wit h t he CHAR declarat ions, avoiding nuisance VALUE_ERROR ex cept ions on init ializat ion. Here are som e ot her code elem ent s for which vert ical alignm ent adds v alue: •

CREATE TABLE st at em ent s t hat define all t he individual colum ns.



Record TYPE declarat ions ( t hey have roughly t he sam e st ruct ure as a CREATE TABLE st at em ent ) .



Series of assignm ent s t o fields of records and ot her m ult ipart dat a st ruct ures.

Be n e fit s

38

Term Fly Presents

http://www.flyheart.com

Careful and appropriat e use of v ert ical alignm ent enhances readabilit y. Used inappropriat ely, how ever, vert ical alignm ent act ually m akes it harder t o see what is really going on in your code.

Ch a lle n ge s Vert ical alignm ent is a " high m aint enance" form at . Add a new, long variable nam e, and you find yourself reform at t ing 20 ot her lines of code t o m at ch. An aut om at ic form at t er com es in very handy when y ou decide t o form at v ert ically.

STYL- 0 9 : Com m e n t t er sely w it h va lu e - a dde d in form a t ion .

The best way t o explain what your code is doing is t o let t hat code speak for it self. You can t ake advant age of m any self- docum ent at ion t echniques, including: •

Define variables and call program s ( local m odules, in part icular; see [ MOD- 04: Use nam ed not at ion t o clarify, self- docum ent , and sim plify m odule calls.] ) t o give nam es t o and hide com plex expressions.



Use t he language const ruct t hat best reflect s t he code y ou are writ ing ( declare CONSTANTS w hen values don't change, choose t he right kind of loop for y our logic, et c.) .

Whenev er y ou find yourself adding a com m ent t o y our code, first consider whet her it is possible t o m odify t he code it self t o express your com m ent . Good reasons t o add com m ent s include: •

Program headers ( see [ STYL- 03: St andardize m odule and program headers. ] )



Explanat ions of workarounds, pat ches, operat ing- syst em dependencies, and ot her " except ional" circum st ances



Com plex or opaque logic

Ex a m ple Let 's follow a t rail of unnecessarily com m ent ed code t o self- docum ent ing code. I st art wit h: /* If the first field of the properties record is N... */ IF properties_flag.field1 = 'N' Yikes! My line of code w as incom prehensible and m y com m ent sim ply repeat ed t he code using t he English language, rat her t han PL/ SQL. No added value, no real assist ance, yet not at all uncom m on. The least I can do is use t he com m ent t o " t ranslat e" from com put er- t alk t o business requirem ent :

39

/* If the customer is not eligible for a discount... */ IF properties_flag.field1 = 'N' That 's bet t er, but I hav e creat ed a redundancy : if m y requirem ent ever changes, I have t o change t he com m ent and t he code. Why not change t he nam es of m y variables and lit erals so t hat t he code explains it self? IF customer_flag.discount = constants.ineligible Much bet t er! Now I no longer need a com m ent . My rem aining concern wit h t his line of code is t hat it " exposes" a business rule; it shows how ( at t his m om ent in t im e) I det erm ine whet her a cust om er is eligible for a discount . Business rules are not orious for changing over t im e—and for being referenced in m ult iple places t hroughout m y applicat ion. So m y best bet is t o hide t he rule behind a self- docum ent ing funct ion call: IF NOT customer_rules.eligible_for_discount (customer_id)

Be n e fit s By em phasizing reliance on code and not com m ent s t o explain, your program becom es m ore concise and m ore readable. When business requirem ent s change, y ou don't have t o change t he code and t he com m ent t hat explained t he code. The business rule is likely t o be reused in m any ot her places in your applicat ion ( see [ MOD- 01: Encapsulat e and nam e business rules and form ulas behind funct ion headers.] ) .

Ch a lle n ge s I t can be difficult t o recognize form ulas and business rules ( especially when y ou have been ask ed t o m aint ain or m odify som eone else's program s, or when y ou are new t o an applicat ion) . Once y ou recognize an exposed form ula, you have t o be careful about ext ract ing it from t he code and replacing it wit h a variable or program call.

STYL- 1 0 : Adopt m e an in gfu l n am in g con ven t ion s for sou r ce file s.

This is a " m et a- code" st yle issue. You should define a st andard for t he way y ou nam e t he operat ing syst em files t hat cont ain your source code ( som e organizat ions now st ore and edit source code ent irely in t he dat abase, but t hey are st ill in t he m inorit y) . These files can cont ain m any different kinds of " code" : •

40

DDL definit ions of dat a st ruct ures ( t ables, indexes, GRANT st at em ent s, et c.)

Term Fly Presents

http://www.flyheart.com



SQL* Plus script s t hat cont ain a variet y of anony m ous block s and st andalone SQL st at em ent s, as w ell as SQL* Plus form at t ing / cont rol com m ands



PL/ SQL program definit ions

You need t o be careful of how y ou organize t he code in your files. Ot herwise, you will end up wit h a plet hora of files in a m ish- m ash of subdirect ories. Your dev elopm ent t eam will have a hard t im e figuring out where anyt hing is, and what all t hose files are supposed t o do. You should also be deliberat e in how y ou nam e t hose files, including t heir ext ensions. There is a st rong t endency in t he Oracle world t o use t he .sql ext ension for all files. Why? Because .sql is t he default ext ension of SQL* Plus: if you don't specify an ext ension, t hat t ool aut om at ically looks for a .sql file. Laziness, however, is a poor excuse for a nam ing convent ion; by relying on a single ext ension, you forego valuable " real est at e" in t hat filenam e. Here are som e recom m endat ions for file- usage conv ent ions: •

Use separat e files for each dist inct program or package. Don't j um ble a bunch of st uff t oget her in a single file. I n part icular, put your package specificat ion in a different file from t he package body. That way, you can recom pile t he body wit hout recom piling t he specificat ion ( t he lat t er act ion causes all dependent program s t o be m ark ed invalid) .



Use filenam es t hat accurat ely describe t he cont ent s of t he file. I f y our file cont ains t he definit ion of a procedure, use t he nam e of t he procedure as t he filenam e.



Set a st andard for file ext ensions t hat indicat es t he t ype of code inside t he file ( as shown in Exam ples) .

Ex a m ple s Here are som e suggest ions for st andard file ext ensions: Cont ent s of Fi l e

Ext ensi on

Package specificat ion

.pks

Package body Package specificat ion and body

.pkb [ 2]

.pkg

Procedure

.pro ( or .sp for st ored procedure)

Funct ion

.fun ( or .sf for st ored funct ion)

Creat e t able script ( s)

.t ab or .ddl

Synonym creat ion st at em ent s

.syn

I ndex definit ions

.ind

Const raint definit ions

.con

Test script

.t st

41

[ 2]

This m akes sense t o do only for sm all, self- cont ained packages t hat don't reference ot her program unit s.

Be n e fit s The nam e of t he file ( including it s ext ension) will " t ell a st ory" about it s cont ent s, such as t he t ype of code, t he nam e of t he program , et c. This increased t ransparency m akes it easier for all dev elopers t o work wit h and m aint ain t he code.

Ch a pt e r 3 . Va r ia ble s a n d D a t a St r u ct u r e s PL/ SQL is a st rongly t yped language. This m eans t hat before you can w ork wit h any kind of dat a st ruct ure, y ou m ust first declare it . And when you declare it , y ou specify it s t ype and, opt ionally, an init ial or default value. All declarat ions of t hese variables m ust be m ade in t he declarat ion sect ion of your anonym ous block, procedure, funct ion, or package.

3 .1 D e cla rin g Va r ia bles an d D at a St r u ct u re s Use t he best pract ices described in t his sect ion when y ou declare y our dat a st ruct ures.

D AT- 0 1 : M a t ch da t a t ype s t o com pu t a t ion a l u sa ge .

Gee, t hat 's a general best pract ice, isn't it ? Of course you should do t hings t he right way. So t he quest ion becom es: what dat at ype is t he correct dat at ype? The following t able offers som e concret e advice on pot ent ial issues y ou m ight encount er: Dat at ype

I ssues and Recommendat i ons

NUMBER

I f y ou don't specify a precision, as in NUMBER( 12,2) , Oracle support s up t o 38 digit s of precision. I f y ou don't need t his precision, you're w ast ing m em ory .

CHAR

This is a fixed- lengt h charact er st ring and is m ost ly available for com pat ibilit y purposes wit h code w rit t en in earlier versions of Oracle. The values assigned t o CHAR variables are right - padded wit h spaces, which can result in unexpect ed behavior. Avoid CHAR unless it 's specifically needed.

This variat ion on t he VARCHAR2 variable- lengt h declarat ion is provided VARCHAR by Oracle for com pat ibilit y purposes. Eschew VARCHAR in favor of VARCHAR2. The great est challenge you will run int o wit h VARCHAR2 is t o avoid t he t endency t o hard- code a m axim um lengt h, as in VARCHAR2( 30) . Use VARCHAR2 % TYPE and SUBTYPE inst ead, as described lat er in t his chapt er. Also, prior t o Oracle8, VARCHAR2 variables are t reat ed like variable-

42

Term Fly Presents

http://www.flyheart.com

lengt h st rings for purposes of m anipulat ion and evaluat ion, but Oracle does allocat e t he full am ount of m em ory upon declarat ion. I f y ou declare a variable of VARCHAR2( 2000) , t hen Oracle allocat es 2000 byt es, even if you use only t hree. I NTEGER

I f y our int eger values fall wit hin t he range of –2 31 + 1 .. 2 31 –1 ( a.k.a. – 2147483647 .. 2147483647) , you should declare y our variables as PLS_I NTEGER. This is t he m ost efficient form at for int eger m anipulat ion.

D AT- 0 2 : An ch or va r ia ble s t o dat a ba se da t a t ype s u sin g % TYPE an d % ROW TYPE.

When y ou declare a variable using % TYPE or % ROWTYPE, y ou " anchor" t he t ype of t hat dat a t o anot her, previously defined elem ent . I f y our program variable has t he sam e dat at ype as ( and, as is usually t he case, is act ing as a cont ainer for) a colum n in a t able or view, use % TYPE t o define it from t hat colum n. I f y our record has t he sam e st ruct ure as a row in a t able or view, use % ROWTYPE t o define it from t hat t able.

Ex a m ple Here is an exam ple of a " hard- coded" declarat ion: DECLARE l_title VARCHAR2(100); I pret t y clearly want t o put a book t it le int o t his variable. And I check ed t he dat a dict ionary and found t hat t he t it le colum n is defined VARCHAR2( 60) . So t hat declarat ion seem ed pret t y safe. Unfort unat ely, t wo m ont hs lat er, t he DBA expanded t he colum n size t o VARCHAR2( 200) —and a m ont h aft er t hat , m y code st art ed get t ing VALUE_ERROR ex cept ions. Bad news! A m uch bet t er approach is shown in t he following declarat ion sect ion: DECLARE l_title book.title%TYPE;

Be n e fit s Your code aut om at ically adapt s t o underlying changes in dat a st ruct ures. Whenev er t he dat a st ruct ure against which a declarat ion is anchored changes, t he program cont aining t he anchoring is m arked I NVALI D. Upon recom pilat ion, it aut om at ically uses t he new form of t he dat a st ruct ure. These declarat ions are " self- docum ent ing" ; a variable declarat ion t ells anyone who reads it what kind of dat a t his variable is supposed t o hold.

Ch a lle n ge s

43

You need t o know t he nam es of colum ns in t ables. The USER_TAB_COLUMNS dat a dict ionary view cont ains t his inform at ion; in SQL* Plus you can use t he DESCRI BE com m and t o find t his inform at ion. A person reviewing anchored declarat ions doesn't necessarily know t he t ype of dat a; he m ust look up t he definit ion of t hat colum n or t able in t he dat a dict ionary.

D AT- 0 3 : Use SUBTYPE t o st an da r dize a pplica t ion - specific da t a t ype s.

The SUBTYPE st at em ent allows you t o creat e " aliases" for exist ing t ypes of inform at ion, in effect creat ing your own specially nam ed dat at ypes. Use SUBTYPE when y ou want t o st andardize on a set of nam ed dat at ypes t hat aren't anchorable back t o t he dat abase.

Ex a m ple Suppose t hat m y book t able has a page_count colum n, defined as I NTEGER( 4) . I t hen writ e a program t hat calculat es t he t ot al num ber of pages I hav e writ t en across all books. I f I declare a variable t o hold t his value as: DECLARE l_total book.page_count%TYPE; I could run int o problem s. My t ot al count m ight exceed four digit s. I n fact , I m ay w ell not have any dat abase colum n I can use for anchoring in t his case. Yet , I st ill should not hard- code a declarat ion like t his: DECLARE l_total INTEGER(10); I nst ead, I will creat e a package and define a v ariable t here t hat is big enough t o hold t he t ot al count : CREATE OR REPLACE PACKAGE book_data IS SUBTYPE total_count_t IS INTEGER (10); and t hen use t hat in m y declarat ion sect ion: DECLARE l_total book_data.total_count_t;

I f you use Oracle7 or Oracle8, t he SUBTYPE st at em ent j ust shown will fail; Oracle doesn't recognize const rained SUBTYPEs unt il Oracle8i. I n t his case, you can do t he following:

44

Term Fly Presents

http://www.flyheart.com

CREATE OR REPLACE PACKAGE book_data IS total_count INTEGER(10); SUBTYPE total_count_t IS total_count; Be n e fit s You st andardize or " norm alize" all dat at ype definit ions. I n ot her words, any definit ion appears only once in your applicat ion. Ev eryt hing is anchored from t hat .

Ch a lle n ge s You will eit her need t o t ake t he t im e t o build a single " dat at ypes" pack age cont aining t hese definit ions, or need t o rem em ber t o place your st andard definit ions in t he appropriat e packages in your applicat ion.

D AT- 0 4 : D o n ot h a r d- code VARCH AR2 len gt h s.

Sure, in general, you shouldn't hard- code your dat at ypes; inst ead, you should rely on anchoring ( see [ DAT- 02: Anchor variables t o dat abase dat at ypes using % TYPE and % ROWTYPE. ] ) and SUBTYPEs ( see [ DAT- 03: Use SUBTYPE t o st andardize applicat ion- specific dat at ypes.] ) . This best pract ice is a special- case em phasis of t hose ot her best pract ices. Don't hard- code VARCHAR2 lengt hs, like: DECLARE -- Gee, should be big enough big_string VARCHAR2(2000); Such a declarat ion m ay seem like a big- enough st ring, but it 's also a t icking t im e bom b in your applicat ion. Eit her % TYPE back t o a dat abase colum n, or define SUBTYPEs in a package specificat ion t hat give nam es t o st andard VARCHAR2 usages.

Ex a m ple The basic problem wit h hard- coding a VARCHAR2 lengt h is t hat st uff changes. Consider t he m axim um lengt h possible for a VARCHAR2 colum n in t he dat abase. I t was 2000 up t hrough Oracle8, and t hen it expanded t o 4000 in Oracle8 i. The best way t o handle t his sit uat ion is t o creat e a special t ype: CREATE OR REPLACE app_types IS SUBTYPE dbmax_vc2 IS VARCHAR2(2000); Then, when y ou upgrade t o Oracle8i, you sim ply change t he definit ion of t he SUBTYPE. All usages of t hat t ype st ay t he sam e.

45

Be n e fit s Your code is less likely t o raise VALUE_ERROR except ions ov er t im e.

D AT- 0 5 : Use CON STAN T de cla r at ion s for va r ia bles w h ose va lu es do n ot ch a n ge .

I f y ou know t hat t he value of y our variable isn't going t o change, t ake t he t im e, and m ake t he effort , t o declare it as a const ant .

Ex a m ple The following script runs during business hours ( 9 A.M. t o 6 P.M.) and is used t o analyze book check out s in t he MYSTERY cat egory: DECLARE c_date CONSTANT DATE := TRUNC (SYSDATE); c_category CONSTANT book.category%TYPE := 'MYSTERY'; BEGIN checkouts.analyze (c_date, c_category); ... -- 75 lines later FOR rec IN ( SELECT * FROM book WHERE category = c_category) LOOP ... END LOOP; Aft er writ ing, t est ing, and deploying t his script , I can be confident t hat a dev eloper won't , six m ont hs from now, m ake a change t o t he c_cat egory st ruct ure bet ween t he call t o checkout s.analyze and t he FOR loop.

Be n e fit s Your code self- docum ent s t he usage of t his dat a st ruct ure: it should not and cannot change. A dev eloper can't lat er m ist akenly change t he dat a st ruct ure's value.

D AT- 0 6 : Pe r form com plex va r ia ble in it ia liza t ion in t h e e x e cu t a ble se ct ion .

The except ion sect ion of a block can t rap only errors raised in t he execut able sect ion of t hat block. So if t he code you run t o assign a default value t o a variable fails in t he

46

Term Fly Presents

http://www.flyheart.com

declarat ion sect ion, t hat error is propagat ed unhandled out t o t he enclosing program . I t 's difficult t o debug t hese problem s, so, you m ust eit her: •

Be sure t hat init ializat ion logic doesn't raise an error.



Perform your init ializat ion at t he beginning of t he execut able sect ion, preferably in a separat e " init " program .

Ex a m ple Here's som e dangerous code, since it isn't at all apparent what t hese funct ions do and what t hey pass back: CREATE OR REPLACE PROCEDURE find_bestsellers IS l_last_title book.title%TYPE := last_search (SYSDATE); l_min_count INTEGER(3) := bestseller.limits (bestseller.low); BEGIN And here is a m uch safer approach: CREATE OR REPLACE PROCEDURE find_bestsellers IS l_last_title book.title%TYPE; l_min_count INTEGER(3); PROCEDURE init IS BEGIN l_last_title:= last_search (SYSDATE); l_min_count:= bestseller.limits (bestseller.low); EXCEPTION -- Trap and handle all errors -- inside the program END; BEGIN init;

Be n e fit s Your program s will behave m ore reliably; if an error does occur as you init ialize variables, you can t rap t he error locally and decide how you want t o handle t he sit uat ion.

3 .2 Usin g Va ria ble s an d D a t a St r u ct u r es Use t he best pract ices described in t his sect ion when y ou reference t he dat a st ruct ures you have declared in your program s.

47

D AT- 0 7 : Re pla ce com ple x ex pre ssion s w it h Boole an va ria ble s an d fu n ct ion s.

A Boolean expression evaluat es t o one of t hree values: TRUE, FALSE, or NULL. You can use Boolean variables and funct ions t o hide com plex expressions; t he result is code t hat is virt ually as readable as " st raight " English—or what ev er language you use t o com m unicat e wit h ot her hum an beings.

Ex a m ple IF total_sal BETWEEN 10000 AND 50000 AND emp_status (emp_rec.empno) = 'N' AND (MONTHS_BETWEEN (emp_rec.hiredate, SYSDATE) > 10) THEN give_raise (emp_rec.empno); END IF; Wow , t hat 's hard t o underst and! I t 'd be m uch easier if t he code look ed like t his: IF eligible_for_raise (totsal, emp_rec) THEN give_raise (emp_rec.empno); END IF; And ev en if you don't w ant t o ( or need t o) bot her wit h creat ing a separat e funct ion, you can st ill m ove t he com plexit y t o a local variable, as in: DECLARE eligible_for_raise BOOLEAN := total_sal BETWEEN 10000 AND 50000 AND emp_status (emp_rec.empno) = 'N' AND (MONTHS_BETWEEN (emp_rec.hiredate, SYSDATE) > 10); BEGIN IF eligible_for_raise THEN give_raise (emp_rec.empno); END IF;

Be n e fit s I t will be m uch easier for any one t o read your code; you can lit erally read it . I f you t hen need t o underst and how t he Boolean expression is com put ed, you can look " under t he cov ers." This is a t echnique t hat can be applied ( wit h care) t o exist ing " spaghet t i code." As you go int o a program t o fix or enhance it , look for opport unit ies t o sim plify and short en execut able sect ions by shift ing com plexit y t o local variables and program s.

48

Term Fly Presents

http://www.flyheart.com

Ch a lle n ge s Before y ou m odify exist ing code, m ake sure you have solid unit t est script s in place so you can quickly verify t hat your changes hav en't int roduced bugs int o t he program .

Re sou r ce s ht t p: / / oracle.oreilly.com / ut plsql: ut PLSQL, a unit t est fram ework for PL/ SQL dev elopers.

D AT- 0 8 : D o n ot ove r loa d da t a st r u ct u r e u sa ge .

This is j ust one ent ry of a m ore general cat egory: " don't be lazy! " When you declare a variable, you should give it a nam e t hat accurat ely reflect s it s purpose in a program . I f y ou t hen use t hat variable in m ore t han one way ( " recycling" ) , y ou creat e confusion and, v ery possibly, int roduce bugs. The solut ion is t o declare and m anipulat e separat e dat a st ruct ures for each dist inct requirem ent . And here's a general piece of advice: reliance on a " t im e- saver" short - cut should raise a red flag. You're probably doing ( or av oiding) som et hing now for which you will pay lat er.

Ex a m ple I hav e a few different needs for an int eger value, so I will declare one and use it t hroughout : DECLARE ... other declarations intval INTEGER; BEGIN intval := list_of_books.COUNT; IF intval > 0 THEN intval := list_of_books(list_of_books.FIRST).page_count; analyze_book (intval); END IF; I t 's pret t y m uch im possible t o look at any usage of int val and underst and what is going on. You hav e t o go back t o t he m ost recent assignm ent . Com pare t hat t o t he following: DECLARE

49

... other declarations l_book_count INTEGER; l_page_count INTEGER; BEGIN l_book_count := list_of_books.COUNT; IF l_book_count > 0 THEN l_page_count:= list_of_books(list_of_books.FIRST).page_count; analyze_book (l_page_count); END IF;

Be n e fit s I t 's a whole lot easier t o underst and what your code does. You can m ake a change t o one variable's usage wit hout worrying about it s ripple effect t o ot her areas of your code.

D AT- 0 9 : Rem ove u n u se d va r ia ble s a n d code.

You should go t hrough your program s and rem ov e any part of y our code t hat is no longer used. This is a relat ively st raight forward process for variables and nam ed const ant s. Sim ply ex ecut e searches for a variable's nam e in t hat variable's scope. I f you find t hat t he only place it appears is in it s declarat ion, delet e t he declarat ion and, by doing so, delet e one m ore pot ent ial quest ion m ark from your code. There's never a bet t er t im e t o review all t he st eps y ou t ook, and t o underst and t he reasons y ou t ook t hem , t han im m ediat ely upon com plet ion of your program . I f y ou wait , you will find it part icularly difficult t o rem em ber t hose part s of t he program t hat were needed at one point , but were rendered unnecessary in t he end. " Dead zones" in your code becom e sources of deep insecurit y for m aint enance program m ers. You should also leverage t ools t hat will perform t his analysis for you, such as Rev ealNet 's PL/ Form at t er.

Ex a m ple The following block of code has sev eral dead zones t hat could cause a v ariet y of problem s. Can y ou find t hem all? CREATE OR REPLACE PROCEDURE weekly_check ( isbn_in IN book.isbn%TYPE, author_in IN VARCHAR2) IS l_count PLS_INTEGER; l_counter PLS_INTEGER; l_available BOOLEAN;

50

Term Fly Presents

http://www.flyheart.com

l_new_location PLS_INTEGER := 1056; l_published_date DATE := SYSDATE; BEGIN l_published_date := te_book.published_date (isbn_in); IF ADD_MONTHS (SYSDATE, -60) > l_published_date THEN review_usage; ELSIF ADD_MONTHS (SYSDATE, -24) > l_published_date THEN check_availability (isbn_in, l_available, l_count); IF

l_available AND /* Turn off due to Req A12.6 */ FALSE THEN transfer_book (isbn_in, l_count - 1, l_new_location); END IF; -- Check for reserves -- reserve_pkg.analyze_requests (isbn_in); END IF; END; Here's what I found: •

The aut hor_in param et er is declared but nev er used. I t doesn't ev en have a default value, so you have t o pass in an ignored value.



l_count er is declared but not used.



l_published_dat e is assigned a default value of SYSDATE, which is im m ediat ely overridden by t he call t o t e_book.published_dat e.



The call t o t ransfer_book has been t urned off wit h t he addit ion of AND FALSE.



The call t o reserv e_pkg.analyze_request s has been com m ent ed out .

Be n e fit s I t 's m uch easier t o m aint ain, debug and enhance code t hat doesn't hav e " dead zones."

Ch a lle n ge s There are som et im es valid reasons for keeping dead code in place. You m ay want t o t urn off code t em porarily. Also, you m ay need t o com m ent out som e logic but st ill show t hat t his act ion was done and why . I n such cases, m ak e sure t hat you include t he necessary docum ent at ion in t he code. Ev en bet t er, use problem t racking or bug report ing soft ware t o k eep a com prehensive hist ory of any changes m ade t o code.

D AT- 1 0 : Cle an u p da t a st ru ct u r e s w h en you r pr ogr a m t e rm in a t e s ( su cce ssfu lly or w it h an

51

e r r or ) .

PL/ SQL does an awful lot of cleanup for y ou, but t here are m any scenarios in which it 's absolut ely crucial for you t o t ak e y our own cleanup act ions. The best way t o do t his is t o st andardize on a local cleanup procedure t hat is t o be included in each program . Call t his program bot h at t he end of t he ex ecut able sect ion and in each except ion handler WHEN clause.

Ex a m ple The following program m anipulat es a packaged cursor, declares a DBMS_SQL cursor, and writ es inform at ion t o a file: CREATE OR REPLACE PROCEDURE busy_busy IS fileid UTL_FILE.file_type; dyncur PLS_INTEGER; BEGIN dyncur := DBMS_SQL.open_cursor; OPEN book_pkg.all_books_by ('FEUERSTEIN'); fileid := UTL_FILE.fopen ( '/apps/library', 'bestsellers.txt', 'R'); ... EXCEPTION WHEN NO_DATA_FOUND THEN err.log; RAISE; END; I f I 'm not careful, I can end up wit h an unclosable dynam ic SQL cursor, a st ill- open packaged cursor t hat causes an " ORA- 06511: PL/ SQL: cursor already open" error, and a file t hat can't be closed wit hout a call t o UTL_FI LE.FCLOSE_ALL or a disconnect . Here's a m uch bet t er approach: CREATE OR REPLACE PROCEDURE busy_busy IS fileid UTL_FILE.file_type; dyncur PLS_INTEGER; PROCEDURE cleanup IS BEGIN IF book_pkg.all_books_by%ISOPEN THEN CLOSE book_pkg.all_books_by; END IF; DBMS_SQL.CLOSE_CURSOR (dyncur); UTL_FILE.FCLOSE (fileid); END;

52

Term Fly Presents

http://www.flyheart.com

BEGIN dyncur := DBMS_SQL.open_cursor; OPEN book_pkg.all_books_by ('FEUERSTEIN'); fileid := UTL_FILE.fopen ( '/apps/library', 'bestsellers.txt', 'R'); ... cleanup; EXCEPTION WHEN NO_DATA_FOUND THEN err.log; cleanup; RAISE; END;

Be n e fit s Your program s are less likely t o have m em ory leaks ( open cursors) and t o cause problem s in ot her program s by leaving dat a st ruct ures in an uncert ain st at e. By defining a st andard cleanup procedure, fut ure developers can easily add new cleanup operat ions in one place and be cert ain t hey will be run at all exit point s.

Ch a lle n ge s Set up a st andard form at for y our program s, including init ializat ion and cleanup procedures. I t 's t hen a challenge t o m ak e sure dev elopers use t hat t em plat e.

D AT- 1 1 : Bew a re of a n d a void im plicit da t a t ype con ve r sion s.

Som et im es, PL/ SQL m akes life j ust t oo darn easy for us dev elopers. I t will, for exam ple, allow you t o w rit e and ex ecut e code like t his: DECLARE my_birthdate DATE := '09-SEP-58'; I n t his case, t he runt im e engine aut om at ically convert s t he st ring t o a dat e, using t he default form at m ask . You should, howev er, avoid im plicit conversions in your code. There are at least t w o big problem s wit h relying on PL/ SQL t o conv ert dat a on y our behalf: •

Conversion behavior can be non- int uit ive. PL/ SQL m ay conv ert dat a in ways t hat you don't expect , result ing in problem s, especially wit hin SQL st at em ent s.



Conversion rules aren't under t he cont rol of t he dev eloper. These rules can change wit h an upgrade t o a new version of Oracle or by changing RDBMSwide param et ers, such as NLS_DATE_FORMAT.

53

You can conv ert explicit ly using any of t he following built - in funct ions: TO_DATE, TO_CHAR, TO_NUMBER, and CAST.

Ex a m ple The declarat ion of t he m y_birt hdat e variable is a st erling exam ple of t he drawback s of im plicit conversion. DECLARE my_birthdate DATE := '09-SEP-58'; This code raises an error if t he default form at m ask for t he inst ance is anyt hing but DD- MON- YY or DD- MON- RR. That form at is set ( and changed) in t he param et er init ializat ion file—well out of t he cont rol of m ost PL/ SQL developers. I t can also be m odified for a specific session. A m uch bet t er approach is: DECLARE my_birthdate DATE := TO_DATE ('09-SEP-58', 'DD-MON-RR');

Be n e fit s The behavior of your code is m ore consist ent and predict able, since y ou aren't relying on som et hing ext ernal t o y our code. Explicit conversions, for exam ple, would have av oided t he vast m aj orit y of Y2K issues in PL/ SQL code.

Re sou r ce s bool.pkg : A package t o conv ert bet w een Booleans and st rings, since Oracle doesn't offer any built - in ut ilit ies t o do t his.

3 .3 D e cla rin g an d Usin g Pa ck a ge Va ria ble s Use t he best pract ices described in t his sect ion when y ou are declaring variables for use in packages.

D AT- 1 2 : Pa ck a ge a pplica t ion - n am ed lit e r al con st a n t s t oge t h e r.

Nev er place a hard- coded lit eral, such as " Y" or 150 in your code. I nst ead, creat e a package t o hold t hese v alues and publish a nam e t o be used in place of t he lit erals. You will probably find it best t o: •

54

Define const ant s t hat are referenced t hroughout your applicat ion in a single, cent ral package.

Term Fly Presents •

http://www.flyheart.com

Define const ant s t hat are m ore specific t o a single area of funct ionalit y wit hin t he package t hat encapsulat es t hat funct ionalit y.

Ex a m ple Here is a port ion of a general const ant s package: CREATE OR REPLACE PACKAGE constants IS -- Standard string representation of TRUE/FALSE tval CONSTANT CHAR(1) := 'T'; fval CONSTANT CHAR(1) := 'F'; -- Earliest valid date: 5 years past min_date CONSTANT DATE := ADD_MONTHS (SYSDATE, -5 * 12); And here is a pack age t hat cont ains const ant s specific t o one area of funct ionalit y: CREATE OR REPLACE PACKAGE nightly_transform IS c_max_weeks CONSTANT INTEGER := 54; c_active CONSTANT CHAR(1) := 'A'; c_inactive CONSTANT CHAR(1) := 'I'; c_english CONSTANT INTEGER := 1; c_usa CONSTANT INTEGER := 1; c_namerica CONSTANT VARCHAR2(2) := 'N'; END nightly_transform;

Be n e fit s You're less likely t o hard- code lit eral values in your program s, t hus im proving t he readabilit y and m aint ainabilit y of your code. Youv e est ablished a place t o go when a developer needs t o add anot her const ant t o hide a lit eral.

Ch a lle n ge s The ent ire developm ent t eam needs t o know about t he packages and use t he const ant s t hat hav e been defined for t hem . Be careful about t he values y ou assign t o your const ant s. Wit h cut - and- past e, it 's easy t o end up assigning a value t hat 's t oo long and raises t he " ORA- 06502: PL/ SQL: num eric or value error" at runt im e—when t he package is init ialized.

D AT- 1 3 : Cen t r a lize TYPE de fin it ion s in pa ck age spe cifica t ion s.

55

As y ou use m ore and m ore of t he PL/ SQL language feat ures, y ou will define m any TYPEs of t hings, including: •

SUBTYPEs t hat define applicat ion- specific dat at ypes



Collect ion TYPEs, such as list s of num bers, dat es, or records



Referenced cursor TYPEs, from which cursor variables are declared

Som e of t hese TYPEs can be used unchanged t hroughout y our applicat ion ( t here is only one way, for exam ple, t o declare an index - by t able of dat es) ; ot her t ypes are specific t o som e part of an applicat ion but are st andard wit hin t hat . I n eit her case, creat e a package t o hold t hese st andard TYPEs, so t hat t hey can be used in m ult iple program s.

Ex a m ple Here is a port ion of a package specificat ion t hat cont ains st andard TYPE st at em ent s for nest ed and index- by t ables: CREATE OR REPLACE PACKAGE colltype IS TYPE boolean_ntab IS TABLE OF BOOLEAN; TYPE boolean_ibtab IS TABLE OF BOOLEAN INDEX BY BINARY_INTEGER; TYPE date_ntab IS TABLE OF DATE; TYPE date_ibtab IS TABLE OF DATE INDEX BY BINARY_INTEGER; ... END colltype;

Be n e fit s Dev elopers w rit e t heir code m ore rapidly and wit h few er bugs by relying on predefined TYPEs. As y ou need t o m aint ain your TYPEs ( t hose based on applicat ion- specific elem ent s are, aft er all, very likely t o change) , y ou go t o one package and m ake t he change in one place.

Ch a lle n ge s Dev elopers m ust be disciplined enough t o seek out predefined TYPEs or t o add new TYPEs t o exist ing packages.

56

Term Fly Presents

http://www.flyheart.com

Re sou r ce s collt ype.pks : A package specificat ion of st andard collect ion TYPE definit ions.

D AT- 1 4 : Use pack a ge globals j u diciou sly an d on ly in pa ck a ge bodie s.

A global variable is a dat a st ruct ure t hat can be referenced out side t he scope or block in which it 's declared. I n t he following block, for exam ple, t he l_publish_dat e is global t o t he local display_book_info procedure: DECLARE l_publish_date DATE; ... PROCEDURE display_book_info IS BEGIN DBMS_OUTPUT.PUT_LINE (l_publish_date); END; Globals are dangerous and should be avoided, because t hey creat e hidden " dependencies" or side- effect s. A global doesn't have t o be passed t hrough t he param et er list , so it 's hard for you t o ev en know t hat a global is referenced in a program wit hout looking at t he im plem ent at ion. Globals are m ost oft en defined in packages. I f you declare a variable at t he package level ( not wit hin any specific program ) , t hat variable exist s and ret ains it s value for t he durat ion of your session. The general solut ion t o t his problem is t o pass t he global as a param et er in your procedure and funct ion; don't reference it direct ly wit hin t he program . Anot her general t echnique t o keep in m ind is t o declare variables, cursors, funct ions, and ot her obj ect s as " deeply" as possible ( i.e., in t he block nearest t o where, or wit hin which, t hat obj ect will be used) , in order t o reduce t he chance of unint ended use by ot her sect ions of t he code.

Reliance on global dat a st ruct ures can be a part icularly acut e pr oblem in Oracle Developer's Form sbuilder ( previously known as Oracle Form s) . Developers have hist orically relied on ( and overused) : GLOBAL dat a st ruct ures t o pass inform at ion bet ween form s. I n t he lat est versions of Oracle Developer, avoid : GLOBAL variables. I nst ead, build and share packages ( and variables declared wit hin t hose packages) am ong form s. Ex a m ple

57

Here is an exam ple of a funct ion wit h a hidden dependency on a global variable: CREATE OR REPLACE FUNCTION overdue_fine ( isbn_in IN book.isbn%TYPE) RETURN NUMBER IS l_days_overdue NUMBER; BEGIN l_days_overdue := overdue_pkg.days_overdue (isbn_in, SYSDATE); RETURN (l_days_overdue * overdue_pkg.g_daily_fine); END; The global is t he am ount of t he daily fine. I t 's buried inside t he funct ion. By writ ing t he funct ion t his way, t wo t hings happen: ( a) you lose flexibilit y t o pass in a different daily fine am ount , as m ay be required, and ( b) if t he daily fine has not been set wit hin t he ov erdue pack age, t he funct ion doesn't work properly. You can get rid of t he dependency by adding a param et er: CREATE OR REPLACE FUNCTION overdue_fine ( isbn_in IN book.isbn%TYPE, daily_fine_in IN NUMBER) RETURN NUMBER IS l_days_overdue NUMBER; BEGIN l_days_overdue := overdue_pkg.days_overdue (isbn_in, SYSDATE); RETURN (l_days_overdue * daily_fine_in); END;

Be n e fit s By reducing t he int erdependencies bet w een program s, y ou can m ore easily and confident ly m ake a change t o one wit hout worrying about t he ot hers being affect ed.

Ch a lle n ge s You m ay need t o revam p exist ing program s t o pull out global references and replace t hem wit h eit her param et ers or calls t o " get and set " program s t hat encapsulat e t he global dat a ( see [ DAT- 15: Expose package globals using " get and set " m odules. ] ) .

D AT- 1 5 : Ex pose pa ck a ge globals u sin g " ge t an d se t " m odu le s.

Dat a st ruct ures ( scalar variables, collect ions, cursors) declared in t he package specificat ion ( not wit hin any specific program ) are direct ly referenceable from any

58

Term Fly Presents

http://www.flyheart.com

program run from a session wit h EXECUTE aut horit y on t he package. This is always a bad idea and should be avoided. I nst ead, declare all package- level dat a in t he package body and provide " get and set " program s—a funct ion t o GET t he value and a procedure t o SET t he value —in t he package specificat ion. Dev elopers can t hen access t he dat a t hrough t hese program s, and aut om at ically follow what ev er rules y ou est ablish for m anipulat ing t hat dat a.

Ex a m ple I 've creat ed a package t o calculat e ov erdue fines. The fine is, by default , $.10 per day, but it can be changed according t o t his rule: t he fine can never be less t han $.05 or m ore t han $.25 per day. Here's m y first version: CREATE OR REPLACE PACKAGE overdue_pkg IS g_daily_fine NUMBER := .10; FUNCTION days_overdue (isbn_in IN book.isbn%TYPE) RETURN INTEGER; -- Relies on g_daily_fine for calculation FUNCTION fine (isbn_in IN book.isbn%TYPE) RETURN INTEGER; END overdue_pkg; You can easily see t he problem wit h t his package in t he following block: BEGIN overdue_pkg.g_daily_fine := .50; pl ('Your overdue fine is ' || overdue_pkg.fine (' 1-56592-375-8')); END; As y ou can see, I bypassed t he business rule and applied a daily fine of $.50 ! By " publishing" t he daily fine variable, I lost cont rol of m y dat a st ruct ure and t he abilit y t o enforce m y business rules. The following rewrit e of overdue_ pkg fixes t he problem ; for t he sake of t he t rees, I show only t he replacem ent of t he g_daily_fine variable wit h it s " get and set " program s: CREATE OR REPLACE PACKAGE overdue_pkg IS PROCEDURE set_daily_fine (fine_in IN NUMBER); PROCEDURE daily_fine RETURN NUMBER; and t he im plem ent at ion: CREATE OR REPLACE PACKAGE BODY overdue_pkg IS g_daily_fine NUMBER := .10;

59

PROCEDURE set_daily_fine (fine_in IN NUMBER) IS BEGIN g_daily_fine := GREATEST (LEAST (fine_in, .25), .05); END; FUNCTION daily_fine RETURN NUMBER IS BEGIN RETURN g_daily_fine; END; Now it 's im possible t o bypass t he business rule for t he daily fine.

You will be even bet t er off, of course, if you put your m axim um and m inim um fine inform at ion in a dat abase t able. Then you can use t he package init ializat ion sect ion t o load t hese lim it s int o package dat a st ruct ures. This way, if ( when) t he dat a point s change, you don't have t o change t he program it self, j ust som e rows and colum ns in a t able. Be n e fit s The only way t o change a value is t hrough t he set procedure. The values of your dat a st ruct ures are prot ect ed; business rules can be enforced wit hout except ion. You can t rack all accesses t o y our dat a st ruct ure—t hat is, y ou can put a " wat ch" on a variable. This is a debugging feat ure t hat isn't ev en support ed by Oracle's debugger API ( as of Oracle8i ) . By hiding t he dat a st ruct ure, you give yourself t he freedom t o change how t hat dat a is defined wit hout affect ing all accesses t o t he dat a. Package dat a can now be accessed from Oracle Dev eloper t ools, such as Form sbuilder. You m ay not , from " client - side" PL/ SQL ( i.e., code w rit t en in Oracle Dev eloper com ponent s) reference st ored package elem ent s unless t hey are procedures or funct ions.

Ch a lle n ge s You need t o w rit e get and set program s for your dat a st ruct ures ( see Resources for help in t his m at t er) . Review exist ing packages t o ident ify dat a st ruct ures defined in specificat ions—and t hen fix t hem by m oving t he st ruct ures t o t he bodies. You will have t o rewrit e som e exist ing program s t hat reference t hat dat a, but it will be wort h it .

60

Term Fly Presents

http://www.flyheart.com

Re sou r ce s 1. overdue.pkg : The overdue package. 2. PLVgen: The PLVgen package of PL/ Vision generat es " get and set " code for any scalar variable; t his way y ou w on't hav e t o writ e t he logic again and again. 3. p_and_l.pkg and wat ch.pkg : Dem onst rat ion of " wat ching" a variable.

Ch a pt e r 4 . Con t r ol St r u ct u r e s Oracle PL/ SQL offers a range of const ruct s t hat allow you t o cont rol t he flow of processing, including: • • •

For condit ional logic: t he I F st at em ent For loop processing: FOR, WHI LE, and sim ple loops For branching logic: t he GOTO st at em ent

These const ruct s are relat ively st raight forward in synt ax and usage. There rem ain, howev er, several best pract ices you should t ake int o account when you work wit h t hese kinds of st at em ent s.

4 .1 Con dit ion a l a n d Boole an Logic Follow t he best pract ices in t his sect ion when y ou are using PL/ SQL's I F st at em ent s.

CTL- 0 1 : Use ELSI F w it h m u t u a lly ex clu sive cla u se s.

When y ou need t o w rit e condit ional logic t hat has sev eral m ut ually exclusive clauses ( in ot her words, if one clause is TRUE, no ot her clause evaluat es t o TRUE) , use t he ELSI F const ruct : IF condA THEN ... ELSIF condB THEN ... ELSIF condN THEN ... ELSE ... END IF;

Ex a m ple At first glance, t he following procedure m akes sense, but on closer exam inat ion, it 's a m ess:

61

PROCEDURE process_lineitem (line_in IN INTEGER) IS BEGIN IF line_in = 1 THEN process_line1; END IF; IF line_in = 2 THEN process_line2; END IF; ... IF line_in = 2045 THEN process_line2045; END IF; END; Ev ery I F st at em ent is execut ed and each condit ion evaluat ed. You should rewrit e such logic as follows: PROCEDURE process_lineitem (line_in IN INTEGER) IS BEGIN IF line_in = 1 THEN process_line1; ELSIF line_in = 2 THEN process_line2; ... ELSIF line_in = 2045 THEN process_line2045; END IF; END;

Be n e fit s This st ruct ure clearly ex presses t he underlying " realit y" of y our business logic: if one condit ion is TRUE, no ot hers can be TRUE. ELSI F offers t he m ost efficient im plem ent at ion for processing m ut ually exclusive clauses. When one clause evaluat es t o TRUE, all subsequent clauses are ignored.

CTL- 0 2 : Use I F...ELSI F on ly t o t e st a sin gle , sim ple con dit ion .

The real world is very com plicat ed; t he soft ware w e w rit e is supposed t o m ap t hose com plexit ies int o applicat ions. The result is t hat we oft en end up needing t o deal wit h conv olut ed logical expressions.

62

Term Fly Presents

http://www.flyheart.com

You should writ e your I F st at em ent s in such a way as t o keep t hem as st raight forward and underst andable as possible. For exam ple, expressions are oft en m ore readable and underst andable when t hey are st at ed in a posit ive form . Consequent ly, you are probably bet t er off av oiding t he NOT operat or in condit ional expressions.

Ex a m ple I t 's not at all uncom m on t o w rit e or m aint ain code t hat 's st ruct ured like t his: IF condA AND NOT ( condB OR condC ) THEN proc1; ELSIF condA AND (condB OR condC) THEN proc2; ELSIF NOT condA AND condD THEN proc3; END IF; I t 's also fairly com m on t o get a headache t rying t o m ak e sense of all of t hat . You can oft en reduce t he t raum a of headache by t rading off t he sim plicit y of t he I F st at em ent it self ( one level of I F and ELSI F condit ions) for t he sim plicit y of clauses wit hin m ult iple levels: IF condA THEN IF (condB OR condC) THEN proc2; ELSE proc1; END IF; ELSIF condD THEN proc3 END IF; Don't forget , by t he way, t o t ak e int o account t he possibilit y of y our ex pressions evaluat ing t o NULL. This can t hrow a m onk ey wrench int o your condit ional processing.

An Ex ce pt ion t o t h e Ru le A not able except ion t o t his best pract ice is when you need t o negat e a large AND expression in order t o find out efficient ly whet her one value out of a group is different . For exam ple, I recent ly needed t o t est t he count s of 10 parallel index- by t ables, t o see if even one of t hem was different ; if so, it w as an error. Because AND expressions short - circuit on FALSE

63

( whereas ORs short - circuit on TRUE) , t his was m ore efficient t han using a group of ORs. Moreover, t he logic read m ore nat urally. For exam ple: IF NOT (arr1.count = arr2.count AND arr1.count = arr3.count AND arr1.count = arr4.count AND . . . AND arr1.count = arr10.count) THEN RAISE e_missing_value; —Dan Clam age Be n e fit s Following t his best pract ice will m ake y our code easier t o read and m aint ain. Breaking an expression int o sm aller pieces can aid m aint ainabilit y; if and when t he logic changes, you can change one I F clause wit hout affect ing t he logic of ot hers.

Ch a lle n ge s Mult iple levels of nest ed I F st at em ent s can also decrease readabilit y. You need t o st rive for a w orkable balance. There's a t radeoff bet w een efficiency ( few er condit ional st at em ent s) and ease of com prehension. " Many t im es," w rot e one review er, " I 'll code an I F or ELSE wit h a NULL st at em ent , eit her t o m ak e t he code easier t o read, or as a placeholder for fut ure logic. However, I m ay t hen find m yself repeat ing logic ( such as code t hat reset s a variable) under m ult iple ELSE blocks because I 'v e brok en up t he I F expression int o sm aller pieces."

CTL- 0 3 : Re pla ce a n d sim plify I F st a t e m en t s w it h Boole an ex pr e ssion s.

Som et im es, y ou will writ e or com e across condit ional st at em ent s t hat , while valid, are unnecessary and cum bersom e. Such st at em ent s oft en reflect a lack of underst anding about how you can and should use Boolean expressions and variables. I n general, if you see or writ e code like t his: DECLARE boolean_variable BOOLEAN; BEGIN IF boolean_variable = TRUE THEN ... ELSIF boolean_variable = FALSE

64

Term Fly Presents

http://www.flyheart.com

THEN ... END IF; change it t o sim pler, m ore direct code: DECLARE boolean_variable BOOLEAN; BEGIN IF boolean_variable THEN ... ELSIF NOT boolean_variable THEN ... END IF;

Ex a m ple I n som e cases, y ou can com plet ely rem ove an I F st at em ent . Consider t he following condit ional st at em ent : IF hiredate < SYSDATE THEN date_in_past := TRUE; ELSE date_in_past := FALSE; END IF; I f y ou'v e already validat ed t hat hiredat e can't be or isn't NULL, y ou can replace t he ent ire I F st at em ent wit h t his single assignm ent : date_in_past := hiredate < SYSDATE; I f hiredat e can be NULL, t he following st at em ent offers a com parable ex pression: date_in_past := NVL (hiredate < SYSDATE, FALSE);

Be n e fit s Following t his best pract ice will m ake y our code m ore readable and expressive.

4 .2 Loop Proce ssin g Follow t he best pract ices in t his sect ion when y ou are using PL/ SQL's looping st at em ent s.

CTL- 0 4 : N e ve r EXI T or RETURN fr om W H I LE an d FOR loops.

65

The WHI LE and FOR loops include " boundary condit ions" t hat det erm ine: •

When and if a loop should execut e at all



When a loop should st op ex ecut ing

I f y ou use t he EXI T or RETURN st at em ent s inside a WHI LE or FOR loop, you cause an unst ruct ured t erm inat ion from t he loop. The result ing code is hard t o t race and debug.

Ex a m ple Here's t he bot t om half of a funct ion t hat scans t he cont ent s of a collect ion and ret urns t he row in which a m at ch is found. l_count := titles.COUNT; FOR indx IN 1 .. l_rowcount LOOP IF l_match_against = titles(indx) THEN RETURN indx; END IF; END LOOP; RAISE Exit_Function; EXCEPTION WHEN Exit_Function THEN RETURN NULL; END; Now t his is som e nast y code. You m anage t o get all t he way down t o t he end of t he ex ecut able sect ion, and you are punished wit h an except ion! See [ MOD- 07: Lim it funct ions t o a single RETURN st at em ent in t he ex ecut ion sect ion.] for how t his violat es best pract ice for a " funnel- shaped" funct ion. Of course, you're not supposed t o get t o t he end of t he funct ion. I nst ead, t he funct ion finds a m at ch and zoom s st raight out of t he funct ion wit h a RETURN. Now im agine a funct ion whose body is 200 lines long wit h nest ed loops and sev eral different RETURNs in different part s of t he loop. Chaos!

Be n e fit s By following t he m axim " one way in and one way out " for y our loops, t he result ing code is m uch easier t o underst and and debug. I f y our loop needs t o ex ecut e at least once ( like a Pascal REPEAT st at em ent ) , y ou're bet t er off using a sim ple LOOP const ruct and t est ing for t he exit condit ion wit h EXI T WHEN.

Ch a lle n ge s Your exit t est in t he WHI LE expression can becom e a bit m ore com plex, especially when y ou have t o replace a nat ural FOR loop w it h a m ore m echanical WHI LE loop.

66

Term Fly Presents

http://www.flyheart.com

For exam ple, you have a FOR loop expression t hat it erat es ov er nest ed_t able.FI RST t o nest ed_t able.LAST, but you need t o t erm inat e t he loop when you find a m at ching ent ry. I n order t o put t he exit t est in t he it erat ion schem e, you have t o now use a WHI LE loop, init ialize and m aint ain a loop cont rol variable yourself ( for t he current offset ) , and t est for t he exit condit ion in t he WHI LE expression.

CTL- 0 5 : Use a sin gle EXI T in sim ple loops.

This best pract ice is anot her variat ion on " one way in, one way out ." I t suggest s t hat , whenev er possible, you consolidat e all exit logic in your sim ple loop t o a single EXI T ( or EXI T WHEN) st at em ent . I n general, use t he EXI T WHEN st at em ent in place of code like t his: IF THEN EXIT; END IF; because it 's m ore int uit ive and requires less t yping.

Ex a m ple Here's part of a program t hat com pares t w o files for equalit y. Aft er reading t he next line from each file, it check s for t he following condit ions: Did I reach t he end of bot h files? Are t he lines different ? Did I reach t he end of j ust one file? I n each case, set t he " ret urn value" for t he funct ion and also issue an EXI T st at em ent : LOOP read_line (file1, line1, file1_eof); read_line (file2, line2, file2_eof); IF (file1_eof AND file2_eof) THEN retval := TRUE; EXIT; ELSIF (line1 != line2) THEN retval := FALSE; EXIT; ELSIF (file1_eof OR file2_eof) THEN retval := FALSE; EXIT; END IF; END LOOP; Then rew rit e t his loop body as follows:

67

LOOP read_line (file1, line1, file1_eof); read_line (file2, line2, file2_eof); IF (file1_eof AND file2_eof) THEN retval := TRUE; exit_loop := TRUE; ELSIF (line1 != line2) THEN retval := FALSE; exit_loop := TRUE; ELSIF (file1_eof OR file2_eof) THEN retval := FALSE; exit_loop := TRUE; END IF; EXIT WHEN exit_loop; END LOOP; Som et im es it can be difficult t o com e up wit h j ust one EXI T st at em ent . This usually occurs when y ou need t o check a condit ion at t he beginning and end of a loop. I f y ou run int o t his sit uat ion, consider changing t o a WHI LE loop. You should also be careful t o init ialize your ret urn value and your loop t erm inat or variable, t o av oid unwant ed NULL values t hat m ight disrupt your logic.

Be n e fit s A single EXI T is especially im port ant in large, com plex loop bodies; it allows you t o m ore easily t race and debug your code.

Ch a lle n ge s Depending on how badly t he loop was writ t en init ially, you m ay need t o perform subst ant ial rest ruct uring t o im prov e t he loop code.

CTL- 0 6 : Use a sim ple loop t o a void re du n dan t code r equ ir ed by a W H I LE loop.

Generally, you should use a sim ple loop if you always want t he body of t he loop t o ex ecut e at least once. You use a WHI LE loop if you want t o check before ex ecut ing t he body t he first t im e. Since t he WHI LE loop perform s it s check " up front ," t he variables in t he boundary expression m ust be init ialized. The code t o init ialize is oft en t he sam e code needed t o m ov e t o t he next it erat ion in t he WHI LE loop. This redundancy creat es a challenge in bot h debugging and m aint aining t he code: how do you rem em ber t o look at and updat e bot h? I f y ou find yourself writ ing and running t he sam e code before t he WHI LE loop and at end of t he WHI LE loop body, consider swit ching t o a sim ple loop.

68

Term Fly Presents

http://www.flyheart.com

Ex a m ple I writ e a procedure t o calculat e overdue charges for books; t he m axim um fine t o be charged is $10, and I will st op processing when t here are no overdue books for a given dat e. Here is m y first at t em pt at t he procedure body: DECLARE l_fine PLS_INTEGER := 0; l_date DATE := SYSDATE; l_overdue_count NUMBER; BEGIN l_overdue_count := overdue_pkg.countem ( borrower_id => borrower_in, l_date); WHILE (l_overdue_count > 0 AND l_fine < 10) LOOP update_fine_info (l_date, l_one_day_fine); l_fine := l_fine + l_one_day_fine; l_date := l_date + 1; l_overdue_count := overdue_pkg.countem ( borrower_id => borrower_in, l_date); END LOOP; As is readily apparent , I duplicat e t he assignm ent s of values t o l_overdue_count . I would be far bet t er off rewrit ing t his code as follows: DECLARE l_fine PLS_INTEGER := 0; l_date DATE := SYSDATE; l_overdue_count NUMBER; BEGIN LOOP EXIT WHEN (l_overdue_count = 10) update_fine_info (l_date, l_one_day_fine); l_fine := l_fine + l_one_day_fine; l_date := l_date + 1; l_overdue_count := overdue_pkg.countem ( borrower_id => borrower_in, l_date); END LOOP;

Be n e fit s

69

You av oid redundant code, always bad new s in a program , since it increases m aint enance cost s and t he chance of int roducing bugs int o your code.

Ch a lle n ge s I f y ou have est ablished a habit early on of writ ing WHI LE loops, it can be hard t o ( a) not ice t he redundancy and ( b) change y our st yle.

CTL- 0 7 : N e ve r de cla r e t h e FOR loop in de x .

PL/ SQL offers t wo kinds of FOR loops: num eric and cursor. Bot h have t his general form at : FOR loop index IN loop range LOOP loop body END LOOP; The loop index is eit her an int eger or a record; in eit her case, it 's im plicit ly declared by t he PL/ SQL runt im e engine. The scope of t he loop index variable is rest rict ed t o t he body of t he loop ( bet ween t he LOOP and END LOOP st at em ent s) . You should never declare a variable for t he loop. I f you do declare t he loop index variable, you are act ually declaring a com plet ely separat e ( recordt ype or num eric) variable t hat will ( best case) nev er be used or ( worst case) used out side t he loop in a way t hat is confusing and likely t o int roduce errors.

Ex a m ple The dev eloper who w orked on t he library m anagem ent syst em before Jim ( a PL/ SQL novice) creat ed t his procedure t o delet e book s from t he collect ion by t it le: CREATE OR REPLACE PROCEDURE remove_titles ( title_in IN book.title%TYPE, ) IS CURSOR book_cur IS SELECT isbn, author FROM book WHERE title LIKE title_in; book_rec book_cur%ROWTYPE; BEGIN FOR book_rec IN book_cur LOOP te_book.rem (book_rec.isbn); END LOOP; END; I t w orks j ust fine ( no bugs report ed) , but Jim has been asked t o m odify t he procedure t o display t he last book rem oved. So he adds t his code aft er t he FOR loop:

70

Term Fly Presents

http://www.flyheart.com

END LOOP; pl (book_rec.isbn || ' - ' || book_rec.author); END; The code com piles, but Jim spends t he next t wo hours banging his head against t he wall t rying t o figure out why t he last book inform at ion keeps com ing up NULL. He doesn't quest ion t he exist ing code, since it work ed and was w rit t en by a high- priced consult ant . I t m ust be Jim 's fault . I n fact , t he original code was fault y. The declarat ion of book_rec was unnecessary and m ade Jim 's error possible.

Be n e fit s By av oiding unnecessary code, y ou m ak e it less likely for program m ers t o int roduce errors int o t he code at som e lat er point . You need not t ake out " program m er's insurance" : " Gee, I don't know if I need t o declare t hat or not , so I 'd bet t er declare it ." I nst ead, you m ake cert ain you underst and how PL/ SQL works and writ e appropriat e code.

CTL- 0 8 : Sca n colle ct ion s u sin g FI RST, LAST, a n d N EXT in loops.

A collect ion in PL/ SQL is like a single- dim ensional array. A collect ion differs from an array, however, in t hat t wo of t he t hree t ypes of collect ions ( nest ed t ables and index- by t ables) can be sparse, which m eans t hat t he defined row s in t he collect ion need not be sequent ially defined. You can, in ot her w ords, assign a value t o row 10 and a value t o row 10,000, and now rows will exist bet ween t hose t w o. I f y ou scan a collect ion wit h a FOR loop and t he collect ion is sparse, t he FOR loop t ries t o access an undefined row and raise a NO_DATA_FOUND except ion. I nst ead, use t he FI RST and NEXT m et hods t o scan forw ard t hrough a collect ion, and use LAST and PRI OR t o scan back wards

Ex a m ple I hav e decided t o help all of m y co- program m ers by providing a package t hat offers a st andard collect ion t ype ( list of st rings) and som e ut ilit y program s t o m anipulat e collect ions defined on t hat t ype. Here is t he package specificat ion: CREATE OR REPLACE PACKAGE mycollection IS TYPE string_tt IS TABLE OF VARCHAR2 (2000) INDEX BY BINARY_INTEGER; PROCEDURE show (list_in IN string_tt);

71

FUNCTION eq (list1_in IN string_tt, list2_in IN string_tt) RETURN BOOLEAN; END mycollection; By using t his package, I can easily declare a collect ion, display it s cont ent s, and even com pare t wo collect ions of t he sam e t ype t o see if t hey are equal. That sounds handy! The im plem ent at ion of t his ut ilit y package, however, will det erm ine how widely m y code is used. Here's m y first at t em pt : CREATE OR REPLACE PACKAGE BODY mycollection IS PROCEDURE show (list_in IN string_tt) IS BEGIN FOR indx IN list_in.FIRST .. list_in.LAST LOOP pl (list_in (indx)); END LOOP; END show; FUNCTION eq (list1_in IN RETURN BOOLEAN IS retval BOOLEAN indx PLS_INTEGER l_last1 PLS_INTEGER BEGIN WHILE retval AND indx DECLARE 2 family mycollection.string_tt; 3 pets mycollection.string_tt; 4 BEGIN 5 family (1) := 'Veva'; 6 family (2) := 'Eli'; 7 family (3) := 'Chris'; 8 family (4) := 'Steven'; 9 mycollection.show (family); 10 pets (1) := 'Mercury'; 11 pets (2) := 'Moshe Jacobawitz'; 12 pets (3) := 'Sister Itsacat'; 13 bpl (mycollection.eq (family, pets)); 14 END;

72

Term Fly Presents

http://www.flyheart.com

15 / Veva Eli Chris Steven FALSE Those t w o collect ions cert ainly aren't ident ical. Well, what a handy lit t le package! I ent husiast ically t ell all m y program m ing friends t hat I hav e a present for t hem and invit e t hem t o use m y collect ion. Not an hour goes by before Sriniva asks m e t o visit her cubicle. " What 's t his all about ?" she ask s m e ( wit h a subt ext of " Gee, I guess your code is not t o be t rust ed..." ) : SQL> DECLARE 2 authors mycollection.string_tt; 3 pets mycollection.string_tt; 4 BEGIN 5 FOR rec IN (SELECT * FROM author) 6 LOOP 7 authors (rec.author_id) := rec.last_name; 8 END LOOP; 9 10 mycollection.show (authors); 11 END; 12 / FEUERSTEIN DECLARE * ERROR at line 1: ORA-01403: no data found ORA-06512: at "SCOTT.MYCOLLECTION", line 8 I scrat ch m y head for a while, t hen ask t o see t he dat a in t he aut hors t able. " Why should t hat m at t er?" is t he response. I t 's a good response. Em barrassm ent soon propels m e t o t he heart of t he difficult y: her aut hor_id values are probably not sequent ial—but m y loops assum e a densely filled collect ion! Check out t he m yCollect ion.pkg file for a rew rit e of t he package body t hat fixes t his problem .

Be n e fit s Your scan is less likely t o raise an except ion. This is t he m ost efficient way t o scan a collect ion. You can, as is shown in t he files list ed under Resources, build prot ect ion wit hin t he FOR loop t o avoid raising NO_DATA_FOUND, but t hen you m ight well do excessive looping. What if, for exam ple, t he second_row_in were t w o m illion?

Re sou r ce s 1. plsqlloops.pro : Script t o com pare t he perform ance of several alt ernat ives t o scanning a collect ion.

73

2. m yCollect ion.pkg: I m plem ent at ion of a ut ilit y package t hat displays t he cont ent s of a collect ion and com pares t he cont ent s of t wo collect ions.

CTL- 0 9 : M ove st a t ic ex pr e ssion s ou t side of loops an d SQL st a t e m en t s.

Whenev er y ou set out t o t une y our PL/ SQL program s, y ou should first t ake a look at your loops. Any inefficiency inside a loop's body will be m agnified by t he m ult iple ex ecut ions of t hat code. A com m on m ist ake is t o put code t hat is st at ic or unchanging for each it erat ion of t he loop inside t he body . When you can ident ify such sit uat ions, ext ract t he st at ic code, assign t he out com es of t hat code t o one or m ore variables, and t hen reference t hose variables inside t he loop.

Ex a m ple This procedure sum m arizes book review s. I t 's run every m orning at 8 A.M. and t ak es about 15 m inut es t o com plet e: CREATE OR REPLACE PROCEDURE summarize_reviews ( summary_title_in IN VARCHAR2, isbn_in IN book.isbn%TYPE) IS CURSOR review_cur IS SELECT text, TO_CHAR (SYSDATE, 'MM/DD/YYYY') today FROM book_review WHERE isbn = isbn_in; BEGIN FOR review_rec IN review_cur LOOP IF LENGTH (review_rec.text) > 100 THEN review_rec.text := SUBSTR (review_rec.text, 1, 100); END IF; review_pkg.summarize ( UPPER (summary_title_in), today, UPPER (review_rec.text) ); END LOOP; END; / There are a num ber of problem s wit h t his code: •

74

Since m y j ob st art s and finishes on t he sam e day, I don't need t o select SYSDATE wit h each row of m y query . And unless I really want " t oday" t o be a

Term Fly Presents

http://www.flyheart.com

st ring expression, or I am ready t o absorb t he overhead of m ult iple im plicit conv ersions, I should use TRUNC t o get rid of t he t im e elem ent . •

I writ e over t he t ext field of t he review_rec record. While t his is allowed by PL/ SQL, y ou are generally bet t er off not m odifying t he index variable loop. Treat it as a const ant .



Since m y sum m ary_t it le_in argum ent never changes, I shouldn't UPPER case in each it erat ion of t he loop.



Rat her t han check t he lengt h of t he t ext for each row and t hen SUBSTR ( and UPPER case) , why not j ust SUBSTR inside SQL?

Here is a rew rit e of t he sum m arize_reviews procedure: CREATE OR REPLACE PROCEDURE summarize_reviews ( summary_title_in IN VARCHAR2, isbn_in IN book.isbn%TYPE) IS l_summary book_types.summary_t := UPPER (summary_title_in); l_today CONSTANT DATE := TRUNC (SYSDATE); CURSOR review_cur IS SELECT UPPER (SUBSTR (text, 1, 100)) text FROM book_review WHERE isbn = isbn_in; BEGIN FOR review_rec IN review_cur LOOP review_pkg.summarize ( l_summary, l_today, review_rec.text ); END LOOP; END; /

You can, in general, expect t he perform ance of built - in funct ions such as SUBSTR t o work m ore efficient ly in SQL t han in PL/ SQL, so m ove t he processing t o t he SQL layer whenever possible. Be n e fit s Your code doesn't do any unnecessary work and so execut es m ore efficient ly.

Ch a lle n ge s Don't be obsessed wit h t his sort of opt im izat ion as y ou w rit e your code. I t 's t heoret ically t rue t hat calling UPPER j ust once before t he loop is m ore efficient com pared t o calling it 100 t im es inside t he loop. I t 's also v ery likely t o be t he case

75

t hat t he cycles sav ed on t his opt im izat ion are never not iced by t he user. You are always bet t er off saving t he bulk of y our opt im izat ion effort s unt il you have ident ified t he bot t leneck s in your applicat ion as a whole.

Re sou r ce s insql.sql: A script t o com pare t he perform ance of funct ions in SQL versus PL/ SQL.

4 .3 M iscellan eou s The best pract ices in t his sect ion are grouped t oget her sim ply because t hey don't fall int o eit her of t he ot her cat egories.

CTL- 1 0 : Use an on ym ou s block s w it h in I F st a t e m en t s t o con se r ve re sou r ce s.

One of t he nice t hings about PL/ SQL is t hat you, t he dev eloper, can define any set of ex ecut able st at em ent s as a dist inct block, wit h it s own declarat ion, ex ecut able, and except ion sect ions. I f y ou not ice t hat cert ain operat ions and dat a st ruct ures aren't needed unless a cert ain condit ion is sat isfied, m ov e all t he execut ion of t hose operat ions and t he declarat ion of t hose dat a st ruct ures inside t he condit ional st at em ent . The result is t hat you won't incur t he ov erhead ( CPU or m em ory) unless it 's absolut ely needed.

Ex a m ple I n t he following block, I declare a set of local v ariables and even init ialize l_nam e wit h a funct ion t hat usually t akes 10 seconds t o ex ecut e ( m in_balance_account ) . But when I w rit e m y block , it t urns out t hat in m any sit uat ions, t hose st ruct ures are ignored: DECLARE TYPE account_tabtype IS TABLE OF account%ROWTYPE INDEX BY BINARY_INTEGER; l_accounts account_tabtype; l_name VARCHAR2(2000) := min_balance_account (SYSDATE); BEGIN IF balance_too_low (1056) THEN use_collection (l_accounts); use_name (l_name); ELSE -- No use of l_accounts or l_name ... END IF; END;

76

Term Fly Presents

http://www.flyheart.com

Once I recognize t his sit uat ion ( usually ident ified t hrough a code walkt hrough) , I should change it t o t his: BEGIN IF balance_too_low (1056) THEN DECLARE TYPE account_tabtype IS TABLE OF account%ROWTYPE INDEX BY BINARY_INTEGER; l_accounts account_tabtype; l_name VARCHAR2(2000) := min_balance_account (SYSDATE); BEGIN use_collection (l_accounts); use_name (l_name); END; ELSE -- No use of l_accounts or l_name ... END IF; END;

Be n e fit s Your program s w on't ex ecut e unnecessary code, im proving perform ance and reducing m em ory requirem ent s for t he program .

Ch a lle n ge s I t can be hard t o realize as y ou first writ e y our program t hat t his kind of sit uat ion exist s. Use code walkt hroughs t o uncov er t hese opt im izat ion opport unit ies. You can also use Oracle8i 's code profiler ( t he DBMS_PROFI LER built - in package) t o ident ify unused or lit t le- used code. A num ber of PL/ SQL I DEs offer a GUI int erface t o t his profiler.

Re sou r ce s The following product s current ly offer GUI s t o DBMS_PROFI LER: 1. ht t p: / / www.quest .com / sql_navigat or/ : SQL Navigat or. 2. ht t p: / / www.sfi- soft ware.com / sql- program m er.ht m : SQL Program m er. 3. ht t p: / / www.em barcadero.com / product s/ Dev elop/ develop.ht m : Rapid SQL.

CTL- 1 1 : La bel an d h igh ligh t GOTOs if u sin g t h is n orm a lly u n n e ce ssa r y con st r u ct .

77

I suppose t hat it was t horough of Oracle t o include a GOTO st at em ent in t he PL/ SQL language. This st at em ent , however, should generally be avoided, as it leads t o unst ruct ured code design t hat is hard t o analyze and debug. There are scenarios in which a GOTO can be j ust ified; t hese m ost ly relat e t o going int o exist ing spaghet t i code t o fix a bug or enhance t he code. For an ext ensive review of GOTO- relat ed issues, see Chapt er 16 in St eve McConnell's book, Code Com plet e.

Ex a m ple Here is a use of GOTO t hat calls at t ent ion t o it self: CREATE OR REPLACE PROCEDURE someone_elses_mess /* || Author: Longgone Consultant || Maintained by: Sad Employee || || Modification History || When Who What || -------------------------------------------|| 11/2000 Sad E. Fixed bug in overdue logic. || Used GOTO to bypass Gordian || Knot of code left by L.C. */ IS BEGIN IF ... THEN IF ... THEN FOR rec IN cur LOOP -- 11/2000 Bypass with GOTO GOTO END LOOP; ... lots more code END IF; -- 11/2000 GOTO Target

END IF;

Be n e fit s Ev en if you can, at t im es, j ust ify t he use of a GOTO, y ou can alm ost always achieve t he sam e effect wit h a m ore st ruct ured and m ore easily underst ood use of condit ional and loop logic.

Re sou r ce s Code Com plet e, by St ev e McConnell: See Chapt er 16, Unusual Cont rol St ruct ures, for an in- dept h discussion of t he GOTO st at em ent and recom m endat ions for when it can j ust ifiably be used.

Ch a pt e r 5 . Ex ce pt ion H a n dlin g 78

Term Fly Presents

http://www.flyheart.com

Ev en if you w rit e such am azing code t hat it cont ains no errors and nev er act s inappropriat ely, your users m ight st ill use your program incorrect ly. The result ? Sit uat ions t hat cause program s t o fail. PL/ SQL provides except ions, a flexible and powerful archit ect ure t hat raises, t raps, and handles errors. Before get t ing int o specific best pract ices, y ou should be sure t o underst and how except ion handling work s. For exam ple, rem em ber t hat an ex cept ion sect ion handles only errors raised in t he ex ecut able sect ion of t he block, not errors raised in t he declarat ion sect ion. Next and ev en m ore im port ant , I offer t he following m et a- best pract ice of t his chapt er.

EXC- 0 0 : Se t gu ide lin e s for a pplica t ion - w ide e r r or h a n dlin g be for e you st a r t codin g. I t 's im pract ical t o define EXCEPTI ON sect ions in your code aft er t he fact —in ot her words, aft er t he program s hav e been w rit t en. The best way t o im plem ent applicat ion- wide, consist ent error handling is t o use a st andardized package t hat cont ains at least t he following elem ent s: •

Procedures t hat perform m ost ex cept ion- handling t asks, such as w rit ing t o an error log.



A raise program t hat hides t he com plexit y of RAI SE_APPLI CATI ON_ERROR and applicat ion- specific error num bers.



A funct ion t hat ret urns error m essage t ext for a given error num ber.

These ideas are cov ered in specific best pract ices in t his chapt er. A sim ple errorhandling package m ay be found in t he err.pkg file on t he Oracle PL/ SQL Best Pract ices w eb sit e.

5 .1 Ra isin g Ex ce pt ion s The following best pract ices cov er how t o check for condit ions t hat m ight require t he raising of an except ion, deciding how t o propagat e except ion inform at ion, and how t o best raise except ions.

EXC- 0 1 : Ver ify pr e con dit ion s u sin g st a n da r dize d a sse r t ion r ou t in es t h a t r aise viola t ion e x cept ion s.

Ev ery t im e you writ e a program , you m ake cert ain assum pt ions. A user of your program doesn't necessarily know about t hose assum pt ions. I f you don't " code

79

defensively" and m ake sure t hat your assum pt ions aren't violat ed, your program s can break down in unpredict able ways. Use assert ion rout ines t o m ak e it as easy as possible t o validat e assum pt ions in a declarat ive fashion. These rout ines, st andardized for an ent ire applicat ion, t ak e care of all t he housek eeping: what t o do when a condit ion fails, how t o report t he problem , and whet her and how t o st op t he program from cont inuing.

Ex a m ple Here's a sim ple assert ion program t hat check s t o see if a condit ion is TRUE. I f t he condit ion is FALSE or NULL, t he procedure displays a m essage t o t he screen and t hen raises an ex cept ion ( if so desired) wit h dynam ic PL/ SQL ( t his im plem ent at ion relies on Oracle8i ` s nat ive dy nam ic SQL) : CREATE OR REPLACE PROCEDURE assert ( condition_in IN BOOLEAN, message_in IN VARCHAR2, raise_exception_in IN BOOLEAN := TRUE, exception_in IN VARCHAR2 := 'VALUE_ERROR' ) IS BEGIN IF NOT condition_in OR condition_in IS NULL THEN pl ('Assertion Failure!'); pl (message_in); IF raise_exception_in THEN EXECUTE IMMEDIATE 'BEGIN RAISE ' || exception_in || '; END;'; END IF; END IF; END assert; Wit h t his program in place, y ou can easily, and in a declarat ive fashion, m ake sure t hat all input s are hunk y- dory before proceeding wit h your business logic. Here's an exam ple: BEGIN assert (isbn_in IS NOT NULL, 'The ISBN must be provided.'); assert (page_count_in < 2000, 'Readers don't like big, fat books!');

Be n e fit s Wit h easy- t o- use, declarat ive assert ion rout ines, y ou're m ore likely t o act ually check for valid input s and condit ions.

80

Term Fly Presents

http://www.flyheart.com

Validat ion will occur in a st andard way t hroughout your applicat ion if everyone uses t he sam e assert ion program s.

Ch a lle n ge s Dev elop a habit of t hinking t hrough and assert ing all your assum pt ions at t he t op of your ex ecut able sect ion, before you st art writ ing any business logic.

Re sou r ce s 1. assert .pro : A sim ple assert ion procedure 2. assert .pkg : An assert ion package t hat offers assert ions for different condit ions

EXC- 0 2 : Use t h e defa u lt ex cept ion - h a n dlin g m odel t o com m u n ica t e m odu le st a t u s ba ck t o ca llin g PL/ SQL pr ogr a m s.

Wat ch out for carrying baggage from ot her languages int o t he w orld of PL/ SQL. Your last language m ight not have had a sophist icat ed error- handling archit ect ure. As a consequence, you relied on param et ers in ev ery program t o pass back error st at us ( code and m essage) . Don't do t his in PL/ SQL! Rely on t he default m odel: raise ex cept ions and handle t hose ex cept ions in t he separat e ex cept ion sect ion of y our block s.

Ex a m ple Here's t he kind of code you want t o av oid: BEGIN overdue.analyze_status ( title_in, start_date_in, error_code, error_msg); IF error_code != 0 THEN err.log (...); GOTO end_of_program; END IF; overdue.send_report ( error_code, error_msg); IF error_code != 0 THEN

81

err.log (...); GOTO end_of_program; END IF;

Be n e fit s Your ex ecut able sect ions are clean, sim ple, and easy t o follow. You don't have t o check for st at us aft er ev ery program call. You sim ply include an except ion sect ion t o t rap and deal wit h crises as t hey arise.

Ch a lle n ge s I t can be hard t o break old habit s. You m ight inherit code t hat looks like t he ex am ple. I n t his case, I w ould suggest t o your m anager t hat it 's wort h it t o proact ively clean up t he code. I f y ou are calling PL/ SQL from a non- Oracle front end, you m ay need t o pass back error inform at ion ( see [ EXC- 03: Cat ch all except ions and convert t o m eaningful ret urn codes before ret urning t o non- PL/ SQL host program s. ] ) .

EXC- 0 3 : Ca t ch all ex ce pt ion s an d con ve r t t o m e an in gfu l r e t u r n code s befor e r e t u r n in g t o n on - PL/ SQL h ost pr ogr am s.

Suppose t hat you are calling PL/ SQL program s from Visual Basic, Powerbuilder, Java, or som e ot her language. These non- Oracle dev elopm ent languages m ay not underst and, or be able t o handle, PL/ SQL except ions very gracefully. I n t his sit uat ion, you m ay need t o pass back error st at us ( code and m essage) wit h at least som e of your program s. You should do t his only on an " except ion" basis—as needed. The best way t o do it is t o overload t he original program in your package wit h anot her of t he sam e nam e and t wo addit ional param et ers.

Ex a m ple Suppose you need t o call overdue.analyze_st at us bot h from wit hin t he Oracle RDBMS ( i.e., from anot her st ored procedure) and from wit hin a Visual Basic applicat ion. You can use package ov erloading t o offer t he " sam e" program wit h a different int erface: CREATE OR REPLACE PACKAGE overdue IS PROCEDURE analyze_status ( title_in IN book.title%TYPE, start_date_in IN DATE := SYSDATE); overdue.analyze_status (

82

Term Fly Presents

http://www.flyheart.com

title_in IN book.title%TYPE, start_date_in IN DATE := SYSDATE, error_code OUT INTEGER, error_msg OUT VARCHAR2);

Be n e fit s Dev elopers can call PL/ SQL st ored code and gracefully check for errors in t he way t hat 's m ost appropriat e in t heir own program m ing language. The dat at ype for t he error code and m essage m ust be generic ANSI SQL t ypes, not Oracle- specific t ypes. For exam ple, you can't use a BOOLEAN param et er or PL/ SQL index- by t able as a ret urn value for any funct ion t hat int erfaces wit h non- PL/ SQL t ools.

EXC- 0 4 : Use you r ow n r a ise proce du re in pla ce of ex plicit ca lls t o RAI SE_ APPLI CATI ON _ ERROR.

When it com es t o m anaging errors, Oracle requires a lot of developers. I f y ou're raising a " sy st em " except ion like NO_DATA_FOUND, y ou use RAI SE. But when you want t o raise an applicat ion- specific error, you use RAI SE_APPLI CATI ON_ERROR. I f you use t he lat t er, y ou have t o provide an error num ber and m essage. This leads t o unnecessary and dam aging hard coding ( see [ EXC- 09: Use nam ed const ant s t o soft code applicat ion- specific error num bers and m essages.] ) . A m ore fail- safe approach is t o provide a predefined raise procedure t hat aut om at ically check s t he error num ber and det erm ines t he correct way t o raise t he error. An exam ple of such a procedure m ay be found in t he err.pkg file on t he Oracle PL/ SQL Best Pract ices w eb sit e, and is described briefly in t he following sect ion.

Ex a m ple I nst ead of w rit ing code like t his: RAISE_APPLICATION_ERROR ( -20734, 'Employee must be 18 years old.'); you should writ e code like t his: err.raise (errnums.emp_too_young); Here's an exam ple of how you m ight const ruct a generic ex cept ion raiser ( from err.pkg ) : PROCEDURE raise ( errcode IN PLS_INTEGER := NULL,

83

errmsg

IN

VARCHAR2 := NULL

) IS l_errcode PLS_INTEGER := NVL (errcode, SQLCODE); l_errmsg PLS_INTEGER := NVL (errmsg, SQLERRM); BEGIN IF l_errcode BETWEEN -20999 AND -20000 THEN RAISE_APPLICATION_ERROR (l_errcode, l_errmsg); /* Use positive error numbers -- lots to choose from! */ ELSIF l_errcode > 0 AND l_errcode NOT IN (1, 100) THEN RAISE_APPLICATION_ERROR (-20000, l_errcode || '-' || l_errmsg); /* Re-raise any other exception using dynamic PL/SQL. */ ELSIF l_errcode != 0 THEN PLVdyn.plsql ( 'DECLARE myexc EXCEPTION; ' || ' PRAGMA EXCEPTION_INIT (myexc, ' || TO_CHAR (l_errcode) || ');' ||'BEGIN RAISE myexc; END;' ); END IF; END;

Be n e fit s I ndividual developers don't hav e t o m ak e j udgm ent calls about how t hey should raise t he ex cept ion ( RAI SE? RAI SE_APPLI CATI ON_ERROR?) . They sim ply pass t he appropriat e error num ber ( hopefully ident ified via a nam ed const ant ) and let t he raise " engine" do t he heavy lift ing. You can choose t o use posit ive error num bers for y our own applicat ion- specific except ions. By t aking t his approach, you aren't const rained t o error num bers bet ween –20,999 and –20,000, som e of which Oracle also uses. The err.raise procedure int ercept s posit ive error num bers and passes t hem back t o t he calling program as an except ion by bundling t he error num ber and m essage int o a single st ring in t he call t o RAI SE_APPLI CATI ON_ERROR.

Ch a lle n ge s First , you m ust set y our st andards on what kind of ex cept ions ( t he num bers and m essages, in part icular) you will use for your applicat ion. Then you need t o m ak e sure t hat ev ery one uses t he err.raise procedure.

Re sou r ce s err.pkg : A sim ple, but funct ional prot ot ype of a generic error- handling package.

84

Term Fly Presents

http://www.flyheart.com

EXC- 0 5 : On ly RAI SE ex ce pt ion s for e r r or s, n ot t o br a n ch e x e cu t ion con t r ol.

The RAI SE st at em ent is an easy and powerful way t o abort norm al processing in a program and im m ediat ely " go t o" t he appropriat e WHEN handler. You should, howev er, nev er use RAI SE in t his way. You should raise an except ion only when an error has occurred, not t o cont rol program flow.

Ex a m ple Here's a program t hat dem onst rat es t he problem ; it perform s a full t able scan of a collect ion and im m ediat ely exit s when it finds a m at ch. The exit _funct ion except ion abort s t he funct ion if t he input t it le is NULL; it 's also used as t he last line in t he funct ion: CREATE OR REPLACE FUNCTION book_from_list ( list_in IN book_tabtype, title_in IN book.title%TYPE) RETURN book%ROWTYPE IS exit_function EXCEPTION; BEGIN IF title_in IS NULL THEN RAISE exit_function; END IF; FOR indx IN list_in.FIRST .. list_in.LAST LOOP IF list_in(indx).title = title_in THEN RETURN list_in(indx); END IF; END LOOP; RAISE exit_function; EXCEPTION WHEN exit_function THEN RETURN NULL; END; Whew. St range st uff. You m anage t o m ak e it all t he way t o t he end of t he funct ion, and t hen you are punished by having an ex cept ion raised! This is very poorly st ruct ured code: hard t o underst and and hard t o m aint ain. Here's a bet t er approach: CREATE OR REPLACE FUNCTION book_from_list ( list_in IN book_tabtype, title_in IN book.title%TYPE) RETURN book%ROWTYPE

85

IS indx PLS_INTEGER; retval book%ROWTYPE; BEGIN IF title_in IS NOT NULL THEN indx := list_in.FIRST; LOOP IF list_in(indx).title = title_in THEN retval := list_in(indx); END IF; indx := list_in.NEXT (indx); END LOOP; END IF; RETURN retval; END; Be on t he lookout for a clear sym pt om of t his m isuse of error handling: declared except ions whose nam es describe act ions ( exit _funct ion) rat her t han errors ( null_t it le) .

Be n e fit s Your code is m ore st raight forward and is easier t o read, debug, and m aint ain.

EXC- 0 6 : D o n ot ove r loa d an ex ce pt ion w it h m u lt iple e r r or s u n le ss t h e loss of in form a t ion is in t en t ion al.

Don't declare one generic except ion such as bad_dat a and t hen raise t hat except ion under different circum st ances. Users of your code will have t rouble underst anding precisely what caused t he problem . I nst ead, declare a separat e except ion for each different kind of failure.

Ex a m ple Oracle is guilt y of violat ing t his best pract ice, as can be seen wit h NO_DATA_FOUND. This except ion can be raised by a SELECT I NTO t hat finds no rows, by at t em pt ing t o read an undefined row in a collect ion, or by reading past t he end of a file. How can you t ell what went wrong inside your NO_DATA_FOUND handler? This dilem m a is shown in t his exam ple: CREATE OR REPLACE PROCEDURE two_reads IS l_title book.title%TYPE; l_line VARCHAR2(1023); fid UTL_FILE.FILE_TYPE; BEGIN SELECT title INTO l_title

86

Term Fly Presents

http://www.flyheart.com

FROM emp WHERE 1 = 2; fid := UTL_FILE.FOPEN ( 'c:\temp', 'justoneline.txt', 'R'); UTL_FILE.GET_LINE (fid, l_line); UTL_FILE.GET_LINE (fid, l_line); EXCEPTION WHEN NO_DATA_FOUND THEN pl ('Who did that?'); END two_reads; I f y ou do run int o sit uat ions like t his, whet her due t o Oracle's design or anot her dev eloper in your organizat ion, you can use nest ed blocks t o avoid t he am biguit y. By declaring a block around a set of lines of code, you can rest rict t he propagat ion of t he am biguous ex cept ion and t ransform t hat ex cept ion int o a unique ident ifier. Here's an exam ple of t his approach: CREATE OR REPLACE PROCEDURE two_reads IS l_title book.title%TYPE; l_line VARCHAR2(1023); fid UTL_FILE.FILE_TYPE; no_table_data EXCEPTION; no_file_data EXCEPTION; BEGIN BEGIN SELECT title INTO l_title FROM emp WHERE 1 = 2; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE no_table_data; END; BEGIN fid := UTL_FILE.FOPEN ('c:\temp', 'justoneline.txt', 'R'); UTL_FILE.GET_LINE (fid, l_line); UTL_FILE.GET_LINE (fid, l_line); EXCEPTION WHEN NO_DATA_FOUND THEN RAISE no_file_data; END; EXCEPTION WHEN no_table_data THEN pl ('Query on table returned no data!'); WHEN no_file_data THEN pl ('Attempt to read past end-of-file!'); END two_reads;

Be n e fit s

87

Dev elopers can check for, and handle, t he different kinds of errors y our code m ight produce.

Re sou r ce s excquiz6.sql and ex cquiz6a.sql : Dem onst rat ions of how y ou can t ransform a single, ov erused ex cept ion such as NO_DATA_FOUND int o m ult iple, dist inct except ions.

5 .2 H a n dlin g Ex ce pt ion s Once an except ion is raised, it generally needs t o be handled. These best pract ices offer advice on w rit ing except ion- handling sect ions.

EXC- 0 7 : H an dle e x ce pt ion s t h a t ca n n ot be a voide d bu t can be a n t icipa t ed.

I f y ou are w rit ing a program in which you can predict t hat a cert ain error will occur, you should include a handler in your code for t hat , allowing for a graceful and inform at ive failure. The form t hat t his failure t ak es doesn't , by t he way, necessarily need t o be an except ion. When writ ing funct ions, you m ay w ell decide t hat in t he case of cert ain except ions, y ou will want t o ret urn a value such as NULL, rat her t han allow an except ion t o propagat e out of t he funct ion.

Ex a m ple This recom m endat ion is easily dem onst rat ed wit h t he ubiquit ous SELECT I NTO lookup query. An error t hat oft en occurs is NO_DATA_FOUND, which indicat es t hat t he query didn't ident ify any rows. Now , following [ SQL- 04: Put single- row fet ches inside funct ions; never hard- code a query in your block.] , I put SELECT I NTO inside a funct ion, but I don't allow t he NO_DATA_FOUND ex cept ion t o propagat e out of t he funct ion: CREATE OR REPLACE FUNCTION book_title ( isbn_in IN book.isbn%TYPE) RETURN book.title%TYPE IS l_ title book.title%TYPE; BEGIN SELECT title INTO l_title FROM book WHERE isbn =isbn_in; RETURN l_rec.title; EXCEPTION WHEN NO_DATA_FOUND THEN RETURN NULL; END;

88

Term Fly Presents

http://www.flyheart.com

I n ot her w ords, if t he I SBN passed t o t he funct ion finds no book , ret urn NULL for t he t it le. This is an unam biguous indicat or of failure; a book m ust have a t it le. I hav e decided in t his case not t o allow NO_DATA_FOUND t o propagat e ( go unhandled) out of t he funct ion. I use a SELECT I NTO ( im plicit query) t o fet ch t he book t it le; Oracle's im plem ent at ion of im plicit queries m eans t hat NO_DATA_FOUND ( as well as TOO_MANY_ROWS) m ight be raised. That doesn't m ean, howev er, t hat wit hin m y funct ion, it really is an except ion when no row is found. I n fact , I m ight be expect ing t o not find a m at ch. By ret urning NULL rat her t han propagat ing an except ion, I leav e it up t o t he user of m y funct ion t o decide how t o deal wit h a " no row found" sit uat ion. She m ight raise an except ion, as in: BEGIN l_title := book_title (l_isbn); IF l_title IS NULL THEN RAISE NO_DATA_FOUND; END IF; or she m ight decide t hat such a result m eans t hat ev eryt hing is, in fact , as desired: BEGIN l_title := book_title (l_isbn); IF l_title IS NULL THEN process_new_book (l_isbn); END IF;

Be n e fit s Your program s are bet t er- behaved and m ore likely t o be useful and used. I f y ou let t he ex cept ion propagat e out , t his funct ion would be unpredict able and hard t o int egrat e int o y our applicat ion, since ex cept ion handlers m ust be coded in t he caller's code block.

Ch a lle n ge s I t 's one t hing t o set a st andard, and quit e anot her t o hav e ev eryone im plem ent program s according t o t he st andard. See [ MOD- 02: St andardize m odule st ruct ure using funct ion and procedure t em plat es. ] for ideas on generat ing funct ions t hat com ply wit h your best pract ices.

EXC- 0 8 : Avoid h a r d- code d e x posu r e of e r r or h an dlin g by u sin g st a n da r d, decla r a t ive pr oce du r e s.

89

The best way t o achieve consist ent , high- qualit y error handling t hroughout your applicat ion is t o offer a set of predefined procedures t hat do t he basic plum bing of error handling: record t he error inform at ion if desired, propagat e t he except ion, and so on. I t 's crucial t hen t o m ake cert ain t hat developm ent t eam m em bers alway s and only use t hese procedures in t heir WHEN clauses.

Ex a m ple Here's t he kind of code you should never writ e inside an except ion handler: EXCEPTION WHEN NO_DATA_FOUND THEN v_msg := 'No company for id ' || TO_CHAR (v_id); v_err := SQLCODE; v_prog := 'fixdebt'; INSERT INTO errlog VALUES (v_err, v_msg, v_prog, SYSDATE, USER);

WHEN OTHERS THEN v_err := SQLCODE; v_msg := SQLERRM; v_prog := 'fixdebt'; INSERT INTO errlog VALUES (v_err, v_msg, v_prog, SYSDATE, USER); RAISE; There are sev eral problem s wit h t his code: •

Exposure of logging m et hod. What if y ou change t he st ruct ure of t he t able, or decide t o writ e t o a file inst ead? Ev ery handler has t o change.



Hard- coded program nam es. This inform at ion is available from t he built - in funct ion DBMS_UTI LI TY.FORMAT_CALL STACK.

A bet t er approach is t o rely on predefined handlers. Here's a rew rit e of t he sam e except ion sect ion: EXCEPTION WHEN NO_DATA_FOUND THEN err.handle ( 'No company for id ' || TO_CHAR (v_id), log => TRUE, reraise => FALSE); WHEN OTHERS THEN err.handle (log => TRUE, reraise => TRUE);

Be n e fit s

90

Term Fly Presents

http://www.flyheart.com

All developers handle errors in t he sam e way, achieving consist ency in logging and also in user present at ion of error feedback. Enhancem ent s or changes in logging st andards can be easily ( alm ost inst ant ly) im plem ent ed.

Ch a lle n ge s Well, you need t o im plem ent t he generic package, but t he predefined procedure gives you a funct ional st art ing point for t hat . Dev elopers m ust be t rained in using t he package, and t hen t hey m ust use it . Use code walkt hroughs and/ or aut om at ed code analysis t o ensure t hat program m ers are following t he st andard.

Re sou r ce s err.pkg: A sim ple, but funct ional prot ot ype of a generic error- handling package.

EXC- 0 9 : Use n am ed con st a n t s t o soft - code a pplica t ion - specific e r r or n u m be r s an d m e ssa ge s.

Oracle allocat es 1000 error num bers, bet w een - 20,000 and - 20,999, t o use for our own applicat ion- specific errors ( such as " Em ployee m ust be 18 years old" or " Reservat ion dat e m ust be in t he fut ure" ) . Define all error num bers and t heir associat ed m essages in a dat abase t able or operat ing- syst em file. Build a package t hat gives nam es t o t hese errors, and t hen raise t he errors using t hose nam es and not any hard- coded values.

Ex a m ple Here's a fairly t ypical t angle of hard- coded, error- prone program m ing wit h RAI SE_APPLI CATI ON_ERROR. Sam Developer is t old t o w rit e a procedure t o st op updat es and insert s when an em ploy ee is younger t han 18. Sam t hinks t o him self " Surely no one has used error 20734 y et , so I w ill use it " and produces t his code: CREATE OR REPLACE PROCEDURE check_hiredate ( date_in IN DATE) IS BEGIN IF date_in < ADD_MONTHS (SYSDATE, -1 * 12 * 18) THEN RAISE_APPLICATION_ERROR ( -20734, 'Employee must be 18 years old.');

91

END IF; END; Check out all t hat hard- coding! And while Sam is writ ing his code, of course, Nat asha Program m er also decides t hat 20734 is a fine error num ber. What a m ess! Here's a m uch cleaner approach: CREATE OR REPLACE PROCEDURE check_hiredate ( date_in IN DATE) IS BEGIN IF emp_rules.emp_too_young (date_in) THEN err.raise (errnums.emp_too_young); END IF; END; First , I have m oved t he logic defining a " t oo young" em ployee t o a funct ion, as recom m ended in [ MOD- 01: Encapsulat e and nam e business rules and form ulas behind funct ion headers. ] . For error handling, Sam now sim ply knows t hat he calls t he err.raise procedure t o raise his error. Which error? Sam goes t o t he list of predefined ex cept ions ( eit her in docum ent at ion or via a GUI int erface) and picks, by nam e, t he one t hat m at ches.

Be n e fit s Dev elopers avoid conflict s ov er t he sam e error num ber; such conflict s can lead t o m assive confusion. Dev elopers don't hav e t o decide t he best w ay t o raise an error. Just call t he err.raise procedure and let it do t he work for you.

Ch a lle n ge s The sam e as for [ EXC- 08: Avoid hard- coded ex posure of error handling by using st andard, declarat ive procedures.] ( predefined handler procedures) . You need t o build code and dat a t o m aint ain known errors, associat ed wit h num bers and error t ext .

Re sou r ce s m sginfo.pkg : I nfrast ruct ure package and associat ed t able t o m anage error num bers and t ext , and t o generat e a package wit h nam ed ex cept ions.

EXC- 1 0 : I n clu de st a n da r dize d m odu le s in pa ck a ge s t o du m p pa ck age st a t e w h e n e r r ors occu r .

92

Term Fly Presents

http://www.flyheart.com

When an error occurs in one of y our PL/ SQL blocks, it 's oft en useful t o det erm ine t he values of persist ent package variables at t he t im e of t he failure. You can do t his t o som e ext ent wit h t he debuggers available wit h m any I DEs. That approach doesn't , howev er, give you access t o t he dat a values wit hin a user's applicat ion session. One w ay t o obt ain t his inform at ion is t o w rit e a " dum p" procedure in each of y our packages. This dum p procedure displays or records t he cont ent s of any relevant variables or dat a st ruct ures—what ever y ou det erm ine is of value inside t hat package. You can t hen feed t his inform at ion t o an error handler, t o provide as m uch inform at ion as possible t o t he person debugging your code. Providing such dum p procedures can dram at ically reduce t he t im e spent insert ing debug m essages only t o be rem ov ed lat er, as w ell as t o record problem s t hat appear int erm it t ent ly, and are hard t o reproduce. This approach obviously relies on t he conform ance t o st andards est ablished in advance, so t hat m et hod nam es and st ack form at s can be int erpret ed, but all of t hese det ails can be hidden from view in a pack age, such as t he error_ pkg included in t he callst ack .sql file ( see Resources) . This package ( provided by Dwayne King, ace reviewer and PL/ SQL dev eloper) k eeps t rack of t he call st ack by recording in a PL/ SQL t able each piece of code as it " announces" it self. I t t hen uses t hat st ack t o det erm ine which dum p m et hods need t o be called when an error occurs. Unfort unat ely, t here's no reliable ( and support ed) way right now t o easily det erm ine which packages " have st at e" ev en if t hey aren't in t he call st ack, but t his m ay be possible in t he fut ure. Anot her st raight forward ex ercise is t o ext end t his package t o writ e t o a log file or pipe inst ead of j ust using t he st andard DBMS_OUTPUT package.

Ex a m ple The dem o_ pkg file ( see Resources) conform s t o t he " dum p API " by including a procedure nam ed inst ant iat e_error_cont ext in t he specificat ion: CREATE OR REPLACE PACKAGE demo_pkg IS PROCEDURE proc1; PROCEDURE instantiate_error_context; END; / The proc1 procedure set s t he m odule nam e in t he st ack, assigns a value t o a variable, and t hen calls proc2, which also " announces" it self and m odifies a package variable. I t t hen, how ev er, raises an EXCEPTI ON: PROCEDURE demo_pkg.proc1 IS BEGIN --announce entry into this module error_pkg.set_module_name ('demo_pkg.proc1');

93

-- Application processing here. application.field_1 := 'test string'; proc2; error_pkg.remove_module_name; EXCEPTION WHEN OTHERS THEN error_pkg.set_err_msg ('DAT023'); error_pkg.raise_error ('Failed Operation'); END; The inst ant iat ion procedure passes t he values of t he package dat a ( t he package st at e) t o t he error pack age: PROCEDURE demo_pkg.instantiate_error_context IS BEGIN error_pkg.add_context ( 'DEMO_PKG', 'Field #1', application.field_1); error_pkg.add_context ( 'DEMO_PKG', 'Field #2', application.field_2); error_pkg.add_context ( 'DEMO_PKG', 'Field #3', application.field_3); END; When y ou run dem o_pk g.proc1, you see t he following out put : SQL> exec demo_pkg.proc1 Adding demo_pkg.proc1 to stack Adding demo_pkg.proc2 to stack Error Log Time: 13:15:33 Call Stack: demo_pkg.proc1 --> demo_pkg.proc2 Comments: Failed Operation CFRS Error No: DAT027 Oracle Error: ORA-01403: no data found ----------DEMO_PKG---------------------Field #1: test string Field #2: -37 Field #3: NULL

The error_pkg used in t he exam ple and found in t he callst ack.sql file requires you t o explicit ly list t he packages t hat cont ain inst ant iat e_error_cont ext procedures. An im proved im plem ent at ion is t o rely on dynam ic SQL ( eit her DBMS_SQL or nat ive dynam ic SQL) t o aut om at ically const ruct t he program call and execut e it . Be n e fit s

94

Term Fly Presents

http://www.flyheart.com

Changes t o t he w ay errors are handled or logged don't require changing any code, ot her t han t he one generic raise procedure You can validat e t hat packages conform t o t he st andard by querying ALL_ARGUMENTS t o check for packages t hat don't cont ain t he inst ant iat e_error_cont ext procedure.

Ch a lle n ge s To be useful, a m et hod like t his relies on dev elopers following previously defined st andards.

Re sou r ce s callst ack.sql : Cont ains t he error pack age and a dem onst rat ion package cont aining a dum p procedure.

EXC- 1 1 : Use W H EN OTH ERS on ly for u n k n ow n e x ce pt ion s t h a t n e ed t o be t r a ppe d.

Don't use WHEN OTHERS t o grab any and ev ery error. I f you know t hat a cert ain except ion m ight be raised, include a handler for t hat specifically.

Ex a m ple Here's an except ion sect ion t hat clearly expect s a DUP_VAL_ON_I NDEX error t o be raised but t hat buries t hat inform at ion in WHEN OTHERS: EXCEPTION WHEN OTHERS THEN IF SQLCODE = -1 THEN update_instead (...); ELSE err.log; RAISE; END IF; Here's a m uch bet t er approach: EXCEPTION WHEN DUP_VAL_ON_INDEX THEN update_instead (...); WHEN OTHERS THEN err.log; RAISE;

95

Be n e fit s Your code m ore clearly st at es what y ou expect t o have happen and how you want t o handle your errors. That m akes t he code easier t o m aint ain and enhance. You av oid hard- coding error num bers in your check s against SQLCODE.

5 .3 D e cla rin g Ex ce pt ion s I n addit ion t o raising and handling except ions, you also m ust pay at t ent ion t o how and when t o declare ex cept ions and t o assign nam es t o error num bers.

EXC- 1 2 : St an dar dize n a m e d applica t ion e x ce pt ion s in pa ck a ge spe cifica t ion s.

I t 's likely t hat a developer will raise a cert ain error or errors in t he process of using your code, y ou should declare ex cept ions in t he package specificat ion. Users of your code can t hen t rap and handle t hose errors by nam e. This approach is used m ost oft en for applicat ion- specific except ions, but if your program m ight also raise an Oracle ex cept ion t hat has not been given a nam e in t he STANDARD or ot her built - in package, you can give it a nam e and associat e it wit h t hat num ber. See [ EXC- 14: Use t he EXCEPTI ON_I NI T pragm a t o nam e syst em except ions t hat m ight be raised by your program . ] for m ore det ails.

Ex a m ple Suppose t hat m y ov erdue.analyze_st at us procedure m ight raise one of t he following t wo errors: " Ov erdue m ore t han one m ont h" I hav e defined t his as a serious error in m y dat abase. I m ust im m ediat ely st op processing and raise an except ion. " Fet ch out of sequence" This is an Oracle error t hat occurs when som et hing goes wrong in m y cursor FOR loop. I t hen add t hese lines t o m y ov erdue package: CREATE OR REPLACE PACKAGE overdue IS excessive_lateness EXCEPTION; PRAGMA EXCEPTION_INIT ( excessive_lateness, -20700);

96

Term Fly Presents

http://www.flyheart.com

fetch_out_of_sequence EXCEPTION; PRAGMA EXCEPTION_INIT ( fetch_out_of_sequence, -1003);

Be n e fit s Program m ers hav e a bet t er sense of what t o ex pect —and what kind of except ion handlers t o w rit e—when using your code.

Re sou r ce s sqlerr.pk s: Package of predefined except ions t hat com m only occur when working wit h SQL, and especially dynam ic SQL, inside PL/ SQL.

EXC- 1 3 : D ocu m e n t all pa ck age e x ce pt ion s by m odu le in pa ck a ge spe cifica t ion s.

Different program s m ay well raise different ex cept ions. You need t o com m unicat e t his inform at ion clearly t o users of y our code so t hey know what t o expect and what t o code for. PL/ SQL doesn't offer a st ruct ured w ay t o do t his as part of t he language ( Java, for exam ple, does precisely t hat ) . So y ou need t o com e up wit h a st andard conv ent ion for including such docum ent at ion in your code.

Ex a m ple The following package specificat ion offers one sim ple exam ple of how y ou m ight docum ent t he except ions individual program s m ight raise: CREATE OR REPLACE PACKAGE overdue IS PROCEDURE analyze_status (...); /* analyze_status can raise: overdue.excessive_lateness overdue.fetch_out_of_sequence */ FUNCTION count_by_borrower (...) RETURN INTEGER; /* count_by_borrower can raise: NO_DATA_FOUND borrower.does_not_exist */

Be n e fit s Program m ers hav e a bet t er sense of what t o ex pect —and what kind of except ion handlers t o w rit e—when using your code.

Ch a lle n ge s 97

I t can be hard t o t ak e t he necessary t im e t o do t his. Define it as part of y our st andard docum ent at ion for packages; t hen use code walkt hroughs t o ident ify om issions.

EXC- 1 4 : Use t h e EXCEPTI ON _ I N I T pr a gm a t o n am e syst em ex ce pt ion s t h a t m igh t be r aise d by you r pr ogr am .

There are hundreds upon hundreds of Oracle error codes and m essages. Only a sm all handful are act ually assigned a nam e for use in t he PL/ SQL language. This assignm ent occurs in t he STANDARD package; here, for exam ple, is t he code defining t he first t hree nam ed except ions in t hat package: CURSOR_ALREADY_OPEN exception; pragma EXCEPTION_INIT(CURSOR_ALREADY_OPEN, '-6511'); DUP_VAL_ON_INDEX exception; pragma EXCEPTION_INIT(DUP_VAL_ON_INDEX, '-0001'); TIMEOUT_ON_RESOURCE exception; pragma EXCEPTION_INIT(TIMEOUT_ON_RESOURCE, '-0051'); And since STANDARD is t he default package, you can t hen w rit e code in your own program s like: EXCEPTION WHEN CURSOR_ALREADY_OPEN THEN ... You can also give nam es t o sy st em ex cept ions, and you should do so w hen your program m ight raise one of t hose except ions.

Ex a m ple When I built PLVdyn, a PL/ Vision package t hat m akes it easier t o ex ecut e dynam ic SQL, I gav e nam es t o a num ber of errors t hat com m only occur when const ruct ing and ex ecut ing SQL st rings wit h DBMS_SQL. I realized t hat no m at t er how good m y code was, a user m ight pass a dynam ic st ring t hat , for exam ple, referenced an undefined t able or colum n. Wit hout a nam ed except ion, y ou ended up writ ing code like t his: BEGIN cur := PLVdyn.open_and_parse ('SELECT ... '); ... EXCEPTION WHEN OTHERS THEN IF SQLCODE = -904 THEN -- invalid column name ... ELSIF SQLCODE = -942 THEN - no such table

98

Term Fly Presents

http://www.flyheart.com

and so on. So I added a sequence of EXCEPTI ON_I NI T pragm as, som e of which are shown here: CREATE OR REPLACE PACKAGE PLVdyn IS /* Exceptions */ no_such_table EXCEPTION; PRAGMA EXCEPTION_INIT (no_such_table, -942); invalid_table_name EXCEPTION; PRAGMA EXCEPTION_INIT (invalid_table_name, -903); invalid_column_name EXCEPTION; PRAGMA EXCEPTION_INIT (invalid_column_name, -904); Now PL/ Vision developers can w rit e code like t his: BEGIN cur := PLVdyn.open_and_parse ('SELECT ... '); ... EXCEPTION WHEN PLVdyn.invalid_column_name THEN ... WHEN PLVdyn.no_such_table THEN

Be n e fit s You can t rap except ions by nam e inst ead of using condit ional logic inside t he WHEN OTHERS clause. The result is code t hat is m uch easier t o read and also t o m aint ain, since t he logic for handling each except ion is clearly segregat ed int o different handlers.

Ch a lle n ge s None. I n fact , you can always add t hese EXCEPTI ON_I NI T pragm as aft er t he fact ( aft er, t hat is, you have writ t en your package) and t hen ret rofit exist ing ex cept ion sect ions t o use t he newly nam ed ex cept ions.

Re sou r ce s sqlerr.pk s : Package of predefined ex cept ions t hat com m only occur when working wit h SQL, and especially dynam ic SQL, inside PL/ SQL.

Ch a pt e r 6 . W r it in g SQL in PL/ SQL One of t he reasons dev elopers like PL/ SQL so m uch is t hat it 's so easy t o w rit e SQL inside a PL/ SQL block of code. One of t he m ost dangerous aspect s of PL/ SQL is t hat it 's so easy t o w rit e SQL inside a PL/ SQL block of code. Paradox? I rony? SQL is, in fact , a sort of Achilles heel of PL/ SQL developm ent . Now, given t hat PL/ SQL was first conceived as a procedural language ext ension t o SQL,

99

such a st at em ent should raise ey ebrows even furt her. The sim ple fact of t he m at t er, howev er, is t hat if you aren't careful about how you place SQL st at em ent s in your PL/ SQL code, y ou will end up wit h applicat ions t hat are difficult t o opt im ize, debug, and m anage over t im e. You should follow several sim ple ( t o st at e) guidelines when w orking wit h SQL inside PL/ SQL. I collect all of t hese t oget her in t he following m et a- best pract ice of t his chapt er.

SQL- 0 0 : Est a blish a n d follow cle a r ru le s for h ow t o w rit e SQL in you r a pplica t ion . •

Nev er repeat a SQL st at em ent .



Encapsulat e all SQL st at em ent s behind a procedural int erface ( usually a package) .



Writ e y our code assum ing t hat t he underlying dat a st ruct ures will change.



Take advant age of PL/ SQL- specific enhancem ent s for SQL.

All t hese t opics—wit h ex am ples, benefit s, and challenges—are explored in t he m ore det ailed best pract ices in t his chapt er.

6 .1 Gen e r al SQL a n d Tr a n sa ct ion M a n agem en t This sect ion cont ains som e general- purpose best pract ices for writ ing SQL st at em ent s and som e specific best pract ices for handling t ransact ions.

SQL- 0 1 : Qu alify PL/ SQL va ria ble s w it h t h eir scope n am e s w h en r e fe r en ce d in side SQL st a t e m en t s.

You could declare a variable t hat has t he sam e nam e as a t able, a colum n, or a view. The PL/ SQL com piler won't get confused, but y ou m ight , and your SQL st at em ent s inside PL/ SQL m ight not work as int ended. So y ou should always m ake sure t hat t here is no am biguit y bet ween SQL and PL/ SQL ident ifiers. The best way t o do t his is t o qualify all references t o PL/ SQL variables wit h t heir scope nam e.

Ex a m ple Consider t he following block: CREATE OR REPLACE PROCEDURE show_fav_flavor ( pref_type IN VARCHAR2)

100

Term Fly Presents

http://www.flyheart.com

IS pref VARCHAR2(100); BEGIN SELECT preference INTO pref FROM personal_preferences PP WHERE PP.pref_type = pref_type; pl (pref); END; You m ight t hink t hat t he WHERE clause rest rict s t he query t o only t hose row s where pref_t ype equals t he value passed in t hrough t he param et er. I n fact , it 's no different logically t han " 1 = 1" . SQL always t akes precedence over PL/ SQL when resolving ident ifiers. There are t wo solut ions t o t his problem : • •

• • •

Use prefixes/ suffixes on variable and param et er nam es t o dist inguish t hem from colum n and t able nam es, as in: CREATE OR REPLACE PROCEDURE show_fav_flavor ( pref_type_in IN VARCHAR2) Always qualify references t o PL/ SQL elem ent s inside t he SQL st at em ent , as in: SELECT preference INTO pref FROM personal_preferences PP WHERE PP.pref_type = show_fav_flavor.pref_type;

I recom m end t he second approach. I t requires m ore t yping, but it 's foolproof. Wit h t he first solut ion, for ex am ple, a DBA can conceivably add a colum n t o t he personal_preferences t able called pref_t ype_in and com plet ely m uck up m y code!

Be n e fit s The behavior of your SQL st at em ent s will be predict able and consist ent ov er t im e, regardless of changes t o t he underlying dat a st ruct ures.

Ch a lle n ge s You hav e t o writ e m ore code, qualifying all references t o t hose variables inside SQL. For SQL inside anonym ous blocks, y ou need t o creat e a label for t he block in t he form < < blocknam e> > so y ou hav e a nam e t o use inside t he SQL st at em ent .

SQL- 0 2 : Use in cr e m e n t al COM M I Ts t o a void r ollba ck se gm en t er r or s w h en ch a n gin g la rge n u m be rs of r ow s.

I t 's v ery easy t o issue an UPDATE st at em ent t hat can ( t heoret ically) change one m illion rows or 10 m illion row s. I t 's m ore of a challenge t o get t hat st at em ent t o

101

succeed wit hout running out of rollback segm ent space. And t hese errors are oft en hard t o predict , because t hey depend on t he volum e of dat a for a specific run. I f y ou have t his problem , y ou should swit ch t o increm ent al com m it s: issue a COMMI T st at em ent ev ery 1,000 or 10,000 row s—what ever level work s for y our rollback segm ent s.

Ex a m ple You can declare a count er variable and updat e t he variable wit h each ex ecut ion of t he loop body. I f y ou process dat a from wit hin a cursor FOR loop, you can also t ak e advant age of t he built - in % ROWCOUNT at t ribut e, as shown here: DECLARE c_commit_plateau CONSTANT PLS_INTEGER := 10000; CURSOR my_cur IS SELECT * FROM my_table; BEGIN FOR my_rec IN my_cur LOOP INSERT INTO temp_data VALUES (my_rec.id); IF (MOD (my_cur%rowcount, c_commit_plateau) = 0) THEN COMMIT WORK; END IF; END LOOP; COMMIT WORK; END; You can also build an API t o t he PL/ SQL COMMI T st at em ent t hat aut om at ically handles increm ent al com m it s and adds value ( logging, on- off t oggles) t o COMMI T. You can find an exam ple of such a package in t he PL/ Vision library.

Be n e fit s Make your code m ore robust by av oiding hard- t o- predict rollback segm ent errors in your program s. When y ou use % ROWCOUNT, t here's no need t o declare a local variable t o k eep count of how m any records hav e been processed.

Ch a lle n ge s You need t o ident ify pot ent ial problem areas in your code ( far bet t er t han wait ing unt il a program fails) .

102

Term Fly Presents

http://www.flyheart.com

Calling t he MOD funct ion t hrough ev ery it erat ion of t he loop is likely t o be slow er t han checking t he value of a count er.

Re sou r ce s plvcm t .sps and plvcm t .spb : I n t he PL/ Vision Lit e v ersion of t he PLVcm t package.

SQL- 0 3 : Use a u t on om ou s t r an sact ion s t o isola t e t h e effe ct of COM M I Ts a n d ROLLBACKs ( Or a cle 8 i) .

A new feat ure in Oracle8i called aut onom ous t ransact ions allows y ou t o m ak e and save ( or roll back) changes wit hin a single PL/ SQL block —wit hout affect ing t he out er or m ain t ransact ion. To m ake a PL/ SQL block an aut onom ous t ransact ion, sim ply include t his st at em ent in t he declarat ion sect ion of t he block: PRAGMA AUTONOMOUS_TRANSACTION; You can use t his st at em ent in any procedure and funct ion and in any non- nest ed anonym ous block.

Ex a m ple CREATE OR REPLACE PROCEDURE log_error ( code IN INTEGER, msg IN VARCHAR2) AS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN INSERT INTO error_log (errcode, errtext, created_on, created_by) VALUES (code, msg, SYSDATE, USER); COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; END;

Be n e fit s Wit h aut onom ous t ransact ions, you can w rit e and save m essages in an dat abase log wit hout affect ing t he m ain t ransact ion. You can ex ecut e from SQL PL/ SQL funct ions t hat change t he dat abase.

103

You can writ e PL/ SQL com ponent s or cart ridges t hat behav e nicely in a dist ribut ed com put ing environm ent .

Re sou r ce s log.pkg and log.t st : A sim ple logging package t hat uses aut onom ous t ransact ions, and a com panion script you can use t o t est t he funct ionalit y.

6 .2 Qu e ryin g D at a fr om PL/ SQL The best pract ices in t his sect ion apply when you are querying dat a from PL/ SQL program s.

SQL- 0 4 : Pu t sin gle - r ow fe t ch e s in side fu n ct ion s; n e ver h a rd- code a qu e ry in you r block .

Always put your single- row query inside a funct ion, and t hen call t he funct ion t o ret urn t he inform at ion y ou need ( whet her it 's a scalar value, an ent ire record, or ev en a collect ion) t hrough t he RETURN clause.

Ex a m ple I nst ead of w rit ing code like t his: BEGIN SELECT title INTO l_title FROM book WHERE isbn =isbn_in;

-- HARD-CODED -- QUERY... -- BAD IDEA!

you should creat e a funct ion, ideally wit hin a " t able encapsulat ion pack age" : PACKAGE te_book IS FUNCTION title (isbn_in IN book.isbn%TYPE) RETURN book.title%TYPE; Now your applicat ion code look s like t his: BEGIN l_title := te_book.title (isbn_in);

Be n e fit s Opt im al perform ance: The query is w rit t en once, presum ably by t he dev eloper who best knows how t o w rit e it . Since t here is a single physical represent at ion of t he query in code, t he parsed v ersion of t he cursor is cached and used repeat edly.

104

Term Fly Presents

http://www.flyheart.com

Easy m aint enance: I f you need t o change t he query , y ou only have t o do it in one place. Consist ent error handling: I ndividual developers don't hav e t o rem em ber t o writ e handlers for NO_DATA_FOUND and TOO_MANY_ROWS.

Ch a lle n ge s Discipline is required in a m ult i- person t eam environm ent t o ensure t hat t he t eam has at least one person ov erseeing t his t ype of encapsulat ion and t hat t he whole t eam adheres t o t his st andard. There will be a larger volum e of code t o writ e and m anage ( your DBA m ust size t he Syst em Global Area accordingly) . Explore t he possibilit ies of generat ing t hese funct ions from t he dat a dict ionary.

SQL- 0 5 : H ide r elian ce on t h e du a l t a ble .

This is a special case of [ SQL- 04: Put single- row fet ches inside funct ions; never hard- code a query in your block.] but is wort h m ent ioning. The dual t able is a " dum m y" t able t hat is used by Oracle it self and by m any dev elopers t o access funct ionalit y in t he SQL engine t hat is ot herwise not available in PL/ SQL. Use of t he dual t able is, t herefore ( and by definit ion) a workaround or " kludge." We all know we hav e t o do t hese t hings, but we also know or hope t hat ov er t im e, we will no longer have t o do t hem . So hide your kludges behind a funct ion or procedure and t hen, when t hey are no longer needed, you can change t he im plem ent at ion wit hout affect ing t hose usages.

Ex a m ple I nst ead of: DECLARE my_id INTEGER; BEGIN SELECT patient_seq.NEXTVAL INTO my_id FROM dual; you should build yourself a funct ion: CREATE OR RETURN IS retval BEGIN SELECT INTO FROM

REPLACE FUNCTION next_patient_id patient.patient_id%TYPE patient.patient_id%TYPE; patient_seq.nextval retval dual;

105

RETURN retval; END; And t hen you only need t o w rit e t his t o get y our next prim ary key value: DECLARE my_id INTEGER; BEGIN my_id := next_patient_id;

Be n e fit s You gain t he abilit y t o rem ove workarounds and kludges from code m ore easily as underlying soft ware im prov es.

Re sou r ce s next seq.sf : A funct ion t hat uses dynam ic SQL t o offer a single funct ion t hat ret riev es t he n t h NEXTVAL from any sequence y ou specify.

SQL- 0 6 : D e fin e m u lt i- r ow cu r sor s in pa ck a ge s so t h e y ca n be u sed fr om m u lt iple pr ogr am s.

Creat e a package t o hold all t he m ult iple- row queries for a given business ent it y ( which m ay be m ade up of one or m ore t ables and views) . You can t hen open, fet ch, and close t hose cursors from m ult iple program s. Here's an exam ple of such a package: PACKAGE book_pkg IS CURSOR allbooks IS SELECT * FROM book; CURSOR books_by_category ( category_in IN book.category%TYPE) IS SELECT * FROM book WHERE category = category_in; and here is a use of a packaged cursor: BEGIN OPEN book_pkg.books_by_category ( 'THRILLER'); LOOP FETCH book_pkg.books_by_category INTO thrillers_rec; ... END LOOP;

106

Term Fly Presents

http://www.flyheart.com

CLOSE book_pkg.books_by_category; END;

Be n e fit s I ndividual developers don't hav e t o learn all t he ins and out s of pot ent ially com plex, m ult ij oin queries. When changes t o queries are required, y ou only have t o go t o one place in your code t o m ak e t he fix.

Ch a lle n ge s Packaged cursors are persist ent ; t hey st ay open unt il you explicit ly close t hem or unt il you disconnect ( unless y ou use t he SERI ALLY_REUSABLE pragm a) . This is different from locally declared cursors, which close aut om at ically when t he current block ends. Team processes m ust be defined. Developers need t o know where t o go t o find t he code, whom t o go t o when it needs changing, and so on.

SQL- 0 7 : Fe t ch in t o cu r sor r e cords, n e ve r in t o a h a r d- coded list of va r iables.

Whenev er y ou fet ch dat a from a cursor, whet her it 's an explicit cursor or a cursor variable, you should fet ch int o a record defined from t hat cursor wit h % ROWTYPE.

Ex a m ple Suppose I hav e declared a cursor in a package as follows: PACKAGE book_pkg IS CURSOR books_by_category ( category_in IN book.category%TYPE) IS SELECT title, author FROM book WHERE category = category_in; END book_pkg; Now I want t o fet ch inform at ion from t his cursor. I f I fet ch int o individual variables like t his: DECLARE l_title book.title%TYPE; l_author book.author%TYPE; BEGIN OPEN book_pkg.books_by_category ('SCIFI'); FETCH book_pkg.books_by_category INTO

107

l_title, l_author; t hen I am hard- coding t he num ber of values ret urned by a cursor, as w ell as t he dat at ypes of t he individual variables. ( I could use % TYPE, but I am m ore likely t o be lazy.) This is a dangerous assum pt ion t o m ak e. What if t he owner of t he book_pkg pack age decides t o add anot her colum n t o t he SELECT list ? My code will t hen fail t o com pile. I f, on t he ot her hand, I writ e m y code like t his: DECLARE scifi_rec book_pkg.books_by_category%ROWTYPE; BEGIN OPEN book_pkg.books_by_category ('SCIFI'); FETCH book_pkg.books_by_category INTO scifi_rec; t hen, if t he cursor ev er changes, m y code will/ can be recom piled, and it will aut om at ically adapt t o t he new cursor st ruct ure.

Be n e fit s Code adapt s aut om at ically t o changes in t he underlying cursor st ruct ure. You w rit e less code, since you don't hav e t o declare individual variables.

SQL- 0 8 : Use COUN T on ly w h en t h e a ct u a l n u m be r of occu r r e n ces is n e e de d.

Don't use t he COUNT funct ion t o answer eit her of t he following quest ions: •

I s t here at least one row m at ching cert ain crit eria?



I s t here m ore t han one row m at ching cert ain crit eria?

I nst ead, use an explicit cursor inside a funct ion. You should use COUNT only when you need t o answer t he quest ion: " How m any rows m at ch a cert ain crit eria?"

Ex a m ple Suppose I hav e been asked t o w rit e a program t hat ret urns TRUE if t here is at least one book in a given cat egory. I could writ e it like t his: CREATE OR REPLACE FUNCTION atleastone ( category_in IN book.category%TYPE)

108

Term Fly Presents

http://www.flyheart.com

RETURN BOOLEAN IS numbooks INTEGER; BEGIN SELECT COUNT(*) INTO numbooks FROM book WHERE category = category_in; RETURN (numbooks > 0); END; But I am asking t he RDBMS t o do lot s of unnecessary w ork . I t m ight find, for inst ance, t hat t here are 12 m illion books in t he NON- FI CTI ON cat egory . A bet t er approach is: CREATE OR REPLACE FUNCTION atleastone ( category_in IN book.category%TYPE) RETURN BOOLEAN IS retval BOOLEAN; CURSOR category_cur SELECT 1 FROM book WHERE category = category_in; BEGIN OPEN category_cur; FETCH category_cur INTO category_rec; retval := category_cur%FOUND; CLOSE category_cur; RETURN retval; END; I n ot her w ords: all I have t o do is see if t here is a single row , and I am done.

Be n e fit s Wit h t his pract ice, y ou get opt im al perform ance out of your query . The readabilit y of your code also im prov es, since it 's a m ore accurat e t ranslat ion of t he requirem ent .

Ch a lle n ge s You will writ e a bit m ore code, especially if you t ake t he t im e t o put y our query inside a funct ion, as recom m ended in [ SQL- 04: Put single- row fet ches inside funct ions; never hard- code a query in your block. ] .

Re sou r ce s at least one.sql : A SQL* Plus script com paring different approaches t o answering t he quest ion " I s t here at least one em ployee in depart m ent 20?"

109

SQL- 0 9 : Use a cu r sor FOR loop t o fe t ch a ll r ow s in a cu r sor u n con dit ion a lly.

The cursor FOR loop const ruct is a w onderful addit ion t o t he PL/ SQL language, reflect ing t he t ight int egrat ion bet ween SQL and PL/ SQL. Use it whenev er you need t o fet ch ev ery single row ident ified by t he cursor, but don't use it if you have t o condit ionally exit from t he loop.

Ex a m ple I need t o display t he t ot al num ber of books sold for each of m y PL/ SQL t ext s. That 's easy: DECLARE CURSOR sef_books_cur IS SELECT title, total_count FROM book_sales WHERE author = 'FEUERSTEIN, STEVEN'; BEGIN FOR rec IN sef_books_cur LOOP pl (rec.title || ': ' || rec.total_count || ' copies'); END LOOP; END; Perfect use of a cursor FOR loop! Suppose, on t he ot her hand, t he requirem ent was t his: " Display all t he books and t heir num bers sold unt il t he t ot al reaches 100,000; t hen quit ." I n t his case, I should use a WHI LE loop wit h an EXI T WHEN st at em ent . Here's an exam ple: DECLARE total_sold

PLS_INTEGER := 0;

CURSOR sef_books_cur IS SELECT title, total_count FROM book_sales WHERE author = 'FEUERSTEIN, STEVEN'; rec sef_books_cur%ROWTYPE; stop_loop BOOLEAN; BEGIN OPEN sef_books_cur; LOOP FETCH sef_books_cur INTO rec; stop_loop := sef_books_cur%NOTFOUND; IF NOT stop_loop THEN pl (rec.title || ': ' || rec.total_count || ' copies'); total_sold := total_sold + rec.total_count;

110

Term Fly Presents

http://www.flyheart.com

stop_loop := total_sold >= 100000; END IF; EXIT WHEN stop_loop; END LOOP; CLOSE sef_books_cur; END;

Be n e fit s The cursor FOR loop sav es you coding effort and does m ore work for y ou—t hat is, it opens, fet ches from , and closes t he cursor. The result ing code is very readable; use of t he FOR loop says t hat you are fet ching all rows from t he cursor.

Ch a lle n ge s Aft er t he END LOOP st at em ent , you can't t ell anyt hing about what happened inside t he loop, such as: " Was anyt hing act ually ret rieved?" or " How m any rows w ere ret rieved?" You hav e t o add your own count ers and variables inside t he body of t he loop t o figure t hat out . I f a dev eloper isn't careful, t he body of code inside t he cursor FOR loop can becom e very large. Since t he FOR loop record isn't referenceable out side t he loop, it m ay be hard t o apply st ep- wise refinem ent wit h local m odules t o m ake t he code m ore m anageable. See [ MOD- 03: Lim it execut ion sect ion sizes t o a single page using m odularizat ion.] for m ore inform at ion about t his t echnique.

SQL- 1 0 : N e ve r u se a cu r sor FOR loop t o fe t ch j u st on e r ow .

I f y ou have a single- row query , y ou can use a cursor FOR loop, but it 's m isleading. A cursor FOR loop is designed t o fet ch all ( m ult iple) row s from a cursor. The only rat ionale for using a cursor FOR loop for a single- row query is t hat you don't hav e t o writ e as m uch code, and t hat is bot h dubious and a lam e ex cuse.

Ex a m ple Doesn't t his look silly: CREATE OR REPLACE FUNCTION book_title ( isbn_in IN book.isbn%TYPE) RETURN book.title%TYPE IS CURSOR title_cur IS SELECT title INTO l_title FROM book WHERE isbn =isbn_in;

111

l_rec title_cur%ROWTYPE; BEGIN FOR rec IN title_cur LOOP l_rec := rec; END LOOP; RETURN l_rec.title; END; I nst ead, use a SELECT I NTO or explicit cursor; for exam ple: CREATE OR REPLACE FUNCTION book_title ( isbn_in IN book.isbn%TYPE) RETURN book.title%TYPE IS CURSOR title_cur IS SELECT title INTO l_title FROM book WHERE isbn =isbn_in; l_rec title_cur%ROWTYPE; BEGIN OPEN title_cur; FETCH title_cur INTO l_rec; CLOSE title_cur; RETURN l_rec.title; END;

Be n e fit s Your code doesn't look silly. I t sat isfies t he requirem ent in t he m ost direct and underst andable way. A cursor FOR loop is less efficient t han eit her a SELECT I NTO or an explicit cursor fet ch.

Re sou r ce s explim pl.pkg and explim pl.sql : Script s t hat com pare t he perform ance of cursor FOR loops t o ot her fet ching m et hods for a single row.

SQL- 1 1 : Spe cify colu m n s t o be u pda t e d in a SELECT FOR UPD ATE st a t e m en t .

Use t he SELECT FOR UPDATE st at em ent t o request t hat lock s be placed on all rows ident ified by t he query . This is done when y ou k now y ou will change som e or all of t hose rows, and y ou don't want anot her session t o change t hem out from under y ou. Specify t he colum ns t o be updat ed so t hat ( a) anyone reading t he code knows t he int ent ions of y our program , and ( b) if your query cont ains a j oin of m ore t han one

112

Term Fly Presents

http://www.flyheart.com

t able, Oracle will lock only t he row s in t hose t ables t hat cont ain any of t he specified colum ns.

Ex a m ple The following code set s t he fav orit e ice cream flavor of t he Feuerst ein fam ily t o ROCKY ROAD, but doesn't lock any rows in t he person t able: DECLARE CURSOR change_prefs_cur IS SELECT PER.name, PREF.name flavor FROM person PER, preference PREF WHERE PER.name = PREF.person_name AND PREF.type = 'ICE CREAM' FOR UPDATE OF PREF.name; BEGIN FOR rec IN change_prefs_cur LOOP IF rec.name LIKE 'FEUERSTEIN%' THEN UPDATE preference SET name = 'ROCKY ROAD' WHERE CURRENT OF change_prefs_cur; END IF; END LOOP; END; /

Be n e fit s You k eep t o a m inim um t he num ber of lock s placed on row s in t ables. You self- docum ent t he behavior of y our code, which is im port ant for t hose who com e t o y our code lat er in it s life t o m aint ain it .

Re sou r ce s forupdat e.sql : Cont ains t he code for t he exam ple in t his sect ion.

SQL- 1 2 : Pa r a m e t e r ize ex plicit cu r sor s.

Cursors ret urn inform at ion, j ust as funct ions do, and t hey can accept param et ers j ust as funct ions do ( but only I N param et ers) . By defining your explicit cursors t o accept param et erized values, t hese cursors are m ore easily reused in different circum st ances and program s. This added v alue becom es m ost apparent when y ou define cursors in package specificat ions.

Ex a m ple I nst ead of t his:

113

DECLARE CURSOR r_and_d_cur IS SELECT last_name FROM employee WHERE department_id = 10; BEGIN OPEN r_and_d_cur; m ov e y our cursor t o a package: CREATE OR REPLACE PACKAGE dept_info_pkg IS CURSOR name_cur (dept IN INTEGER) IS SELECT last_name FROM employee WHERE department_id = dept; and t hen open it like t his: BEGIN open dept_info_pkg.name_cur (10); or, even bet t er, do t his t o av oid t he hard- coded lit eral: DECLARE r_and_d_dept CONSTANT PLS_INTEGER := 10; BEGIN open dept_info_pkg.name_cur (r_and_d_dept);

Be n e fit s Applicat ion im provem ent is likely t o im prov e, because param et ers in a cursor are t reat ed as bind variables. So, no m at t er what v alue is passed t o t he cursor, t he SQL st at em ent st ays t he sam e and isn't parsed repeat edly. You will achieve higher lev els of reuse in your applicat ion, reducing m aint enance requirem ent s.

SQL- 1 3 : Use RETURN I N G t o r e t r ie ve in form a t ion a bou t m odifie d r ow s ( Or a cle 8 ) .

The RETURNI NG clause, available in Oracle8 and abov e, allows y ou t o ret riev e inform at ion from rows y ou have j ust m odified wit h an I NSERT, UPDATE, or DELETE st at em ent . This clause allows you t o perform —in a single operat ion—what you w ould previously have done in t wo operat ions ( I NSERT, t hen SELECT, for exam ple) .

Ex a m ple Suppose t hat I am using a sequence t o generat e t he prim ary k ey of t he pat ient t able in m y universal healt h care sy st em . I t hen need t o use t hat new prim ary k ey for anot her operat ion. Prior t o Oracle8, I w ould have w rit t en code like t his:

114

Term Fly Presents

http://www.flyheart.com

INSERT INTO patient (patient_id, last_name, first_name) VALUES (patient_seq.NEXTVAL, 'FEUERSTEIN', 'STEVEN'); SELECT patient_id INTO l_patient_id FROM patient WHERE last_name = 'FEUERSTEIN'; or ev en like t his: SELECT patient_seq.NEXTVAL INTO l_patient_id FROM dual; INSERT INTO patient (patient_id, last_name, first_name) VALUES (l_patient_id, 'FEUERSTEIN', 'STEVEN'); Wit h RETURNI NG, I can collapse t wo st at em ent s int o a single I NSERT st at em ent : INSERT INTO patient (patient_id, last_name, first_name) VALUES (patient_seq.NEXTVAL, 'FEUERSTEIN', 'STEVEN') RETURNING patient_id INTO l_patient_id; You can also use t he RETURNI NG clause in dynam ic SQL and FORALL st at em ent s t o obt ain inform at ion about m ult iple rows affect ed by DML st at em ent s.

Be n e fit s You will see im prov ed perform ance in your applicat ions. Code v olum e will be reduced.

Ch a lle n ge s Your code will not run in versions of Oracle prior t o Oracle8.

Re sou r ce s ret urning.t st : A script com paring t he perform ance of I NSERT- SELECT t o I NSERTRETURNI NG.

SQL- 1 4 : Use BULK COLLECT t o im pr ove pe r form an ce of m u lt i- r ow qu e rie s ( Or a cle 8 i) .

Recognizing t hat you oft en need t o ret urn large num bers of rows from t he dat abase, Oracle8i offers a new BULK COLLECT clause for queries. When you use BULK COLLECT, you ret rieve m ult iple row s of dat a in a single request t o t he RDBMS. The dat a is t hen deposit ed int o a series of collect ions.

Ex a m ple

115

To use BULK COLLECT, you need t o declare collect ions t o hold all t he ret rieved dat a. Then, preface your I NTO clause wit h t he BULK COLLECT k eyw ords, and you are done: CREATE OR REPLACE PROCEDURE process_employees (deptno_in IN dept.deptno%TYPE) RETURN emplist_t IS TYPE numTab IS TABLE OF emp.empno%TYPE; TYPE charTab IS TABLE OF emp.ename%TYPE; TYPE dateTab IS TABLE OF emp.hiredate%TYPE; enos numTab; names charTab; hdates dateTab; BEGIN SELECT empno, ename, hiredate BULK COLLECT INTO enos, names, hdates FROM emp WHERE deptno = deptno_in; ... END process_employees; Or, if you are using an explicit cursor: BEGIN OPEN emp_cur INTO emp_rec; FETCH emp_cur BULK COLLECT INTO enos, names, hdates;

Be n e fit s You will see an im prov em ent ( in som e cases, a dram at ic im provem ent ) in query perform ance.

Ch a lle n ge s You m ust declare a separat e collect ion for each elem ent in t he SELECT list . You m ust be careful when t he SELECT ret urns m any t housands of row s. There could be m any users running t he sam e program in a session, which can lead t o m em ory problem s. Try t o rest rict t he bulk collect ion by using ROWNUM, for inst ance.

6 .3 Ch an gin g D a t a fr om PL/ SQL Wit h PL/ SQL, you can not only query inform at ion from an underlying Oracle dat abase but also change dat a in t ables wit h t he I NSERT, UPDATE, and DELETE operat ions.

SQL- 1 5 : En ca psu la t e I N SERT, UPD ATE, an d D ELETE st a t em e n t s beh in d pr oce du re ca lls.

116

Term Fly Presents

http://www.flyheart.com

Writ e a st andalone procedure or put such procedures inside a single " t able encapsulat ion package," but never, ever em bed DML st at em ent s direct ly wit hin applicat ion code.

Kn ow Th y SQL Take t he " Know Thy SQL" t est : pick a t able, any crit ical t able in your applicat ion schem a. Ask yourself t his quest ion: " Do I know where all or any of t he I NSERT st at em ent s for t his t able appear in m y code?" Chances are t hat you can't answer definit ively, and t hat is because we PL/ SQL developers are som ewhat haphazard about m anaging our SQL st at em ent s. The result ? Trem endous obst acles t o perform ing accurat e im pact analysis on your code from dat abase changes, am ong ot her t hings. Ex a m ple I nst ead of w rit ing an I NSERT as follows: INSERT INTO book ( isbn, title, author) VALUES ( '1-56592-675-7', 'Oracle PL/SQL Programming Guide to Oracle8i Features', 'Feuerstein, Steven'); use a st andalone procedure, as in: add_book ( '1-56592-675-7', 'Oracle PL/SQL Programming Guide to Oracle8i Features', 'Feuerstein, Steven'); or a packaged procedure: te_book.ins ( '1-56592-675-7', 'Oracle PL/SQL Programming Guide to Oracle8i Features', 'Feuerstein, Steven');

Be n e fit s Your applicat ion runs fast er. All program s t hat perform insert s int o a given t able use exact ly t he sam e I NSERT, which result s in less parsing and reduced dem ands on SGA m em ory . Your applicat ion handles DML- relat ed errors consist ent ly. I t 's not up t o individual dev elopers t o writ e error- logging m echanism s or decide how t o deal wit h part icular errors.

117

Ch a lle n ge s You need t o w rit e or generat e m ore procedural code. Your DBA m ay need t o adj ust t he size of t he shared pool area t o handle t he increased volum e of code. You m ay need t o creat e m ult iple updat e procedures, t o m at ch up wit h various com binat ions of colum ns t hat you updat e in your applicat ion.

Re sou r ce s t e_em ployee.pk s and t e_em ploy ee.pkb : Exam ples of t he specificat ion and body of a t able encapsulat ion package.

SQL- 1 6 : Refe r en ce cu r sor a t t r ibu t e s im m e dia t e ly aft e r ex e cu t in g t h e SQL ope r a t ion .

I NSERT, UPDATE, and DELETE st at em ent s are all execut ed as " im plicit cursors" in PL/ SQL. You don't , in ot her w ords, explicit ly declare, open, and process t hese kinds of st at em ent s. You sim ply issue t he I NSERT, UPDATE, or DELETE st at em ent , and t he underlying Oracle SQL engine t akes care of t he cursor m anagem ent . You can obt ain inform at ion about t he result s of t he im plicit operat ion m ost recent ly ex ecut ed in your session by checking any of t he following cursor at t ribut es: At t r i but e

Ret ur ns

SQL% ROWCOUNT Num ber of row s affect ed by t he DML st at em ent SQL% I SOPEN

Always FALSE, since t he cursor is opened and t hen closed im plicit ly

SQL% FOUND

Ret urns TRUE if t he st at em ent affect s at least one row

SQL% NOTFOUND Ret urns FALSE if t he st at em ent affect s at least one row There is only one set of SQL% at t ribut es in a session; t hey always reflect t he last im plicit operat ion perform ed. You should, t herefore, keep t o an absolut e m inim um t he code t hat falls bet ween t he DML operat ion and t he at t ribut e reference. Ot herwise, t he value ret urned by t he at t ribut e m ight not correspond t o t he desired SQL st at em ent , result ing in hard- t o- resolve bugs.

Ex a m ple I hav e t he good fort une t o hav e published eight books wit h O'Reilly & Associat es on t he subj ect of PL/ SQL. Let 's suppose t hat all m y t it les cont ain t he word " PL/ SQL" ( and t hat all O'Reilly books wit h PL/ SQL in t he t it le were w rit t en or co- writ t en by yours t ruly) . We hav e decided t o change t he font size in t he books t o cut t he page count in half and now need t o updat e t he dat abase:

118

Term Fly Presents

http://www.flyheart.com

DECLARE PROCEDURE show_max_count IS l_total_pages PLS_INTEGER; BEGIN SELECT MAX (page_count) INTO l_total_pages FROM book WHERE title LIKE '%PL/SQL%'; DBMS_OUTPUT.PUT_LINE (l_total_pages); END; BEGIN UPDATE book SET page_count = page_count / 2 WHERE title LIKE '%PL/SQL%'; show_max_count; DBMS_OUTPUT.PUT_LINE ( 'Pages adjusted in ' || SQL%ROWCOUNT || ' books.'); END; My int ent ion in t his program is t o display t he num ber of books t hat have been updat ed. Bet w een m y UPDATE and m y reference t o SQL% ROWCOUNT, I call a procedure ( show_m ax_count ) t hat ex ecut es an im plicit SELECT MAX. The reference t o SQL% ROWCOUNT will, t herefore, reflect t he out com e of t he SELECT rat her t han t he UPDATE.

SQL- 1 7 : Ch e ck SQL% ROW COUN T w h e n u pda t in g or r em ovin g da t a t h a t " sh ou ld" be t h e r e.

The SQL% ROWCOUNT cursor at t ribut e ret urns t he num bers of rows affect ed by t he m ost recent I NSERT, UPDATE, or DELETE st at em ent execut ed in your session. Check t his value t o v erify t hat t he act ion com plet ed properly. ( Not e t hat updat es and delet es don't raise an ex cept ion if no rows are affect ed.)

Ex a m ple Let 's suppose t hat t he local library spelled m y nam e incorrect ly when t hey ent ered m y book s int o t heir syst em . Now t hey need t o fix it and t hey want t o m ake sure t hey got t hem all ( eight , including t his t ext ) : BEGIN UPDATE book SET author = 'FEUERSTEIN, STEVEN' WHERE author = 'FEVERSTEIN, STEPHEN'; IF SQL%ROWCOUNT < 8 THEN ROLLBACK; Pl (

119

'Find the rest of his books, rapido!'); END IF; END;

Be n e fit s Your program s will check for and be able t o handle problem s m ore effect ively.

SQL- 1 8 : Use FORALL t o im pr ove per form an ce of colle ct ion - ba sed D M L ( Or a cle 8 i) .

Recognizing t hat you oft en need t o m odify ( insert , delet e, or updat e) large num bers of row s in t he dat abase from wit hin PL/ SQL, Oracle8i offers a new FORALL st at em ent . This st at em ent can dram at ically im prove y our DML perform ance by reducing t he num ber of " cont ext swit ches" bet w een t he PL/ SQL st at em ent execut or and t he SQL engine. Consider using FORALL whenev er y ou perform a DML operat ion wit hin a loop.

Ex a m ple The following cursor FOR loop perform s m any individual row updat es: BEGIN FOR book_rec IN book_pkg.book_cur LOOP UPDATE borrowings SET borrow_date = SYSDATE borrower_id = book_rec.user_id WHERE isbn = book_rec.isbn; END LOOP; END; Now I use FORALL t o accom plish t he sam e t hing, but wit h a single pass t o t he SQL engine and RDBMS: DECLARE TYPE books_t IS TABLE OF borrower.user_id%TYPE; books books_t := books_t( ); TYPE isbns_t IS TABLE OF book.isbn%TYPE; isbns isbns _t := isbns_t( ); BEGIN FOR book_rec IN book_pkg.book_cur LOOP books.EXTEND; isbns.EXTEND; books(books.LAST) := book_rec.user_id; isbns(isbns.LAST) := book_rec.isbn; END LOOP;

120

Term Fly Presents

http://www.flyheart.com

FORALL indx IN books.FIRST .. books.LAST UPDATE borrowings SET borrow_date = SYSDATE borrower_id = books(indx) WHERE isbn = isbns(indx); END; Not ice t hat I st ill need t he cursor FOR loop t o populat e m y collect ions. The t im e it t akes t o do t his, how ev er, is usually m ore t han offset by t he im prov em ent s in UPDATE processing.

Be n e fit s You can significant ly im prov e t he perform ance of m ult irow DML operat ions. I f an error occurs during t he DML act ivit y, any st at em ent s t hat have already been processed aren't rolled back. This is great if you want t o preserv e any changes t hat " got t hrough."

Ch a lle n ge s You m ay have t o conv ert your program s t o populat e collect ions, which are t hen passed t o t he FORALL st at em ent . I f an error occurs during t he DML act ivit y, any st at em ent s t hat have already been processed aren't rolled back. This is a problem if you want ed " all or not hing" for y our DML.

Re sou r ce s bulkt im ing.sql : A script t o com pare perform ance of row- by- row DML and FORALLbased DML.

6 .4 D yn am ic SQL an d D yn a m ic PL/ SQL " Dynam ic" m eans t hat t he SQL st at em ent or PL/ SQL block t hat you ex ecut e is const ruct ed, parsed, and com piled at runt im e, not at t he t im e t he code is com piled. Dynam ic SQL offers a t rem endous am ount of flexibilit y—but also com plexit y. Wit h Oracle8i and abov e, you can use nat ive dynam ic SQL ( NDS) t o t ake care of dynam ic SQL. Prior t o Oracle8i, y ou m ust rely on t he DBMS_SQL built - in package.

SQL- 1 9 : En ca psu la t e dyn am ic SQL pa r sin g t o im pr ove e r r or de t e ct ion a n d clea n u p.

Dynam ic SQL is t ricky; you generally glue t oget her different chunks of t ext ( wit h t he concat enat ion operat or) t o form what y ou hope is a valid SQL or PL/ SQL st at em ent .

121

Eit her t hrough program m er error or user error, you can end up wit h a bad chunk of SQL, result ing in a parse error. To ident ify and fix t hese errors, you should creat e y our own parsing " engine" on t op of DBMS_SQL.PARSE and t he NDS st at em ent s. This program t raps and displays error inform at ion, and cleans up any cursors.

Ex a m ple This t echnique is m ost crucial for DBMS_SQL. Don't ever call DBMS_SQL.PARSE direct ly in your program . I nst ead, call your own parse encapsulat or. Why would you bot her t o do t his? Consider t he following block of code. I t leaves a DBMS_SQL cursor unclosed and unclosable; you need t o be able t o reference t he dyncur v ariable in t he call t o DBMS_SQL.CLOSE_CURSOR, but t hat variable is erased once t he except ion is propagat ed: DECLARE dyncur PLS_INTEGER := DBMS_SQL.open_cursor; BEGIN -- Whoops, forget the FROM clause! DBMS_SQL.parse ( dyncur, 'select * dual', DBMS_SQL.native); END; Here's a v ery sim ple ex am ple of an encapsulat ion for DBMS_SQL.PARSE: CREATE OR REPLACE FUNCTION open_and_parse ( dynsql_in IN VARCHAR2, dbms_mode_in IN INTEGER := NULL) RETURN INTEGER IS dyncur INTEGER; BEGIN dyncur := DBMS_SQL.OPEN_CURSOR; DBMS_SQL.PARSE (dyncur, dynsql_in, NVL (dbms_mode_in, DBMS_SQL.NATIVE)); RETURN dyncur; EXCEPTION WHEN OTHERS THEN DBMS_SQL.CLOSE_CURSOR (dyncur); pl (SQLERRM); pl (dynsql_in); RETURN NULL; END; / See Resources for a m ore com prehensive solut ion wit h DBMS_SQL. Here's t he nat ive dynam ic SQL equivalent : CREATE OR REPLACE PROCEDURE exec_immed (

122

Term Fly Presents

http://www.flyheart.com

dynsql_in IN VARCHAR2) AUTHID CURRENT_USER IS BEGIN EXECUTE IMMEDIATE dynsql_in; EXCEPTION WHEN OTHERS THEN pl (SQLERRM) pl (dynsql_in); END; /

Be n e fit s You can ident ify and fix errors in your program , or t rain your users t o use t he int erface t o y our dynam ic SQL, m ore effect ively. You will not inadvert ent ly leave open DBMS_SQL cursors t hat are unclosable in your session.

Ch a lle n ge s Wit h DBMS_SQL ( prior t o Oracle8i ) , any SQL st at em ent passed t o open_and_parse is parsed under t he privileges of t he owner of open_and_parse. You should, t herefore, inst all t his program in every schem a t hat want s t o use it . Or, if y ou are running Oracle8i and st ill using DBMS_SQL, use t he AUTHI D CURRENT_USER clause t o ensure t hat t he program runs under t he invoker's aut horit y. Wit h NDS, you can't separat e t he parse and ex ecut e phases; it 's all done by EXECUTE I MMEDI ATE. That m akes it hard t o w rit e a t ruly generic program t o handle any SQL st ring ( you hav e t o account for t he USI NG and I NTO clauses) . The general principle st ill applies, howev er: t rap, handle, and display dynam ic SQL errors !

Re sou r ce s openprse.pkg : A package t hat allocat es new DBMS_SQL cursors only when necessary , and displays SQLERRM and t he SQL st ring if a parse error occurs.

SQL- 2 0 : Bin d, do n ot con ca t e n at e , va r iable va lu e s in t o dyn a m ic SQL st rin gs.

When y ou bind a variable value int o a dynam ic SQL st ring, you insert a " placeholder" int o t he st ring. This allows Oracle t o parse a " generic" version of t hat SQL st at em ent , which can be used ov er and ov er again, regardless of t he act ual value of t he variable, wit hout repeat ed parsing. On t he ot her hand, if you concat enat e, t hen ev ery t im e t he value you concat enat e changes, t he physical SQL st at em ent changes, causing excessive parsing.

123

You can bind only variable values. You can't bind in t he nam es of t ables or colum ns, nor can you bind in part s of a SQL st at em ent st ruct ure, such as t he ent ire WHERE clause. I n t hese cases, you m ust use concat enat ion. Ex a m ple Here's an exam ple of binding wit h DBMS_SQL. This program updat es any num eric colum n in t he specified t able, based on t he supplied nam e: CREATE OR REPLACE PROCEDURE updnumval ( tab_in IN VARCHAR2, namecol_in IN VARCHAR2, numcol_in IN VARCHAR2, name_in IN VARCHAR2, val_in IN NUMBER) IS cur PLS_INTEGER; fdbk PLS_INTEGER; BEGIN cur := open_and_parse ( 'UPDATE ' || tab_in || ' SET ' || numcol_in || ' = :val WHERE ' || namecol_in || ' LIKE :name'); DBMS_SQL.BIND_VARIABLE (cur, 'val', val_in); DBMS_SQL.BIND_VARIABLE (cur, 'name', name_in); fdbk := DBMS_SQL.EXECUTE (cur); DBMS_SQL.CLOSE_CURSOR (cur); END; / Here's one possible usage of t his procedure: SQL> exec updnumval ('emp', 'ename', 'sal', 'S%', 5000)

Be n e fit s Your Syst em Global Area requires less m em ory for t he dynam ic SQL cursors. Applicat ion perform ance im proves due t o reduced parsing. You will find it easier and less bug- prone t o w rit e dynam ic SQL code.

Re sou r ce s

124

Term Fly Presents

http://www.flyheart.com

1. updnval2.pro : I m plem ent at ion of t he updnum val program using concat enat ion so t hat y ou can com pare t he com plexit y of t he im plem ent at ions. 2. effdsql.t st : A script t hat allows you t o com pare perform ance of repet it ive parsing using concat enat ion wit h a single parse t hat relies on binding inst ead.

SQL- 2 1 : Soft - code t h e m a x im u m len gt h of colu m n s in D BM S_ SQL.D EFI N E_ COLUM N ca lls.

When y ou call DBMS_SQL.DEFI NE_COLUMN t o define a VARCHAR2 colum n, you m ust provide t he m axim um lengt h of t he st ring t hat will be passed back t o y our program . I deally, we'd use an at t ribut e like % COLLEN t o aut om at ically draw t hat value from t he dat a dict ionary. There is, unfort unat ely, no such at t ribut e. As a consequence, w e usually sigh and hard- code a m axim um lengt h. Rat her t han do t hat , creat e a package specificat ion and place all colum n lengt hs you need t o reference t here. This way, if t hose lengt hs change, you can updat e j ust t he one package. You can also generat e t his package specificat ion direct ly from t he dat a dict ionary ( see Resources) .

Ex a m ple I creat e a " colum n lengt h" package: CREATE OR REPLACE PACKAGE collen IS city CONSTANT INTEGER := 15; state CONSTANT INTEGER := 2; END collen; And I now reference t hose const ant s whenev er I call DBMS_SQL.DEFI NE COLUMN: DBMS_SQL.DEFINE_COLUMN ( cursor_handle, 1, city, collen.city); DBMS_SQL.DEFINE_COLUMN ( cursor_handle, 2, state, collen.state);

Be n e fit s You av oid hard- coding colum n lengt hs. I f a colum n lengt h changes, y ou updat e t he value only in t he package of nam ed const ant s.

Ch a lle n ge s You hav e t o build and m aint ain t he colum n lengt h package( s) . Code generat ion will m ake t he difference here.

Re sou r ce s

125

genlenpkg.pro : A program t hat generat es t he colum n lengt h package for t he specified t able ( VARCHAR2 colum ns only) . Here's an exam ple of t he out put from t he genlenpkg procedure: SQL> exec genlenpkg ('employee') CREATE OR REPLACE PACKAGE employee$collen AS LAST_NAME CONSTANT PLS_INTEGER := 15; FIRST_NAME CONSTANT PLS_INTEGER := 15; MIDDLE_INITIAL CONSTANT PLS_INTEGER := 1; ENAME CONSTANT PLS_INTEGER := 30; CREATED_BY CONSTANT PLS_INTEGER := 100; CHANGED_BY CONSTANT PLS_INTEGER := 100; END PACKAGE employee$collen;

SQL- 2 2 : Apply t h e in vok e r righ t s m e t h od t o a ll st or e d code t h a t e x e cu t es dyn am ic SQL ( Or a cle 8 i) .

Whenev er y ou creat e a st ored program ( st andalone or wit hin a package) t hat parses a dynam ic SQL st at em ent , you should define t hat program wit h t he " invok er right s" m odel. This is done by adding t he following clause t o t he program header: AUTHID CURRENT_USER This feat ure is available only in Oracle8i and above. This clause ensures t hat t he dynam ic SQL st ring is parsed under t he aut horit y of t he schem a current ly running t he program , which is alm ost always t he desired behavior.

Ex a m ple I f I w ere t o creat e a reusable program t o ex ecut e any DDL st at em ent , I would m ake cert ain it used t he AUTHI D st at em ent as follows: CREATE OR REPLACE PROCEDURE runddl ( ddl_in in VARCHAR2) AUTHID CURRENT_USER IS BEGIN EXECUTE IMMEDIATE ddl_in; EXCEPTION WHEN OTHERS THEN pl (SQLERRM) pl (ddl_in); RAISE; END; /

Be n e fit s

126

Term Fly Presents

http://www.flyheart.com

You can build and share generic dynam ic SQL ut ilit ies m ore easily. Dev elopers don't have t o w orry about which schem a owns t he ut ilit y and whet her or not t he request ed operat ion will affect som eone else's schem a.

Ch a lle n ge s This feat ure is available only in Oracle8i.

Re sou r ce s runddl.pro and runddl81.pro : Generic DDL engine in bot h DBMS_SQL and NDS.

SQL- 2 3 : Form a t dyn am ic SQL st r in gs so t h ey ca n be e asily r ea d a n d m ain t ain e d.

When building long and possibly com plex dynam ic SQL st at em ent s, y ou should apply t he sam e form at t ing rules as are applied t o st at ic code. These st rings are oft en t he result of m ult iple concat enat ions, so t hey st art off being less readable t han st at ic code. Don't com pound t he problem by t reat ing t his dynam ic SQL as sim ply a set of concat enat ed st rings. Consider it , inst ead, as a " program " in and of it self and form at it —as m uch as possible—in t he sam e w ay. Many experienced dynam ic SQL developers build a " t ypical" query or block ( expressing t he pat t ern of code t hey want t o run dynam ically) , and t hen t urn it int o a st ring, wit h all t he linebreaks and indent at ion int act .

Ex a m ple Here's an exam ple of w ell- form at t ed PL/ SQL code: m y very long st ring is brok en int o individual pieces so t hat it can be indent ed nicely. This form at t ing also, unfort unat ely, pleads ignorant t o recognizing t he significance of t hat st ring's cont ent s: DECLARE v_sql VARCHAR2 (32767); BEGIN v_sql := 'DECLARE CURSOR curs_get_orders IS ' || ' SELECT * FROM ord_order; BEGIN ' || ' FOR v_order_rec IN curs_get_orders LOOP ' || ' process_order(v_order_rec.order_id); ' || ' END LOOP; END;'; EXECUTE IMMEDIATE v_sql; END; / Here are t w o alt ernat ive form at t ings of t he sam e assignm ent . I n t he first , I cont inue t o use concat enat ion, but I break up t he st ring and use indent at ion t o present t he block of code according t o m y usual convent ions. I n t he second exam ple, I writ e m y

127

block as a single st ring wit h em bedded carriage ret urns displayed, t o m ake sure it com piles correct ly: v_sql := 'DECLARE ' || 'CURSOR curs_get_orders IS ' || 'SELECT * FROM ord_order; ' || 'BEGIN ' || 'FOR v_order_rec IN curs_get_orders LOOP ' || 'process_order(v_order_rec.order_id); ' || 'END LOOP; ' || 'END;'; v_sql := 'DECLARE CURSOR curs_get_orders IS SELECT * FROM ord_order; BEGIN FOR v_order_rec IN curs_get_orders LOOP process_order(v_order_rec.order_id); END LOOP; END';

Be n e fit s You can read and m aint ain t he code m uch m ore easily.

Ch a lle n ge s I t 's ext rem ely im port ant t o agree upon a st andard approach wit hin your t eam t o form at t ing dynam ic SQL st rings. You m ight ot herwise hav e different dev elopers insert ing different am ount s and kinds of whit espace int o dynam ic SQL st rings, result ing in unnecessary reparsing of logically equivalent cursors.

Ch a pt e r 7 . Pr ogr a m Con st r u ct ion There are t hree kinds of program s ( also known as m odules) in PL/ SQL: Procedure A procedure is a program t hat ex ecut es one or m ore st at em ent s. I t 's called as a st andalone st at em ent . Funct ion A funct ion is a program t hat execut es one or m ore st at em ent s and ret urns a value. I t 's called wit hin an expression ( assignm ent st at em ent , condit ional expression, et c.) . Trigger

128

Term Fly Presents

http://www.flyheart.com

A t rigger is a program whose execut ion is " t riggered" by som e event , usually a SQL operat ion on a t able or colum n wit hin a t able. All of t hese are nam ed, ex ecut able code unit s. A package, as described in Chapt er 8, is a cont ainer for procedures and/ or funct ions, as w ell as dat a. Packages, t herefore, aren't ex ecut able obj ect s t hem selves.

7 .1 St ru ct u r e an d Pa r am e t e rs The best pract ices in t his sect ion offer advice on how t o st ruct ure y our program unit s and how best t o design param et er list s.

M OD - 0 1 : En ca psu la t e an d n am e bu sin e ss ru le s a n d form u la s be h in d fu n ct ion h e a de r s.

This is one of t he m ost im port ant best pract ices you will ever read—and, I hope, follow. The one aspect of any soft ware proj ect t hat never changes is t hat st uff always changes. Business requirem ent s, dat a st ruct ures, user int erfaces: all t hese t hings change and change frequent ly. Your j ob as a program m er is t o w rit e code t hat adapt s easily t o t hese changes. So whenev er y ou need t o express a business rule ( such as, " I s t his st ring a valid I SBN?" ) , put it inside a subrout ine t hat hides t he individual st eps ( which m ight change) and ret urns t he result s ( if any) . And whenev er y ou need a form ula ( such as, " t he t ot al fine for an ov erdue book is t he num ber of day s ov erdue t im es $.50" ) , express t hat form ula inside it s own funct ion.

Ex a m ple Suppose t hat you m ust be at least 10 years old t o borrow books from t he library. This is a sim ple form ula and very unlikely t o change. I set about building t he applicat ion by creat ing t he following t rigger: CREATE OR REPLACE TRIGGER are_you_too_young AFTER insert OR update ON borrower FOR EACH ROW BEGIN IF :new.date_of_birth > ADD_MONTHS (SYSDATE, -12 * 10) THEN RAISE_APPLICATION_ERROR ( -20703, 'Borrower must be at least 10 yrs old.'); END IF; END; /

129

Lat er, while building a bat ch- processing script t hat check s and loads ov er 10,000 borrow er applicat ions, I include t he following check in t he program : BEGIN ... IF ADD_MONTHS (SYSDATE, -122) > rec.date_of_birth THEN err.log ('Borrower ' || rec.borrower_id || ' is not ten years old.'); ELSE ...load the data And so on from t here. I am left , unfort unat ely, wit h a real j ob on m y hands when I get a m em o t hat say s: " The m inim um age for a library card has been changed from 10 t o 8 in order t o support a new cit y- wide init iat ive t o increase lit eracy." And t hen, of course, t here are also t he t w o bugs I int roduced int o m y second const ruct ion of t he rule. Did you not ice t hem and t he inconsist ent error m essages? The I F st at em ent should read: IF ADD_MONTHS (SYSDATE, -120) < rec.date_of_birth I f only I had creat ed a sim ple funct ion t he first t im e I needed t o calculat e m inim um valid age! Som et hing like t his: CREATE OR REPLACE FUNCTION borrower_old_enough ( dob_in IN DATE) RETURN BOOLEAN IS BEGIN RETURN NVL ( dob_in < ADD_MONTHS (SYSDATE, -10 * 12), FALSE ); END; And now I ev en check for a NULL value, which I forgot t o do in t hose ot her program s.

Be n e fit s You can updat e business rules and form ulas in your code about as quickly and as oft en as users change everyt hing t hat was supposedly " cast in st one." Dev elopers apply t hose rules consist ent ly t hroughout t he applicat ion base, since t hey are sim ply calling a program . Your code is m uch easier t o underst and, since dev elopers don't have t o wade t hrough com plex logic t o underst and which business rule is being im plem ent ed.

Ch a lle n ge s I t 's m ost ly a m at t er of discipline and advance planning. Before y ou st art building your applicat ion, creat e a set of pack ages t o hold business rules and form ulas for dist inct areas of funct ionalit y. Mak e sure t hat t he nam es of t he packages clearly

130

Term Fly Presents

http://www.flyheart.com

ident ify t heir purpose. Then prom ot e and use t hem rigorously t hroughout t he dev elopm ent organizat ion.

M OD - 0 2 : St a n da r dize m odu le st r u ct u r e u sin g fu n ct ion a n d proce du re t em plat e s.

Once y ou adopt a set of guidelines for how dev elopers should writ e procedures and funct ions, you need t o help t hose dev elopers follow t heir best pract ices. The bot t om line is t hat guidelines will be followed if you m ake it easier t o follow t hem t han t o ignore t hem . For m odule st andards, y ou can use eit her of t he following approaches: •

Creat e a st at ic t em plat e file t hat cont ains t he generic logical st ruct ure for a procedure and/ or funct ion. Dev elopers t hen copy t hat file t o t heir own file, " de- genericize" t he t em plat e by perform ing search and replace operat ions on placeholder st rings wit h t heir own specific values ( such as t able nam es) , and m odify it from t here.



Use a program ( one t hat you'v e writ t en or a com m ercially available t ool) t hat generat es t he code y ou want . This approach can be m ore flexible and can save y ou t im e, depending on how sophist icat ed a generat or you use/ creat e.

Ex a m ple Here's a sim ple funct ion t em plat e t hat reinforces t he single RETURN recom m endat ion ( [ MOD- 07: Lim it funct ions t o a single RETURN st at em ent in t he ex ecut ion sect ion.] ) and encourages a st andard header and consist ent except ion handling: CREATE OR REPLACE FUNCTION ( _in IN ) RETURN /* || STANDARD COPYRIGHT STATEMENT HERE || Author: || File: */ IS retval := ;; BEGIN -- Put your code here RETURN retval; EXCEPTION WHEN OTHERS THEN err.handle; END ;

131

And here's an exam ple t hat uses PLVgen ( t he code generat ion pack age from PL/ Vision) t o generat e a st andard funct ion for a num eric dat at ype: SQL> exec PLVgen.func ('total_pages', 1); CREATE OR REPLACE FUNCTION total_pages RETURN NUMBER /* || Program: total_pages || Author: null || File: total_pages.SQL || Created: November 16, 2000 15:57:03 || || Modification History: || Date Who Description || ---------- ------- --------------------*/ IS retval NUMBER := NULL; BEGIN PLVxmn.trace ('total_pages', PLVxmn.l_start, 'Starting program'); /* Your executable code here... */ PLVxmn.trace ('total_pages', PLVxmn.l_end, 'Ending program'); RETURN retval; EXCEPTION /* Call PLVexc in every handler. */ WHEN OTHERS THEN PLVexc.recNgo; RETURN NULL; END total_pages; /

One m ight argue t hat it 's overkill t o put t race calls ( PLVxm n.t race) int o such a sim ple funct ion. You need t o decide, on a case- by- case basis, which funct ions can and should absorb t he t racing overhead. I n PLVgen, you can set a swit ch t o t urn off inclusion of t hese calls. Be n e fit s The qualit y of each individual program is higher, since it 's m ore likely t o conform t o best pract ices. Program s are m ore consist ent across t he t eam and t herefore easier t o m aint ain and enhance.

132

Term Fly Presents

http://www.flyheart.com

Ch a lle n ge s First , you m ust decide on your basic form at s, including st andards for error handling. Then, y ou can eit her creat e t em plat e files or a basic code generat or. Make doubly sure t hey are correct . Can you check whet her t hey are being used? Can source code be aut o- validat ed t o check whet her dev elopers are alt ering t he basic fram ew ork ?

Re sou r ce s 1. t em plat e.fun and t em plat e.pro : Funct ion and procedure t em plat e files. 2. genm ods.pkg : A sim ple prot ot ype of a funct ion generat or.

M OD - 0 3 : Lim it e x e cu t ion se ct ion size s t o a sin gle pa ge u sin g m odu la riza t ion .

Sure, you're laughing out loud. You writ e code for t he real world. I t 's really com plicat ed. Fift y or sixt y lines? You're lucky if your program s are less t han 500 lines! Well, it 's not a m at t er of com plexit y; it 's m ore an issue of how you handle t hat com plexit y. I f y our ex ecut able sect ions go on for hundreds of lines, wit h a loop st art ing on page 2 and ending on page 6 and so on, you will have a hard t im e " grasping t he whole" and following t he logic of t he program . An alt ernat ive is t o use st ep- wise refinem ent ( a.k.a. " t op down decom posit ion" ) : don't dive int o all t he det ails im m ediat ely. I nst ead, st art wit h a general descript ion ( writ t en in act ual code, m ind you) of what y our program is supposed t o do. Then im plem ent all subprogram calls in t hat descript ion following t he sam e m et hod. The result is t hat at any given level ( PL/ SQL block) of refinem ent , y ou can t ake in and easily com prehend t he full underlying logic at t hat level. This t echnique is also referred t o as " divide and conquer."

Ex a m ple Consider t he following procedure. The ent ire program m ight be hundreds of lines long, but t he m ain body of assign_workload ( st art ing wit h BEGIN /*main*/) is only 15 lines long. Not only t hat , I can read it pret t y m uch as an ex cit ing novel: " For ev ery t elesales rep, if t hat person's case load is less t han t heir depart m ent 's average, assign t he next open case t o t hat person and schedule t he next appoint m ent for t hat case." CREATE OR REPLACE PROCEDURE assign_workload IS /* Overview: For every telesales rep, if that person's case load is less than their department's average, assign the next open case to that person and schedule the next appointment for that case. */

133

... declarations of cursors and variables -- Local module declarations of full programs PROCEDURE assign_next_open_case ( telesales_id_in IN NUMBER, case_out OUT NUMBER) IS BEGIN ... full, local implementation; END assign_next_open_case; FUNCTION next_appointment (case_in IN NUMBER) RETURN DATE ... END next_appointment; PROCEDURE schedule_case (case_in IN NUMBER, date_in IN DATE) ... END schedule_case; BEGIN /*main*/ FOR telesales_rec IN telesales_cur LOOP IF analysis.caseload ( telesales_rec.telesales_id) < analysis.avg_cases ( telesales_rec.department_id); THEN assign_next_open_case ( telesales_rec.telesales_id, case#); schedule_case ( case#, next_appointment (case#)); END IF; END LOOP; END assign_workload;

Be n e fit s You can im plem ent com plicat ed funct ionalit y wit h a m inim um num ber of bugs by using st ep- wise refinem ent . Local m odules and packaged program s play a m aj or role in keeping each ex ecut able sect ion sm all. A dev eloper can underst and and m aint ain a program wit h confidence if he can read and grasp t he logic of t he code.

Ch a lle n ge s You hav e t o be disciplined about holding off writ ing t he low- level im plem ent at ion of funct ionalit y. I nst ead, com e up wit h accurat e, descript ive nam es for packages, procedures, and funct ions t hat cont ain t he im plem ent at ions t hem selves.

Re sou r ce s ht t p: / / www.const rux .com / : Cont ains lot s of good advice on writ ing m odular code.

M OD - 0 4 : Use n am ed n ot a t ion t o cla r ify, self-

134

Term Fly Presents

http://www.flyheart.com

docu m en t , a n d sim plify m odu le ca lls.

PL/ SQL allows you t o specify t he nam e of a param et er along wit h it s value in a param et er list by using t his form at : parameter name => value This is called nam ed not at ion. Wit h nam ed not at ion, you can change t he order in which you supply argum ent s; y ou can also skip ov er I N argum ent s wit h default values. Use nam ed not at ion whenev er y ou m ak e a call t o a program t hat has any of t he following charact erist ics: •

I t has a long, confusing param et er list .



I t 's used infrequent ly, m eaning t hat t here is lit t le fam iliarit y wit h it or it s param et er list .



I t has default values for m ult iple I N param et ers.



I n som e cases, it act ually requires nam ed not at ion due t o t he param et er list design of ov erloaded program s ( as is necessary wit h t he built - in package, DBMS_OBFUSCATI ON_TOOLKI T) .

Ex a m ple Here's a procedure call t hat relies solely on posit ional not at ion ( t he default in PL/ SQL) : IF perform_insert THEN PLGdoir.ins ( drv, c_table, NVL (aname, c_global), NVL (atype, c_global), text_in, v_info ); END IF; I wrot e t hat code, but I sure can't rem em ber w hich param et er is going t o get set t o t ext _in. Here's anot her call t o t he sam e program : IF v_tab = c_global THEN PLGdoir.ins ( driver_in => drv, objtype_in => c_table,

135

attrname_in => c_global, attrtype_in => c_global, infotype_in => text_in, info_in => v_info ); Now I don't have t o wonder; t he code t ells m e exact ly what is going on.

Be n e fit s You will experience a dram at ic im prov em ent in t he readabilit y of t he program calls inside your code. Nam ed not at ion also offers great er flexibilit y in how y ou const ruct y our param et er list s.

Ch a lle n ge s You need t o know or look up t he nam es of argum ent s. For t his reason, I can't support a recom m endat ion of always using nam ed not at ion. I t w ould be m ost useful, on t he ot her hand, if t ools t hat generat e code aut om at ically follow nam ed not at ion t o enhance readabilit y.

Re sou r ce s nam ednot .sql : A file t hat dem onst rat es t he different way s y ou can use nam ed and posit ional not at ion t o invok e a procedure.

M OD - 0 5 : Avoid side - e ffe ct s in you r pr ogr am s.

Build lot s of individual program s, preferably inside packages. Design each program so t hat it has a single, clearly defined purpose. That purpose should, of course, be expressed in t he program 's nam e, as w ell as in t he program header. Avoid t hrowing ext raneous funct ionalit y inside a program . Such st at em ent s are called " side- effect s" and can cause lot s of problem s for people using your code— which m eans your code won't get used, except perhaps as source for a cut - andpast e session ( or —in hard- copy form —for kindling) .

Ex a m ple Here's a program t hat by nam e and " core" funct ionalit y displays inform at ion about all book s published wit hin a cert ain dat e range: CREATE OR REPLACE PROCEDURE display_book_info ( start_in IN DATE, end_in IN DATE) IS

136

Term Fly Presents

http://www.flyheart.com

CURSOR book_cur IS SELECT * FROM book WHERE date_published BETWEEN start_in AND end_in; BEGIN FOR book_rec IN book_cur LOOP pl (book_rec.title || ' by ' || book_rec.author); usage_analysis.update_borrow_history ( book_rec); END LOOP; END display_book_info; Not ice, however, t hat it also updat es t he borrow ing hist ory for t hat book . Now, it m ight well be t hat at t his point in t im e t he display_book_info procedure is called only when t he borrowing hist ory also needs t o be updat ed, j ust ifying t o som e ext ent t he way t his program is writ t en. How ev er, regardless of current requirem ent s, t he nam e of t he program is clearly m isleading; t here is no way t o know t hat display_book_info also updat es borrowing hist ory. This is a hidden side- effect , and one t hat can cause serious problem s ov er t im e.

Be n e fit s Your code can be used wit h great er confidence, since it does only what it says ( via it s nam e, for t he m ost part ) it 's going t o do. Dev elopers will call and com bine singlepurpose program s as needed t o get t heir j ob done.

M OD - 0 6 : Use N OCOPY t o m in im ize ove rh e a d w h en colle ct ion s a n d re cor ds a re [ I N ] OUT pa r a m e t e rs ( Ora cle 8 i) .

When y ou pass argum ent s t hrough t he param et er list of a program , t hose argum ent s can be passed by reference or by value: •

By reference m eans t hat t he dat a st ruct ure m anipulat ed inside t he program point s t o t he sam e locat ion in m em ory t hat holds t he value of t he argum ent .



By value m eans t hat t he value of t he argum ent is copied int o t he dat a st ruct ure of t he program , and t hen copied back out t o t he argum ent dat a st ruct ure if no ex cept ion occurs.

Param et er passing in PL/ SQL by default follows t hese rules: •

I N argum ent s are passed by reference.

137



OUT and I N OUT argum ent s are passed by value.

This m eans t hat when y ou pass a large dat a st ruct ure ( such as a collect ion, a record, or an inst ance of an obj ect t ype) as an OUT or I N OUT param et er, y our applicat ion can experience perform ance and m em ory degradat ion due t o all t his copying. I f y ou experience such degradat ion, y ou can consider t wo opt ions: •

Use t he Oracle8i NOCOPY hint t o ask t he PL/ SQL com piler t o not m ake a copy of your dat a st ruct ure.



" Globalize" t he dat a st ruct ure, so t hat inst ead of passing t hat large, com plex st ruct ure as an argum ent , you reference it direct ly wit hin t he program .

Ex a m ple Here's a param et er list t hat uses t he NOCOPY hint for bot h of it s I N OUT argum ent s: PROCEDURE analyze_results ( date_in IN DATE, values IN OUT NOCOPY numbers_varray, validity_flags IN OUT NOCOPY validity_rectype ); Rem em ber t hat NOCOPY is a hint , not a com m and. This m eans t hat t he com piler m ight silent ly decide it can't fulfill your request for a NOCOPY param et er t reat m ent . See m y book, Oracle PL/ SQL Program m ing: Guide t o Oracle8i Feat ures, for m ore det ails. To globalize a dat a st ruct ure, y ou want t o creat e ( if not already present ) a package t hat can define and hold t his persist ent dat a st ruct ure. Resources offers an exam ple script t hat allows you t o com pare t he t w o approaches. Here, howev er, is t he package specificat ion, showing t wo v ersions of t he sam e program ( passt ab) —one t hat accept s t he collect ion as an argum ent , anot her t hat references it direct ly: CREATE OR REPLACE PACKAGE pkgvar IS TYPE reward_rt IS RECORD ( nm VARCHAR2(2000), comm NUMBER); TYPE reward_tt IS TABLE OF reward_rt INDEX BY BINARY_INTEGER; globtab reward_tt; PROCEDURE passtab (parmtab IN OUT reward_tt); PROCEDURE passtab; END; /

Be n e fit s

138

Term Fly Presents

http://www.flyheart.com

You can im prove t he perform ance of your applicat ion. You should consider eit her alt ernat ive, however, only aft er y ou hav e ident ified a clear perform ance problem in specific program s.

Ch a lle n ge s I f y ou use NOCOPY or globalized dat a st ruct ures, t he PL/ SQL runt im e engine no longer " roll back" changes when an except ion occurs in your program . You can, as a consequence, end up wit h a dat a st ruct ure t hat is only part ially updat ed.

Re sou r ce s 1. pkgvar.pkg and pkgvar.t st : A package and t est script t o bot h dem onst rat e t he globalizat ion t echnique and t est it s perform ance im pact . 2. nocopy .t st , nocopy2.t st , and nocopy3.t st : Exam ples of script s t hat ex am ine t he im pact of t he NOCOPY st at em ent .

7 .2 Fu n ct ion s Funct ions are program unit s t hat ret urn a value t hrough t he RETURN clause of t he program header.

M OD - 0 7 : Lim it fu n ct ion s t o a sin gle RETURN st a t e m en t in t h e ex e cu t ion se ct ion .

A good general rule t o follow as y ou w rit e your PL/ SQL program s is: " one way in and one w ay out ." I n ot her words, t here should be j ust one way t o ent er or call a program ( t here is; you don't hav e any choice in t his m at t er) . And t here should be one w ay out , one exit pat h from a program ( or loop) on successful t erm inat ion. By following t his rule, you end up wit h code t hat is m uch easier t o t race, debug, and m aint ain. For a funct ion, t his m eans you should t hink of t he execut able sect ion as a funnel; all t he lines of code narrow down t o t he last execut able st at em ent : RETURN return value; Not e t he following: •

You can, and should, st ill have RETURN st at em ent s in your ex cept ion handlers. Not ev ery except ion should be passed unhandled from y our funct ion. See [ EXC- 07: Handle except ions t hat cannot be avoided but can be ant icipat ed. ] for m ore inform at ion.



I t 's possible ( i.e., accept able synt ax) t o use an " unqualified" RETURN st at em ent in a procedure, as follows: IF all_done



139

• •

THEN RETURN; END IF; and t he procedure im m ediat ely t erm inat es and ret urns cont rol. You shouldn't do t his, however, as it result s in unst ruct ured code t hat 's hard t o debug and m aint ain This sam e recom m endat ion holds for t he init ializat ion sect ion of a package.

Ex a m ple Here's a sim ple funct ion t hat relies on m ult iple RETURNs: CREATE OR REPLACE FUNCTION status_desc ( cd_in IN VARCHAR2 ) RETURN VARCHAR2 IS BEGIN IF cd_in = 'C' THEN RETURN 'CLOSED'; ELSIF cd_in = 'O' THEN RETURN 'OPEN'; ELSIF cd_in = 'I' THEN RETURN 'INACTIVE'; END IF; END; At first glance, t his funct ion looks very reasonable. Yet t his funct ion has a deep flaw, due t o t he reliance on separat e RETURNs: if you don't pass in " C" , " O" , or " I " for t he cd_in argum ent , t he funct ion raises: ORA-06503: PL/SQL: Function returned without value Here's a rew rit e t hat relies on ( a) a st andard t y pes package t hat av oids hard- coding a VARCHAR2 variable lengt h ( see [ DAT- 13: Cent ralize TYPE definit ions in package specificat ions.] ) and also gives nam es t o lit eral values, and ( b) a single RETURN at t he end of t he funct ion: CREATE OR REPLACE FUNCTION status_desc ( cd_in IN VARCHAR2 ) RETURN stdtypes.description_t IS retval stdtypes.description_t; BEGIN IF cd_in = stdtypes.c_closed_abbrev THEN retval := stdtypes.c_closed; ELSIF cd_in = stdtypes.c_open_abbrev THEN retval := stdtypes.c_open;

140

Term Fly Presents

http://www.flyheart.com

ELSIF cd_in = stdtypes.c_inactive_abbrev ' THEN retval := stdtypes.c_inactive; END IF; RETURN retval; END; This program also safely and correct ly ret urns NULL if t he program doesn't receive a value of " C" , " O" , or " I " , unlike t he first im plem ent at ion.

Be n e fit s You're less likely t o writ e a funct ion t hat raises t he ex cept ion " ORA- 06503: PL/ SQL: Funct ion ret urned wit hout value" —a nast y and em barrassing error. A single RETURN funct ion is easier t o t race and debug, since y ou don't have t o w orry about m ult iple exit pat hways from t he funct ion.

Re sou r ce s genm ods.pkg : A sim ple prot ot ype of a funct ion generat or.

M OD - 0 8 : Ke ep fu n ct ion s pu r e by a voidin g [ I N ] OUT pa r am e t e rs.

The whole point of a funct ion is t o ret urn a value ( whet her it 's a single, scalar value or a com posit e, such as a record or a collect ion) . I f y ou also ret urn dat a back t hrough t he param et er list wit h OUT or I N OUT argum ent s, t he purpose and usage of t he funct ion is obscured. Oracle also places rest rict ions on how y ou can use funct ions t hat have OUT and I N OUT param et ers—nam ely, y ou can't call t hat funct ion from wit hin a SQL st at em ent . I f y ou need t o ret urn m ult iple pieces of inform at ion, t ake one of t he following approaches: Ret urn a record or collect ion of values Make sure t o publish t he st ruct ure of your record or collect ion ( t he TYPE st at em ent ) in a package specificat ion so t hat developers can underst and and use t he funct ion m ore easily. Not e t hat y ou can't call t his funct ion in a SQL st at em ent if it ret urns a record or index- by t able. Break up t he single funct ion int o m ult iple funct ions, all ret urning scalar values Wit h t his approach, you can call t he funct ions from wit hin SQL st at em ent s. Change your funct ion int o a procedure

141

Unless you need t o call a funct ion t o ret urn t his inform at ion, j ust change it t o a procedure ret urning m ult iple pieces of inform at ion.

Ex a m ple Here's a funct ion t hat ret urns sev eral pieces of inform at ion about a book: FUNCTION book_info ( isbn_in IN book.isbn%TYPE, author_out OUT book.author%TYPE, page_count_out OUT book.page_count%TYPE) RETURN book.title%TYPE; And now I use t his funct ion: l_title book.title%TYPE; BEGIN l_title := book_info (l_isbn, l_author, l_page_count); Very confusing! The funct ion seem s t o ret urn a t it le, but what else does it do? I t 's hard t o t ell what is happening wit h t he ot her param et ers. I f, on t he ot her hand, I rest ruct ure t he funct ion t o ret urn a record: FUNCTION book_info ( isbn_in IN book.isbn%TYPE) RETURN book%ROWTYPE; t he int ent of t he result ing code is m ore clear: one_book_rec book%ROWTYPE; BEGIN one_book_rec := book_info (l_isbn);

Be n e fit s Your funct ions are m ore likely t o be used and reused, because t hey are defined in ways t hat m ak e t hem easy t o underst and and apply in your own code. Your funct ion m ay t hen be callable from wit hin a SQL st at em ent , which encourages ev en wider use of t his program . A funct ion wit h an OUT argum ent can nev er be called from wit hin SQL, Please not e, t hough, t hat t here are ot her rest rict ions on funct ion calls from SQL. You m ay not , for exam ple, call a funct ion t hat ret urns a record ( as shown in t he preceding exam ple) .

Ch a lle n ge s You m ay need ( or feel t he need) t o pass back st at us inform at ion as well as t he dat a ret urned by t he funct ion. This com es up when calling PL/ SQL code from non- Oracle

142

Term Fly Presents

http://www.flyheart.com

languages such as Visual Basic. I n t his case, consider using a procedure inst ead of a funct ion.

M OD - 0 9 : N e ve r r e t u rn N ULL fr om Boole an fu n ct ion s.

A Boolean funct ion should ret urn only TRUE or FALSE. I f a Boolean funct ion ret urns a NULL, how should t he user of t hat funct ion int erpret and respond t o t hat value? Does it indicat e you passed in invalid dat a? Should it be considered TRUE or FALSE? Or should t he dev eloper t est explicit ly for NULL? Well, we should do explicit t est s for NULL if w e are uncert ain about t he funct ion's behavior, but w e rarely rem em ber t o do so or feel it 's necessary t o m ak e t he effort . I nst ead, w e check for TRUE or FALSE and t hus allow bugs t o creep int o our code. I f t he Boolean funct ion can ret urn NULL, y ou probably need t o look at t he im plem ent at ion of t he funct ion t o det erm ine t he act ion t o t ake on a NULL ret urn value. Yet you will not always be able t o ( or want t o) look at t he funct ion's body. A non- Boolean funct ion can use a NULL ret urn v alue t o indicat e failure. A funct ion t hat ret urns t he t it le of a book for an I SBN num ber ret urns NULL for an invalid I SBN. That m akes sense. On t he ot her hand, a funct ion t hat t ells you whet her or not a book is in print doesn't help you m uch if it ret urns NULL.

Ex a m ple Here's a funct ion t hat det erm ines if a st ring is a valid I SBN num ber ( it 's not foolproof, but it get s across t he basic idea) : CREATE OR REPLACE FUNCTION is_valid_isbn ( isbn_in IN VARCHAR2) RETURN BOOLEAN -- Ten digits separated by 4 hyphens IS l_isbn book.isbn%TYPE; BEGIN l_isbn := TRANSLATE (isbn_in, 'A-', 'A'); -- strip hyphens RETURN (LENGTH (l_isbn) = 10 AND l_isbn + 0 = l_isbn); -- adding zero tests for numeric EXCEPTION WHEN OTHERS THEN RETURN FALSE; END is_valid_isbn; / And it works pret t y w ell: SQL> exec bpl (is_valid_isbn ('1-2-3-4')) FALSE SQL> exec bpl (is_valid_isbn ('1-12345-123-5')) TRUE

143

But it ret urns NULL if an " em pt y " st ring is passed t o it , which m eans t hat any use of is_valid_isbn should be com bined wit h t he NVL funct ion, as in: IF NVL (is_valid_isbn (l_isbn), FALSE) The need t o rely on NVL reduces t he usefulness of t he funct ion. I t should be rew rit t en t o guarant ee only one of t w o v alues ret urned: TRUE or FALSE. You will find such a rew rit e in t he file list ed in Resources.

Be n e fit s Dev elopers are m ore likely t o use y our funct ion wit hin t heir applicat ion code.

Re sou r ce s isvalidisbn.fun : This file cont ains t he t wo im plem ent at ions described in t he exam ple

7 .3 Trigge r s Dat abase t riggers are a crucial part of any well- designed applicat ion. By placing logic in a t rigger, you associat e business rules closely wit h t he dat abase obj ect , guarant eeing t hat t hese rules are always applied t o any act ion t aken against t he obj ect .

M OD - 1 0 : M in im ize t h e size of t r igge r ex e cu t ion se ct ion s.

Lim it t he num ber of lines of code in a t rigger—even t o t he point of m oving code int o procedures, funct ions, or packages and calling t hem from t he t rigger. Prior t o Oracle7 Release 7.3, t rigger code wasn't ev en st ored in t he dat abase in com piled form . Each t im e a t rigger was ex ecut ed, it would also have t o be com piled. Under t hese condit ions, it was absolut ely crit ical t o m ov e as m uch t rigger code as possible t o st ored, precom piled procedures in order t o im prov e t he t rigger's ex ecut ion t im e. Now, t riggers are com piled j ust as procedures and funct ions are. St ill, you should m ov e as m uch of y our business logic as possible t o packaged program s.

Ex a m ple Well, I could offer an ex am ple of pages and pages of code t hat are replaced by a single procedure call. That would cert ainly drive t he point hom e—but at t he expense of a few m ore t rees. So inst ead, here is a v ery sm all t rigger, but one t hat st ill exposes a business rule t hat should be hidden: CREATE OR REPLACE TRIGGER check_employee_age

144

Term Fly Presents

http://www.flyheart.com

BEFORE INSERT OR UPDATE ON employee BEGIN IF ADD_MONTHS (SYSDATE, -216) < :NEW.hire_date THEN RAISE_APPLICATION_ERROR (-20706, 'Employee must be 18 to work here!'); END IF; END; A m uch im proved im plem ent at ion would be: CREATE OR REPLACE TRIGGER check_employee_age BEFORE INSERT OR UPDATE ON employee BEGIN IF employee_rules.emp_too_young (:NEW.hire_date) THEN err_pkg.rase (employee_rules.c_errnum_emp_too_young, :NEW.employee_id); END IF; END; Now t hat business rule ( which in t he " real world" m ight have been very com plex and t aken up sev eral lines of code) is m ov ed t o t he package. I hav e also av oided t he hard- coding of a RAI SE_APPLI CATI ON_ERROR call relying on m y st andard error package ( see [ EXC- 04: Use your own raise procedure in place of explicit calls t o RAI SE_APPLI CATI ON_ERROR. ] ) .

Be n e fit s Keeping t rigger code sm all provides a m odular code layout t hat 's easy t o m aint ain and debug. You great ly reduce t he chance of int roducing redundant business rule logic int o your applicat ion if you always insist on m oving such logic t o packages.

Ch a lle n ge s Wit hin a row- lev el t rigger, y ou can reference t he old and new values of t he row 's colum ns wit h t he : NEW and : OLD pseudo- records. You can't , how ev er, pass t hese st ruct ures as param et ers, nor can y ou reference t hem in dynam ic SQL code. These rest rict ions oft en force you t o writ e m ore—and very cum bersom e—logic t han desired in t he t rigger. See Resources for a program y ou can use t o generat e code t hat will at least sav e y ou som e t im e in dealing wit h : OLD and : NEW.

Re sou r ce s genm ods.pkg : The genm ods.use_new and genm ods.use_old procedures wit hin t his package generat e procedure calls t hat " explode" t he pseudo- records int o individual argum ent s ( one per colum n) t hat can be passed t o st ored program s. Here's an exam ple session: SQL> exec genmods.use_new ('employee', 'myprog')

145

myprog ( :NEW.EMPLOYEE_ID, :NEW.LAST_NAME, :NEW.FIRST_NAME, :NEW.MIDDLE_INITIAL, :NEW.JOB_ID, :NEW.MANAGER_ID, :NEW.HIRE_DATE, :NEW.SALARY, :NEW.COMMISSION, :NEW.DEPARTMENT_ID, :NEW.EMPNO, :NEW.ENAME, :NEW.CREATED_BY, :NEW.CREATED_ON, :NEW.CHANGED_BY, :NEW.CHANGED_ON );

M OD - 1 1 : Con solida t e " ove rla ppin g" D M L t r igge rs t o con t r ol ex e cu t ion or de r .

While it 's possible t o creat e m any DML t riggers of t he sam e t ype on a t able, it isn't possible t o guarant ee t he order in which t hey fire. While sev eral t heories abound about firing order ( including rev erse order of creat ion or obj ect I D) , it isn't advisable t o rely on t heories when designing dat abase t riggers. I nst ead, you should consolidat e int o a single t rigger all t riggers t hat fire under t he sam e condit ions.

Ex a m ple When insert ing a value of 1 for t he following I D field, what value will wind up in t he t able? CREATE OR REPLACE TRIGGER increment_by_one BEFORE INSERT ON id_table FOR EACH ROW BEGIN :new.id := :new.id + 1; END; / CREATE OR REPLACE TRIGGER increment_by_two BEFORE INSERT ON id_table FOR EACH ROW BEGIN IF :new.id > 1 THEN :new.id := :new.id + 2; END IF; END; / The answ er is, in realit y, indet erm inat e; you can't accurat ely predict t he behavior of such a syst em of t riggers.

146

Term Fly Presents

http://www.flyheart.com

Be n e fit s You don't have t o be concerned about t he order in which t riggers fire w hen t he applicat ion is rebuilt , m ov ed, or upgraded.

Ch a lle n ge s I t m ay be difficult t o m ove com plex code int o a single t rigger. You m ay also hav e som e t rouble ident ifying t riggers t hat fire under t he sam e condit ions. See Resources for a query y ou can run t hat should help answer t his quest ion.

Re sou r ce s 1. m ult iple_t riggers.sql : Cont ains a det ailed working version of t he exam ple. 2. t rigger_conflict .sql : A sim ple query against t he USR_TRI GGERS dat a dict ionary view t hat helps y ou ident ify pot ent ially conflict ing t riggers.

M OD - 1 2 : Ra ise e x ce pt ion s t o repor t on don ot h in g I N STEAD OF t r igge rs.

I f y ou execut e an UPDATE st at em ent and it doesn't ident ify any row s t o updat e, Oracle doesn't raise an error. I n m any cases, t hat is fine. I n ot her cases, it m ight indicat e an error. The sit uat ion is t he sam e wit h I NSTEAD OF t riggers. These t riggers allow you t o specify an alt ernat ive operat ion t hat will t ake place inst ead of t he norm al DML act ion wit h which t he t rigger is associat ed. I f t he I NSTEAD OF t rigger doesn't ex ecut e any DML at all but doesn't raise an except ion, it doesn't report any error back t o t he calling program . While in som e cases t his m ay be t he desired behavior, y ou usually want t o raise an except ion, and perhaps also log t he fact t hat a failure occurred.

Ex a m ple Consider t he following rat her selfish t rigger. I have creat ed a view called best _sellers t hat sit s on t op of t he book t able. When you insert a row int o best _sellers, it act ually insert s a row only if t he publisher of t he book is O'Reilly & Associat es! CREATE OR REPLACE TRIGGER instead_of_best_sellers INSTEAD OF INSERT ON best_sellers BEGIN IF :new.publisher = 'O''REILLY & ASSOCIATES' THEN INSERT INTO book ( author, title, isbn, publisher) VALUES (

147

:new.author, :new.title, :new.isbn, :new.publisher); END IF; END; That 's an unet hical t hing t o do, but wit h t he way t he t rigger is writ t en, t here's no im m ediat e not ificat ion t hat a best seller by, say , Oracle Press, wasn't added t o t he t able. Here's a m ore principled and appropriat e way t o writ e t his code: CREATE OR REPLACE TRIGGER instead_of_best_sellers INSTEAD OF INSERT ON best_sellers BEGIN IF :new.publisher = 'O''REILLY & ASSOCIATES' THEN INSERT INTO book ( author, title, isbn, publisher) VALUES ( :new.author, :new.title, :new.isbn, :new.publisher); ELSE err_pkg.raise (book_rules.c_errnum_only_oreilly); END IF; END; Now, at least t he user is not ified of t he accept able t ypes of books for best sellers!

Be n e fit s Phant om DML operat ions are diagnosed and report ed; y ou aren't left t o wonder what act ually happened when t he t rigger fired. You get back a posit ive indicat ion t hat t he DML failed, j ust as y ou do in t he case of failed DML on a t able.

Re sou r ce s inst ead_of_not hing.sql : Cont ains a com plet e ex am ple of handling t he sit uat ion versus not handling t he sit uat ion.

M OD - 1 3 : I m plem en t se r ve r pr oblem logs an d " t o do" list s u sin g da t a ba se t r igge r s.

Oracle8i now offers dat abase- level t riggers t hat can be fired on event s such as LOGON, LOGOFF, and SERVERERROR. These t riggers offer all sort s of new possibilit ies for a DBA but can som et im es lead t o problem s. Suppose, for inst ance, t hat an applicat ion encount ers t he ORA- 01659 error ( " unable t o allocat e next ext ent " ) . The solut ion is t o add a dat a file t o t he applicable t ablespace. Wit h a SERVERERROR t rigger, y ou can act ually t rap t his problem and, right on t he spot , add a dat a file. That 's all well and good, but while t he dat a file is

148

Term Fly Presents

http://www.flyheart.com

being added ( which can t ake quit e a while) , t he user process is block ed. I m agine a poor end user sit t ing at his t erm inal st aring at a blank screen for 10 m inut es while t he dat abase adds a dat aflow t o fix an error he wasn't ev en aware had occurred. A far superior approach t o t ak e is t o first , adopt as a guiding principle t hat whenev er possible t he user process is allowed t o cont inue unint errupt ed. The SERVERERROR t rigger should t hen sim ply logs an error or, even bet t er, sends a m essage t o a DBA ( via t he Oracle Advanced Queuing facilit y, for exam ple) or builds a t o- do list . This list can t hen be parsed and processed by a background dat abase j ob run at regular int ervals. Dat abase- lev el t riggers fire as aut onom ous t ransact ions, m aking it far easier t o place an ent ry int o a " t o do" t able, wit hout affect ing t he user t ransact ion.

Ex a m ple Here's a sim ple SERVERERROR t rigger t hat adds an it em t o t he DBA t o- do list : CREATE OR REPLACE TRIGGER db_error_handler AFTER SERVERERROR ON DATABASE BEGIN IF ORA_IS_SERVERERROR (-1659) THEN db_to_do_list.add_item('ADD_DATAFILE'); END IF; END;

Be n e fit s Processes t hat encount er errors don't hav e t o w ait for t he com plet e fix. This is a good way t o build in logging t hat helps ident ify dat abase or applicat ion problem s at a low level of dat abase operat ions.

Ch a lle n ge s You m ust ensure t hat t he " t o- do" list is processed in a t im ely m anner w hile not ov erwhelm ing dat abase resources. Som et im es, t he inform at ion required t o fix an error isn't obvious or available wit hin t he confines of t he t rigger. I n t he preceding exam ple, t he nam e of t he t ablespace requiring a dat a file isn't readily available. You can, how ev er, obt ain t he t able nam e from ora_dict _obj _nam e ( see [ MOD- 14: Use ORA_% public synonym s t o reference dat abase and schem a event t rigger at t ribut es. ] ) , and from t hat derive t he t ablespace nam e.

M OD - 1 4 : Use ORA_ % pu blic syn on ym s t o r e fe r en ce da t a ba se an d sch em a e ven t t rigge r a t t r ibu t e s.

149

Wit hin t he confines of DDL and dat abase ev ent t riggers, t here is a lot of inform at ion available about what specifically caused t he t rigger t o fire, for exam ple, t he exact t able and colum n or t he nam e of t he user. This inform at ion is available via a set of PL/ SQL funct ions cont ained in t he DBMS_STANDARD package. Always reference t hese funct ions via t he public synonym s ( ORA_% ) also provided ( and defined in t he dbm st rig.sql file in t he Rdbm s/ Adm in subdirect ory of inst alled Oracle soft ware) . Here's a sm all subset of t he funct ions available ( consult dbm st rig.sql for a com plet e list ) : •

ora_sysev ent : The sy st em ev ent t hat invokes t he sy st em t rigger



ora_dict _obj _owner : The obj ect owner on which t he DDL st at em ent is being done



ora_dict _obj _nam e : The obj ect nam e on which t he DDL st at em ent is being perform ed

Ex a m ple CREATE OR REPLACE TRIGGER after_create AFTER CREATE ON SCHEMA DECLARE /* || The BAD way. Direct calls to the functions in DBMS_STANDARD */ v_type VARCHAR2(30) := DICTIONARY_OBJ_TYPE; v_name VARCHAR2(30) := DICTIONARY_OBJ_NAME; BEGIN -- the GOOD way; via the synonyms INSERT INTO log_create VALUES(ORA_DICT_OBJ_TYPE, ORA_DICT_OBJ_NAME); -- the BAD way; via direct calls INSERT INTO log_create VALUES(v_type, v_name); END;

Be n e fit s Your code is prot ect ed from fut ure changes.

Re sou r ce s always_use_ora.sql : Cont ains t he preceding exam ple.

M OD - 1 5 : Va lidat e com plex bu sin e ss ru le s w it h D M L t r igge r s.

150

Term Fly Presents

http://www.flyheart.com

Foreign key, NOT NULL, and check const raint s provide m echanism s t o v alidat e sim ple business rules like: An account t ransact ion m ust be for a valid account . or: I f t he t ransact ion t ype is DEP t he am ount m ust be ent ered. How ev er, t here are som e cases t hey sim ply can't handle. Consider t he following requirem ent s: I f t he account t ransact ion has been approved, it can't be updat ed. Account t ransact ions can't be creat ed wit h approv ed st at us. Regardless of t he com plexit y of t he logic behind evaluat ing t he approved st at us of a t ransact ion, it probably isn't som et hing a sim ple const raint can handle. I n t hese cases, dat abase t riggers st ep in wit h t he abilit y t o support arbit rarily com plex logic, while sim ult aneously guarant eeing t hat applicat ions can't sidest ep t he rules.

Ex a m ple Here are som e exam ples of sim ple t rigger logic t hat st ill can't be handled wit h const raint s. I f t he account t ransact ion has been approved, it can't be updat ed: CREATE TRIGGER cannot_change_approved BEFORE UPDATE ON account_transaction FOR EACH ROW BEGIN IF :OLD.approved_yn = constants.yes THEN err_pkg.raise (account_rules.c_no_change_after_approval); END IF; END; Account t ransact ions can't be creat ed wit h approv ed st at us: CREATE TRIGGER cannot_create_approved BEFORE INSERT ON account_transaction FOR EACH ROW BEGIN IF :NEW.approved_yn = 'Y' THEN err_pkg.raise (account_rules.c_no_preapproval); END IF; END; These business rules m ust be v alidat ed in t riggers because t he t ype of DML being perform ed m ust be recognized, and t he dev eloper needs access t o old and new

151

values. This inform at ion isn't available wit hin NOT NULL, referent ial int egrit y, or check const raint s.

Be n e fit s Careful planning and design allow ev en t he m ost com plex business rules t o be validat ed in DML t riggers. I f business rules are check ed wit h t riggers, user int erfaces aren't required t o check t hem . This m ak es changes easier t o im plem ent .

M OD - 1 6 : Popu la t e colu m n s of de r ive d valu e s w it h t rigge r s.

Som e applicat ions require t hat ext ra inform at ion be st ored in a record whenev er it 's insert ed, updat ed, or delet ed. This inform at ion m ay or m ay not be supplied by t he applicat ion it self.

Ex a m ple I n t his exam ple, t he dat e a record is updat ed is recorded wit hin t he record it self: CREATE OR REPLACE TRIGGER set_updated_fields BEFORE UPDATE ON account_transaction FOR EACH ROW BEGIN IF :NEW.updated_date IS NULL THEN :NEW.updated_date := SYSDATE; END IF; END;

Be n e fit s You can guarant ee t hat t he fields will be populat ed because all records are processed by t he t riggers.

Ch a lle n ge s I f y ou have a set of st andard colum ns whose v alues are set t hrough t riggers, t hose colum ns should not be provided values in applicat ion DML st at em ent s. I t w ould probably m ak e sense t o build views on t op of t he base t ables t hat hide t he derivedvalue colum ns.

M OD - 1 7 : Use ope r a t ion al dir e ct ive s t o pr ovide m or e m e an in gfu l e r r or m e ssa ge s fr om w it h in

152

Term Fly Presents

http://www.flyheart.com

t r igge rs.

You can creat e a single t rigger t hat fires for m ore t han one DML operat ion, as in: CREATE OR REPLACE TRIGGER check_for_reserved_status BEFORE UPDATE OR INSERT ON book FOR EACH ROW This allows you t o consolidat e logic t hat m ust be applied t o all t hese operat ions int o a single program unit . I f you do t his, how ev er, y ou should t ake advant age of built - in funct ions defined in t he default DBMS_STANDARD package t o help you det erm ine exact ly which t ype of operat ion was execut ed. Here are t he headers of t hose special funct ions or " operat ional direct ives" : FUNCTION FUNCTION FUNCTION FUNCTION

INSERTING RETURN DELETING RETURN UPDATING RETURN UPDATING (COLNAM

BOOLEAN; BOOLEAN; BOOLEAN; VARCHAR2) RETURN BOOLEAN;

Ex a m ple This exam ple allows a single t rigger t o ensure t hat approv ed t ransact ions are neit her changed nor delet ed, w hile displaying an inform at ive m essage when t hey are: CREATE OR REPLACE TRIGGER check_approved BEFORE UPDATE OR DELETE ON account_transaction FOR EACH ROW BEGIN IF :old.approved_yn = 'Y' THEN IF updating THEN err_pkg.raise ( te_account_transaction.c_no_update_after_approved); ELSE err_pkg.raise ( te_account_transaction.c_no_delete_after_approved); END IF; END IF; END;

Be n e fit s Single t riggers can prov ide m eaningful m essages.

Ch a pt e r 8 . Pa ck a ge Con st r u ct ion

153

Packages are t he fundam ent al building blocks of any w ell- designed applicat ion built in t he Oracle PL/ SQL language ( at least unt il Oracle im prov es t he robust ness of it s obj ect im plem ent at ion! ) . A package consist s of up t o t wo elem ent s: t he specificat ion and t he body . The specificat ion t ells a user what she can do wit h t he package: what program s can be called, what dat a st ruct ures and user- defined t ypes can be referenced, and so on. The package body im plem ent s any program s in t he package specificat ion; it can also cont ain privat e ( i.e., not shown in t he specificat ion) dat a st ruct ures and program unit s.

PKG- 0 1 : Gr ou p r e la t e d da t a st ru ct u re s an d fu n ct ion a lit y t oge t h er in a sin gle pa ck a ge.

Ev en if you don't t ak e advant age of feat ures unique t o packages, such as persist ent dat a, you can st ill use packages t o organize—and st ore t oget her —relat ed code and dat a st ruct ures. Wit hout packages, y ou m ight end up wit h several hundred st andalone procedures and funct ions and m any repeat ed cursor, TYPE, and variable declarat ions. A package gives a nam e t o a set of program elem ent s: procedures, funct ions, userdefined t ypes, variable and const ant declarat ions, cursors, and so on. By creat ing a package for each dist inct area of funct ionalit y, you creat e int uit ive cont ainers for t hat funct ionalit y.

Ex a m ple As I go about building a library m anagem ent sy st em , I quickly ident ify t he following funct ional areas: Borrower inform at ion Maint ain underlying t able, est ablish rules about who is a valid borrower. Book inform at ion Maint ain underlying t able, define validat ion for I SBN, and so on. Borrowing hist ory Maint ain underlying t able t hat t racks who t ook out what and when. Overdue fines Collect ion of all form ulas and business rules for processing ov erdue charges. Special reservat ions

154

Term Fly Presents

http://www.flyheart.com

Maint ain underlying t able and collect all rules governing how a borrower can reserv e a book. Publisher inform at ion Maint ain underlying t able. I can now creat e separat e packages for each bunch of dat a or funct ional specificat ion. For exam ple, t he ov erdue package would cont ain all program s relat ed t o calculat ing and displaying ov erdue fine inform at ion. I f I need any TYPEs ( collect ions and records, for exam ple) t o declare or m anipulat e overdue dat a, I w ould also define t hose in t he ov erdue package.

Be n e fit s I t 's m uch easier t o build, find, and m aint ain program s when t hey are organized by logical area int o separat e packages. Once y ou have segregat ed program s and dat a int o t heir own packages, you can m ore easily leverage special feat ures of packages ( persist ent dat a, init ializat ion sect ion, ov erloading) t o im prove perform ance and funct ionalit y.

Re sou r ce s 1. t e_em ployee.pk s and t e_em ploy ee.pkb : Table encapsulat ion packages feat ure " high cohesion" ( grouping t oget her of relat ed program s) . Such packages offer a set of procedures and funct ions t hat allow a developer t o m anipulat e t he underlying dat a st ruct ure ( t able or view) wit hout writ ing any explicit SQL. 2. xfile.pkg : The xfile class ( built on t op of t he JFile Java class) offers " one st op shopping" for all file- relat ed processing in a PL/ SQL environm ent .

PKG- 0 2 : Pr ovide w e ll- de fin e d in t e r fa ces t o bu sin e ss da t a an d fu n ct ion a l m a n ipu la t ion u sin g pa ck a ges.

Hum ans can handle only so m uch com plexit y at once. The det ails and nuances of any decent - sized applicat ion ov erwhelm t he hum an m ind. Use packages t o hide—or at least at t em pt t o organize—t he m ind- boggling com plexit y. Expose t he underlying dat a and business rules in an orderly and m anageable fashion t hrough t he package specificat ion. This t echnique is crucially im port ant when im plem ent ing core business rules in your applicat ion. Every such rule should be hidden behind a funct ion and defined in t he appropriat e package. I n addit ion, hide all t he SQL for a given t able or business ent it y behind a package int erface ( t his process is called t able encapsulat ion) . Rat her t han writ e an I NSERT

155

st at em ent in your program , call an insert procedure. See [ SQL- 15: Encapsulat e I NSERT, UPDATE, and DELETE st at em ent s behind procedure calls.] for m ore det ails.

Ex a m ple Let 's look at a sim ple ex am ple: building a t im ing ut ilit y. The DBMS_UTI LI TY.GET_TI ME built - in funct ion ret urns t he num ber of hundredt hs of seconds t hat hav e elapsed since an arbit rary point in t im e. You call it t wice and subt ract t he difference t o calculat e elapsed t im e ( down t o t he hundredt h of a second) , as in: DECLARE l_start PLS_INTEGER; l_end PLS_INTEGER; BEGIN l_start := DBMS_UTILITY.GET_TIME; overdue.calculate_fines; l_end := DBMS_UTILITY.GET_TIME; pl ('Calculated fines in ' || (l_end - l_start) / 100 || ' seconds'); END; I hav e t w o concerns: ( a) t hat 's a lot of code t o writ e t o sim ply calculat e elapsed t im e, and ( b) t he form ula is exposed t hat calculat es elapsed t im e. What if t he form ula changes? Ah, y ou're probably asking: How could a form ula t his sim ple change? Well, it t urns out t hat t his form ula can som et im es result in a negat ive elapsed t im e, because DBMS_UTI LI TY.GET_TI ME occasionally " rolls over" t o zero. So rat her t han writ ing code like t hat shown in t he preceding exam ple, you are m uch bet t er served by building a sim ple package as follows: CREATE OR REPLACE PACKAGE tmr IS PROCEDURE capture; PROCEDURE show_elapsed; END tmr; / CREATE OR REPLACE PACKAGE BODY tmr IS c_bignum INTEGER := POWER(2,32); last_timing NUMBER := NULL; PROCEDURE capture IS BEGIN last_timing := DBMS_UTILITY.GET_TIME; END capture; PROCEDURE show_elapsed IS BEGIN pl (MOD (DBMS_UTILITY.GET_TIME last_timing + c_bignum, c_bignum)); END show_elapsed; END tmr; /

156

Term Fly Presents

http://www.flyheart.com

This package- based im plem ent at ion now allows you t o calculat e elapsed t im e as follows: BEGIN tmr.capture; overdue.calculate_fines; tmr.show_elapsed; END;

Be n e fit s By using packages t o hide com plexit y, you nat urally em ploy st epwise refinem ent ( a.k.a. t op- down design) . The result ing code is easier t o underst and, use, and m aint ain. By hiding form ulas, you can fix t hem and enhance t hem as needed ov er t im e.

Re sou r ce s 1. t m r.pkg : The sim plest version of t he t im er package 2. PLVt m r.pkg : A m ore com plet e im plem ent at ion 3. t m r81.ot : An obj ect - based t im er

PKG- 0 3 : Fr e e ze a n d bu ild pa ck age spe cifica t ion s be for e im plem en t in g pa ck a ge bodies.

Dev elop a " specificat ions first " discipline: put off writ ing package bodies as long as possible. I nst ead, sit back, relax, and brainst orm about t he kinds of t hings you want t o do wit h each package ( based, of course, on requirem ent s provided by t he users) . Writ e out t hose t hings- t o- do as procedure and funct ion headers in t he specificat ion. Do t his for a whole bunch of packages y ou need t o build. Then t ry t hem out . Ev en if you don't built t he package bodies, y ou can st ill writ e program s based on t he headers. By doing t his, you oft en uncov er errors in t he requirem ent s, m issing param et ers, and so on. Since you hav en't y et w rit t en t he im plem ent at ions, it 's easy t o clarify what t he user want s and m odify t he package specificat ions. Once y ou are confident t hat t he specificat ions reflect t he applicat ion needs, dive int o t hose package bodies!

Ex a m ple We are building a t elesales call m anagem ent sy st em . Managem ent j ust t old us t hat due t o t he upcom ing I PO, w e hav e t o get every t hing done in t wo m ont hs. Yikes! My first inclinat ion is t o st art writ ing code m adly, but t he DBAs haven't finished

157

designing t he t ables, and t he users are st ill t hrashing. I can't wait , t hough, so I t ak e what requirem ent s hav e been set and brainst orm via package specificat ions. I know t hat I have t o do som e analysis, so I quickly put t oget her t his specificat ion: CREATE OR REPLACE PACKAGE analysis IS FUNCTION avg_workload ( dept_id IN INTEGER) RETURN NUMBER; FUNCTION workload ( operator_id IN INTEGER) RETURN NUMBER; FUNCTION avg_numcalls ( dept_id IN INTEGER) RETURN NUMBER; END analysis; I don't y et know how t o calculat e t he average workload for a depart m ent , but I know I will need it . I also need t o perform som e call m aint enance, and according t o t he docum ent at ion, I need t o t ransfer a call t o a new depart m ent : CREATE OR REPLACE PACKAGE callmaint IS PROCEDURE transfer ( call_id IN INTEGER, dept_id IN INTEGER; END callmaint; Now, I can perform t wo pieces of " m agic" : •

I can com pile bot h specificat ions, since t hey don't rely on any % TYPE declarat ions t o t ables t hat don't exist . That way , I can m ak e sure t heir synt ax is valid.



I can w rit e ot her program s t hat use t hese program s, such as t he loadbalancing " assign a call" procedure, shown here: CREATE OR REPLACE PROCEDURE assign_call (call_in IN INTEGER, oper_in IN INTEGER, dept_in IN INTEGER) IS BEGIN IF analysis.workload (oper_in) < analysis.avg_workload (dept_in) THEN callmaint.transfer (call_in, dept_in); END IF; END assign_call;

• • • • • • • • • • •

Once again, I can v erify t hat t his code com piles ( t hough I can't run it ) . I can also walk t hrough t his very readable code and check for logical errors. Allow m e t o read it t o y ou: I f t he w orkload for t his operat or is less t han t he average w orkload of t he depart m ent , t hen t ransfer t he call t o t hat depart m ent .

158

Term Fly Presents

http://www.flyheart.com

Wait a m inut e, t hat doesn't sound right . Shouldn't I t ransfer t he call t o t hat operat or who is underut ilized? So I check wit h t he users, get confirm at ion of t heir m ist ake, and in m inut es have correct ed t he callm aint specificat ion and t he assign_call procedure. No need t o fix t he package body, t hough, since I held off im plem ent ing it .

Be n e fit s By concent rat ing on t he way t hat different program s int eract wit h each ot her, t he result ing soft ware is bet t er behav ed and m ore easily changed ov er t im e. You spend less t im e fixing errors lat er in t he developm ent cycle, since you can m ore easily ident ify problem s before ext ensive coding has even begun.

Ch a lle n ge s You m ight get nervous because y ou are holding off w rit ing t he " real" code, t he package body , while t he clock t icks away. The t im e spent on t his upfront design verificat ion will, howev er, sav e y ou hours of fixes and debugging lat er on. You can also creat e t he package body t hat cont ains " st ub" subprogram s, in which each procedure or funct ion cont ains t he m inim um am ount of code needed t o get t he package body t o com pile. The code can be run but , of course, it won't act ually do anyt hing ( except perhaps allow you t o t race ex ecut ion and validat e ov erall logical flow) .

PKG- 0 4 : I m plem en t fle x ible , u se r - a dj u st a ble fu n ct ion a lit y u sin g pa ck a ge st a t e t oggle s a n d r e la t e d t e ch n iqu e s.

As y ou rely m ore and m ore on packages t o offer funct ionalit y t o program m ers on your t eam or in your com pany, y ou want t o design t hose packages t o be flexible and responsive t o v arying user needs. You cert ainly don't want program m ers going int o t he package bodies and changing im plem ent at ions. You also don't want t hem m ak ing copies of y our code and t hen producing t heir own variat ions. I nst ead, add program s t o t he package specificat ion t hat allow dev elopers t o m odify ( wit hin cert ain accept ed pat hways) t he behavior of your package t o fit t heir varying requirem ent s. These program s m ight t urn on/ off cert ain feat ures ( " t oggles" ) or m ight set int ernal package values. The m ost im port ant feat ure of t hese program s is t hat t hey allow t he package t o change it s behavior wit hout having t o change t he source code.

Ex a m ple

159

I hav e decided t o build a check- out package t hat allows librarians t o check books out of t heir collect ion. The default rule is t hat a person can hav e a m axim um of 10 book s checked out at any t im e. I can writ e m y package t o hard- code t hat rule as follows: CREATE OR REPLACE PACKAGE BODY checkout_pkg IS c_max_allowed CONSTANT PLS_INTEGER := 10; FUNCTION can_check_out ( borrower_id IN borrower.id%TYPE, isbn_in IN book.isn%TYPE) RETURN BOOLEAN IS l_checked_out PLS_INTEGER; l_book_is_available BOOLEAN; BEGIN l_checked_out := checked_out_count (borrower_id); l_book_is_available := book_available (isbn_id); RETURN (l_checked_out < c_max_allowed AND l_book_is_available); END can_check_out; ... But t his approach doesn't let a librarian override t his rule, which he m ight need t o do wit h a professional researcher, for exam ple. A m uch bet t er approach is t o offer, in t he specificat ion, a way t o change t he checkout lim it s, wit hin reasonable boundaries. Here's a m odified package specificat ion: CREATE OR REPLACE PACKAGE BODY checkout_pkg IS PROCEDURE set_checkout_limit (count_in IN PLS_INTEGER); FUNCTION checkout_limit RETURN PLS_INTEGER; FUNCTION can_check_out ( borrower_id IN borrower.id%TYPE, isbn_in IN book.isn%TYPE) RETURN BOOLEAN; The im plem ent at ion of t his override is sim ple: change t he const ant t o a variable and m odify it from wit hin set _check out _lim it : CREATE OR REPLACE PACKAGE BODY checkout_pkg IS g_max_allowed PLS_INTEGER := 10; PROCEDURE set_checkout_limit ( count_in IN PLS_INTEGER) IS BEGIN g_max_allowed := LEAST (GREATEST (count_in, 1), 20); END set_checkout_limit; FUNCTION checkout_limit RETURN PLS_INTEGER IS

160

Term Fly Presents

http://www.flyheart.com

BEGIN RETURN g_max_allowed; END checkout_limit; Anot her exam ple of t his t echnique is t he on/ off swit ch you build t o im plem ent a " window" in a package ( see [ PKG- 05: Build t race " windows" int o your packages using st andardized program s.] ) .

Be n e fit s The increased flexibilit y m akes it m ore likely t hat t he package is used and reused. Code reuse generally im prov es t he qualit y of your applicat ion and reduces t he resources needed t o t est and m aint ain t he code base. You can oft en layer t oggles and ot her set t ing program s on t op of an ex ist ing package; it doesn't all have t o be figured out in advance.

Ch a lle n ge s From a design st andpoint , you can im plem ent t his flexibilit y eit her as a package- level set t ing or by adding anot her param et er t o y our program s. Choose t he package- lev el approach if t he set t ing or swit ch is a preference t he user will want t o apply t o t he ov erall package behavior, and not j ust a single program . You can even build a sm all GUI applet t hat allows a dev eloper t o easily change t hese set t ings.

Re sou r ce s wat ch.pkg : This package, used t o t race program ex ecut ion, offers t he abilit y t o swit ch out put bet ween t he screen and dat abase pipes.

PKG- 0 5 : Bu ild t r a ce " w in dow s" in t o you r pa ck a ge s u sin g st a n da r dize d progr a m s.

On t he one hand, it 's very helpful t o use packages t o hide com plexit y. On t he ot her hand, it 's oft en t he case t hat users can be great ly aided by being able t o look inside packages and wat ch what 's happening. You can easily build a " read- only" window int o a package. Users can open t he window when and if he want s t o, wat ch what t he package is doing ( or at least what t he aut hor of t he package claim s t he package is doing) , and t hen shut t he window when t hat inform at ion isn't needed. To build a window, you will need t o: •

Add t racing code inside t he package body.



Supply an on- off swit ch in t he specificat ion so t hat users can open and close t he window.

161

Ex a m ple My overdue fines package allows a user t o set t he daily fine rat e, as shown by t his part ial package body: CREATE OR REPLACE PACKAGE BODY overdue_pkg IS g_daily_fine NUMBER := .10; PROCEDURE set_daily_fine (fine_in IN NUMBER) IS BEGIN g_daily_fine := GREATEST (LEAST (fine_in, .25), .05); END set_daily_fine; ... I now want t o add a t race t o t his package so t hat a user of ov erdue_pk g can see what t he daily fine is being set t o when her applicat ion runs. First , I add an on- off swit ch t o t he package specificat ion and body: CREATE OR REPLACE PACKAGE overdue_pkg IS ... other elements in package PROCEDURE trc; PROCEDURE notrc; FUNCTION tracing RETURN BOOLEAN; END overdue_pkg; Then, I add m y t race t o t he set _daily_ fine procedure: PROCEDURE set_daily_fine (fine_in IN NUMBER) IS BEGIN IF tracing THEN watch.action ('set_daily_fine', fine_in); END IF; g_daily_fine := GREATEST (LEAST (fine_in, .25), .05); END set_daily_fine; Rat her t han call DBMS_OUTPUT.PUT_LI NE or even m y enhanced pl procedure, not ice t hat I call wat ch.act ion. The wat ch package is a m ore robust t racing m echanism . Am ong ot her feat ures, it allows you t o direct out put t o a dat abase pipe, bypassing t he buffer lim it at ions of DBMS_OUTPUT. Now, when I want t o wat ch what is happening inside m y ov erdue package, I sim ply t urn on t race for m y session before I run m y applicat ion code: SQL> exec overdue_pkg.trc SQL> exec library_test

162

Term Fly Presents

http://www.flyheart.com

Be n e fit s This " read- only" window int o a package can help you ensure t hat y ou're using t he package properly or allow you t o confirm t hat t he dat a y ou're passing int o t he package is correct . Such windows increase y our confidence in t he pack age and allow you t o use it m ore effect ively. The on- off swit ch for t he window is based on t he " get and set " t echnique discussed in [ DAT- 15: Expose package globals using " get and set " m odules.] . Windows are easy t o add t o y our package aft er you hav e w rit t en t he base code. Just add an on- off swit ch and put an I F st at em ent inside t he appropriat e m odule.

Ch a lle n ge s I f y ou add t race calls, add t hem com prehensively, t aking int o account t he perspect ive and needs of t he user. Looking t hrough a sm ok y window m ay be m ore confusing t han not being able t o see at all. I t 's w ort h com ing up wit h a plan for where and when y ou will insert t race calls in your code. Tak e int o account possible im pact on perform ance and readabilit y. You m ay also find yourself adding t race logic it erat ively as y ou w ork on different sect ions of code, in order t o wat ch several com put at ions or logic pat hs.

A Robu st Tr a cin g M e ch a n ism When wat ching values inside a loop, m any values m ay be generat ed ( hundreds of t housands if you loop t hat m any t im es) . Condit ional logic can be used t o only display values when specific crit eria are m et . For exam ple, while processing t ens of t housands of claim s, we want ed t o t race only specific claim s. So I built a package t hat allowed us t o st ore t hose claim num bers in a packaged index- by t able ( before we act ually r an t he claim s processing program ) . I f any of t hose claim s cam e up, t he t race m echanism was enabled only for t hose claim s. A global debug flag t urned debugging on or off in general, and a local flag t urned it on or off for j ust t hose claim s pre- populat ed in t he index- by t able ( but only if t he global debug flag was t urned on) . A funct ion provided t he int erface t o t he index- by t able. —Dan Clam age Re sou r ce s 1. overdue.pkg : The overdue package 2. wat ch.pkg : A wat ch package used t o perform t racing

163

PKG- 0 6 : Use pack a ge body pe rsist en t da t a st r u ct u re s t o ca ch e an d opt im ize da t a - driven pr oce ssin g.

When y ou declare dat a inside a package but not wit hin any individual procedure or funct ion in t he package, t hat dat a persist s for y our ent ire session. [ 1] A package- lev el collect ion, for exam ple, ret ains it s values ( say, 1000 rows of dat a) unt il you DELETE one or m ore row s from t he collect ion, close your connect ion, or recom pile t he package. [ 1] You can insert t he PRAGMA SERI ALLY_REUSABLE st at em ent int o your package if you don't want package level dat a t o be persist ent .

This dat a persist ence m eans y ou can use package dat a as a " local" cache—local t o t hat single session/ user. The Syst em Global Area ( SGA) act s as a cache for all users and great ly im prov es ov erall dat abase perform ance. Your own session- specific cache can im prove y our applicat ion perform ance. You can cache at m ult iple levels: •

A single value, such as t he nam e of t he current user



A record of values, such as t he default configurat ion for t he current user



An ent ire collect ion or list of values, such as t he result set of a query t hat m ust be processed m ult iple t im es

Regardless of t he com plexit y of dat a, t he condit ions and st eps for caching are sim ilar: •

The dat a m ust be st at ic for t he durat ion of t he session. I t 's possible t o com e up wit h ways t o updat e t he cache, but such effort s are likely t o cancel out perform ance gains.



You need t o declare t he dat a st ruct ures inside t he package body so t hat you can m anage t heir cont ent s and int egrit y ( see [ DAT- 15: Expose package globals using " get and set " m odules.] ) .



You also need t o build access program s t o t hose dat a st ruct ures so t hat ev en inside t he package body , y ou m anipulat e t he cache t hrough a w ell- defined int erface.

Ex a m ple For reasons of space, I will show t he sim plest package- based caching m echanism here. See Resources for m ore com plex sam ple packages. Consider t he USER funct ion. I t ret urns t he nam e of t he current ly connect ed user. This value never changes during your session, right ? The USER funct ion in PL/ SQL is im plem ent ed as follows ( as defined in t he STANDARD package, and wit h less- t hanideal form at t ing) :

164

Term Fly Presents

http://www.flyheart.com

function USER return varchar2 is c varchar2(255); begin select user into c from sys.dual; return c; end; So every t im e you call USER in PL/ SQL, it runs a query. That is quit e unnecessary, and you can use caching t o ensure t hat t his query is run j ust once per session: CREATE OR REPLACE PACKAGE thisuser IS name CONSTANT VARCHAR2(30) := USER; END thisuser; / Now you can reference t he value of USER wit hout m ult iple calls t o USER, like t his: FOR user_rec IN user_cur LOOP IF user_rec.user_name = thisuser.name THEN ... See Resources for a reference t o t he t hisuser package; t here you will find a script you can run t o t est t he perform ance advant age of t hisuser ov er direct , repet it ive calls t o USER.

Pu t USER in a Pa ck a ge Va r ia ble We used t he USER funct ion t o different iat e report dat a being generat ed by each user, so t wo people could run t he sam e report wit h different crit eria and not st om p on each ot her. We used funct ion USER everywhere—in hundreds of packages. Then one day, when t he DBA was figuring out how t o m igrat e our current plat for m t o AI X, he realized t hat t he USER value would have t o be replaced wit h v$session.osuser , for net workrelat ed reasons. We had t o go back and fix a lot of packages. I f only we had st ored t he USER value in a package variable, we would j ust have needed t o change it in one place. —Dan Clam age Be n e fit s This t echnique im prov es applicat ion perform ance by avoiding unnecessary and ( relat ively speaking) slow dat a access t hrough t he SGA.

165

I t 's especially handy when y ou execut e long- running bat ch processes t hat m ust perform m ult iple passes t hrough large result set s. Load up t he query result s in a collect ion of records, and t hen you have bidirect ional, random access t o t he dat a for your bat ch process.

Ch a lle n ge s Each session has it s ow n copy of pack age dat a, and Oracle uses real m em ory for t his session dat a. So if you plan t o cache dat a, be aware of t he volum e of dat a and t he num ber of users who will cache it .

Re sou r ce s 1. init .pkg and init .t st : An exam ple package and script t o com pare t he perform ance of caching a record of dat a. 2. em plu.pkg and em plu.t st : An exam ple package and script t o com pare t he perform ance of caching m ult iple row s of dat a.

PKG- 0 7 : I n su la t e a pplica t ion s from Or a cle ve r sion sen sit ivit y u sin g ve rsion - spe cific im plem en t a t ion s.

Many organizat ions need t o w rit e code t hat will run on different Oracle versions ( such as Oracle 7.3 and Oracle 8.1) . There are t wo approaches you m ight follow in t his sit uat ion: •

Use " lowest com m on denom inat or" feat ures t hat are available in all versions.



Use t he best and m ost appropriat e feat ures av ailable in each v ersion.

I f y ou t ake t he first approach, y ou can m aint ain j ust one v ersion of code, but y ou will also sacrifice significant funct ionalit y and perform ance advant ages. I f y ou t ake t he second approach, y ou can avoid m aint aining m ult iple copies of t he code by ( a) using packages t o isolat e t hose differences, and ( b) relying on t he separat ion of package specificat ion and body t o " ex ecut e around" com pilat ion errors. Here are t he basic st eps you need t o t ak e t o achieve t his effect : 1. Ext ract all version- specific logic int o separat e package bodies, separat ed by dat abase version. 2. Creat e a funct ion t hat ret urns t he current Oracle v ersion. 3. Modify or creat e t he m ain ( public) package t o call each of t he v ersion- specific program s, based on t he current Oracle v ersion. 4. Com pile and use t he code in each different dat abase version. This last point is, in a way, t he m ost int erest ing. You see, at least one of y our package bodies will act ually fail t o com pile—and you w on't care! The package body for Oracle8i, for exam ple, doesn't com pile in Oracle 7.3. But t hat doesn't m at t er at

166

Term Fly Presents

http://www.flyheart.com

all, because ( a) t he package specificat ion com piles, and t hat 's all t he out er package needs for it t o com pile, and ( b) t he code in t he body t hat didn't com pile will never be called. The following exam ple dem onst rat es t his in a sim ple and easy t o underst and way.

Ex a m ple Suppose I need t o build an error- logging package t hat will be used in Oracle 7.3 and Oracle8i ( 8.1) . I n Oracle8i, I want t o t ake advant age of aut onom ous t ransact ions, allowing t he log ent ry t o be im m ediat ely saved t o t he dat abase wit hout affect ing t he m ain t ransact ion. First , I creat e t he specificat ions for m y generic logging package and a separat e package for m y Oracle8i- specific code: CREATE OR REPLACE PACKAGE log_pkg IS PROCEDURE putline ( code_in IN INTEGER, text_in IN VARCHAR2); END log_pkg; / CREATE OR REPLACE PACKAGE log81_pkg IS PROCEDURE putline ( code_in IN INTEGER, text_in IN VARCHAR2); END log81_pkg; / My logging package body det erm ines t he Oracle version wit h a query against t he PRODUCT_COMPONENT_VERSI ON dat a dict ionary view and st ores it in a global package variable: CREATE OR REPLACE PACKAGE BODY log_pkg IS g_version VARCHAR2 (100); FUNCTION oraversion RETURN VARCHAR2 IS retval VARCHAR2 (100); BEGIN SELECT SUBSTR (version, 1, 3) INTO retval FROM product_component_version WHERE UPPER (product) LIKE 'ORACLE7%' OR UPPER (product) LIKE 'PERSONAL ORACLE%' OR UPPER (product) LIKE 'ORACLE8%'; RETURN retval; END oraversion; I t hen im plem ent t he put line procedure using condit ional logic eit her t o call t he Oracle8i logging program or t o do t he " norm al" I NSERT ( t he best I can do in Oracle7 and Oracle8) :

167

PROCEDURE putline ( code_in IN INTEGER, text_in IN VARCHAR2) IS BEGIN IF g_version = '8.1' THEN log81_pkg.putline (code_in, text_in); ELSE INSERT INTO logtab VALUES (code_in, text_in, SYSDATE, USER); END IF; END putline; BEGIN -- Populate the version in the init. section. g_version := oraversion; END log_pkg; / This package body com piles because t he log81_ pkg specificat ion has already been defined; t he package body isn't needed t o get t his far. Now let 's t ry t he package body for Oracle8i processing: CREATE OR REPLACE PACKAGE BODY log81_pkg IS PROCEDURE putline ( code_in IN INTEGER, text_in IN VARCHAR2) IS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN INSERT INTO logtab VALUES (code_in, text_in, SYSDATE, USER); COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; END putline; END log81_pkg; / This package body doesn't com pile in an Oracle7 environm ent , but t he log_ pkg.put _line procedure st ill works because log81_ pkg.put _line is never called. The log.t st script dem onst rat es t his behavior. For t hose of us running Oracle8i, t he second half of t he script " m im ics" an Oracle7 environm ent by hard- coding t he version and causing com pile errors in t he log81_ pkg body .

You m ight also consider using t he DBMS_SQL package ( available in all current and fut ure versions of t he Oracle RDBMS) t o execut e a PL/ SQL block t hat 's const ruct ed at runt im e. The package com piles because t he com piler is ignorant of version- specific dependencies hidden in t he t ext st ring.

168

Term Fly Presents

http://www.flyheart.com

Be n e fit s You can build and m aint ain one version of y our code t hat work s across m ult iple versions of t he Oracle dat abase.

Ch a lle n ge s Make sure t hat t he funct ion ret rieving t he Oracle dat abase v ersion works on y our inst ance ( I hav e confirm ed t he logic for Oracle7, Oracle8, and Oracle8i t hrough 8.1.6) . You will som et im es want t o use v ersion- specific feat ures in t he package specificat ion, such as Oracle8i 's NOCOPY param et er hint , t he DETERMI NI STI C pragm a, and t he AUTHI D clause. This t echnique will t hen not work.

Re sou r ce s log.pkg and log.t st : The log package and t est script t hat dem onst rat e t he t echniques you need for t his best pract ice.

PKG- 0 8 : Avoid bloa t in g pa ck a ge code w it h u n n e ce ssa ry bu t e a sy- t o- bu ild m odu le s.

This best pract ice definit ely applies t o t he aut hor of t his book. Once y ou get st art ed building packages, especially packages t hat provide an int erface t o underlying funct ionalit y, it 's so, so easy t o get ex cit ed about all t he possibilit ies, all t he different program s t hat can and should be a part of t hat int erface. Why not add a funct ion t hat calculat es X, or a procedure t hat displays Y? The result can be best charact erized by a rephrasing of t hat oft - repeat ed riddle: I f y ou writ e a program and no one uses it , does t hat program really ex ist ? And it 's not j ust a m at t er of wast ed effort . By loading up a package wit h program s no one necessarily want s or needs, y ou m ak e it harder for anyone t o find t he program s t hat t hey act ually do want or need. You are bet t er off t aking a m inim alist approach t o building your packages: build only what is needed at t his point in t im e. I m plem ent t hose requirem ent s in t he sim plest , m ost direct m anner possible ( based on best pract ices, of course) .

Ex a m ple Gosh, t here are act ually a num ber of exam ples t o choose from in m y own t oolbox. All right , how about t he PLVgen package of PL/ Vision? This pack age is a handy code generat or. You can, for exam ple, spit out funct ion t em plat es. You can direct t he out put t o t he screen or a file. But wait ! That 's not all. You can also send t he generat ed code t o a dat abase pipe or ev en a " PL/ SQL t able" ( now called an index- by

169

t able or collect ion) . Of course, if you w rit e t he code t o a PL/ SQL t able, y ou t hen need t o ext ract it from t hat collect ion. I t 's t he sam e wit h a dat abase pipe. So t he PLVgen specificat ion ended up cont aining ( am ong m any ot her program s) t he following: CREATE OR REPLACE PACKAGE PLVgen IS -- Direct output to a screen, file, database table, etc. PROCEDURE toscreen; PROCEDURE tofile (file IN VARCHAR2 := c_file); PROCEDURE todbtab (tab IN VARCHAR2 := c_dbtab); -- Pipe related functionality PROCEDURE topipe (pipe IN VARCHAR2 := c_pipe); PROCEDURE pipe2file ( pipe IN VARCHAR2 := c_pipe, file IN VARCHAR2 := c_file); PROCEDURE pipe2dbtab ( pipe IN VARCHAR2 := c_pipe, tab IN VARCHAR2 := c_dbtab); -- PL/SQL PROCEDURE PROCEDURE PROCEDURE

table related functionality topstab; pstab2file (file IN VARCHAR2 := c_file); pstab2dbtab (tab IN VARCHAR2 := c_dbtab);

... END PLVgen; From a purist 's point of view, all t his m akes perfect sense. I hav e a st rong feeling, howev er, t hat t he PLVgen.pst ab2dbt ab program has never been ( and will never be) used.

Be n e fit s You don't wast e t im e building code no one will use. Dev elopers can m uch m ore easily find t he funct ionalit y t hey act ually need.

Ch a lle n ge s This best pract ice is a sobering rem inder t hat w e are, for t he m ost part , engaged in soft ware developm ent not as an art , but as a m eans of em ploym ent . Flight s of fancy don't hav e m uch of a place in our applicat ion- dev elopm ent proj ect s.

PKG- 0 9 : Sim plify an d en cou r a ge m odu le u sa ge u sin g ove r loa din g t o w iden ca llin g opt ion s.

170

Term Fly Presents

http://www.flyheart.com

Overloading ( also know n as st at ic polym orphism in t he world of obj ect - orient ed languages) is t he abilit y t o creat e t wo or m ore program s wit h t he sam e nam e. While you can do t his in t he declarat ion sect ion of any PL/ SQL block , it 's m ost useful and com m on in package specificat ions. The prim ary reason t o overload program s in your package is t o t ransfer t he " need t o know" about how t o use your funct ionalit y from t he user t o t he package it self. You ant icipat e t he different ways t hat dev elopers will want t o use t he packaged feat ure and t hen offer m at ching variat ions of t he " sam e" program .

Ex a m ple DBMS_OUTPUT.PUT_LI NE ( " put a line on t he screen" ) is one of t he m ost com m only used built - in procedures in t he Oracle t oolbox. I t 's overloaded for t hree t ypes of dat a, as shown in t he DBMS_OUTPUT specificat ion. PROCEDURE DBMS_OUTPUT.PUT_LINE (A VARCHAR2); PROCEDURE DBMS_OUTPUT.PUT_LINE (A NUMBER); PROCEDURE DBMS_OUTPUT.PUT_LINE (A DATE); By overloading t his way, Oracle allows us t o pass a st ring, a num ber, or a dat e t o t his procedure, and it aut om at ically " does t he right t hing." I ronically, Oracle could have provided j ust a single VARCHAR2 im plem ent at ion, and t he PL/ SQL runt im e engine would have im plicit ly convert ed num bers and dat es t o st rings for us. What Oracle didn't offer was an ov erloading for DBMS_OUTPUT.PUT_LI NE t hat support s t he Boolean dat at ype, result ing in t his kind of error: SQL> l 1 DECLARE 2 l_book_is_overdue BOOLEAN; 3 BEGIN 4 DBMS_OUTPUT.PUT_LINE (l_book_is_overdue ); 5 END; 6 / ERROR PLS-00306: wrong number or types of arguments in call to 'PUT_LINE' The result is t hat developers oft en w rit e code like t his ( ov er and ov er again) : BEGIN IF l_book_is_overdue THEN DBMS_OUTPUT.PUT_LINE ('TRUE'); ELSIF NOT l_book_is_overdue DBMS_OUTPUT.PUT_LINE ('FALSE'); ELSE DBMS_OUTPUT.PUT_LINE ('NULL'); END IF; Yuck! The point here is t hat it 's im port ant not only t o overload program s but also t o ov erload properly and sufficient ly. You need t o analyze and ant icipat e com m on dev eloper requirem ent s and build packages t o m eet t hose requirem ent s. Oracle

171

doesn't hav e t he best t rack record in t his regard, but you cert ainly can t ake t he t im e and m ake t he effort in y our own program s.

Be n e fit s When y ou overload properly, developers t ak e y our code t ot ally for grant ed. They have no idea about all t he work y ou put int o your various im plem ent at ions. They j ust know t hat t hey call " t hat program " and it does what 's needed, regardless of variat ions in param et er list s.

Ch a lle n ge s Don't build your code in t he abst ract . Think about how t he code needs t o be used. Try out t he usages y ourself. Be ready t o add t o your ov erloadings in response t o user feedback. There are som e t echnical lim it at ions t o ov erloading, for exam ple, when com bined wit h default param et ers or when used wit h im plicit t ype conv ersions, or ev en when used in different client environm ent s ( e.g., Microsoft 's Act ive Dat a Obj ect s, or ADO, doesn't recognize ov erloading and only " sees" t he first declarat ion it com es across) . I n sit uat ions where t here are m any ov erloadings, y ou m ight consider adding a t race so t hat users of t he package can easily confirm which of t he ov erloadings are being used.

Re sou r ce s p.sps and p.spb : PL/ Vision offers a subst it ut e for DBMS_OUTPUT.PUT_LI NE called t he p.l procedure. This procedure is overloaded 18 t im es, allowing a developer t o display, for exam ple, a st ring and a num ber, a Boolean, t w o num bers, and so on.

PKG- 1 0 : Con solida t e t h e im plem en t a t ion of r e la t e d ove r loade d m odu les.

I n m ost cases, when you build overloaded program s, each program perform s a sim ilar operat ion, wit h variat ions t hat are usually relat ed t o different com binat ions of param et ers. I f y ou aren't careful about how y ou im plem ent t hese overloadings, you will end up wit h a m ess of code t hat 's difficult t o m aint ain and enhance. The m ost im port ant st ep you can t ak e is t o isolat e behavior/ feat ures com m on t o all ov erloadings and t hen m ov e t hat com m on code int o a separat e, usually privat e program . All t he overloadings t hen call t hat int ernal program . You should also t ak e care t o organize t he ov erloaded headers cont iguously in t he package specificat ion so t hat t hey are easily ident ified.

Ex a m ple

172

Term Fly Presents

http://www.flyheart.com

Suppose t hat I have decided t o build an encapsulat ion package around t he book t able. Dev elopers will not writ e an I NSERT st at em ent t o add a book; t hey will call an insert procedure. I can t hink of several different ways t o perform t hat insert : •

Pass an individual value for each colum n.



Pass a record cont aining a value for each colum n.



Pass a collect ion of m ult iple records of book dat a.

Here's t he package specificat ion corresponding t o t hese approaches: CREATE OR REPLACE PACKAGE te_book IS TYPE book_tt IS TABLE OF book%ROWTYPE; PROCEDURE ins ( isbn_in IN book.isbn%TYPE, title_in IN book.title%TYPE DEFAULT NULL, summary_in IN book.summary%TYPE DEFAULT NULL, author_in IN book.author%TYPE DEFAULT NULL, date_published_in IN book.date_published%TYPE DEFAULT NULL, page_count_in IN book.page_count%TYPE DEFAULT NULL); --// Record-based insert //-PROCEDURE ins (rec_in IN book%ROWTYPE); --// Collection-based insert //-PROCEDURE ins (coll_in IN book_tt); END te_book; Here are t hree different program s wit h t he sam e nam e, all wit h very different param et er list s. Now let 's look at t he package body: First , I define an " int ernal" insert procedure t hat perform s t he act ual I NSERT and provides st andardized error handling and validat ion: CREATE OR REPLACE PACKAGE BODY te_book IS PROCEDURE internal_ins ( isbn_in IN book.isbn%TYPE, title_in IN book.title%TYPE DEFAULT NULL, summary_in IN book.summary%TYPE DEFAULT NULL, author_in IN book.author%TYPE DEFAULT NULL, date_published_in IN book.date_published%TYPE DEFAULT NULL, page_count_in IN book.page_count%TYPE DEFAULT NULL ) IS BEGIN validate_constraints; INSERT INTO book ( isbn, title, summary, author, date_published, page_count) VALUES ( isbn_in, title_in, summary_in, author_in, date_published_in, page_count_in);

173

EXCEPTION WHEN OTHERS THEN err.log; END internal_ins; As for m y various insert procedures, I im plem ent t hem eit her by using t he int ernal_ins procedure direct ly or by calling anot her insert procedure, w hichever is m ost int uit ive: PROCEDURE ins ( title_in IN book.title%TYPE DEFAULT NULL, summary_in IN book.summary%TYPE DEFAULT NULL, author_in IN book.author%TYPE DEFAULT NULL, date_published_in IN book.date_published%TYPE DEFAULT NULL, page_count_in IN book.page_count%TYPE DEFAULT NULL, isbn_inout IN OUT book.isbn%TYPE) IS v_pky INTEGER := new_isbn_number; BEGIN internal_ins (v_pky, title_in, summary_in, author_in, date_published_in, page_count_in ); isbn_inout := v_pky END; PROCEDURE ins (rec_in IN book%ROWTYPE) IS BEGIN internal_ins (rec_in.isbn, rec_in.title, rec_in.summary, rec_in.author, rec_in.date_published, rec_in.page_count ); END; PROCEDURE ins (coll_in IN book_tt) IS indx PLS_INTEGER := coll_in.FIRST; BEGIN LOOP EXIT WHEN indx IS NULL; -- Just use the record-based version ins (coll_in(indx)); indx := coll_in.NEXT (indx); END LOOP; END; END;

174

Term Fly Presents

http://www.flyheart.com

Be n e fit s When y ou need t o fix or change som e aspect of t he im plem ent at ion, you go t o one place in your package body. Once t he change is m ade, y ou are t hen sure t hat it will affect all overloadings.

Ch a lle n ge s Dev elop t he discipline required t o t ake t he t im e t o ident ify com m on areas of funct ionalit y and isolat e t hem int o t heir own program s.

Re sou r ce s t e_book.pkg : The t able encapsulat ion package for t he book t able ( well, j ust t he I NSERT funct ionalit y of such a package) .

PKG- 1 1 : Sepa r a t e pa ck a ge spe cifica t ion s an d bodies in t o diffe r e n t sou r ce code file s.

Don't com bine t he specificat ion and body of a package in t he sam e file. I nst ead, st ore t hem in t heir own files and t hen, in your inst allat ion script for your product , com pile all specificat ions first , followed by t he package bodies. By t aking t his approach, y ou will find it easier t o inst all and m aint ain your code base. Ov er t im e, it 's likely t hat your package specificat ion will st abilize, and m ost changes will t ake place in t he package body . All references t o elem ent s in a package are resolved wit h t he specificat ion. I f t he specificat ion is recom piled, all dependent obj ect s are m ark ed I NVALI D and m ust be recom piled. By put t ing t he body in it s own file, you can change and recom pile it wit hout affect ing t he st at us of any ot her program s.

Ex a m ple See t he various .pk s and corresponding .pkb files provided on t he Oracle PL/ SQL Best Pract ices w eb sit e. You will also find a num ber of .pkg files on t he sit e. I adm it t hat t hese files violat e t his best pract ice. I decided t o t ake t his approach because t hey are sm all, selfcont ained packages, designed t o be deployed easily in your own applicat ion environm ent . PL/ Vision is an exam ple of a m uch m ore com plex base of code. For t his library, all package specificat ions and bodies are st ored in t heir own files.

Be n e fit s

175

You can m aint ain and recom pile packages wit hout causing a " dom ino effect " t hat invalidat es m any ot her ( unchanged) program s. Your code will inst all m ore cleanly, since all references t o packaged funct ionalit y in package bodies and st andalone program s are resolved by specificat ions t hat have already been com piled.

Ch a lle n ge s Pick, and st ick wit h, a consist ent , suit able ext ension for package specificat ion and body files. Most PL/ SQL I DEs can be t aught t o recognize specific suffixes.

PKG-12: Use a standard format for packages that include comment headers for each type of element defined in the package. Packages are likely t o be t he largest , m ost com plex code elem ent s of y our applicat ion. The int ernal st ruct ure of a package ( bot h t he specificat ion and t he body) is usually com posed of m any different t ypes of elem ent s, including variables, userdefined t ypes, funct ions, and procedures. Your st andard package form at should help you organize t he elem ent s of a package, m inim ize t he need for forward declarat ions, and m ake it easier for y ou t o rapidly find const ruct s. Mak e t he st andard form at available for dev elopers eit her in a t em plat e file or by generat ing it upon com m and.

Ex a m ple Here's a t em plat e for a package body ( see Resources for where t o find t his code) : CREATE OR REPLACE PACKAGE BODY nam e IS /* PUT YOUR PACKAGE HEADER HERE */ / * Const ant s * / / * Variables * / / * Ex cept ions * / / * Types ( records, collect ions, cursor variables) * / / * Privat e Program s * / / * Public Program s * /

176

Term Fly Presents

http://www.flyheart.com

END nam e;

Be n e fit s I f every one in your dev elopm ent t eam builds packages t he sam e way, it will be easier t o m aint ain t he code base. By separat ing code elem ent s int o dist inct sect ions ident ified by headers, y ou can quickly find t he desired code.

Re sou r ce s 1. t em plat e.pks and t em plat e.pkb : Tem plat e files, one for t he package specificat ion and one for t he body. 2. PLVgen: Use t his package of t he PL/ Vision library t o generat e a package t em plat e, eit her t o t he screen or direct ly t o a file.

Ch a pt e r 9 . Bu ilt - in Pa ck a ge s Oracle provides a wide- ranging and ever- increasing set of built - in packages— packages t hat are inst alled int o t he dat abase upon inst allat ion and t hat are officially support ed by Oracle. These packages usually give you access t o t echnology and feat ures t hat would ot herwise be difficult , if not im possible, t o im plem ent in nat ive PL/ SQL.

You should becom e fam iliar wit h t he built - in packages; t he Oracle Built - in Packages book and t he Oracle HTML docum ent at ion are t wo excellent sources for t his inform at ion. You m ust , how ev er, also be careful about how you im plem ent program s based on t hese packages. I n m any cases, t he packages are som ewhat hard t o use and underst and; hence, you should hide t hat com plexit y so t hat your result ing code is easy t o m anage ov er t im e. I recom m end y ou follow t hese general guidelines: Encapsulat e access t o t he built - in funct ionalit y I oft en find it very w ort hwhile t o build m y own packages on t op of t he Oracle packages. I can t hen enhance t he base pack age's funct ionalit y. I t also is t hen easier t o use t hat package in a consist ent fashion t hroughout m y applicat ion. Read t he fine print —and run your own t est s—on any built - in packaged funct ionalit y Don't assum e, j ust because Oracle docum ent at ion say s t hat a program will do X, t hat it will, in fact , do X in your environm ent and your version of Oracle. DBMS_UTI LI TY cont ains several program s, for exam ple, t hat don't w ork as advert ised ( COMPI LE_SCHEMA, COMMA_TO_ TABLE, TABLE_TO_COMMA) . [ 1]

177

[ 1]

COMPI LE_SCHEMA is supposed t o recom pile all invalid obj ect s. Som et im es it works, som et im es it does not hing, and som et im es it invalidat es ot her obj ect s as it recom piles current ly invalid obj ect s. COMMA_TO_TABLE and TABLE_TO_COMMA work wit h com m a- delim it ed list s, but t he elem ent s in t he list have t o be valid PL/ SQL ident ifiers. I f you pass " 1,2,3" t o COMMA_TO_TABLE, for exam ple, Oracle raises an except ion.

9 .1 D BM S_ OUTPUT The DBMS_OUTPUT built - in package allows you t o display out put as your PL/ SQL program ex ecut es.

BI P- 0 1 : Avoid u sin g t h e D BM S_ OUTPUT.PUT_ LI N E pr oce du r e dire ct ly.

I am v ery glad t hat Oracle provided DBMS_OUTPUT in Version 2 of PL/ SQL. Before t hat , it was difficult t o debug code, because t here was no easy way t o t race program ex ecut ion t o t he screen. However, t he im plem ent at ion of DBMS_OUTPUT leaves m uch t o be desired. Here are m y com plaint s: 1. I t 's a product ivit y disast er. You have t o t ype 20 charact ers j ust t o ask PL/ SQL t o show y ou som et hing. 2. The ov erloading is inadequat e. You can pass only single st rings, dat es, or num bers. You can't pass it a Boolean value, nor can y ou pass it m ult iple values t o be displayed ( wit hout doing t he concat enat ion yourself) . 3. I f y ou t ry t o display a st ring wit h m ore t han 255 charact ers, y ou get one of t wo errors: ORA- 20000 ( a.k.a. ORU- 10028 line lengt h overflow) or ORA06502 ( num eric or value error) . I don't know about you, but a whole lot of m y st rings are longer t han 255 byt es. 4. Your program can display a m axim um of 1 m illion lines—and it can be lot s less if you forget t o specify a high num ber in your SET SERVEROUTPUT com m and in SQL* Plus ( result ing in an out - of- buffer error) . 5. You don't see anyt hing on y our screen unt il your PL/ SQL program has finished ex ecut ing—whet her t hat t akes five m inut es or five hours. When y ou are faced wit h a ut ilit y such as DBMS_OUTPUT t hat is sim ult aneously necessary and fault y, y ou should say out loud ( it will m ake you feel bet t er) : I am fed up and I am not going t o t ak e it anym ore! Specifically, set a rule t hat you will never call DBMS_OUTPUT.PUT_LI NE direct ly but inst ead build ( or use) a layer of code over DBMS_OUTPUT.PUT_LI NE t hat correct s m ost , if not all, t he prev iously list ed problem s.

Ex a m ple You can t ake a num ber of different approaches t o encapsulat ing and im proving upon DBMS_OUTPUT.PUT_LI NE. I offer im plem ent at ions for each approach in Resources. They are:

178

Term Fly Presents

http://www.flyheart.com



One sim ple procedure t o display st rings, dat es, and num bers, and a second procedure t o display Boolean values—t aking care of problem s 1, 2 ( part ly) , and 3.



A package replacem ent for DBMS_OUTPUT t hat offers a variet y of ov erloadings of dat at ypes for display, such as a Boolean, a st ring and a Boolean, t w o num bers, et c. This im plem ent at ion t akes care of problem s 1 t hrough 4 ( you can av oid buffer ov erflow problem s when a sm all buffer size has been set ) .



A m ore general t race package t hat hides DBMS_OUTPUT, but also allows t he user t o redirect t he " t arget " of t he out put . I f, for exam ple, y ou know t hat you will be generat ing 5 MB of inform at ion, y ou m ight want t o send out put t o a file. I f y our program runs for t wo hours, y ou m ight want t o send out put t o a dat abase pipe, so you can " wat ch" from anot her session.

Be n e fit s You can avoid m ost , if not all, t he nuisance problem s wit h DBMS_OUTPUT, t hus im proving product ivit y and debugging flexibilit y.

Ch a lle n ge s Select a st andard approach for generat ing out put for y our applicat ion, t hen find or build t he right im plem ent at ion. You can check t o see if people are using t he subst it ut e by querying t he ALL_SOURCE dat a dict ionary view t o check for inst ances of DBMS_OUTPUT.

Re sou r ce s 1. pl.sp and bpl.sp : St andalone procedure im plem ent at ions; t hese are used t hrough- out t he book in place of DBMS_OUTPUT.PUT_LI NE. 2. p : This package, part of PL/ Vision, provides a direct pack age encapsulat ion of DBMS_OUTPUT. 3. wat ch.pkg : A generalized t race package wit h t he abilit y t o send out put t o a screen or dat abase pipe.

9 .2 UTL_ FI LE The UTL_FI LE built - in package allows you t o read and writ e sequent ial lines from a file on t he sam e com put er as t he dat abase inst ance from which you run your program .

BI P- 0 2 : I m pr ove t h e fu n ct ion alit y a n d er r or h an dlin g of UTL_ FI LE by u sin g a com preh en sive e n ca psu la t ion pa ck a ge .

179

UTL_FI LE offers only t he m ost prim it ive file I / O capabilit ies and leaves m uch t o be desired. Here's a list of som e of t he t hings you can't do: •

Delet e a file



Obt ain or change t he privileges on a file



Read or writ e a random line in a file ( sequent ial operat ions only)



Obt ain inform at ion about direct ories ( files in a direct ory, whet her or not a nam e indicat es a file or a direct ory, et c.)



Define a pat h for finding and opening files

I n addit ion, t he way t hat UTL_FI LE raises ex cept ions can m ake it difficult t o ident ify and resolve file- handling errors ( see [ BI P- 04: Handle expect ed and nam ed except ions when perform ing file I / O. ] for det ails on t his issue) . Ah well, we've j ust got t o m ak e do wit h what Oracle gives us, right ? Wrong! You should inst ead creat e y our own ( or t ake advant age of som eone else's) package t hat sit s on t op of UTL_FI LE and enhances it s funct ionalit y. I f y ou don't want t o go t o t he t rouble of im plem ent ing an ent ire " replacem ent " package, you can also creat e alt ernat ives t o individual program s, as I dem onst rat e in [ BI P- 05: Encapsulat e UTL_FI LE.GET_LI NE t o avoid propagat ing t he NO_DATA_FOUND ex cept ion. ] .

Ex a m ple You can find one exam ple of an encapsulat ion for UTL_FI LE in t he Rev ealNet Act ive PL/ SQL Knowledge Base. This package, called PLVfile ( PL/ Vision file m anagem ent ) , is im plem ent ed ent irely in PL/ SQL and so inherit s som e of t he lim it at ions of UTL_FI LE. I t is, however, generally easier t o use and offers som e added funct ionalit y, such as support for pat hs. Moreov er, if you are using Oracle8i ( or abov e) , you can now also t ake advant age of Java t o great ly expand t he possibilit ies of file I / O from wit hin PL/ SQL. I have creat ed a working prot ot ype of such an im plem ent at ion. I t 's com posed of t wo code elem ent s: The JFile class A Java class t hat exposes underlying Java File m et hods in ways t hat can be called from PL/ SQL. The x file package A PL/ SQL package t hat calls JFile m et hods, t hereby allowing PL/ SQL dev elopers t o do j ust about anyt hing t hey need t o do wit h files and direct ories—all from wit hin PL/ SQL. The x file specificat ion is a superset of t he UTL_FI LE specificat ion ( except t hat it doesn't declare a record st ruct ure corresponding t o UTL_FI LE.FI LE_TYPE) . So inst ead

180

Term Fly Presents

http://www.flyheart.com

of calling UTL_FI LE.GET_LI NE, y ou w ould call xfile.get _line. But xfile also offers m uch m ore. For exam ple, wit h xfile you can delet e a file, as shown: did_it_work := xfile.delete ('c:\temp\garbage.dat'); You can also delet e all t he .t m p files in a direct ory: xfile.delete ('/tmp/apps', '%.tmp'); You can also m ake a direct ory , change privileges, obt ain t he list of files in a direct ory, find out if you can read t o or w rit e from a file, and so on. As y ou will see if you look in t he JFile code, t he Java required t o t ake t hese act ions is absolut ely m inim al. The work required in PL/ SQL t o lev erage t he funct ionalit y is also light .

Be n e fit s You aren't const rained by t he w eak im plem ent at ion of file I / O offered by UTL_FI LE.

Ch a lle n ge s Try t o design t he API of your encapsulat ion package t o be as sim ilar as possible t o UTL_FI LE ( at least where t here is ov erlap) . This will m ake it easier for dev elopers t o " swit ch ov er" —perhaps involving j ust a careful global search and replace of " UTL_FI LE" for " xfile," for exam ple. Som e const raint s current ly can't be circum v ent ed, such as t he need t o st op and rest art t he dat abase t o m ake a part icular pat h available for file I / O ( if your encapsulat ion relies on UTL_FI LE and not Java) .

Re sou r ce s 1. PLVfile : RevealNet 's Act ive PL/ SQL Knowledge Base offers t his package, which enhances t he funct ionalit y of t he underlying UTL- FI LE built - in package. Visit t he PL/ SQL Pipeline Archives as described in t he Preface. 2. JFile.j ava and xfile.pkg : The Java- enhanced file I / O pack age for PL/ SQL, along wit h t he required Java class.

BI P- 0 3 : Va lida t e t h e se t u p of UTL_ FI LE w it h sim ple t est s.

The hardest part about using UTL_FI LE is t o get it up and running. You m ust add one or m ore UTL_FI LE_DI R ent ries in your init ializat ion param et er file, and t hen rest art your dat abase t o have t hose changes t ak e effect . The UTL_FI LE_DI R param et er specifies t hose direct ories in which UTL_FI LE can operat e. The form at of t he param et er for file access in t he I NI T.ORA file is: utl_file_dir = directory

181

I nclude a param et er for UTL_FI LE_DI R for each direct ory you w ant t o m ake accessible for UTL_FI LE operat ions. The following ent ries, for exam ple, enable four different direct ories in Unix: utl_file_dir utl_file_dir utl_file_dir utl_file_dir

= = = =

/tmp /ora_apps/hr/time_reporting /ora_apps/hr/time_reporting/log /users/test_area

To bypass server securit y and allow read/ writ e access t o all direct ories, you can use t his special synt ax: utl_file_dir = * Don't use t his opt ion on product ion syst em s. I n a developm ent syst em , t his ent ry cert ainly m akes it easier for developers t o get up and running on UTL_FI LE and t est t heir code ( but it also allows t hem t o writ e " Long Live PL/ SQL! " on t op of your dat abase cont rol files! ) . You should, however, allow access only t o a few specific direct ories when you m ov e t he applicat ion t o product ion. Here are som e observat ions on working wit h and set t ing up accessible direct ories wit h UTL_FI LE: • • •

Access isn't recursive t hrough subdirect ories. I f t he following lines were in your I NI T.ORA file, for exam ple: utl_file_dir = c:\group\dev1 utl_file_dir = c:\group\prod\oe utl_file_dir = c:\group\prod\ar t hen you couldn't open a file in t he c: \ group\ prod\ oe\ report s subdirect ory .



Don't include t he following ent ry in Unix syst em s: utl_file_dir = . This allows you t o read/ writ e on t he current direct ory in t he operat ing syst em .



Don't enclose t he direct ory nam es wit hin single or double quot es.



I n t he Unix environm ent , a file creat ed by UTL_FI LE.FOPEN has, as it s owner, t he shadow process running t he Oracle inst ance. This is usually t he oracle owner. I f y ou t ry t o access t hese files out side of UTL_FI LE, y ou need t o have t he correct privileges ( or be logged in as oracle) t o access or change t hese files.



You shouldn't end your direct ory nam e wit h a delim it er, such as t he forward slash in Unix. The following specificat ion of a direct ory will result in problem s when y ou're t rying t o read from or writ e t o t he direct ory: utl_file_dir = /tmp/orafiles/ -- WILL NOT WORK!

182

Term Fly Presents

http://www.flyheart.com

Aft er rest art ing your dat abase wit h your UTL_FI LE_DI R param et er( s) , t est your abilit y t o read from and writ e t o y our desired direct ories, using sim ple t est script s ( see Resources) . Once you hav e successfully read from and w rit t en t o a file, you are ready t o use UTL_FI LE in your applicat ion.

Ex a m ple Here's a sim ple SQL* Plus script t hat t est s UTL_FI LE's abilit y t o read from and writ e t o a file: DECLARE fid UTL_FILE.FILE_TYPE; v VARCHAR2(32767); BEGIN /* Change the directory name to one to which you at least || THINK you have read/write access. */ fid := UTL_FILE.FOPEN ('e:\demo', '&1', 'R'); UTL_FILE.GET_LINE (fid, v); pl (v); UTL_FILE.FCLOSE (fid); fid := UTL_FILE.FOPEN ('e:\demo', '&2', 'W'); UTL_FILE.PUT_LINE (fid, v); UTL_FILE.FCLOSE (fid); END; See Resources for t he file cont aining t his logic ( plus t he recom m ended except ion handling) .

Be n e fit s You will save y ourself m any hours of frust rat ed debugging by t aking t hese sim ple st eps and following t he basic recom m endat ions for set t ing up t he UTL_FI LE_DI R param et er( s) .

Re sou r ce s ut lfile.t st : A sim ple script t o t est t he abilit y t o read and w rit e files.

BI P- 0 4 : H a n dle e x pe ct e d an d n a m e d ex ce pt ion s w h en pe rfor m in g file I / O.

You m ay encount er a num ber of difficult ies ( and t herefore except ions) when working wit h operat ing syst em files. The UTL_FI LE pack age it self offers a set of nam ed except ions t hat are specific t o t he package, such as UTL_FI LE.I NVALI D_OPERATI ON. ( The UTL_FI LE.GET_LI NE procedure can also raise t he st andard NO_DATA_FOUND except ion.) These nam ed ex cept ions are all user- defined except ions, w hich m eans t hat t he SQLCODE is t he sam e for all t he except ions: + 1. For t his reason, y ou m ust

183

handle UTL_FI LE ex cept ions by nam e, or y ou w on't be able t o det erm ine which error was raised. Ev ery block of code t hat works wit h UTL_FI LE should t herefore hav e an ex cept ion sect ion t hat : ( a) t raps each UTL_FI LE except ion by nam e, ( b) " t ranslat es" t he except ion int o a st ring t hat can be displayed so you can t ell which error was raised, and ( c) closes any opened files.

Ex a m ple The best way t o do t his is t o build a "local procedure" t hat displays error inform at ion and closes t he file, as shown here: IS fid UTL_FILE.FILE_TYPE; PROCEDURE recNgo (str IN VARCHAR2) IS BEGIN pl ('UTL_FILE error: ' || str); UTL_FILE.FCLOSE (fid); END; BEGIN ... your code EXCEPTION WHEN UTL_FILE.INVALID_PATH THEN recNgo ('invalid_path'); WHEN UTL_FILE.INVALID_MODE THEN recNgo ('invalid_mode'); WHEN UTL_FILE.INVALID_FILEHANDLE THEN recNgo ('invalid_filehandle'); WHEN UTL_FILE.INVALID_OPERATION THEN recNgo ('invalid_operation'); WHEN UTL_FILE.READ_ERROR THEN recNgo ('read_error'); WHEN UTL_FILE.WRITE_ERROR THEN recNgo ('write_error'); WHEN UTL_FILE.INTERNAL_ERROR THEN recNgo ('internal_error'); WHEN OTHERS THEN recNgo (SQLERRM); /* TVP 9/2000 */ END;

Be n e fit s You can debug y our UTL_FI LE code m ore rapidly since y ou can im m ediat ely see what error was encount ered. Files aren't left open, w hich can cause " false alarm s" in your code. You run y our program once, and it fails ( leaving t he file open) . You fix your program and run it again, and now your program fails because it 's t rying t o open a file t hat 's already open!

184

Term Fly Presents

http://www.flyheart.com

Ch a lle n ge s UTL_FI LE also cont ains a procedure called FCLOSE_ALL. While t his m ay seem a conv enient choice, y ou m ust be careful in using it . Since t his closes all file handles current ly open in t he session ( ev en in t he calling block of code) , it can cause errors in sect ions of code t hat are t ot ally unrelat ed t o t he real error.

Re sou r ce s ut lflexc.sql : A t em plat e of code cont aining a local error- handling procedure and an except ion sect ion for use wit h UTL_FI LE.

BI P- 0 5 : En ca psu la t e UTL_ FI LE.GET_ LI N E t o a void pr opa ga t in g t h e N O_ D ATA_ FOUN D e x ce pt ion .

UTL_FI LE.GET_LI NE raises t he NO_DATA_FOUND ex cept ion when it reads past t he end of a file ( a com m on and ev en necessary " error" when y ou are reading t he full cont ent s of a file) . This reliance on an ex cept ion t o signal EOF result s in poorly st ruct ured code. Consider t he following: BEGIN LOOP UTL_FILE.GET_LINE (file_id, l_line); process_line (l_line); END LOOP; ... lots of code EXCEPTION WHEN NO_DATA_FOUND THEN UTL_FILE.FCLOSE (file_id); END; The problem wit h t his code is t hat t he sim ple loop look s, for all int ent s and purpose, like an infinit e loop. I t 's im possible t o t ell by looking at t he code what m akes t he loop t erm inat e. Upon t erm inat ion, be sure t o close t he file. This logic is im plem ent ed in t he ex cept ion sect ion, w hich m ay be far away from t he loop. This physical separat ion of logically relat ed code can lead t o a m aint enance night m are. I nst ead of using UTL_FI LE.GET_LI NE direct ly, build your own " get next line" procedure and hav e it ret urn a Boolean flag indicat ing whet her t he EOF was reached.

Ex a m ple Here's a sim ple subst it ut ion for UTL_FI LE.GET_LI NE:

185

CREATE OR REPLACE PROCEDURE get_next_line ( file_in IN UTL_FILE.file_type, line_out OUT VARCHAR2, eof_out OUT BOOLEAN ) IS BEGIN UTL_FILE.GET_LINE (file_in, line_out); eof_out := FALSE; EXCEPTION WHEN NO_DATA_FOUND THEN line_out := NULL; eof_out := TRUE; END; Using t his program , t he earlier block of code becom es: BEGIN LOOP get_next_line (file_id, l_line, l_eof); EXIT WHEN l_eof; process_line (l_line); END LOOP; UTL_FILE.FCLOSE (file_id); ... lots of code END; Now, any dev eloper can easily see under what crit eria t he loop will t erm inat e, and t he file is closed im m ediat ely aft erwards.

Be n e fit s Your code is easier t o underst and and m aint ain. Logically relat ed code is k ept close t oget her physically.

Re sou r ce s get next .sp : The get _next _line procedure replaces UTL_FI LE.GET_LI NE.

BI P- 0 6 : Soft - code dir e ct ory n am e s in you r ca lls t o UTL_ FI LE.FOPEN .

Oracle requires t hat y ou pass t he direct ory nam e along wit h t he filenam e when you open a file. The t endency am ong dev elopers is t o place t hese direct ory nam es direct ly in t he call t o UTL_FI LE.FOPEN, t hinking t hat t he locat ions of files will not change or not really envisioning an alt ernat ive. A direct ory nam e is j ust one exam ple of an operat ing syst em dependency wit hin PL/ SQL code, and you should m ake every effort t o isolat e such dependencies from y our business logic.

186

Term Fly Presents

http://www.flyheart.com

There are sev eral dist inct approaches t o av oiding such hard- coding: •

St ore direct ory nam es in a dat abase t able. I nst ead of calling UTL_FI LE.FOPEN direct ly, call your own file open funct ion t hat obt ains t he direct ory from t he t able, based on various charact erist ics, such as inst ance nam e, dev elopm ent phase, applicat ion com ponent , et c.



Obt ain t he current set t ings for UTL_FI LE_DI R ( t he allowable direct ories for read/ w rit e act ivit y) and t hen ext ract your direct ory from t hat st ring. This is possible if you can ident ify t he needed direct ory from it s nam e.



Add support for a pat h in UTL_FI LE, in which you define a list of direct ories from which a file m ay be read. Again, provide y our own encapsulat ion of UTL_FI LE.FOPEN t hat reads from t he pat h list inst ead of a st at ic, single direct ory.

The following exam ple dem onst rat es each t echnique.

Ex a m ple First , let 's t ak e a look at " soft coding" direct ory nam es in a dat abase t able ( I will not show all t he code here; see Resources for t he relevant file references) . I want t o change direct ories according t o phase of developm ent and t he applicat ion wit h which I am w orking. I creat e a t able: CREATE TABLE dir ( phase INTEGER, app VARCHAR2(100), name VARCHAR2(100)); and t hen wit hin m y fdir package creat e an encapsulat ion of UTL_FI LE.FOPEN as follows: FUNCTION fopen ( app_in IN dir.app%TYPE, file_in IN VARCHAR2, mode_in IN VARCHAR2 := 'R' ) RETURN UTL_FILE.file_type IS retval UTL_FILE.file_type; l_name dir.name%TYPE; BEGIN l_name := fdir.name (app_in); IF l_name IS NOT NULL THEN retval := UTL_FILE.fopen (l_name, file_in, mode_in); END IF; RETURN retval; END fopen;

187

where fdir.nam e ret riev es t he nam e for an applicat ion. The phase of developm ent is set as a global variable, since I don't want m y act ual code t o cont ain references t o t he phase. Wit h t he package in place, I can set t he phase t o " dev elopm ent " in m y current session as follows: EXEC fdir.setphase (fdir.c_dev); And t hen all calls t o fdir.open will aut om at ically use t he developm ent direct ory for what ever applicat ion I specify. Here's an exam ple: DECLARE fid UTL_FILE.file_type; l_line VARCHAR2 (100); BEGIN fid := fdir.fopen ('LIBMEM', 'fdir.txt'); UTL_FILE.get_line (fid, l_line); pl (l_line); UTL_FILE.fclose (fid); END; / You can also obt ain t he value for t he UTL_FI LE_DI R param et er used in your dat abase init ializat ion file eit her by querying from t he V$PARAMETER file: SELECT value FROM v$parameter WHERE name = 'utl_file_dir'; or by calling DBMS_UTI LI TY.GET_PARAMETER_VALUE ( available in Oracle8i and abov e) . See dbparm .pk g for an exam ple of how t o use t his built - in. Finally, if you want t o explore adding a pat h t o your UTL_FI LE open operat ion, check out t he filepat h package ( see Resources) .

Be n e fit s You can m ore easily port your code from dev elopm ent t o t est t o product ion, or t o different operat ion syst em s/ hardw are plat form s. You can change t he locat ions of files in your applicat ion wit hout having t o change your code.

Ch a lle n ge s Set t he rules for opening files before you st art building your applicat ion code. Creat e a package t hat im plem ent s t he rules, and m ak e sure ev ery one uses t hat package. You can check for com pliance wit h t he rule by using t he valst d.pkg pack age list ed in Resources, along wit h t he following call:

188

Term Fly Presents

http://www.flyheart.com

SQL> exec valstd.progwith ('UTL_FILE.FOPEN')

Re sou r ce s 1. fdir.pkg and fdir.t st : A package t hat allows y ou t o define direct ories in a t able based on developm ent phase and applicat ion, and t hen open files wit hout hardcoding t he direct ory locat ion. There is also an accom panying t est script . 2. filepat h.pkg : An encapsulat ion of UTL_FI LE.FOPEN t hat adds support for a user- specified pat h ( it can only be used t o open files in Read m ode) . 3. valst d.pkg : A general ( and sim ple) st andards v alidat ion package t hat searches ALL_SOURCE for t he specified st ring and report s on t hose program s t hat cont ain t he st ring.

9 .3 D BM S_ PI PE Use t he DBMS_PI PE built - in package t o creat e, writ e t o, and read from dat abase pipes. Dat abase pipes are chunks of m em ory in t he Sy st em Global Area t hat serv e as conduit s of inform at ion, prim arily bet ween Oracle sessions. Since t he inform at ion is st ored in m em ory , all inform at ion in a pipe is lost when a dat abase is shut down. Prior t o Oracle8 and Oracle8i, dat abase pipes w ere used t o build " a bet t er debugger" ( bet t er t han DBMS_OUTPUT, in any case) and, am ong ot her act ivit ies, int eract wit h nat ive operat ing syst em program s. The ext ernal program could t hen perform t ask s t hat would ot herwise be im possible from wit hin PL/ SQL.

BI P- 0 7 : En ca psu la t e in t e r a ct ion w it h spe cific pipes.

A pipe is ident ified by it s nam e: a st ring of up t o 128 charact ers. Messages t hat are writ t en t o, and read from , a pipe can be com posed of one or m ore " packet s," and each m essage can be m ade up of different num bers and t ypes of pack et s ( for exam ple, t w o st rings and a num ber or 12 dat es) . Working wit h a pipe can raise bot h pipe- specific and general errors. For all t hese reasons, w henev er y ou are w orking wit h a dat abase pipe ( or pipes) , y ou should encapsulat e access t o t hat pipe behind a package int erface. Mak e sure t hat no user of a pipe hard- codes t he nam e of t he pipe in his logic. Avoid t he explicit packing and unpacking of m essage cont ent s; inst ead, call procedures t o do t hat work for y ou and for any ot her developer.

Ex a m ple Here's an exam ple of a pipe encapsulat ion pack age around t he book t able: CREATE OR REPLACE PACKAGE pe_book -- Wrapper around pipe based on book

189

-- NOTE: EXECUTE authority on DBMS_LOCK is required. -Issue this command from SYS: -GRANT EXECUTE ON DBMS_LOCK TO PUBLIC; IS c_name

CONSTANT VARCHAR2 (200) := 'BOOK_pipe';

/* Overloadings of send */ PROCEDURE send ( isbn_in title_in summary_in author_in date_published_in page_count_in wait );

IN IN IN IN IN IN IN

book.isbn%TYPE DEFAULT NULL, book.title%TYPE DEFAULT NULL, book.summary%TYPE DEFAULT NULL, book.author%TYPE DEFAULT NULL, book.date_published%TYPE DEFAULT NULL, book.page_count%TYPE DEFAULT NULL, INTEGER := 0

PROCEDURE send (rec_in IN book%ROWTYPE, wait IN INTEGER := 0); /* Overloadings of receive */ PROCEDURE receive ( isbn_out title_out summary_out author_out date_published_out page_count_out wait );

OUT OUT OUT OUT OUT OUT IN

book.isbn%TYPE, book.title%TYPE, book.summary%TYPE, book.author%TYPE, book.date_published%TYPE, book.page_count%TYPE, INTEGER := 0

PROCEDURE receive ( rec_out OUT book%ROWTYPE, wait IN INTEGER := 0 ); END pe_book; Wit h t his package in place, I can easily writ e t he cont ent s of a record t o a pipe: DECLARE book_rec book%ROWTYPE; BEGIN book_rec := last_book_reserved; pe_book.send (book_rec); I leav e it t o t he package t o do all t he hard work; I don't even hav e t o know t he nam e of t he pipe; heck, I don't ev en hav e t o know how DBMS_PI PE w orks! And if t he pipe nam e needs t o change or ev en if a colum n is added t o t he book t able, t his code doesn't hav e t o be m odified at all.

Be n e fit s

190

Term Fly Presents

http://www.flyheart.com

Users of t he pipe encapsulat ion package can concent rat e on t he logical work t hey want t o accom plish, rat her t han on t he det ails of m anaging pipes. This im proves dev eloper product ivit y and t he result ing qualit y of t heir code.

Ch a lle n ge s The biggest challenge is t o build t hese packages. I t can t ake lot s of work, especially if you are encapsulat ing a t able wit h lot s of colum ns. You should invest igat e way s t o generat e, rat her t han w rit e such code. See [ DEV- 05: Generat e code w henev er possible and appropriat e.] for code generat ion opt ions.

Re sou r ce s pe_book.pkg : The full im plem ent at ion of t he pipe encapsulat ion package for t he book t able.

BI P- 0 8 : Pr ovide ex plicit a n d a ppr opr ia t e t im e ou t va lu e s w h en you sen d a n d r e ceive m e ssa ge s.

When y ou send or receive a m essage via a dat abase pipe, you can specify how long you are willing t o wait for t he operat ion t o succeed. A pipe m ight be full, which m eans t hat y ou can't im m ediat ely send t o t hat pipe. A pipe m ight be em pt y, which m eans t hat y ou can't im m ediat ely receive a m essage from t hat pipe. The default wait t im e for DBMS_PI I PE is 86.4 m illion seconds, ot herwise known as 1,000 days. This is an awfully long t im e wait for an operat ion t o com plet e, and could cause problem s in your applicat ion. You should never rely on t he default t im eout values in any DBMS_PI PE calls. Always provide an ov erride.

Ex a m ple Here's t he im plem ent at ion of t he pe_book .send procedure: PROCEDURE pe_book.receive ( isbn_out OUT book.isbn%TYPE, title_out OUT book.title%TYPE, summary_out OUT book.summary%TYPE, author_out OUT book.author%TYPE, date_published_out OUT book.date_published%TYPE, page_count_out OUT book.page_count%TYPE, wait IN INTEGER := 0 ) IS BEGIN -- Receive next message and unpack for each column. g_status := DBMS_PIPE.receive_message (defname, wait); IF g_status = 0

191

THEN DBMS_PIPE.unpack_message DBMS_PIPE.unpack_message DBMS_PIPE.unpack_message DBMS_PIPE.unpack_message DBMS_PIPE.unpack_message DBMS_PIPE.unpack_message END IF;

(isbn_out); (title_out); (summary_out); (author_out); (date_published_out); (page_count_out);

g_action := 'RECEIVE_MESSAGE'; END; I n t his case, I always ov erride t he default wait t im e wit h t he value passed in by t he wait param et er. The default value on wait is seconds ( im m ediat e success required or it t im es out ) , but it can be overridden by a user of t he package.

Be n e fit s You can avoid having your applicat ion appear frozen as it wait s virt ually forever for a " green light " from t he pipe.

Ch a lle n ge s Your code t o handle t he lack of a m essage on t he pipe needs t o be j ust as robust as t he code t o handle a valid m essage. This m ay not be a valid condit ion depending on your applicat ion, but should always be handled.

Re sou r ce s pe_book.pkg : The full im plem ent at ion of t he pipe encapsulat ion package for t he book t able.

BI P- 0 9 : Use RESET_ BUFFER in e x ce pt ion h an dle r s a n d befor e you pa ck da t a in t o t h e m e ssa ge bu ffe r.

Each session connect ed t o Oracle has a m essage buffer t hat can cont ain up t o 4096 byt es of inform at ion. You can place dat a int o t he buffer wit h calls t o DBMS_PI PE.PACK_MESSAGE and DBMS_PI PE.RECEI VE_MESSAGE. Prior t o packing dat a int o t he buffer, you should not assum e it 's em pt y. Your last unpack operat ion m ight have left som e dat a in t he buffer, or a previous pack- andsend operat ion could have failed wit h an except ion. For t hese reasons, y ou should call DBMS_PI PE.RESET_BUFFER bot h before you pack dat a int o t he m essage buffer and in except ion handlers in blocks where t he buffer m ay have been part ially filled.

Ex a m ple

192

Term Fly Presents

http://www.flyheart.com

The pipe encapsulat ion package for t he book t able, pe_book, offers t his im plem ent at ion of t he send operat ion: PROCEDURE pe_book.send ( isbn_in IN book.isbn%TYPE DEFAULT NULL, title_in IN book.title%TYPE DEFAULT NULL, summary_in IN book.summary%TYPE DEFAULT NULL, author_in IN book.author%TYPE DEFAULT NULL, date_published_in IN book.date_published%TYPE DEFAULT NULL, page_count_in IN book.page_count%TYPE DEFAULT NULL, wait IN INTEGER := 0 ) IS BEGIN -- Clear the buffer before writing. DBMS_PIPE.reset_buffer; -- For each column, pack item into buffer. DBMS_PIPE.pack_message (isbn_in); DBMS_PIPE.pack_message (title_in); DBMS_PIPE.pack_message (summary_in); DBMS_PIPE.pack_message (author_in); DBMS_PIPE.pack_message (date_published_in); DBMS_PIPE.pack_message (page_count_in); -- Send the message g_status := DBMS_PIPE.send_message (defname, NVL (wait, g_sendwait)); g_action := 'SEND_MESSAGE'; EXCEPTION WHEN OTHERS THEN DBMS_PIPE.reset_buffer; RAISE; END; The first st ep t aken is a reset t ing of t he buffer. I f y ou don't do t his, it 's quit e possible for t he buffer t o hav e a previous set of book inform at ion t hat was nev er sent , or som e ot her dat a. Finally, t he ex cept ion sect ion m akes sure t o em pt y out t he buffer before re- raising t he sam e except ion.

Be n e fit s The cont ent s in t he dat abase pipe are m ore dependable, leading t o a higher likelihood of correct program behavior.

Re sou r ce s pe_book.pkg : The full im plem ent at ion of t he pipe encapsulat ion package for t he book t able.

9 .4 D BM S_ JOB

193

The DBMS_ JOB built - in package offers an API int o an Oracle subsyst em known as t he j ob queue. The Oracle j ob queue allows for t he scheduling and execut ion of PL/ SQL rout ines ( j obs) at predefined t im es and/ or repeat ed j ob ex ecut ion at regular int ervals. DBMS_ JOB provides program s for subm it t ing and execut ing j obs, changing j ob ex ecut ion param et ers, and rem oving or t em porarily suspending j ob ex ecut ion. And t hat 's all great , but DBMS_ JOB has sev eral key w eaknesses, including: •

Lit t le or no j ob m anagem ent feat ures. A j ob is assigned an I D num ber, but you can't give y our j ob a nam e, which m akes it hard t o locat e and m anage t he j ob aft er subm ission.



Scheduling t he frequency of execut ion can be com plicat ed process. I f y ou want a j ob t o run ev ery Monday, Wednesday , and Friday at noon, for exam ple, you need t o pass t he following st ring t o DBMS_ JOB.SUBMI T: 'TRUNC(LEAST(NEXT_DAY,(SYSDATE, ''MONDAY''), NEXT_DAY(,(SYSDATE, ''WEDNESDAY''), NEXT_DAY(,(SYSDATE, ''FRIDAY''))) + 1/2'

• •

As wit h t he ot her built - in packages discussed in t his chapt er, you can overcom e such weaknesses by building your own layer of code around t he DBMS_ JOB procedures.

BI P- 1 0 : Use you r ow n su bm ission pr ocedu re t o im pr ove j ob m an a gem e n t ca pabilit ie s.

As not ed earlier, t here's no way t o assign a nam e t o a j ob wit h DBMS_ JOB. A j ob nam e com es in very handy for a num ber of purposes, including easy analysis of j ob st at us and a way t o handle j obs t hat fail. Rat her t han call DBMS_ JOB.SUBMI T direct ly, y ou should build an encapsulat ion around t hat procedure, which subm it s t he j ob, but also keep t rack of addit ional j ob inform at ion.

Ex a m ple The m yJob package ( see Resources) offers a sim ple encapsulat ion of DBMS_ JOB subm it t hat also populat es a dat abase t able wit h addit ional j ob inform at ion ( in t his case, only t he nam e of t he j ob) . Here's t he subm it procedure: FUNCTION myJob.submit ( name_in IN job.name%TYPE, what_in IN job.what%TYPE, next_date_in IN DATE := SYSDATE, interval_in IN job.interval%TYPE := NULL ) RETURN job.id%TYPE IS

194

Term Fly Presents

http://www.flyheart.com

retval job.id%TYPE; BEGIN DBMS_JOB.submit (retval, what_in, next_date_in, interval_in ); INSERT INTO job (id, name, what, next_date, interval) VALUES ( retval, name_in, what_in, next_date_in, interval_in ); COMMIT; RETURN retval; END submit; Now t hat t his inform at ion is available t o m e, y ou can use it in your ot her procedures. You can, for exam ple, now rem ov e a j ob by nam e: BEGIN myJob.remove ('weekly_analysis'); Most im port ant , you can reference t he j ob by nam e wit hin t he ex cept ion sect ion of t he j ob's st ored procedure, which is crucial for m anaging j obs t hat fail ( see [ BI P- 11: Trap all errors in DBMS_ JOB- execut ed st ored procedures and m odify t he j ob queue accordingly.] ) .

The m yJob.subm it procedure also perform s a COMMI T aft er subm it t ing t he j ob and insert ing t he j ob inform at ion int o m y j ob t able. You should always COMMI T aft er a call t o DBMS_ JOB.SUBMI T, especially if you want your j ob t o execut e im m ediat ely. Be n e fit s I t 's m uch easier t o m anage a j ob when you can give a nam e t hat is m eaningful in t he cont ext of your applicat ion.

Ch a lle n ge s Ensure t hat all developers know about and use t he j ob encapsulat ion code. And, of course, y ou need t o build t he pack age as w ell!

Re sou r ce s 1. m yj ob.pkg : A prot ot ype package t hat dem onst rat es how t o give a nam e t o a j ob and t hen m anage t hat j ob by nam e. 2. PLVj ob : The Act ive PL/ SQL Knowledge Base of Rev ealNet includes t he PLVj ob package, which offers m any addit ional feat ures for DBMS_ JOB users. You can m anage j obs by nam e and also schedule j obs t hrough a variet y of m eans, including cron synt ax.

195

BI P- 1 1 : Tr a p a ll e r r or s in D BM S_ JOB- ex e cu t e d st or e d pr oce du re s an d m odify t h e j ob qu e u e a ccor din gly.

Oracle keeps t rack of t he num ber of t im es a j ob fails ( raises an unhandled ex cept ion) ; once it fails 16 t im es, t he j ob is m ark ed as " broken." That 's handy, but not very pract ical. I f a j ob break s once, it will probably break again. More im port ant ly, t hough, t he DBA should be m ade aware of a j ob failure so t hat t he code ( or t he ex ecut ion environm ent ) can be m odified t o allow t he j ob t o run successfully. You can im prove upon DBMS_ JOB by t rapping all errors t hat occur in your j ob block s and st ored procedure calls. I m m ediat ely m ark t he j ob as brok en, so t hat Oracle doesn't t ry t o run t he j ob again and again, and send out an alert of som e sort so t hat t he j ob can be fixed.

Ex a m ple Now t he m yJob package will com e in handy. Wit h m yJob, I can assign a nam e t o a j ob and use t hat nam e t o obt ain t he j ob I D num ber. This m eans t hat I can writ e an except ion sect ion in m y st ored procedure like t his: CREATE OR REPLACE PROCEDURE calculate_overdue_fines (...) IS c_program CONSTANT VARCHAR2(30) := 'calculate_overdue_fines'; BEGIN ... EXCEPTION WHEN OTHERS THEN myJob.broken (myJob.id (c_program); dba_beeper.notify (c_program, SQLERRM); END; Not e t hat I don't offer an im plem ent at ion of t he dba_beeper.not ify procedure!

Be n e fit s Following t his best pract ice will help you avoid repet it ive, failed ex ecut ions of j obs. You can also repair brok en j obs m ore quickly.

Ch a lle n ge s Ensure t hat all developers know about and use t he j ob encapsulat ion package in error handlers. And, of course, y ou need t o build t he pack age as w ell!

Re sou r ce s

196

Term Fly Presents

http://www.flyheart.com

m yj ob.pkg : A prot ot ype package t hat dem onst rat es how t o give a nam e t o a j ob and t hen m anage t hat j ob by nam e.

Coloph on Our look is t he result of reader com m ent s, our own experim ent at ion, and feedback from dist ribut ion channels. Dist inct ive cov ers com plem ent our dist inct ive approach t o t echnical t opics, breat hing personalit y and life int o pot ent ially dry subj ect s. The anim al on t he cov er of Oracle PL/ SQL Best Pract ices is a red w ood ant . Red w ood ant s ( Form ica aquilonia) are oft en t he dom inant ant s of forest s t hroughout t he nort hern hem isphere. F. aquilonia can build nest m ounds of dried spruce needles and t wigs t hat are t hree feet or m ore in diam et er and height . Each nest can cont ain t housands of ant s as w ell as sev eral queens. The insect s have no st ing but can defend t hem selves by firing form ic acid from t heir rear ends when dist urbed. The w orkers vary in size up t o about 1/ 2 inch in lengt h wit h a red t horax , black abdom en, and red and black m ark ed head. The ant s are bot h scavengers and general predat ors of insect s, carrying m any soft - bodied cat erpillars, flies, and sawflies along t heir sev eral m aj or t rails back t o t he nest . Red w ood ant s are a k eyst one species ( i.e., wit hout t hem t he ecosyst em changes fundam ent ally) . When red ant s disappear from a sy st em , herbivorous insect s can subsequent ly dam age forest t rees. I n forest s w eak ened by pollut ion and acid rain in cent ral Europe, red wood ant populat ions are oft en endangered, which in t urn causes furt her im balances in predat or- prey dynam ics and t he ecosyst em . These rare ant s are prot ect ed by law in som e European count ries because of t heir great value in dest roying forest pest s. For 28 y ears, Professor Seigo Higashi has been st udying a supercolony of Japanese red wood ant s ( Form ica yessensis) , which dwell along a st rip of shoreline on t he I shikari coast of nort hern Japan. When first discov ered in 1973, t he colony consist ed of approxim at ely 45,000 nest s wit h connect ing t unnels ext ending nearly 12.4 m iles along t he shore of t he Japan Sea. I t was est im at ed t hat t he colony had about 306 m illion workers and 1.1 m illion queens, and is t hought t o be about 1,000 years old. Since 1973, t he colony has been under siege, t hreat ened by t he dev elopm ent of infrast ruct ure for a new port on I shikari Bay, which has occurred on t op of 30% of t he ant m egalopolis. This has reduced t he num ber of red w ood ant s living t here by m ore t han half. The I shikari ant s are one of only t wo known ant supercolonies in t he world. The ot her, sm aller one is in t he Swiss Jura m ount ains. Mary Anne Weeks May o was t he product ion edit or, and Clairem arie Fisher O'Leary was t he copyedit or for Oracle PL/ SQL Best Pract ices . Mary Sheehan, Mat t Hut chinson, and Jane Ellin provided qualit y cont rol. Rachel Wheeler and Gabe Weiss provided product ion assist ance.

197

Ellie Volckhausen designed t he cov er of t his book, based on a series design by Edie Freedm an. The cover im age is a 19t h- cent ury engraving from t he Dov er Pict orial Archive. Em m a Colby produced t he cov er layout wit h QuarkXPress 4.1 using Adobe's I TC Garam ond font . David Fut at o designed t he int erior layout based on a series design by Nancy Priest . Clifford Dy er and Anne- Marie Vaduva conv ert ed t he files from Microsoft Word t o Fram eMaker 5.5.6 using t ools creat ed by Mike Sierra. The t ext and heading font s are I TC Garam ond Light and Garam ond Book. This colophon was com piled by Mary Anne Weeks May o.

198