PK  artistName Oracle Corporation book-info cover-image-hash 973436979 cover-image-path OEBPS/dcommon/oracle-logo.jpg package-file-hash 231083501 publisher-unique-id E12862-03 unique-id 956194180 genre Oracle Documentation itemName Oracle® Streams Extended Examples, 11g Release 2 (11.2) releaseDate 2010-07-26T10:29:21Z year 2010 PKKFPK PKYuPK Cover

Oracle Corporation

PK[pTOPK Oracle Streams Extended Examples, 11g Release 2 (11.2)

Oracle® Streams

Extended Examples

11g Release 2 (11.2)

E12862-03

August 2010


Oracle Streams Extended Examples, 11g Release 2 (11.2)

E12862-03

Copyright © 2008, 2010, Oracle and/or its affiliates. All rights reserved.

Primary Author:  Randy Urbano

Contributors: Nimar Arora, Alan Downing, Thuvan Hoang, Tianshu Li, Jing Liu, Edwina Lu, Pat McElroy, Valarie Moore, Neeraj Shodhan, Jim Stamos, Byron Wang, Lik Wong, Jingwei Wu

This software and related documentation are provided under a license agreement containing restrictions on use and disclosure and are protected by intellectual property laws. Except as expressly permitted in your license agreement or allowed by law, you may not use, copy, reproduce, translate, broadcast, modify, license, transmit, distribute, exhibit, perform, publish, or display any part, in any form, or by any means. Reverse engineering, disassembly, or decompilation of this software, unless required by law for interoperability, is prohibited.

The information contained herein is subject to change without notice and is not warranted to be error-free. If you find any errors, please report them to us in writing.

If this software or related documentation is delivered to the U.S. Government or anyone licensing it on behalf of the U.S. Government, the following notice is applicable:

U.S. GOVERNMENT RIGHTS Programs, software, databases, and related documentation and technical data delivered to U.S. Government customers are "commercial computer software" or "commercial technical data" pursuant to the applicable Federal Acquisition Regulation and agency-specific supplemental regulations. As such, the use, duplication, disclosure, modification, and adaptation shall be subject to the restrictions and license terms set forth in the applicable Government contract, and, to the extent applicable by the terms of the Government contract, the additional rights set forth in FAR 52.227-19, Commercial Computer Software License (December 2007). Oracle USA, Inc., 500 Oracle Parkway, Redwood City, CA 94065.

This software is developed for general use in a variety of information management applications. It is not developed or intended for use in any inherently dangerous applications, including applications which may create a risk of personal injury. If you use this software in dangerous applications, then you shall be responsible to take all appropriate fail-safe, backup, redundancy, and other measures to ensure the safe use of this software. Oracle Corporation and its affiliates disclaim any liability for any damages caused by use of this software in dangerous applications.

Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners.

This software and documentation may provide access to or information on content, products, and services from third parties. Oracle Corporation and its affiliates are not responsible for and expressly disclaim all warranties of any kind with respect to third-party content, products, and services. Oracle Corporation and its affiliates will not be responsible for any loss, costs, or damages incurred due to your access to or use of third-party content, products, or services.

PKPK Preface

Preface

Oracle Streams Extended Examples includes detailed examples that use Oracle Streams features.

This Preface contains these topics:

Audience

Oracle Streams Extended Examples is intended for database administrators who create and maintain Oracle Streams environments. These administrators perform one or more of the following tasks

  • Plan for an Oracle Streams environment

  • Configure an Oracle Streams environment

  • Administer an Oracle Streams environment

  • Monitor an Oracle Streams environment

To use this document, you must be familiar with relational database concepts, SQL, distributed database administration, general Oracle Streams concepts, Advanced Queuing concepts, PL/SQL, and the operating systems under which you run an Oracle Streams environment.

Documentation Accessibility

Our goal is to make Oracle products, services, and supporting documentation accessible to all users, including users that are disabled. To that end, our documentation includes features that make information available to users of assistive technology. This documentation is available in HTML format, and contains markup to facilitate access by the disabled community. Accessibility standards will continue to evolve over time, and Oracle is actively engaged with other market-leading technology vendors to address technical obstacles so that our documentation can be accessible to all of our customers. For more information, visit the Oracle Accessibility Program Web site at http://www.oracle.com/accessibility/.

Accessibility of Code Examples in Documentation

Screen readers may not always correctly read the code examples in this document. The conventions for writing code require that closing braces should appear on an otherwise empty line; however, some screen readers may not always read a line of text that consists solely of a bracket or brace.

Accessibility of Links to External Web Sites in Documentation

This documentation may contain links to Web sites of other companies or organizations that Oracle does not own or control. Oracle neither evaluates nor makes any representations regarding the accessibility of these Web sites.

Access to Oracle Support

Oracle customers have access to electronic support through My Oracle Support. For information, visit http://www.oracle.com/support/contact.html or visit http://www.oracle.com/accessibility/support.html if you are hearing impaired.

Related Documents

For more information, see these Oracle resources:

Many of the examples in this book use the sample schemas of the sample database, which is installed by default when you install Oracle Database. Refer to Oracle Database Sample Schemas for information about how these schemas were created and how you can use them yourself.

Conventions

The following text conventions are used in this document:

ConventionMeaning
boldfaceBoldface type indicates graphical user interface elements associated with an action, or terms defined in text or the glossary.
italicItalic type indicates book titles, emphasis, or placeholder variables for which you supply particular values.
monospaceMonospace type indicates commands within a paragraph, URLs, code in examples, text that appears on the screen, or text that you enter.

PKQ! PK Index

Index

A  C  D  E  G  I  L  M  N  O  P  Q  R  S  T 

A

ADD SUPPLEMENTAL LOG DATA clause of ALTER TABLE, 3.5
ADD_COLUMN member procedure, 4.4
ADD_PAIR member procedure, 6.2, 6.4, 6.5, 6.7
ALTER TABLE statement
ADD SUPPLEMENTAL LOG DATA clause, 3.5
apply process
DML handlers
creating, 4.4
heterogeneous environments
example, 2.4.1
reenqueue captured LCRs, 4.4
ARCHIVELOG mode
capture process, 1.2, 2.2, 4.2

C

capture process
ARCHIVELOG mode, 1.2, 2.2, 4.2
CLOSE_ITERATOR procedure, 6.3
COMPATIBLE initialization parameter, 1.2, 2.2
conflict resolution
MAXIMUM handler
example, 3.5
time-based
example, 3.5
preparing for, 3.4
constructing
LCRs, 5

D

DBMS_RULE package, 6
DBMS_RULE_ADM package, 6
DEQUEUE procedure
example, 4.4
DML handlers, 4.4

E

EVALUATE procedure, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8
event contexts
example, 6.8
EXECUTE member procedure, 4.4

G

GET_ALL_NAMES member function, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8
GET_COMMAND_TYPE member function, 4.4
GET_NEXT_HIT function, 6.3
GET_OBJECT_NAME member function, 2.4.1
GET_VALUE member function
rules, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8
GET_VALUES member function, 4.4
GLOBAL_NAMES initialization parameter, 1.2, 2.2

I

instantiation
example, 2.4.1, 2.4.2, 3.5, 3.5

L

LOBs
logical change records containing, 5
Oracle Streams
constructing, 5
logical change records
LOBs, 5
logical change records (LCRs)
LOB columns, 5

M

messaging client
example, 4.4

N

n-way replication
example, 3

O

Oracle Streams
adding databases, 2.8
adding objects, 2.6
initialization parameters, 1.2, 2.2
sample environments
LOBs, 5
replication, 1, 2, 3
single database, 4

P

propagations
database links
creating, 1.3, 2.3, 2.3, 2.8

Q

queues
ANYDATA
dequeuing, 4.4

R

RE$NAME_ARRAY type, 6.5, 6.6, 6.7, 6.8
RE$NV_LIST type, 6.2, 6.4, 6.5, 6.5, 6.6, 6.7, 6.7, 6.8
RE$RULE_HIT_LIST type, 6.2, 6.4, 6.5, 6.6, 6.7, 6.8
RE$TABLE_ALIAS_LIST type, 6.5, 6.6, 6.7, 6.8
RE$TABLE_VALUE type, 6.5, 6.6, 6.7, 6.8
RE$TABLE_VALUE_LIST type, 6.5, 6.6, 6.7, 6.8
RE$VARIABLE_TYPE_LIST type, 6.2, 6.4, 6.6, 6.7, 6.8
RE$VARIABLE_VALUE type, 6.2, 6.3, 6.4, 6.6
RE$VARIABLE_VALUE_LIST type, 6.2, 6.3, 6.4, 6.6
reenqueue
captured LCRs, 4
replication
adding databases, 2.8
adding objects, 2.6
heterogeneous single source example, 2
multiple-source example, 3
n-way example, 3
simple single source example, 1
rules
event context
example, 6.8
example applications, 6
explicit variables
example, 6.2, 6.6
implicit variables
example, 6.7
iterative results
example, 6.3
MAYBE rules
example, 6.4
partial evaluation
example, 6.4
table data
example, 6.5, 6.6, 6.7

S

SET_COMMAND_TYPE member procedure, 4.4
SET_ENQUEUE_DESTINATION procedure, 4.4
SET_OBJECT_NAME member procedure, 2.4.1, 4.4
SET_RULE_TRANSFORM_FUNCTION procedure, 2.4.1
SET_VALUES member procedure, 4.4
supplemental logging
example, 3.5

T

transformations
rule-based
creating, 2.4.1
example, 2.4.1
PKjOe8`8PK>>yyy|||vvvdddUUU!,`XXX¯ĵƻëαзҽٌ߈X:D"%JS'J5-4aE=+Qc4=N9Rd5'M^ cKˬ5CK1iTfJ5z`yCx;O6U pժX> +lٱ̞%+ւ+w]f i}V _ƒ6tXqⲋ 5Ad,-W`ß<sΛSFqkʯ5vpm3]vn߽woW^ɝ/sѭO^zvӫ_Ͼ˟OϿ(G& 6F( J`!fv ($h(,0(!`e4<HDW$L6PF)TViXf$Tz |O_.X$k\_ "@>zݍt$?M6A 0Oy|Odx1 B9yw#WA,maS24D<3kBҕ w`Â4@T WШFo"GF]1daALÂ6@b"[nTS A$I{wh!/J*h5Ssd,,d@Ie33Z%*\L #IL< $&CXvK4I G ;X!"}A]c\y,cg^i{ dVL\Ag1śLjyT 1 %8Օ QYE/zKQ!k"1th "I-:A⛂\ASЙ&5feX]3] ZIiP:RUzUOiԴDuXjLmJ>8}OYǰ^ EgPgv/`|=V7 xnmyLJY( jc?dZVo]!,ֺlLٽvYJgm8[ֲwI-KlIMzupjd9'.UUU:uq,16Ţ6Dͯ6ig&TQ! 'ħl<&XTr!gV6K)VZU hycZ^תo)|hz/1njWkuY7h2q!N{8 x1)Ƨ) {R$Mpqd(۪q&coٶDճOQ=tП'GҕmƂ@XL!A ̫p iU-uTv!|Nim= zzr4+^a=0b# 3r~$vVuZ A btt 0XmT*m#V/VƗR-&jB 6> @ߡAKtI  AwυP]_vԘ \B5TPxT 3U>s^x,!8(#&\ OTJ6VlG)ւ.'Se=h>ʤ.=}%v=hPX7]Fmqov`?x^HsWy7k͢NXSE(GFze6Wq81sWS+j3NBS8es6G/͒{aF2-(bQRYUZ(S,؈smwRC@Fr烅whAX|=uև"`[xb4O#}LH 8'rb82S3s1u8W6"Ȍr8$*8('*E8GGQ+ cC]X*z#b:Xku $u@wcx?m:?T979 : =vO%4?W^up}7Jga1`oZz.co4&f+'4`}UQK`Fa#`Ӌ&e,ۖhp*fƔCq[(SOAs~k;̣9iCR9a|U7݆:̙mWg]>c"Q>l@Ybf蘈?Ɏ_MIq2rX0B+tɣYc֏e2Úr؞)7G_2eY$mu^i-'E걠Fv!&* ap 0Zl#OFmwOעehy A 5&ry>:<17UꡥQHEOG$GhjL0xwygunۣjwZg>フ`v%0F~uu" 5\? *u'wml vl}ϣk4wvunu`ޣmA7Gi1 |O~MP~Љ?zڭߚ9r@MxF<IJ[}C݇hR8qk'\ 9]kJZ#[EF݇kDu +Պ< Kv"K~TXXG2JB=ņ,׌^CKPJ9U7p|mX4"x z&%00 c@J󸵟XݵN[7qm. hhѩq؉Ȋ IQto=QPI]\+VfY٥a 16 ~8Thw0J0 @9F˄} $C0Q>J40gL"  NQ [אhg%$mѠf::.}Ew9hEKXNU(*LR\RgRY ^]uy gw"9Cͭw{ցnAZ?քޠrvd9=NFܗ=X ZyIۛKv7ۖKv諾=m[dDx\_gdd^1JhdfT2 ncAJr.竆ޏEN+yڬB>dWشq>!rBL)ř4ӭZ>dM L6y:% k7K?y ɉT|cbœ*,.ZJU?YfsdȲX 38|>=_ : IAp>|IQv},0@~*G]n"<g"tǺ=pr?t_,D2.[>)42+H ~n枩dY+L:;C<\(SEŐHCT!c?.gg" * l?[BwS1U֮deubaAOT޴TA D/FR#}J97H秳97dq}ZXTg_!L)Lj0lXW X    X  X!XW!˄ۄ""ھȃ!ن WÅ)*ۡ )\Ȱaxԃ$ @8To##0LH$ `D -J F ӂDdi(AB9g Dz-n7GP:]H/f1؍oy I+Y荅H+,{Ϯf'ؖWJ 0> =p6HNGh1WWa yxB&Ÿ#%k6b@as@9!Z@9Aۛ qpsW NeO ={9RLҁv%D%)6JXnvU[ W# "t ]q:U@h0R%!@X M6&OX=E Pk AX@ Q2՚`%\e%%hL \ Bf#}&L @)k@Z Ykx"y R)Q  :":ꅦJ!{VE"#b!d+^6RVRB $2,JŤElkU]^W"U (N@[\1 -:6ٌUUOy՝aLLj"(Z*7dY{ K*p6Jgw;!c(,,ICUY+7oqC2*Vkqɚ !͑@0A몥X4 p5b'W,jPۣ[Hu0w&S%XAm;j1o0nAB|7G.#g#;s :Ȫ8H@ 2 3\[#bf&!&t? zCEvXVy ARLe]ٕ3X%fǴ|}"rcQ0A=VB/͆a`5 00+;-sZXE(C}49(!n$kGK}5. HF}麛eD•Du5JFĺFP&`EBOAa))N1~i.z` HF.B Ӹ +ycW>4  ?saADè Ӂx( !Ʊ1,RpX@kj2#J/J%"PJ*7:Ɗ>yQ%P&lP5su(q* PT#AGLAWҢh:wȢɈ0 q7D99r1bn&xp'|9x6q3Vs2MĀiXZіddKCCHb]B+d^f<;*͗6"h:*938L46]!~%Qy#сbD1F꿘$j!2ڤx4*XW&Gl3"°4gE`!VBZ¡`֯u!P*Bt{lsTHVt]hY,7e5IjֺlgKX.%G8&UZBJ[Ў`.!R[dͮvz /v MvL>V!?R$7`v|#ʹ + J%"N; @ L [ 7ߋHQXbR_4._,H0α+,@{Dc`kB F}xAE FF1W.{`,[hYtAN{859CndڻJcͳ>ߥBЈNFz r'MJ+/up|foӠGgDDr3.j$=k6Zָ^pV{duE_-Y/+NkCZ vK-:v:MK{MnIc8$!u]TMzη~Njh7he;^yj7B^qG< iؐ&_;r]<5Qs$8Ϲw@Їs荔yi;1ҙ[XG+ճ{:NԔ|NNw[SguOW<.]=Ԫц3BhМ߼;O: xW}KOA>te{}p{7,:@4 }?~}EW} ~/?/,0 BGWh8hZ  ZigXXrWxlȁ8VH(x!H+(-(834H})X:ȃ!;h=h?ACȄEXGI(KHMhO8]SUȅWY8Q@egiHG!m8oqhcXshxxp682h7x؈h9舋8Ȃx肙a([((]Hh_8hhHxXH}ȇŨɈnȇR'R3XبQ׈XhL xEȎ(hh؎xȏx}9Xnx)5 ) ِ66Yy#ɑۈّ "$ &Y*YiG+799iГRX!DF?r'xPyRYVy&AX\ٕ^bٕ`9fyOYhlwjٖpfrYvutyz50* |YjN jδ T ` x}q)ɚ9 Wٙt WPtp 0L9`mY@1P @1 p yXi SPry@ypٹ W@XПI қyIs:9yL P L Is) ꚬ9$zrI ࢁѣ Q ):z|Isy = LR剙jEj=ɜ? piXqt y1lj p1c i p1znVY LzmtB  @ P|+60 1[䨅 zP ExʩJIEp^4z~٬ Wlz$pҺBz @E::9᪟pP  \ʭtܰ Qѯ@ʰRqiRښ}Q {+{IP  𜹉 UP)  <[j 9`p?I9{3 6PjX]p 8 pH Ю MKpiz<` p /۵TEЧ03*2;[q8 ;{&xkiX@М*W`PX9zv+v @jmkδp^05] Xм׋P [K :+Z1᪰p k  /k v{3x1a0Eʽ` g i9zW ;lʵYr[ygJYK, ,Z=KPɯ3,Zăæ׻ +Fꣂ ;1; 2<ș„Y1\ ߹Ez jRL0Dձ[M*(£ -Ɗ V?{J|;*=&ȋeBΖ2 2V].ٚf>[~jDn~ir^q>vm~z檹~ޙu~>>{7|{|'|'G|.Hi鎾n^^~ꦞꬾN}}.Gn뻮g7n^~>N~n xp (z(HH> >^~:>N^苼/Ho/xH^_h)0.-~ߊ5/6/8:@7/A?،oSs嘍X8x Z_dh`iY\q^v_Ȓ/13)5z~'4?2>YG)wj^GwMW覿 ?*ǑGwa/t ?'0 akb{]~t@ < CRNPRL \ь z.s oK XXW   WX ͊ ĺ؈ Wɍ XW ̏qI#M $Dj@d -H&X.*Θ#3\ɲ%#H@Ab)Q!P=BQ(5U V B&5Ps˫XbۡWJ [ ]ib[:灂@([1terQT`ПsEH"!Gp7@ 7 WzfaaωR|7a X@8ރυZ+2d}y$R ԅ1҂ &@LWL؅BHWc"i9`<@n-CeE5b )&K1Eb#A!hU1債矇ȘYe \\\qc= g-UYeJbYrdb#h_X6:M2aw&P"*Xpٓ)@):t`0ACFCN#dS\ T^w L+Vy BT* (ˠF+xԢfH_ % QP@ÁG %lx] $sVdi%K\s-m,wpT>.i8Nk < )6襪u?.{^͞M%i˺J`U VUO(Wogw=+ NwEO/i `~[>L:'FlOaBI G b~;a*rb7V+34W\V@"HL&:P2)Cs%4*-q&Jb6% tP &< dtYi8BI\tbPxDt`&LjA$* s T2 "&( f859L0<p^%1r? (@{7GnF-t dϮ*l>hZBר@z@"0.d2٤qFU4`q,0UI; Yl ,-) [@ED]UY \2Ig^RA%b̘kQT3'd ,̃E%z(ʘr7tL4(fE@Q8*@Uu1x@ӬhU 9,CcĶ6* QQ2bW -U>B%@t @rutҘ()l Գ%H@gA ,+< :uXήL)5^iS&&ZpZiV VA4\h,YP,U"1'\ % E|T@UDu!*t Ѐ̔,*+{E͌Y.BNQ N+5]t,u2OġT{THo$Q!D  Bjx0X BP 5%zbWHm\  ;f \!.` W 󓉜ffg0+!4Ǻm;^ '(2KbP@.r Z7NjkYQ\,`` h3/JjGBWbe9@"<1e(`T(0OkS%'K%512g P+k 4_%@&%{XH@B  Ad;\^oFd `adnUAoz;NlM(#F @-X\W5!*w5!QI2̉87AٱA\!+4ac,@:K뵀& o Jx!( `=~*;nЭn7#qt_r->fȵ#zO7VgSt|&0'gym`jaN oox zKP}sĺs/H.KPV’uD0@xpo!yCc>W7/B F C/BqT d&,Ʊ0pB@P7Vn7 vD$zT_qgBsP?`4 6@K: ̀|1$T \d'c^8cFUU!̇{ i1^^9FD opcgUdV 'k1@ p@bUWUEXpL"ifJŕfj6XvTEڗJtRЂR.f`@zoFD|$pr w}P7c u{vd=i$nІ;[ bSioHXhӰБ]0X|!hx"\-FW׈(jvpn0}X@LU}rLX (JLrMqH6x~R5YX#pr~w_]@:A~ 4yQ 3h 59WW#!(y?eBvQ")-W!@X(ɓL$Me4e4J38PohjeE(cb1&epcQVMUP&ecJF{+jj5bJ"PVCeeְYniFceXZ"ӆc$@V<۳>@B;D[CK{h AT^k%;+O(\ ߢ :K|A' m 2Bfj! #bj?'<+v[EXWUnV|0D)pdufvpc{tvvjizVvULw.VKPK@dTIp6HdxJYD`A{$k#t! gs3ӟVk4iB x؁`MZY: UB}"%%@K1.Yx%&_b)|n |'W`U p{wZ)(hWiVu'_^{ 42&TD:>%, 6JB!Z$-! *?c <~[5pX !epĐ c, A"`ZkBvV% LPUډ?cijbrYv[iUiMXni_)W b԰q Ud 2Bj( 9$Ü ̆JH~< 8mH(؆{؈i;͐<ɟȸpʻ9>61a"ńhϘe*AZ" $] #0)WtLѷСzbY"(X ٬f')X7َlxPPU J>$.9&HD؆x4nUfjP2: ;n =BqJpJ GI^ TȊPy!|گ3̈f@pWPo:RH r!XhqVʆ5@ @E_s u> ˬ ɅI呃pic򘘄n "heϞX~ ~PW!.wl.6b eEdeF6e1vj1jInfjcfve+fce"[V&{L*+cԹ놰G%^GjNol_ VޘEt@)<t4߮Kn.e( ;ڄIo\$wpH8o7tn[opEdgr\oXhtkU( z|UT>?+.5Se! y6j\`,FNԐ|nu "0ltnV}vq߽h-hoi\!^tϗv ӡ ~O 2$9tK?#cM q ` 9t.vyȆȍ8ȍH2åDvLEx^Z.  XXWX!"#XW WW X … X X۷ݹX$%"#%`4^K2JiD>y0LAC- !&HHP<(3G,e)Mp4lɳϟ@ JhP jr./cf j/a1}vefT0 j[hӒꕂ^lP )KDsz!Kӄ!DR?TH."@@ <&!3+!U|P$ AexKȓ+_μys#@ЃbWAXZbq`|u <e6{Cx.?WXE$HL!J &OXpn̆@!a Da 8Q&!8nzp')EߎX@ˏ@)DiHMv=@5 @@VvP@VA d-d{+$ޚw~7䈚9r{>&)蠄jؗ@p_P eH.E %l@{'r󩊉t j~YpgjS+찃J `(2e lP .iZk-j-}$"+kk-o٤^➢-zqZpp&,MNlPbjU8)ʬT00 x1q Rl4&.%tp6_Q\w`-Oh ArQɂ]\1Y/Łs|߀.xrDD7o*ԈϽN6x?#Vnkl㜫 nqoj(vyEy欷囓.;jyjOd\պ.z#{Z+Woxܫqo觯.Sp& @ w+h`L:'H j`hˠ7mM#MhAUi(L?J^t! tS$Ȑ.EWCHlEcP0O< L^xDy` H2WC  %#x̀"0X3V) #, 19s 0 &0U7+T=0y#)J!(PI,qX<(ȐLGD+$5 Jd0&2"q x 4Lh4+0)\`yMs2 P}H`S@و7ˆj0{t*@a$L#hB@F$!ISL"!q*JGN@TEC\MDfJ‘<2`I.p#r !Bi:R&DpRʴƬ)RM@`"\9%/1".@zV=vi"!tL '>t*ZTE/BFB h"Y@B)J>ΚWk=OC`#av]trG. B)(K %iV|FmYRڴR&],Z %9B 49C Еb2xȅr\"'D*I]0tZ`f1j[u-3BIMӖ72D@T,ܵD&p(E1j8`dDDsͣ\[sńqT T8pC@R?к&@S$ na*׉}}3Ln49B]8n3L\O]fݸ,LF "$%&fNh!pA$Ij9Z9s H9/ v&t^/&:ԒSpW?!4a\级 UlacbiI3Kqb#"i⭂@ X6.ۡ7Z}(XgM2(sKtCVϸ7q^P9ּ kB vL2J A]ш;p׀@ЇNExїZ{Ɗ`yʙc[{{c _΁h [{*ʯ RKZ6*?A3084[6{8r3@C< $[@2%Š <[W\R;T[V{XSK7}y'. sls)4Z k J( z JkPP{۸k۔ h"{8TJJw~[ { wExJwssA˚I^(#"q#gPXTY@𷣋 ۼAt 7:_{ $xb{j tN"PD[.uME$ -{ XIz R н %p##f C0\*Q]۴@&|(*,.),]@0&WU 3C=< ei n ,2[.eodL|bw)"ܿ 3d&ʻ 4 d1p4P @S Q ~QŅuŦ)0x[Yk Ƙ QJL-p1a},RR usLQT`-ɺʗ1(q13A\>\Q>| | ,b N !O8 +dLVCgLgCԌqQr !-ܜr;  Pt Qu0]+[4c: @ ra:$@-^b֗r &]iMҿV ՀԜf+ Erhؾ#[ò}CQ6-X= )m{Q8*RU߀)@oq̮ۛks\q<%ۼڒue<;\Η2%>| nX PK6`ݞ٭ RܲraJL& @ ^s )N%; *z ޿XM:ݽW,j%+.aޗaOPU; <0{0A)ݏr⎕\0۫ӗ{QqDŽ@MI}7g6 #.,U/ݺH &V[m]8)c1;}J\] xs:mb1m} @^Bh( 0pQ j2P j\a#< 6U5|;Gn뼎뼾M?X>4<Ȟ/enVf@^ >.|.t.^>(  _~#@ "$&_(*-9/>.X@A HFEQ N@g-Z?lZ\_c]OgZdf/rt[jYyp(c/_OZof۠ /Oo?/OoZ/Of?__v¿w}ol?p?oп/Oo/OﯓXXX*HWĽƸŭɷXͩ*܎ݬ߈XO_>~ 6CSЄDG%RQ#F="ʘ (S\ɲ˗0 ͛8sɳϟ@ J+*]ʴS2=JիXjy4֯`^JSٳhӪ]˕!۷p㒍Kݻx_v˷ҹ~ LxˆDžC1˘33[˞C3YA^ͺְΌMv׶sTo  ~+{_ΜuMͯk^tu[.=O>)I @Az @w } 蟅础ij'( J(B^ &y@+)lhjK k֨~~iVi 颓bji6{2*";·8 5&y-B'+vWZL$V,DA , 0oV{0 ) o -` 9"0H -x,. @ l 03J ^ Xp/HAr@w`Y"L'|^G-c;,JC (cXm 7m..6`7܀~۠w 4@x X1|^F|ҵbjB,wEG7 8p К\Z[5=- 6@ =t1-@8&P 3̰6-232λ69B/%}n UCd%\ uYUTBwZ#4!oyKDh'ٷd ;Y pбb5 hI Wz   pd~4+S[]ZD+ԧW`ox  b `2͠Xb# 88`c "D%b!.x9A@(v Wt5Иޭmm A:r0@Ƹ p!IeC` t-nlq2YḊ'Ag1 p NJ\Vt %"굹 8qeBej4ۿ^PZ4EGGaC!AnP_<ŽNyLx01x@CD5\ p`R nH&JG,VS M${ǦXj,jV`hY7*Vi ֦jr(:*r7Z:Ӓd&+VLhmԄ| -Әᒩ.>LUQl]8Z @٬p K0ej)I~ŢVAd+߻rQ,"ʉ UArX&䰗Þ#Z*z. a OUe'qGbU9cHXVpS ö]kJMPORAMp 'fZAm^S{K|-op$N9_(l/\VO>˹|AEeT-oB&a;6e'qqsgLas?9q]G# 3CMt@qc7T\.of`C4=%E:GDcS1jp^0J.}p `u dA+_фPlPp(eD1R|TI^8E $RJp;eE5 dxk}fy~ynB4pT @u9af3f_RDH^Έ1WhxWXPFA>6f V;4"wO}{gvXKfnB'i~ܑ> `aNԨ(Yfs*ts"g]6EV|JI4yᗀe%+wsVFu+Ib;fKVde7bcccC&T2oDb5Rc.buGk06E2ѐ93mL 4nR܈N@ar3C`V{5؍bv褏 $ /Vۡ}f RhPL痖3ضfNqIh5 Jffd=5;6ffqrhbop&iW׈TY8W_NSND4H8FI@kh5cT; u(B XxR~RwgTnzaekr$kI򂐅lӘ'fk_mUm y4Ty{S*ȑR# s@+A 0nйz928ٖLX21?jl!,s0W|{W/3*"uY)qa?LqM$m49kyrIE蟉{_\$lb1 Vvpe7%YlFFK)n8lFz Wl$vXY-3-צ}="0~EUsIhes^Tx4~hySr&gnKy^ZZbY7 z֩٤|J~J-0Ȣi^}nBWf}?F }ʗB:hf$b~dIZ|1e W:+Q#B8!xx(?YY+AsE 3fA"eѮhysTIr8jʆ`8j98`0ʱx@6C"SՇіSsfg[Hs@׊Jx3*(z?'2tK)kRGG^$iEiɓ?sG"F`tgo nwqYEX;8e3&`^nko1u6ti3f¸BY[{'9Jzq檥5lTXV~]#.w)9SaFO"`KpY\H;jQhs@7Gw4r@ܙ+NOD{$y̩p7[@cmʄ3il` ?>s4!x*)z k;?q癉T*`> &>!2:”KO诔A1:&0L/OU(BE#GI~*FN(^) +^Ǝ0dlƈ_Ƃe,d ruwgG`epL{lǁǃkE~ޱV‰V7_.#S#.y:&wEȱVНxm >Jjꮜ(:ң8xATfQF֡~ p E͞>^Į޸.~zʣj9S}dFŖ. @u!Q흎`^@S+.䞳+ݣ#F*ɢ] ש^aPSGA; "2We^Q5N`BL>!f/NnFIwaswdLs HJ?OQSW/#AYo[]^\ceZ? +!bX&-f|(+J`*|@  Oo_Vxzg*G9H790ŏOoϯ/OoVM-vm@8٘=@ *XXWW,)WĹXцdž  X==` *͐P``h R XAĊĒC S*wN˗0eCB{1Y@:p%pegB2b9@8*ҨZe =0kĆ YKvé eC$ b *( (m&@ PAp 7 `[.D1H[f wC HA+|zŴaS Õ ,pBѣ/TpIBs9RN e %P @[Eeg6!ˈh48.֔16܂8 4`x@@TxRhQFeQ hwTS~B@ja9N h~x)E/a[ !BVaLDW /9gEѷF=ji,hH{uPt<1] : 0=h~ng@R`ZI~-CR겫d u#P1NR| MWmj9(p@@e誻AgeHFlB&n@Gj2gDzܪ$񽅅g lAn 4@hS$4%/Lg"(Yb`$zI$CEpfi]m!my(F\|x{m3N~YMPS6Պ8g4آiLC[ F"iUDQ` ֌YXY|ک CD%>ѐ!@ACTQ|!*u6L1Ĥ>MFem)fT3ˀq@ PJԢ5Q+,"!H<[h-ibR5ԏxDUJ)e(1z ]ԏU}TM~ɔ"z+Ln !֐[H~TV0mH-! |H!&# iO+t"RDzizr]BbCwb׽uZl@@=Rz` dgktk9DOe6aFҵXc'd@P} zT %DiT7O{dU3 J ,RJ8`=`_dŢ #H3$Ĵf"1w8 ,.&@ QΑI<05V=MLZʼ8`FD `ۉ>騤xIei0kph j^V8Ԧ]B8[^QȚ&Y k_~ { F' WA"+ 0JX z=My̖hb*I4@!/;VZ(fd&[5poD[5 ] 1X˯ K Eape.-]Nc.& }GQK2LŒfF' Eb*I$VG[ 0wwY@uD@+ ZG?ĆΥd

S:f ae46 2MH2 āz`d9"Z#i8ee Ar+F.Qb&J+\dbYO# %4aqm^b|eh>=Rs~Wgw$ VdQC `A CF'Dq.=ZȠ @EeQ]$qlf VZ6# Oڸ؍ި ft-qnF! 'D %i|ez6Up^ A虘h-f[,9!@,p1pis7I}jF'j' Wseq| 9 sꐞXH a,* $pp+H{"s =şJ#J}W z p=sR,o"*0=u;Ǧ঱t|W桮h?ʠ9:G !yyygi)%Q{p0Glze0;Xp#!0j t vDɧX0V*Z! 1H|E:Z :; pnW7fw!s%П*d~ZF}J"@"pj2:Aʟ_WjX@l!ny h|W!ٱ "+JU0&G p_ڥ৘ާ<4,H zՂ?{0s:v j0{:7wZ 3 !sW0w#zXJXw5X1gpf2J^ pyE۲Kfgh7Zۛiе`85ʉpPPZn@;slzPy;7U «it*xKy 4/* @Z3 +ۜۛ:ѩ4p`3ۭ*ZAƜz; ˥'/۫.r#0+<˫ Ld6o݇skZQ h ;z)|Uk` J@f2: G8ڟ9*ƒ%0E\)XĻ{s;iPsࢌ@Ŏ{T [le/jXƮŰŮǐ~' Qt±= "@7 9\ GsB:' ]m% >[g?h)cJL; Jl7{"YXy?K+ʄ !iǸƖ ʷ{iL>;~6F; :{=@ʞQ7Go̙Vڭ lj dX9[T{y{s@|ϧWƯ{:kJ}J[J &,,.02=4Mo z#id\K"P{JhXfA`JsJKU-I*ݡ{amL#Ҹ{|~LҌuJyE׵ɞwʒKf|v v:w]K"N; {;{rʹv :6ͩK13JKWVdf 0oR}ֆLםmܰ T{j oƷ">kKp&:P6]]:}]릛oܕ-ϛ*PݛٞƖ%s*ާZ>[SfpӌPek" l-="N-S|-Wz] \#㛵Gok׻oL 8ll\ozk!8wMZn^ΜP> (O=pԸ7z8tP;mg o˵R!x}#6{ԕnjĜfg p $P9w\갠n>^>5,ï> ^# {&LƎގV~O\:۾ < ׭+ {,~l\)k5"?$_&!_yNN ݧn J+Nm@vGԣﺰJL+ /ۤ| #$>9N J'phjlnm?bvxzo> S"HW`+8gl1tna;meokb2[SWiT>:.GN`/A3o.~O`~ ?_K)mo1Ǐ_^ށN}Qo=}/_3XW,)JU#& u6Y2PBB >1†/6(F<6IH$BLDcH%arK~ .ϟ@&H*@px uԩ ]ZhfuS{BN`Ygߺ  [smTn_A L0Kc  e+_֚3wދ^ӃF]z_׫a6kg]ڸs:agIȓ+_#q+H΋P>] .;yB)̯(zLJ^~}j~% h> F(a0(8ʢ &v (∱lx" `$ <@0(4Fb"8$ P@)ި\ (Kba<("Q`) &€XP 8A,y@X8 9柀*(F"r@Xpp'H r@X\E.92c":I@¡W9訊>ni#Y`>j!3 bbqb\k2,! WlFAkvm~+JP 0R0.F.Zk3xo{ ]WFȸML',!8x̴U42"t쬡 D ΉP jPG-P,W,\w[WPi -eOvM xHKrŪJcA-xbPPRiߝʅH*ȉl9W*P;{gق7\IMen{"z;o rPAOj@Am.P0ʼW_֏BuNILlbc-x%go=g8d 'ԥF,Y} ;i]hrEw;QpB&rT5XN:t`XQ@"lz׺,(=%e!bJS@uM hD18`Ȭΰ( ti`2 4.DR&6fIH:ҋ]`^=   (ID#drȯ1LDIl$Ř(w@ 1$(ɓE$(L*WV򕰬%QfK$D ^ 0IbԀQ\ t2DiGw $ )&pz#g̉vvM('+sP5}wh,+0Aԓb L5Qztd5CL &4Pb"IPRLx@9E*Vя>"F+ %) 'u4@b`WiR3ꔙ *̐X@T,UB",j9ȚK)/`ׄS@/oJ^A\_{L4 TД,AZEOFi% :T3]_Xς C)v\`Zt  U o W K w@L`@(Ak`8("]1m3fK`^F.u`%xSg[K))6{X5D/Lb MW0]^cw* hİ8F@VL"0J]&ٟf*;&^sHUוi-nva;Y``@<vY#ثCGЅ7!ACDTgdRoIO@\P \@KjI'=3ݼvO΂03խmN(A. vqy;"EW Meo,nEFp" On.oy%6V#lcSqp;t6wZ[$\H ˟-^h"x:SQDN3v_z!P}r"I? 5ՙaK6 ŗvYm'{\x^Y!\ epE\Z?;0ym^usUƍBP+zQGZF9}Yzt.@G]SB[I~kO`յTzWzӽBtO-g9ĩGq8uͯplFyeUWt|nby0%7qS_'|8o`~45VhpTEiFqDWoͲVQf6H H_ %ocV,e@UVЦ57mfo(FxP 9o$o0uRWj$Z6v[}iej[cxv[vQk]$ Fu7j5h/fgsgtdoHGH'"7@Qt!(HW 0}yG|zxjg e]vgiWV|iA9q|rx&jfjdVjY45]Vݷj)wRl$(Tl~6mh.XиL_}l؍荅r-GopGbff1oۆ(WQ8orr\xqPpy ys^=8fGxsImKX㠑 Y t%W\b8ue(@S-nF6tUhpiwrW(:1f䈉#VوfR9 GV`_\Ysx_Y aiGc!QٖM]wtTNq!x{ XxɑF $.Ҙv!9HWrrIr9YyYP9Aٙ@Vyٚv> 0YyșʹٜySSFxI }O'ڹ)^Y}YE G*9Yy elОiI 9z ڠz),1 թ ʡT Q"$ ')C!*005Q6#@z'A0)/5ʤ0K 7#UJ8Se!XASaހaJ;Ql jo:sꦮ u?3!!姲Q:jXxztJv*mzyʩ꩘:Zz@j{zvꨌ JZ^کr:U*;: j͊Ϫ%q4:Z!1*J%*zWap=MʮOQ81ZE*J;PKGPK>>_GGG***|||UUU!,jmm m¨ŲǸɾƮѴӺۄ ߌ7;IBE( E$I!&%fT֍YGg٠q6ZIl'cK)E49eM4]ySN?q`]vE%`Ӄj(@OF:kUF* {k$*`%XE)mu njeזݾ[n J*f0bŏ3`qJԭ+~xwߙ>>;у7M˟OϿ(h& 6F(Vhf [v ($h ,0Ƙa2h8 )DiHH7B&P>TViXf\v`)db)~K$% Lp@ @tigl |矀*蠄j衈&}n "LIesƣwv!:i~iElP@

P`k/ @P lƙr<`s'Z[l*L_ f^\д4 q*h; A ;[mO{ ֈu}~kRj)-0RT1J7x~F&BQ#h-m`\0#X9%8!-f鱯 ,HҚLW bgyzxh ;d篿-V@Vщu`ڰ`wTo @TJ:u{|`g= ǂk@L>mw9BMTېQH"@l RkaH=ܣ1茇``6DK 0(o A DյTŧ`4X`uj"V$6H+h i\@sm IRTOS9&`ʗ0\NKZ0]@`J-E.Asl5!&!42m!Hqj<WA2Y/BZ<יp ѤE $CLXԢJ{$"H[h,i$* `Q\a?;"Wƪ @gN)zx+EduBzo0 8`՞ +}zIah4(*ϦL`';s BdL^;VJȔ$0KAU@@hQ}5Wɖ\T`me/ZQͭnwꚼ7,VmV2a &0d@ۘi9TsB\SLӳ \oUzM@U|u%8@a^#62%_Aix\xImж y𫈵+D@O@@ur}i4m8'f=oӻ敶Ap|9D5qlʆ_^5m ^ɂp(G#d0cFrWX @NEN]1*aY.<,43}d3WAx>Уӛ"m%f#)Pȉ"X(@V9n"6GJl\Jhk[@c5V9XS ɦTsvYkֆmO @!tݔ(:h_WE馈+b%iieLF# , GXD a\ERs0ݴLBj9@77o ջ=d4|ҢV}V%je̜G/cJ}3ňoUnN!ewwi(tnxȀ삚.ͷDNOcg`w|Pg@a .R@5@h`\|%ɰ -%gc8l` Xα~3$0b ?ƈoE倪~2K#lqO[ϹGF/t"`x{pT\e`l vo4@N7@&ۏtEy#P@$!C+)"7"_l4W0j|Q+c-_tFU,Ucd-]$,F2Xf67k/S)}"rvx0$P;Ep~~ZcFSw0uIxmv&' @:W5'#T(g& kҀ y80Std4ԲtTL1%!E\2R7҂򂠂VP|67V9G)4:!5rJxhS/I8~0#~`70iGX;w{wZH @ ! ~yCzlNHDCRczTbgQm1Ň!R07416n|"z-)bQZZ@-xZ`wPDyw'eESƏDd}SZnH {g8GD ! KSys+c]\F0(\Ì.X'e)L7.*B9(*QW9SQ1fv^m0r+ņ{,#Xc_aUHgg`$Pv*@g>yKv$@:Ch: ;8n `Tld`~Jґhcapf4PC&7,6`yVV_Lapγ<`G2WbMI+OSQ|SYyD:SQD:#DxAO,;wG;CDuC 9:Zw5&;өnciGAdd"_CmF c1`RyÉ"Bg#Z?%gY/RV{Hh))+jXEjh _bf ZZ7-S6ZTZ3颈l0 mbH0fH3c|atP5,f'?㕄w#v`pxv>Qv : 00*ex}JFwwi:y@zG~G;tgkvjq A)' 2}VP: $C@ >$w&P@׷>}psPWﳫ~(ܧ5& i5%v%x$}#0*D`}*WKU{ ]=H Й|}oi`Lh>%rY"IO(?Z3~VHY\GakwF4K5|s|s~[/ 1 m0X1kad|3L5Z(rIHryۅhd:zViS~7xZnr]H}xsvcBd8Z6,iG]*"Z23'@2*Txڨ);$`[bkZ9~;ö(I$$ %vj$8)Q“g_N5'6_en2Gh7Fj5$T:wl' nڶIXryIDP淕k$@>7Ga9qxf*{ɫd&i'e'#c2+a˃թ+E`Bȹ/˺w`8ՉiP)o*y#)JQLg 4Й@B,橡OLc!`U!udr2U4G^R]\1o1ƏipiVq6gBep\ǘFLƂtUoțVRLNmF-i jK)#m2Z\=eř蓳ztB@+ҽW9kI{qd~ ^ CXp: :)V>ߛj^SS ,akFzmiEGZxK Z/'#H\Q*މ~*R?/\,HMx)&Kchٻ*b 5jKZ̅ tM;ӹ3+a5X<:z~X/Sz|>2[NCVe1r S Q\D,~~AëjÅm:,wk!lxrB?_DV\NC^ TGh?x$VMJ)PE0`}ERypEHSW1R[l3TVe9(xg*v"/d=xS[0|=lD?2B '0YWUh_#9&7\qP)0NāN`cΉRkƞ:v9rci1 79nP&Bz#&9Bn& D5R!卹"x!8○Hk`^ ɋc8 4'O婾` uwЩ+ʹ-%!%g%UXE:lD9ƽfzc?d@]n^rO;#=b AW ♒̡"V(,'?0 d8<sꬷ:jC5)>Ҁ#F+,PύG U<{X׺ϮјكBl ƽ 9&4vxx`Lio} o;Ũ~J+zJ#@աy.x#H$QpӐ_$R80Ӣo"IHzW=(SjLĵ{JJJ.0}wzŎ :Nڰ,YB_%jBX\ )96tF9H:x̣Pp5,ڋ.4Z L0Nd53HUn42"Z HBh@s|I j ށ S=D+QqOwwO E>V"$s6V LdNT}!ȆxFM$#lJȮ/ 1*gω$HXBEH%Ci-Bq?zPbކUM1"ٙweJ$0e`T%=/aO|E)E;ofu P4%b3` AԜe_bãb搻8`l˧Rg5<rԖX@(( ފNk):{f'r-TGbJ62><Nt\JKD֮leט*K1qq}IDKݬl1IX'ɬ; pK5pnHHBJ`$hLj RMzh{KfHj( * "؁O1|$D?g^ympRA [ΰ7\a Gaΐ6Ն41)i~ʕ[.$C&:C"9̑d(D ^pP8$sIiغ":8M6/ҽ4KN2;?yQPKc쾈NF; o&XҘδ7N{!ѧGMRzӒgrBϰgM?;8+~̑}N WN}a[7.|]iCY֎n{Mv6#=l߂ڃ6FvusMzSW0Uݳ@79JO;'p,l{+f3^ s[0I,y?y&؄KRXXȋp8XȌĘ،8XHh0y#pHȎmp[xHH8 َ ɐ ُฑ "Y$ &i(y#, В.02Y4i8y:<3ٓBYDyFH铋@hSYWɕ[gx7dY!fjlp:yrYvyx|IzzٗYwy7zvY7km0< @ ) F9I sy) ou)l"00@!ĠSF0` "@ 9ɜ]N9qlPm l`U0Н Z` Q m p 0j(FmoĚ'@n3J ) Ӱސ)ڤitA *ҩ j!1ɢ8&,:2J;j YljF78j*Zi"! aJm Z9mڨxitJlP @@]jlE *P*Gਸ਼z]7lҹF#c7ڝ NG1P#Z2NZuYv Y7 jfjjy YGttZZI2:G! 0 H @ pp୐0jr$r4 |40I:𣋱p`8O@1JVՐӰ kٚ  p` *y pКu rгK; ր  @2ˣ) Pඃ r Yɀ PJհ fK#z۹ RKI;@B۸:@`* !PZp"C p2x _K"  P zkk㥨 p#K*+ʺɻD;"û|<ś۵. zPYܻ" ' Jp띸|$@¯y]'S:ྍ۩@ *G!2K{'+|Gq 4nT#[F1ڡYL>;!@\ƋS|?jzFKTX:PݸNW6АWЍ ) ,P1NjݠokŐ0тL!p|f-)˚jӬZ5];Y+q ,>ϒP0%rMN p  !@WnpȮy{!}RÌn 脮mnꤞ^ꬎ.%!x뷾.NNg j%nώ.N>ɎӮޏ.NN"h6~in>PvNn?Pp /Oo(x+-X/ω)O3/2OU "0o9,A./EO=?CGI?7_8RoQ8(m#d_p6jOloos?uiy_{nptvx?m_H?6UW?;_YoKP?r/oяϏx_]Io ?" )m*m*)‚m ٻۿ l H*\ȰÇ#JHQ_w*jX_@ CIɓ(SnϝʗƵg)p2;@kl.#٫iۘlbrm2K(B-,7צ@7NKsQae'vTRkg])rC2j XM{@Cye;tniVv]jrPXTv#>T wcK`gV8w6wr KPj9t-`09kխEZai>-no oZnsh|@^|އf|͙U`o مm Z($tF.@áh|qL(Օڲ*yGPWt05F 50C`ħQ#ED=dRs,Xlv4DL8༎)OOOy*=2)##8< DC"%GȈ8 ~"zp9@Fl,~a<%h<r_M 3 udVlWz̢JUdW \wЗ -&wՕg(A8elMgZYLGrfG \ 8{@d$-#k&Lef,⺩^]YU-$O Ň0}('{O_av6UD q=$Y+?vA3q.GF..hlLQngaF@+ `.H6`<9/@f51>e*ӟ iJe1Oǖe X,OړZ ؈d޲OKP>25!bԹr  @[ABD (A "``_M6 %@'hm0 eCF`#|B\;)o|NkgZ7_3 3]H.3p$Έxʆw-qa3nCX s2P geuョ6Xgedo36]/nr.Pt%ި?2N@}b)V K=?HXңm6 h#0 m lP7 64  $` 7tw|^{ 0Ax;h@Cp1)n/[yuH5-}!CqCd`@wܽ`<ߟpsHA O}K{ڇ~: #I$S(aBDm!C!P*B6VH#^KbVrKT#{=y!A/UFkxuWe&@nחxlWra}qq-X:hmmGlk*7$gׂOd&q g>rWo?i(?"S1Cit-[tFqDtEa8[[wa6yƒ$(xOv~gaƇ%`h$0&a&p&nm^^s?!4;1NdwQf`eUehG zYhpx ^(0Nh*%0(_hpw^89p aplPzHLxl:wւ'^R88#^ȊЅD :a]t1*tXXfor!&x-w*@ ~G%0Y׈WmXхV13Lո `Gh0wYUHnGY`qziO s xY_;!jzV8_XWzV~搁wv&lH? a3a49P*֒%!= 6i$}y B ~8`x&g )x'9 @pTEYXyi(qiPIlg߷iٍbyJyPI4$ytezv_јŶ`lxy~g]h)ҩ8Ėzfi~(ɚPSq"? i!n᧡&eq]leor$:rGIW3QE!gƖi4jj0ձ{9$T `{S:b$:Ց\^qU2A:1#\:cjEud1eUMV.jg;Ze\)EJ[%EI:%B*8ʱVoe&`H"%pEѨ}/$6f$Hרq꧅JסzEQbp\ Fз"b41q RET+$+511d•bioS0Jw.*W+M6-!..e^DS&FJ1הSEU;["M!v *s!ځ1yhiʲW9MU1dGײJjSLtQxD* 0wD UVOPx"I#H{x#1љvkQ3G~2:~KLrWȱґ(Gwי@lHmP?Ӝnzy38'%ԮkN-OH#|:7m@<%s`0%U0QԨPǨu ,/̓~yؙMfln]i~w}\_6އ؝YX糛e0lP4rXzgIe|Q a+ 2(i*2']c%p桯,nmb<*J.mBJ!*n,*SYEuaqU3hz]oIx])i PaNxF1f4%"q'#b'uΘϬfN.pw#qU䶔vȆvr:ꑲ&`W,UhPN1 47Zq~Dz aXPfW ol98P~(Qvgi꒴gzA"W|.}Y%ATghpw>{xU9ؘhEMMD `q }:E`8شDnS7eVsy[Wnz7N9֚Xԥ_aO !S;Q&ӗ yۍ➋.[Fs~Ix(lyLanQ_yn%zilxm sm {luu?]N7ɾX##Dx}јX_1|ކkLxΦ7nw‘߷x[wOyg_wdy.Ú7xGzw}pP'?P{aben<"m&TQ!T@c!l!YF)mlmmmmlm&$mml#&lҨ llm"  mmm(b)" @ IHY :Ӫ9 ƒT96J@JhQ#Q.)u4`ۅ6Dj(68Wn0@ՃYzsfSd@)rn$̜tNlsBDhI B 08`@ۄ"Aj#K蠰጖,,ѣ\ea5)@o~萉 jZD JOmn!$kH`s4 XG]s)# H/a^3cwhݭY*K`(p@m gpт n 4%H!\A!&mеAqtvN,6" HBcjٌp[$LԜJtX.YTmDPW,wp1(TViXfD^$R_f֗ `mWXo䝑SexvYSeVTbQrXpŰMX#xciPA-h=/$0Fj X)u%Ek&6b[}W] <m@@t0! PA!hEɠ&xՋ u\0 M^cQX4Ah]B\3(:d04i(,7֗q~@zb) ? p B@#7}RP:MH48#Dr'b h2E3Gwc mlг5 N}P#|؂7ζՂA Po]bNyVwnD^Q .n0;z=:g~4?/ h :7wO}Vܫo`($5/ א5AaoK'H ZPMB;5Iڃ6w%΁Q@h0 gHCc $̡Ɔ"1w|(!KD :>*|Ab.*ъ`D2"Z2I6pHt3pX>* =6nL"E>,Nȝ$'IJZ򒘤 7m=dCtCRL*WVtK8IZR" Z †<$3 jc"8m˗ `IBVEb1iYcD6@0Rlhn >aO% $*xpY_|3sS35PF$(r[x^g0y' W6֩x oNFqK.=vh {Ņ|ca|6r[ztia)P{E5P7m& M@J)VƉPZ s7f7\Yζ%r1=k]U8P`g5v "Ծ %QdeBJ_Upr0|\`-ʓ "cB.:u5#oY;cfOnu+. _j|^IN} Oޔ/:ϝ }oSZ˨EGOz *W1,T0a6ʙb6œF O ^SO==%+' #H!cgxy'@rqӡ;d^D226 D8D/30 рB􀂓I M!X3@I,8Cho(/3؂6fq58/=x@x mcUFxHU5WrO?FT$m6XBЄ tDz?#Uxj97ɀ@R8Ҍ@&Y昐) ?fi6 VF a`ӕ2%QQVR%q0gk3v*2 z!؅$ Y 斜?yxʅ^Q~Puҙ5e智&O&WUD mE x#6y(UPN阜 &pV&]9]2)sUJ=Qc95hC1J#h`Xto 9Ӱ5qbXve6 _i ' u#w+8`Y[d8`a`; n`lfZdkmթ /jNxbࠚB*a"j"%YÜUsY"xEJJVeiW ըuN~*ilB *ŅH!@z Zڜʩ1bb'zO** jA*q X^׋4)2-!lRUj oҬ|ȧXР :`R-6P^`0o#)1SȊ@zɯ  48ZHU5iȱ"XZpMs WGB;D[F{HmdFw,I(~=f9t ص^]{cp1K.!2!r;c+`{x$MO2`Q{@%P"#zKB_[{ ·^)وZKB+˲Ѡ@ d?鹞{Zi+t +;[{[1*,;,$%K@Ͱ3 [{蛾껾۾XȼöSۋObpmC+ { x)yzT%ön #{*РѽL < Mx l!,HWaS8卝^!Ul;TC )>u+꠹H,£ZM T0WYY RU*Ubrş֋4)CE <9t\v|xz|l^tM3c{ qȊZ]IªPE`)0 W5` 5o ,[sjK&o-r-"p-/EC TBsȌ * V`ڙ*$V pUZv*˳ |}jҰm"vBŬω Pt'Ԫ bqUVZ"< .nXF lRנ uq\9H< VX0i1W Bvva $ Z V qJȊ0<4&* ZmWRql 5u51[ Cv 9 n2Tm = B&@~'"ύ=B-o*GN% HXޡoLز r,m9 @9 AQ4]!04})7˕NC3ڼ0Ӛ!7 M q"p@p#,oarx]߇xP]d=b1'C N M `3'xaM|wK}a7|,>6 ~Qm7/~\,)' =G.+ ߶\@l-"+r@A] et\.LN߫Xq7{W{Ll&` <0L dD0N3k 7PٜcσP Qg-k lJbϞ.vR(n AJ)RQX6cf2f-5T%JjƝ-Q9m.or.DJi LݻONL"nL2 j ?L OcoLL$4T&(oK2.ȯ -/_K,_.<,gxs FϓH_KOM_QS=YCՂmOZW/eOgo0jkmmcOuzHvox u{ws}zxP.D% 26-_D"͛j޴s'OF:w9T(P |h` 6`s>(%Hf!znᇿY$Xb&x*H8戝:؞>)䐼HLhdP:dTVIVf ܔZv}d#f6(噂pl~wcЦjiUg)eie~*oF hJ4䢒V:g~ j鑔nCvJT: fij"wj@)㭾; "&k({lrF+!KA4m|.r-mݪ咻mn+ob `} |$;pN\{/ qG1 bC2 o8ôjvM| S̆6Wh)A; [1&q\Q3tð 5Ƞ 464Cm،=1@`ā@N?$8#q"rsb NyA Fi0lz8Єb{۠쀏*4#DDih >`ߔ=FSpE,YLde\y,rџ^4#aO-#P=#%imXIԠ>P l MA sDGyAi+lP +1zڛ>fѴ(r:bԥިS:K0s5@ l tkka2 YVԵk&J(( S!8&p ) s{bi7@r{\M@zFjTC{֋Vu{7on'^qJbF{0{Z8%j*U^Eh;(Q,W`Q1$1Q@*q&lR:RNC5j<t mLYmn`=&.!@DЁpr p,F(nSzuMg7 A km`VqggVׅ7 w&Yn߬Wnum Y=PAV{R-p)y4;HDT@;]59p!'ɩƸ +`*is&wL֔M5w<)oNF)2+N|=x=ϸ?3?=gDJ)pΓMp*Ёri{Ӵ>YWwAgpas n9f6}8|7.4logvSϷP`uJ271'Q^FX^7{8dV}]+yf酋(ՋOؔŧw)d%17Eu@wc&\f%ZUu)jZdžq8E9*gI={qa(@=(AXxZ7YɸfuaTxW [k& PE+89ȉ/}KT8ڐ#'jMڋs[pGڈZW7G~ujz˽gxXkUO;WEgBf8E%y]?w۽wvDkT lEPdžE,udd&†8rz#͂ XxcXYI2&.‘~ծWgfvxݡu&Iw=(,{ĕ5%,DȹoǼT|j{k{󗊃Ȍx:kX88ɂۧ@ڻ}t%fաX)S\I@x!]K()Ԓ 8IΒ˺,-aAϼ9<,@A4,lc|ּ+|,L+͎ԼL lЈ<]},pgIyvLBtær2TH%uѼzҽb1` 79;=?- lFKM-O CM IUWY][-]M_ac]gmTX\` d]ԪuH,3liwϦ7K+7~5Ei8X(zǀ|gȍؓ$MJt ٠ԞMڣԥڧ ե7)ڰڲmڢkշչ-ֻmָֽ=]/eWĝ)yC;Cɯ}Bf |ͺ0"Hռmr߆͑ެ-m 6 Mn )*@ ~n.!NnnI~EY(iQyhBJR(|x]Y}~5]߷jx %\BpxJ,J旂fALg# hyq~ɁS\b.0֊bPb,S䶞 (7ǀLrm~y0ȀCA p%O>u#ssd4*VNc'Ixx?rigN3xYaN.^p=!¦ȇNJ%ѴQWьܥ  ˍeX(́8rEL+>rN3BDrrb][4(B0fS7W? d|rma ցΛ_?(NmsK6cI+wC6Ww//.h7cG)=LT0& B]_8=U4?]\#- ?p7Z,= ?*Ec3,uS>3 Y߆yJHO͔ ̒͗P?OP O/H!vT;4(od96s4l,+Rvt3OFxtӃʏG˂( `pڟܿޏG`O|\&  mm   lm!! lmm!О՗ږGG Z*lJ‡SE WED!H4!" l+/H@̚6-yP'ϞvɓPE hNI. ("O6UM` JP̖(cP8ثܷFB֡ڿ$PÝIi+b H`ʏ!6&`Q *E铗P$G @xejr ?qYn%b_MJm3u&C+"(WxAׄPP5 wV;@pBT` (H&u 2Q]LR & BeYg 3 "Jl_,1#j<@j'_LW䒧 0:y$v V*" K(Ew!II4p&^ gvvos}eܟׁRhJ<m tF.# ˖ Ĺ a'}j5 Z x(: E7 {[J\5GjZVW$/ʶaCV1YhCFl"Q8",Юtmwf^9)cnlikpP.IHnr$o F R*_jƶक^Ȋ"K8*b+B<`R^眉|o|I:]_ƟEИZV@fYSZ^V[:H!"qQW2=%" Fg/<Yd`bء "V; "WRK_b&~"mIdUh4rwi-xPp$Nm kt^t~|,-l`ߕg|"Yjqm_+nj]G?t(D6G ޷y*|K r b܉[1גfۣ $V4mj:0CSbBu)iJ(D DyUמC 4cI}qD.(/0$ %5@CV& 3(2 1Cѻ  h#`mpx}/xzspt6WR6 &FFwxv؃$P0}՗hا}axHwKHigR~Q5\"vh:r1 v# p/57shcǀgF-8KUu(X%YFRyoP:p "{i ]GvaiBidIx""!fSaIH&20(մ=ɲ&cJTd,g p)N=dۡ 20R 82vZ2\rg)2;5&"sssh-Ppr9"t(GIZYY,|P%+)k_ x,"Q,"\|9BIu+H(0yI~e)wh';Uಁ9YyvydKt^oA `68x1[C.4r&}iZ3\mS$PI.ņ{ؙ@О9ٞ%@hfE6kɖ@Y YJb LlhA|,eE, %5,OB[%RC/Z֘ :8!i ǟES>c:iUDM%L(gil  Sod!QM"9!ʖ;!f^!03!OĥdbhIגfԚ9J ^Оv Azaah >8 i/*1ʦrK: #` v 0{ ٖ1'Ǚzpf S5o }8='0`v: 0f?Hz }5۪`m SXx' j 1]pq Эl05'fH7&vlg {0z%04ݗ`!; kAk`Pjw3q>@B;D;娣1 P [ j&p#pPrv:JڅZi䪲`d}ڃ z[ w` ³۸[Zuoa~ўz`/xʰ:j_h˪+ױ;zfغx *}z`6ks"1m` x[jʛ;[{k=8:P|s#@Q޻O'껺|hw} o;h-h{ z˫ @#$ lG'H Bl+8-W׭ K h#p-Gk iv | L 雳KpI(hYlģdS /{ mK4}J-ȿ`u| J& Ȅ,) ys"Ķh+}VpŘ1[w Į]R> xP Bw k\|m繝GjWs$yS\ LgĪ Z--7hڳ7ʞP˨LnLċ ܄,h5+fX {lu6[ :m 𴉘 &}-hy\Ѧ+v-g-K}f,zKstz'}:zޗfr ݚ5ẁ$-f#0ߌ }  ,i?~wpW[:Lgj%xu-'1Wx}{#4}˱zk ;k2{љ i|^]},Njƕ1} }(l/+ >V-@,; ^}*Z}4[ קjjɑܴ ]ɽĽݥɻ͗PzIJ) H^ˎyLۂ]yΝʿ4| ;޸o1ƭae%׌}>.*Q͔y np1~G3Ko%c&H`}SAG߂~1 ~g!&f%jxa"rHb#%!I ' $b3xc9c6 XHZt I yDIVbe~Z%[FFWz%fikgfɚ)'&t.ep٧$) yܡ&!D裐F*餔*ޙz)&]jZiZn'։睗٪Z+zI讼5@tM) w fZlpͶp&Z pw zk mmK{›hӯ٫. @@'Y.p<J@sgE o4Xzdt ̟ l(P @[ʆT|1SL7݆N3m'V@"tP:;rm,@OFڠAS{BtF ` d-O@AT̑o'Yz*t*?3x}K 8 LAʇ褗>|`xTm5# 6mpp=!n <%8;@P 4`4!<{ ܇B| A&}e#\k=(c"&!= wO|t&DqU68@&H*n Lj>o%C2L,`es 3>o>l!~%aqh* H[(dz-VF ce\%F/`::%bhA6y\ p=j ," sFZBbp=/o|K` ,h482]<<Y|~H*pΑ=@$cyJn *jp2 fc ˺E*` w9f/̝,kR gQb!/q FRX5N A-yJ@8JЉT: Ą:>/TUto@.͛+HG \ǐܠ# )"P'NBJ6|$N1t@NzLguDM*5ԡ,`:kT}ԩ^AUru"NTǯ̫fsJua|kYtUxb]Ҽ)D`KMb:դ&Rɬf7zl/(k #(jWֺlgKͭnY;J Dذ }ZiFܑ7M[̚k\Bc҅u5x:w~Ͻ̲f`{^L7+l( %n.s'@[ &H|A ⸅r!x2qhw_5AK 7Amxp|N%@16ÃF[0E'+ @NWMDZ {D_I6lEq!_@b4$8{_γ2chfCeA%0J׹{' >(@ŏ8\ x$ȇiJ^1N, `&hx̏6 $Qe o$fעUb:Ҟvkw25mʴnA&bvn }x{7MoK 'Ŀ-p_ո3m |'NCāo?wAw!8r?|>?$wsc".ɁGOyҍ]˻hH6wX!s_3O! oB ZW}dw589@q}nwph\@x#Խx숱bn WE]p'DL2\&P/qDH)Tfx=&P>-hQȄ%6׀w=PQH~hE[!<63>60Vcξ@>ڻ wwH/`P  }җ_@` h@ |oa|̑u xl $k3Fyqk'(0W qN+`q kP1? l0pHf g,H k@]P#_8c$ %0k#Ɓ zg %gx `77%0jZ  yDz(|7}80!'F_#ZE@f_(xvoHF# zW.h ׇYȉ@Z! 2`戢P`4QP%~4kxH|0~yȊH+gs8x27 G؇4|pH%ZF `wWzx+_%` `PwA13?(;`5p<Dlc {+ |}Kmٙ 뽞T%dOmMy3s6=ɍПc- ؟}^Ȁ< Jz w.0[ zb_`p1\S_ J9V`SLZ0TI!<s ]Lst C/8 l:ڍͥLZ>h)|hG1K % BHST RHIn<L* >* }&P2u]W[%X&Su5V#G`G `MvC8Y3`J| k/ Kbv^( H:JjwXǧX\@ ְJ 3@E [DƋxtj qnyǸz/ȫy$&?̘S}]ч/ ܆>2*F@˘b p<Ǯ`y8cjN. l}kX1 @ e!b϶7;7~ñ(y̥ZH Z4 {GF} ă8Hf~ GcRaynնL F0m),e |V 8؊p-αr( >r |{o] \ek ssp>s1(BTڐ`aya|Zؼ۞ƣ}302K$ݗQz ,b.-v`\B$IǫK5ݷB`qѫ}=7cG=C}Jd59S6}ߨ߾ |ƅcO73gU>.]kjf_'l6XVfcgq2`D-9Ie\L0";L*N-X ifZ8%Rh7: \>CI4:sC >FL׽h$Wগ@ b&q \=NE6E7 ΖbE<:t~vEj#vN =S@=r+n.NϪ16k8()s3eQ ` -v8q\`pE.bdQvh7^Z$0^Ȯڍ Mm gL@F@x9J?@ ?H /@ _v/nDaN(O<'pF)+ -0/ 1?ޙ-喠)m>oDD3Cu햵pNo P S UnZYVF !?(JL/W/moT^/o]\&c3H8gvv_|8E v  B}/T6@?cZ_ ^:8Jž?_ȟʿQ|x?D%?I՟o_F?_!?G e m öճݍ,( Hӊ|pBz = A %(D #ƍ:z9HdF=b,"˗,_$RA5etR@ *4$Ѣ% hSF+Hj6UJ YgEVmPovnAd3?XQu 6H] ;PKn >4PK>>xxxҽq{777'''UUU!,}} 'ž}ճ׋ '} _A 6Zp!M2-D-2 FjBJ+[.%TH _ʌIe2pɒ˚=s)%ݷt`ӣS:iUW"u+<'NhPYQfӲ0ZUmEvve[Zn;6අNXb<>]8pı#Μ;tԯAŸ˟OϿ(h|a!6F(Vhf_ Ǡ ($h(h}0(4h#,<@)d,_X L6PF)T)Xf\v`)dihri\p!0Atig| |矀*蠄j衈&}V`\%sǣwf@a} ȉ}@pBr +[(j .pN szAZIl3[XbɗC|!jjF&!k3\A޾ゅ ʻkЁb0k|,p%4wȬ;;i|trɳ+L7˂C@" PL36{: RꩩJ+pO`@\7Pϻ փy]3\1CfI}k1-oϽ-".އQd|h[i :(6 :.Y r 1}V AD 'b:DG {}dl#?B}6YEJ' j}yv =֨{%.O37})ڋr: QTg>DvB`0黚g8 KK*XC. } Tw7pB䕪  [Vܔ>$ ( +Y t}HI;X"G /!2AD"ےUBa)sɺVli*͙ |LJI[2D@+5]iϺAr RYWV΂vmf ! 0pkO|6"%8=N$0Qtl^khG7ʇh i"> 2tiRK'CBcBg[{ʖQA2gh$dJ&R.4T@b%L` @Ge-T@DSԮ:ԇ+h6lNrq_$U',dtՅ"[d0Q>Ίc0@hsuy5ȔjHPT`g FQͭnwjw ѫ!blKr[)7Y f.o)Y6%35 a+&WX]k|52rbųljZ]I!8p,d%7fvYf x)HIv̫}ֱk˜FBg$Z  ԃV|edlLkN"WK^z2uj*m-5@rwJBrG(>4_U/ QWϫnU5VdzEo{jv ud B 8@׳\8ڑBT~ʆ^ ݇h{#[>jw@#QwO u Y4qw3Bּl `mf" <+圫aI.>YXsFXb VRHw~N87~ ~}`@zʀݎ* NOxc>|]8 @a]>Lb @( ވbp!D`aaHʉu|gM}]m佦eCvMV2-/\D鶦uk$;s}ۉ8nqjI<K9s *(:R񸛠0_D﮺ ؄ x#0'BU\cY8og9YQ8+spN"&0.e/vh1V~Sc1(qQ)vk&%g4nB.~GAv 58%w:ZnC@xaPPeg-+wy!TQm(+Z&P0Yu9kb ksp&2-p"l/C{/H⋡*Z<eP_%/Lf{5 6wC(Weu'vD>X'Z|Ft5c8P*we,7-]m8WRpQ P6]4$tDtIwz8k^}`*XUp*ccLYﱏ7uT]'r~C;PDHxgZCScw3#P5@F} (%DUQrBlHco$&H5fvatRɈCbs,"H9 7 pTqٙF.V-& :tFCuK;C8@n Ht;uRt;Fdu ڙu[nsu; ")t*҆jɆde`QkR'RQrDCg[dcI8fTW,P#g":/bZ(6"Z4c.Z5S^š ៶0"mbo{S)sb{64VrC WK։BHѤQ1AJZqdȥn5fu|uZ'8>q>^GjtxJ:u:nwyJ]|w>u7wy2w*{xS*t;cvcvl?ɧvz!w$qQGxB7ȧ|80Aʖkuu7 | `57W~~$?Ϸ;Cd퇡~4 T|[3pWq'B}ۗu~ڦ{}uoT/Xu@ 8зDЗ{$rC6H׷K}#5OTX1+V} 0vdU8 8>(WXQ8;\Km0X™VcVU;VI''ؗxne:vnWyVHtIHdsٲU4 ZxʉzȉBZIKZ$75Q⎨*{"Zqs9s҈˵4x;k䖱 $E x;^[ iDS3w;Cl;~+귀#᳗6ZȸIWE^`^Ħ,սto]ۏ9˃o`hsJ~j[[ٖpyJ[)xF}\F+}hq@boDxPHdث+&Fi%%X8v57Oڛ7Ln0:xP#w- Oم;njP|5P𝿳ǹ\wH͉NP!.Y!u+ ( PU Yi.l_z4C*2:`:@Z na7vu>[{xC~czwVǬqGAJp}}Cn ΍@ aUP) Az5z:Z8J~t:}@S.J} wctF$05e$Bg Ǯ#DJïJ;=b(yA3u)'k6 1Z'_H #ˏ8)ۄ1{1p?J܇ˑLرĄ:U,,-x@K=/.Fk!#l- Yeuq* 'Us'# 0Cń"뻤n;` 8:b~}yH]Y8X؊;yb[E&qE뵎tuiu^+6"r#-5X@d@2ZpxҤ`ŻxY DdI<%N[ݼ : `NW6*R, Z }53>c>66/źJc'y5z0J~5~n7:_O\꧿@C; nf޻ ΖbL;LqlF܄ge{Kuee%#Ic $nxbi@}1n@;ܐ=/h캲5a4I \/LOzkM>"6 Z:™y;Wzw]\ŃAiDyqF<ӹ'FiYmIF ֜JLN35̽=g:.-\UI, 1)ޏc@n/sFÄtnwĺOmh2h*1Rwy!"#ϐ?_Ρtɵ_*R*24ac(HMYzbk@moeU&;L(MV3t_~.^x/\  #p<)b#8%`VZ_V'Vz<9+&ONU%]:.D$`S}}|} }#| #}} |  îˮҽ†!}!܇!  } *ꓡ  X! r`d`@]XYca]<: tHXi0 T` 5)k8Ih/Ȍ*hPX ,.(>*BCJT@Y#`dlZR QCHiNX 'ܜ*wѾ鄕a-cV#Y[5fnu&P 0@ fаO(XV4@C a⓲ X2lYknH-'N6  ",49ƒ7* h_S`<00,ނ J!Y!(LzC1z5iԍxe;T#8$dTR!1 + Htž.HDPp .q pLP=B=u$&BtTJ@T%PO EڐqQgtxaM"d 䁆RK2WlRZl+i+NEKaWHua0L!0,bξty'^k2>]*Ȓiojl ćTc4LH2NOvqw=dwڇ(֫a&& [@-D|GL7PG}xB2MdNJeV(hNvhUPbMU;-uKBH/#eAdp; o!U4}\V0`?%i!#Te/GQTB3 ׮3DL/lYmLi=:> qdԹTX2>H61?ݭ]K_ mH0ϿߑFׇl BWIn[+@I"$ 3hlG6 w[  V/ N)(@Z]S{"BSJRV ^I%?M; ,r|eH& -jX4b+KIA>Hw>X3eiʠ"F:򑌴@ň-?# ЅPlC HmBiX&x!(:J`]nJL:R@â1 8$!"2׸~iьY is7̃ A(QE&2$b;ҢЀDãeٲJYFid Ы1'M/A[ZdOkGE:lT']R:QBOT+|V -0q{4rĝ 5%=V]  .8MWSќ^dQ)N)ܪWi8P-@Ug4f٣4QsJr&"0&!}X$ֱ(Y=la³ ˠqVYiI3Tf+ERjbXƚ0kiO.>ʠ@\bI" g%HYd?Z-k۝񐏼䉲xV4~菟7[l%3қ'=W]gOspmJ=O|%6]'5)Z7_׵"~OS{?gO?և?_?O]]8khXx }Ѐ 8hȁH(!H#h%')k8,9g0iOT'SDf xEGJMO(QhUȄD~BPȅTVXu[HSfxxcke}D`sxuw(yH{h}HȈ舅(HX'ȉ艕'Hh' pxHhH_VkpȆo(K؋n]uH_hØΘ(јw~xȍ8Px瘊(H(HhhЏI/KƃymK y )zI) r~i'i!I } %+3/ɒ,:ɓ9>K@ =D9TxF)HJLɏNٔPIT9U_yVXZ)\d9fYhy]ɕNw/siuiwyiyW|9WI٘9)Ywٙz9Ǚywٚ0y l6p|pқ #ҩ a0` ȩ`㉝Y |`9Y t;ٹ ;p"`l; |*pʛ`י ٛ ,00j|`p7!}4j N*6ǣ:ʟi pФU* .ɟ}O:is9Ti] hzhzc Jښ6y pH   Wj@9JBG*" *ppzqp*I ;ZتBǧ&y`!_!l ppʪ Pjqת {۰ k>Ф;wB;Kѝ00K` ʲ@y W ڱ J? M,k`1`۹ y z + >˰* $K [+imD{F,PipajJ @ljP}p˝Gzi! qpk   <; *׹и"p k.pΉ@ Фp&;;; )| ɠJUI)׉k曷݉! {i zˣ&p+ʛ { lʙV poZ1 _|Н( }0 @k}@p`纬) |QKK|)0i+J1Zn,NJ i +vE0rZ@։4li zН4_+WH`I!kN,9 &{,fZʺe[P!$j ņ@ې}zЛ'} <·0`V ڥJzKW__,3k˽?vNj!K?k _YɳG+ P+_tI C<-l ʒ;ɇ@> mj'JꙜZUZ 1ܳO,<iඩl \+{@`JV*ʲЇ `ݪ&B }d=:ibM "0l}j⻴?-Οi*3逸y*ټ(m㪹Gb Р|?|=P"[[Aڝ|4iB>_<2⳷:aJ!P1Q= ߐٌ塠^ k B-(˺Ӱ/+N+^Q^־{>⎚>~ꎙ>>g~NT  ? /O72486/#$߂").ς/,o%4?65 'ʈXEOHόBI5mFIJ_TR_ˈVX8b_cegdfhjrstv?^][Do'M?Z?/?`ߍ?_~_B2y4 6 -9O0T_8?_o^9iIpmɖoqyؿ/C8z~,t~}   ΗӒŮ! ="@0 4!nܩ D0`.B G ,ѣ  s(e">&%Is@G O #|T 4τ}1ݨR =%iͥAZD0KA, U좮݋ArBV-D˅;鹶؊2b>vP@$O\a]UI{ 0lb,(5i  m@Z}@T@5=}>{Q t~;>:U3Uf fYQXЎ&qT!}㩴!X^q$riK!̤aW|d `[Ddtx Kԉ┠W!9P&1 A^h+$:!`u&q.ItS!c(AT3W饧* fuM;vڥG꟦>VirDK:}ӗ 5Ten{+Jm'oUXE :@mfM1:oiY)bKf dg}H黃ς%#lS9`jB%&1V6dbLj64g)<ځwB@arE[j-VԦ8Or)eܢ.hk ҵ,4\޼hL6B SrpJKd=!\;O48" Oyw0~f5 )\&c\t6XL)W\H M?֮5Tע!|a0k };aZ"DXwϗ@ϛdLF1 !1\H"AQ F.AF\t$C +00 gH8̡w̄Apă<킇HL0!!Kt0!)ҞLB)C2hL6pca =&؋8 F'"B`)c7(;[ܥE8?B @xG@$XY6h6Ý,J!՞LxPݱ 1ֵZKc%̴AT^@}Ff5#`= -mzG' tD^Ely4ǞB͝*(X9Lj-/,jd} L]JlQ/ `P`_/%}8=M3y/h# t39!'G *3!P<$}A xX$1$RN o Nt)5@^W u  hzAi~|UV VHPm h> D "><=$h>^$zWs}0u}CW5t~Wt  `Ju~z{ @Eҡ=l1]?t_a`ZsNWf!]h6nt~V~9EF!Vn8jaa( h v8?|6   rEF@F/|,ш~>؇ЈFzH[`Wwzd{v7 _#vG^u7usr ja؈ [g0u{88tא!؎2e(n6F0ph8x0,ь#T5X H.gnv=7kbG 8A&~XV Ps ЈF P` hd!6C[]pm0 Y' 07uj Rt`,s]h 4W'xsEtK Ą wohm{8kh1( Xx`iAvƙ ɃXJ78Y &Y}90y `6# taט y9YJ4MQCuv U E u+ G0ĞcI# dM8dYv!̡%%u@PY3jhKgDЃŃj*nH#ٞ4P)zImi 99PXA2GFS;hR@SZSX;w!X>#QS:8 2Z,zvTv` 4y 7 ٣:HOS"Y0rc&ʂT%P@e_2+[B5P ̢\Wf%Ń%ZgJ 1YF9 Б. @ wa+w=ᝥ, S+G8*$/%MIꭅ5e FzW꺮ڮ4`s @pPf;Я2;[{{Gi 蚱G> j ZÚyg= aO=eJ7),XZ'k*ZF{b9yu6jP6hhcji i[n,W|rjxpNyhU&km.{[{튜۸ [_GX{*~j1&ͣM "XቝJ"75<%"DMʹ@+٫{Rdb[VKm)vtA^]IItM@>kLȶ}PS۔'j `u+hxy_wkiwi7\WEwtn1[4vHk#Vq 47x OʧC >eX1>!8Rs՚S(c11u^j_0LچMԛ몸) fht莱rxh>Rn^ [G[©}ŁV%(y؏#LQZ+AǏRcVī)9C3ա9se.@D)_=)hG9 קN1vL}9C{ L4za8aX H[˱|8/?&sLu?hbw 8kG(dnJ 슢  :΋`7'<  (q",q7D: JT 𮼺̃,(# MRm7H[zhu0Ƹa0 Z,ݪҦڠ\Ψ`ٵ0֢ \ q5XO5q l @}˅t}0Gck=yK=y/ httݕLxc%dj lu613M jӑYdDdێ@}0ַ1uRc(~7PzBUM m~iz5.;,TBПᡀ*N.Ӗ{ ' 5;a_NxƳ5t<>rC^l4cF4& VNAZ~Wm=m``躦(i*".Zy%.!Ը?c U`rRs ݒd{gGoN.`ٍ8g\1[x7x ~/) = x)^گ/췹 r s,׈E_%gW>- D : _Oi&P.hƍ E8hw۵_njfhևL6nچ㦆jEf{dp;p~(F00xx8 BIJU[KwWITI#^d;kz  w?zc&}Nuk_gJt߇Ax^yGJtCwxIvfGuuivg|-i_n!@;ˡXiB )X =C wA@aIh@NOnBu@Jn,f\ OWJAaտk#Ъk{}|%+4|&"}|"}"&& † ȿŖɿ|LJ}̅|ل| ӄ#טiSZ(dPZoj@f3"z`!}flaJS8ҕAB \F Jt2hEMJctQF! L Cs}F լKhA,`pU>y0?h! NsV,'G.2<˘3k̹Ϡ;idk' |` pg6  X0EDG(bLr-Ԑbd恂ƅ+ ^)%Aɩ5.]*Q`C<_GH` T3bP-VUcI]R!$h(,biP N- @Rl6 `L ( P~(0tLTue]_Qf:p`5ene[ `Ug€DsQ<0 \ǟfh&A@%}<<<(YaIYE*ꨤjꩨшc)4'$KO`k;H2\P"Ngq6ۇIq LA`Sa#*kH4Ȓ`I.dۼ-,,R[ i @Dt ,.JĪHʵ!07&\3wADAsQ'%CgL7PG-TW=u BBR9'݅0C - pkuFt```n9ބ/v'NH_|Wv؊'%is-ڕ~x砳Dg4MXWkYh.n;f{@+屻K; L#7Go=Ujpt_i%[Q|Ql_޷/il\8g^\c_8c{s8'H*2_%biܝ(L WBE!0,a)0Yzc r@ H"H<*D#,P:xj`(/( (` H2hL4V 5S18n\Lx d*Z{$SF:s38DQ NzȠH8f$X9?.Me% Z̥.w^,T*x@{"P sc98M$H 3krcE ˂KB*",ƒNw0P`YBx`jO(pd #K20 dJ<P.@i2kPi60”K1ъRq eoq:@Җ5XP UDUl1'A@j A= k5i$_e*-uȤ133Y,Y5"KAE`1e9(v XW²Xf`q5yx1,,.jHv-XQ[r96, UNV֚w lzCq=rv _ +_ 9F@%/hO1R{ gzF͐g,``?>}?Ɖ~/g'm+o|_\GG~ﱿ+0-4805_P|_@)dBw= sR@P|#4"Xh='p.`0L'H ?#8QàQ 41bPFx"c&{ւă>h@(T8 #F#Zƃ P1oUXCr$Ƅ1焀$?(fV @vRBAK8pr)6;gxjzXS X,rXbnq?~!0SI!FQkIqJpg^X@|8(EDZquIӲOBdE)tNɖMMFNMrLBRqbULAȐQ}t8"ETdRqrSW5ZqVES1ES5R.5RT9ETEStqQ%T汍JI!@e $&G+JwB}Yo 9-}`X(g We 5 YQXb UHtƆ|$EA`R -vҐSj@5 !#d 5w ph\hք\fu)[dsD@TX4Q `5e0['5 !pz2TRe'oX9 VLݠ_MaWp; &9E `kfvЁ=8N|ve1PUt|Fuw۰aZ` swU bySf&fW$dYBA5P1 Z`1i\9\$ dp@8crC0d&nes Xcؐ 2p7u TgҢƖž?`i4'fУEi9]1iv&hǠqB ]R"t w[ - p_)Й4:xΖ6MFMQv@}blɂXaСu!vQ[ Oqd #MfLvoZ9Xn1 xHRzI:i]1`aW&FQSSS#U q*7ꆠDZxfVAb.(#| X )Id6&ٱnv!/ (٪~ Z:Fp]C{ @$oP .;Uj1Ͱ3:Y GIGBBL۴NPR;T[V{XZS6:8;K{hʠfCD>~npr;!x_ :a[Jce+;;Lieظғt;Kv#*($d{33=,p`ۺ;[{+zk P:E뷠 A3K3u;[z۹}4PԼ{л2F_8o.!B(`@[2Fb$(P| ׄ{8Fjk 1 o(l*#) jf˳K8GVNFь/M|/g M4O0$N,*JLjZf@"7o(`T۹bsRXa"e U- 8UMa K™d½|.TpQ3ӤWq6F] ɎY0rnu)1&wLjē \ʦ|ʨʪʬʧٗ &ɐ` }/|p "pJ$"˱"Ţ}@$ Lȇ[۵_h&fSEC5j +ʠȱDȐ3QP12}`#2$ `QޥI Ppl W`W"IɠlI J< e*:a$7!*3 \,|pmc;i iXˉtj\ bqB-'z,K!04mSDCS-Ϻ3m#CЬ%&hSMj1.eW'PUuSfĵ3:]ձ Q̃hҴqٲb3 3. mS1mV &A؅-M5!W\K+p7\$V3 !-"p )#:q |K M.|ֽ.p JK;鼰5`+ }`(ż#P_ ߶#7 XĀ>,OHaI2auو }@"߀n^HW;~/`$B 3ϧC >V.HM@nل/*=,Үϙm߸<Kb@5+:2̝֮ܘ^$0N3<+7b$n~7 ``'ϵ81RmI.fFeE<_f>Z*ކ>wk}qPR?EN W<҄>VBfRmfϞhEjOnmoEŮ gxx{_}|w??sO_Oo}`?} O_Oo/Oxo/Oׯߏx>/pO? -}}Y$ eƎŌȊτцփ}TB'&)p@ NZPX0[|=5QV}l(RR⭍1R4eH,ST)9||8w9gΠTtӦPzhUT2ZAB] }Xej"m۾\sڥnܼ=Q ;_r&XrdʃWnk㢆t:u՝ZV-lӴgM6%ٱs߭6o|M|vFpϝGn9aLbAW<#qBV˟O ?(h& (4(Vh^v (#>@$,Nabq4h8"`㧀yȱ\ko D,A@یl N|H-Ǻ_{GbZ0o6 tJN0{um^ИR о<@ tɾ2@s3P-*B==-8\lp~:P =i4K }*;7Nb !Xآ$2sY-4ZŔBT2.DEs[P>NgsLW H:pMT#,R[pM :DNX5*`F:P$(fIl,kIҖ,[n`NtJhl F F qf iܲ:0M&8" @Ne γ0 {5@-O|g<'-fY b;'ا:`Y{E3e<1:OЗS%m 2q㦀=.d ]>Mtf7۽nw^W5/t\׹l$.n}$d7-gc89Nr|!'MƱ\d)4ruI )1۞hQC,>T= 9̵+ԊU zIS{jp>1UCd^|hNoܭ_W|8Cbϕle"kY[2,Rթ2c*x԰n52?3C:@d&bEY-=S@8u \`vgGn,GIXjgjv4|=Y N%Lh|a v\<]y/>Bpj*zոn2MMqVǸ\k,3Y3rr!EAB1p!<bN|tB ws1fU~S1d!3F_}Sd6|!Ig3]g@Y!ћWG5?W|I/jOYD%AbĀ}H9 )wyb0܏fdȿ9IT}Nzh+|[iovwK~' -w0ǧ`JHAz0C|O?0WWN Q0|j'1(/_$h|p|qr'|R&r1sf2)ǀ0ws1-w):+7rJw:=`|@HI(_Idcbw{ ww=9{>}}|<}s=xs$PrK8}@Ȁ\2rG؅$g~0y:x8*Br`P_z3`i0Y9z<|90s;' C/N3M#ru/x?3 ;SO')&u?4"h{8jFwPƍ(W<@A%(D@9%JHA@rA: sC?`WلzgXdWec)i_b*UVؓDn_b@s2:J#O3S0}2" 4/rG–"' G%+>s1"3{?郕jxvt'r GxXmeff*=&yGB/s6 #-I7d;YbUhi42p#^^^^]e]9)p q \y\̉)@ybɂI&baIb0< W92.@#6??@-B2'ڂ=-f-ɩ!H*dNpe"hd*u4_M'r;14j3_3<ţ@s,%,`o<*ԟ/ʛ"١ c&N(coFIn o2ayTvFAD$:v/'?o"6P/|3+3H߃6r:Q.Pgs_WJK:)B50X3*,SJnBG$3*&$qөĩ%$qRʡ&ڪ Sz2$* j$] ʺڬ:ZzؚںJʪ$ʭZz蚮z >2:Zzڮ#c;[{ k^ ;[{!;P1){+-k&顲3˲57 t1'9+/ ?;!ۯF;!#reqO!^AbafP1][_{a=K`ik d2?p Kk No˶y{+m}zxkc{f"˷ skr[Rrg0 / .๡+[{k[  };  k{ `KkEM&Eq K ; 7{; %2[#;Ѿ5o5T#G"ȴZ8s<"fHQj4 P\K>=4!p'@ 6{!UҨ\Nd;,!'м L%;  \?%I)8Xt8ɲ,,#-Ĩג-.`-9iM\T,2U,'y,P&PI R/ 1//40 Q0 cB @D1hu1cCAQdr(u\0# +x*M\2-/3s3-3?*Bs4E)4`%@{'|!LhEu{r\׭_׀e6 7̓m*^)z#|G]!)C&2@ڰ۲=۴@-97K/fyhP:Lz72RIFH-!(ecb#ѭ ,5̂#<=<}So3=#<uFȲ=c3.e4',d1~$حݫ}ҵb?39p6t @*l@j@CA*qvA&=G<|>lWWR=݌.-u+p,a23jX#l=DR>VĒIU!.#|+U2̈\HHpDCAC2-Ic**aS-V0Ŭ*"W#|e lK\ʷm9LWaGWpɾ|)$NAjɼB颃4<#~#cFM""FFNjvx,9~)27ݓMc0oY >c~\+xY!)42$;3R"* o3' ̽y.P|PPpGٽ#~;.ٞYIR,,Ken(dJJKK$ŵ0s])t0ЄFK4TLMd"6f)š'I//)etE#X*A>f>^o.ZNqNNO1t<d3 EPP RO"_΃QVSY 5b'R+O3[0%S_O8N"MO*Tt(RC{$'R/'>_|NS^E||@ w)AD0xcVIUp0U_EUQuUPaxUx|} }}&!!& "!}|}}•$ $ʕ!ה}Թ!Є|#|}| 6P(PBv0‡#JHE hejO\U"%& t4tT H$Ҁ8%rYȓ)M4P>8mp`A ⴫'.%$Vb"k䮋C˶ۉ½|S ύ!o+^̸&l$ FuvDjK(ki3^&vai3խC#ٳCBXXU!&^Ǿ:4lH Yg<SLO> i@p_N ! LAD*|Am}`Ȃ`,""5r! j5&^ oO 7"RlTGW%K ~PWr%1Mc%4|PGxgx[vD pl2'8H_Yyv _a ԉ^ޤ'/b|'lR!j ZbbTUHyڈ^ ^XobdE.2U0 NPK Td, Ͷ{ff58JBRըn<n.2o-Jo 9P01A"/S+E I°K @xe,!!T\589\+!PYApfr@ϩ\G6CBνDi.Qߚnk>mՋ+Q`HD.&舖d}hu,X&ڍ GK`}حe)zJ"hYJ΋Z>7|4/N[]&b[5֨^M*~db[~vL[ Drm1=2_%I39{;l|.)xGp: b(HP&&7+gR#Cӆ1@|#c"`ld- @E0$|pJZAInkM+'D|w* VEtp/ WqOD-" ,bAu rВ>,WnIBAЄ0N~04 GXJ,KẒ>1 HBB.to|C04-8O u ^C" "&BXb.rU"@,$h@' '>;dA0a!M݉P{*B=>`fDSh&|yQd! d.ЈƒH_Zw12 qFփ(Sh #~8x+pfI,ēA$ݳKz={ r\.?0aiι1@zmu !!.=Mcuw`܊A17;!`S${ۇ,3 X}  /z`wZYyz֣؞/}.{9}2.~ΡI/|Ny_3sup au>OME'9ٿ-?ÊJoI͂;;C (8Ȁ} ؀x8X#؁C vȂ*0X .C3x579=؂+2(/H6a1~G}wlF7g x 7i"~Qx\^ȅX؅dcX`xb jX5n(s؆uH[{c(.Y &g8\P؈(ph u!8XxhGgShyȇaw(H}hvXhtqlhxȋh芚(HȌ(HݨЉl1']I 긎؎ b1֊ЏjHyِ I9Yy 9) Y 0Vx)9+-/8* 4I68y;)=ɓ<~ t)`LٔNPR)pt7sHSb9SYvJZ?\k9 `Ir9ai↖Go:m{9 Q9Yy1:ԗfYˠI.6e}@mrx/I n)Mr5s!-ס wcjjDIҕsaf7*UHIPйa0 HF#`ޣ%#u㣛>&I)_xY 3;|"QMb?`>J> HjAҖM4 `"0 'H1e!ˉ:٠nj i~I}pY(WI(G7&V ʢnEȅ0YX_(HKJ-MzOʤSʅחWYj]_ʥ T )(jj} աJ"ƚ=ZH"u{@F&Q U*J:ZPTbZ d*`:Jzʩzf.8)$]s=QBd泪utDmקlaڨګ Yj}کgƘ;Y|R=;gZ 3-`8M?#&f"P:ӓ37C::g9@e>y MdVA >!+$*ce <ǯ@dVe1(#! a"@De"ΠRE 65nbV{]I!Fa!=I;" 7⦲,1{ s0N{PG0@/A(ѱWB/#9s2?+AZR D.&EB P6QƭB(d 5\06)'&1#NxcBr(ښ9kyk&E2N 1a"!@P3d;.3 M| +X$ eS ^ j^󵴫f{F @ dC];>߫ ࡹ0{bt&1g) ɠPk@ k[ {[0}, lPl! \'\ &,*,1‡{񦕃EL68dMhV9#+zKdmŴ Q<}@R X|[l].z(oɴRSWWq(B"2>A\j \`JC4:0` `!b JA/ +E:Ţ+ 3a< 5#331 :"#d'z40Gռ׀VCq #E+L1#7[\`811Mן;'"cm 2;q# `P !Yr^Çٕ0Ҷc, Ao5cC46`eMF5!]B#L &`g6`H6|zi$"t4֪Q;urU$=Y % jM."Pu@)1 g Ks K4kJ1FiVM `6. =1ѹae#尲7Z:;i8GN ճ2)գQJu漰#r  ۇ4ޤrv∼`gVMq+#`<b"  [T~ .zEt"d(.b 9Y!Z /: D= 0S4ллhM 'g+o+n{V c(|g{k!먍Ӯ=!=3!$ۭ,T=`E,9YH}J9V/%$ TuC4'X @fc Z5h]X\`^6>1\)QR'K> K5C TJ=v BKE|G;^J B!W#{= +%1פ M$Ը@ؓ%(G Ҥg䩞!F u%N; 0m@v#$ U +n|?=DM3%t♋9:=}}"|  $ $ ||!}|&}}}Юۛⓝ㡣ꌩ/ ,b`ODc"|.ŋalDꨭ"ɓje f|X1B>Ap 'MiK"9@AG^HA Ju(8$lٷp7:I*ݻ܅)&"@Xk|`Ɖ.+y_ez/kv5CC Zg" >[H*77gԁ.,~1Ȁ Ȅcr@(! 2 `2Bz F! Ņx@QB$VG`x ":XZ$\\ C^P )DJdb 0O}HE1 @ t1"HDOP H6(#|eD8C<TBy!:d"@URhfyWXpbb&1,Gfkhbؐ [rL/ڔC&1&iRMHc (6A I#a- ,=& P`q=><,TR}?)@XeP GJre#,5BI/%N HI() b'((e>3( Ic!ɓXQET('AEZUtbD!diyV@TW|hE #hD* x|`#^&ҷhFt=T 2,u,l~8αw}<# yx^6 +Ɍ|uo-o Jټ+ T -+^8kpa_\&͓ B%4q Рh'18I"B.%(F4UV"#>x  ^C` (_Cb9ՀLC0"%V>|t %?,h9j"5B:ũ ip# z[Z3}Q¬#K `"uiS!_vĕ1Ŧװ0xTxZ @زlU\;zLK˞2B+&"qg>( N޷ I&~≠\0! "[78YvD;hPGx1`aGԽd@ʽW@!+x+c," uhGeEn:B$H,W* |U-9j @ 2@jg'%`F*N|PKp^`vAT `c3}HC~F4h&I֟DYўJ:@QS|Y-'zd 94iO F1w#0#@OQER.oDrc8QdAꣁVo|BQ0%r%AHR>0IW._AhXPp0. |y#VΗ@NQ$rX}_@&WX.TPk{#o eQbDlj|g}J6GX}MhA jH| B5 pI0}8XxPuxcvzG-x^x`H7b#piU UOeWF5kT^UzepTNppr.CxWi(~5 pPPEi tFr9Yy|eŋ#6Ei!yّU Y7ͨI`pOև%2gPXy(z||U2;4[60r*J Wv(ˈ.L۴NPRۑyuPN&jiW%5euH,Pמ9 s|۷~{)?%O9iP jF\'{ A d f teGm3P>vxˀki(;[{țʻKdgJ|QpH(PP!u+ӹV )pOYlP _ ګI #Y eųq7#dr8AX@d*\ ^i khEz%%PEPT'kpEP#5raz ܻ ,T\V| L߰zJhUHifaY, .,!mfʎ uΈ#y~ _l8Ə{ɚɜɞɠʢ<ʤ\ʣ`Q*J"'PINt5llK |0vªg{e' *qč^LńR0~pw7*`佱l¥§yƍ ³\˒%0Kpy2: dGǖzΜ$l͇"eˤf+ڊqp\QrQq lkcy%oؽ-O{q gWIl^ w%FgPݝLl_̥~KR= ·p}՝кj $nƞJn%Rpi ŞC uvpFӮ GDuFqdQ:W _#P0Xn%i ohHJ6=]<^^06lٛK vJ yDaQ_ʛH|jYNet&ȮfA ,uهH,taOJr}!)P;Xƛl` q3Hs QKE 3VOOQ"vhBZeؔo>n JL$  N> ~Kz\,j`  d}b+X Z;5Β2߭|ii`4}ƒ|!P~,kBQS.UXɭ -,q9Px^+:jdjpYcnDq^HHoIrwNW{tNPr:{Q$p\3p {0,R +`G~.>x nxJL\OkF,,`nk짰Ti(Am J`6ȯFߐP ȸyn|F] 78>n'}R[ @j4jP~^Jm;ϻ1k!{ڞ-| mPd$G,BD <5by`pOMsϭ@PHknDeفȃu_VNx#z_$Փ ] lncwx^k eektO|z0( r!3 1 |M>_دFA`%q8'"˖ZѫӧPJJիXfE a (pP .RK)K#,,IѻxK LC>|G&]˘3kiDH@ËҔ!Q"˞M6:]!'>&.tǁc":Xl'KNrg2TĹ~@Ɖ._%M@$ u`p!\{h`^]0Zxmv!&7YNe⋠$c2ٕɅn8<"0ar&H× L PF)TViXfPh#&8jdc.S4&~Y`4Y<*蠄j衈&*&}tF*%gj Q); 7n4b;M#|B-?c5& 0g63y!}1~=#~]rwbqw A}߀L@b6Yb)(|fC2K\#.ZB >G'`#-Ѓ0B8K5[o d";+,IscKku{-N}VhfSzZ;6v,.lOا%*N!=L8㬢[kkSFW"0a"3amofKzOĭ(E:W/}._Ȅ٠0.֛02/Bv7Ƒ GLf/k3afp7Ȃ8αw@LdsWɛN ')UgYledR>FAF.ʚXd3/QN+N;{2+ eKs|D$tzpD]}ǀM%"^.i&:RhHC|n4@I4А]"s]!~)tZ-*m"D5jJ:ӗ(@戭jF{_v]'m,/)4i'WږGۍ D&"nSp".nx2B66x "lM\w{ms(mE`m_(utmi> nw뮬GXK %r#,,38-n7d\^<腎@+:c}{݇ t9Dv>nt`  {2D[ކh~RV{u,^bkOAX7ߨ8‡HєF5[__x?vM4Ӯ缢,:A_塏tq{b|5> ާrvj V cFjrf{jo5mVePPmM jRluh mps'x w^ ,,.H$u-_B8DXF_ mj5jY< 1(]3pbXZ^QlN5PJ-Vdhiw9+![r8tXvxxh%!li mm+o8yxqqRhprܣ]5Wxi8h Ҁ*h x3vWYHqP~ x(6x|~1P>wȉp88mjXmj:' Ћ^ve|&kЀq f `|qM֋G~o 5둋vu | tpVW #@> 'byiU# u q @ 8Ik+€tHPw 5SX A~dx`5'2 w1#R.ʗ@ qa ) 1$C27t"Pcm1!Qoq1ޑoĖv_x *8 * IP@Gm0\{P_7"Ț R9W@t"WhykхŚyaхz1eXx  pO%Xa)N Nٙgovhy]q E9}oڧʧ K 2nq\Ye֙ Ѕde \靫q"+m6!i(I)ã%LV63 x|ل Dj BZC_R7Rwᢸ W 9!}Syv r"A0dٝ/yyp ,vh()SUj:k)*QgS ] _PPف+euԸ%\\ `vC9k0  p@yЪx tꭂv!kJK*O*3jًCفh: [ꪁ) Fi Y|:H;3@j *ښ)rw饥@#k,)vivZ0 1mD)  r8:!|:Yj`5tV$xtq4tVz R`vfgnK)PY87vbK) ( ɚ  +Z)yIr:vNj}  i ɸ@ظP[!뇌 nK"@W){G]7$(Vm@߈qkXG[})9z+˯5W}JqoqB[% Ou ޸ ({[ZKǑtgiqdw!1:| qьjR: 'IF 'I"&)>Ay"<0^) O~eY喸[` jΘ1g)J>ZvInXɊ ڬ` ݺX ̺:ˮg *Z+ }ʢ_( pj܂&SM1M١Y]m 'yZܸfoahh SMۄH>$kIqm|CH~:mxgv }/n]K[L n:u˶jt {=KG˾u(Hλlǫq^떁{id5,11ʰV𼸑&IF%̩gy|'p;{W}7.ܓ1<Iy4P2>f=K\Adpo޵#>çA`4g0 *˜Viɳϟ@Zh! -*Ty P}2H|A@6\rJu>Zºoe lPB\Vȳ\W)ژ`lmpuJudH0v P <* 'PxHI) -$;z[X!)?G %013-!h!#v)2Oy@! óNU-$|C@6 l6bWḜl#-wr'+3ս9w-% vc PވeVU и 9.-.@ C[˫c Hԍu|[m`M׶8,m䷸zG5Du̵(?|ɬ2x?_yچdG\KYN1P1V0pca[lԍ}Zw#-hus3UbD%E{]=>`D Y*V<q <)_8V~d@Rβ+fsH6pL:۹>bMBЈNF7:U.sO:`J ȴ7N{ӠGMRԨNu`%}Y&gMZָεw^mHK(𚲗f;ЎMj[ar^dcMp1.vbz66#l0IܠwN_[ ;\I%o~ ϸ7{ GNwkWN5j3nA,/g1xgw|%?{^ <~_=!ZػǽA۾s?>5Ά q嗸w~ϣ;}_ү_}gZx}?!䟍?G7gɡKu7P(gx(H!#%Ȃ'H)+(-H/H357ȁ9;}~G|I}K}駄OȄQ凄S~VXPRx} 8cXg`Xbd h(j>>:::UUU<<0{ܧ*SZ,.Rv=O*띙}vxz~|EB; UWUFp_tdicf[ouùŽjˢӤԇءզ׍ݨILOrͼ|m aI;E<5qUT]GwCb8bI$5g]{`B͛8sɳ@ JѣH*]ʴi=NJJիXj4]ÊKٳhӦH`EڷpʝK.UW˷߿wILv"^̸MCLe(J` ӨSܣ+tH @G:ͻi7+@ A 4;T-ڷ< :D;wРmv x=u"4] 5^M=Qp9<~d7z<`[v65 M%EgaDn:n(=-NYǩ=RpL6dkle4{lGb~ xdn4 A P7d\un\X柀vfjM&裐YB -i~>F*tM:ps^Q`MGꬴZs;X~;ĉ9 |h'ƜsDwVkPD8Au^6@lWNp.Q`w>5MJB0q"`0H:@<@J"mfqI+Đ૵qB`WgJ~+-4k#XZ'Bo9 tg@ZF'=HC`K)&lZ){ @\"P@jF}S0sKgیhGWnO_OLy_or.:j*b*q |ܤfE@J{]"+⏢YMMᙝNx#V̈%IhFB+_ʭ3:qԥl@'FX|D9u`0n8*ӏ0.>ijS&6C ޒ4lE~Pw0GYBJ~0R7dk Q0XD]c(` ,7+W'!X(>;h^pfilSt%; @tIv=`D3# c¨y@)B־l* !W_3~F+:Αǥ~u:bRŎm+Vl iN'Y i-:(u(V-iN@"I;nB*qRLi;xvPt.sI V|\ %Qne>I"~)4JNBx_mDPo eHIOYg(pF!1[|O)$nfK8)`靥f$R:oa#YZR @2ഝ)FbDEj4A(KNckWv`ֶVKFEaBPJ".)Cϲ>jJGShR9)r&+ 7y>|<@";0&Y I@\Ѫ բ8j!iG eځ>EXJ4bsbH=Iy5@{}JgvL@n :LiRW sB4uX HHԹL.O89 !me sn*4F\  8EqRl›5#@Ф@b'7G8Nlٟ,NF* 3?DPOZx(ޑNJI&]!nէ]Ц?f)V@`ҍ痄ɘdSENXVY^MfSWIJWadZj9l&4Jw:Q r399:ծiE-Vj}3" 7 cӡM(+V9+ݕakϜLӔ&(_'NMѝgPِ^nzHO̷T`iR k̚WR6|F,r@BfiP47bȱl&-@pԫ]!!Yh~[4y "w)@?9ϙן򤀵~=,!G $˪dJM׋e1eh~#~+9 y>UWJ#gZ,t!7G&mqbeCN! ggRr &Kyiv#}'PAY &-G)vj/O4ا ]-T]́Rsgg3}'&qrugFC]z d]\:ۿ||DY_NLw(ESR0 ^usNmJJT]UXg9kL3Jn$'}';&8t$ n!?,zc57|oVU]<cDс#@ǡ}qe<I7MN%`S.!Mʤrr@+6H:فZ$U4MT<1)n'ڔmFtSpuurSQG1S'g extq&A9YyّdX04fcTs6CƢc;#b&&d|gF, mPtɒYD"s4y'`JLٔNPR9TYOy Mhha~1hr])B')T.2f27y+lYpgѱ`kVSq PUj8P\9!iq39 9!Iij2!@/:0;Q#К8qy#bQ I{pufpH9Q?/H@aET:p=!8Jy p:03I;p`p'p0"@`)';@i4'0 :J*韪YɞyBp */뉁 P-?T8A' ;p%@ 'C"OpIP9E%P$pyXJ+::QI扂$?&; К:R @zJ#> Z#x 0RjzJ@p;0:?:y Йd*#Pq 4a _r0|R**pZs5zb!N£7)@wjyBP CJpjr &AZ;h:Pqsz( 1@BۯJ i J& 9[%`٤ +˫yj˫dS V;عkY827>@B;D[F{HA2H:Й+zI72.9;`V*:왮#J {ʦ$?"p#Й)? :&4+믄0 @)?{VzGʰ4/}{sI@:uzIFS yT'O{:К+H0   keI 4˫"`\jث4q|+0ԟjڸ*J&`8+K+˧ ۨKھ$EdKߙI5K`Ky`˻\|78!Ky09'PPjJ) * lIئʞ { "p:[KJi홰&̱ [FHܰF<8+u$`Xٞgۮ80˫|/bxz|~ǀȂ<Ȅ%KK6O;Û`9VƒyJKJ;pՙ )虠z*jER*띚I{PZ ʩ̩ [KL ,H6AK#$P˺ ٲ2ÊĽW`O@' ë`[:P7˧ P0w x[D c|PTܽ v;"}+ j&S\.;K4sʍqaɣ9:0Ӵ.)EmtӌLr ԳBHyJ'M1TmYщ5Ƚqօl154Gmmz|qpz kY؆}؈؊،؎ّؐ} 5|ٚ9y a75}ڨڪڬڮڰ۲=۴.lּ؃am!@=]}ȝʽ̽ܕ4:M؝ڽmUi19q޽M3Em=?l>^~ n1 sLF0!+s)U~B}t Dll1N& p.sNz{B]S~^ciLh||C1H2'&Ba!'b];A [Fl{K{~k|@k1>^a8afhܵ; GF&8AR,bk۱ħ>AvDVƸ$XfGbAVce{sZyIВ FDaCяc8 J({2~ aS(VasXNL5TiH2yS=XDCTyB12pŽΎ41(JA]i چn"MTdxs|HԒt6u@+m3/d|"p ?o_KD`C+4O'?,$$NUgJ>B?%t<>u"9۾uQGd#2 dSKkL˔I`Ew4F?mrTM*(:ࣜ]m膑LJ? p]F|] Q,w]CP`a\vq^  \- &˸.57+)6ACEGUWY[]_84`kmoq[Iy#{:uB3($ ƐLPSs[euhaw%CЫӰCu˻$w/ú ޻Q=8QN}c.'-P ;Ks f`D/AZDG7y)"AOA A4(#C   lRORTJx.qE's&jN]6NA ]2(@!<@!C (LVR^C4)mk*ʢ/E3`@} 6qnݠe(H A;Erb o&6a8IK塙|yѧW}{Wͷf8 W;Aa)Ů,:֖ޮ <"9)a=h $Pn.X̚BPLQYl?ϭ72 x * XQ ^S A$#C$q%Lܡ;-zAk$ (8-3b BFbX 4B:) TA ="`2`Mj`!秂 Z,Y2%C,|߉jQOhU#v`"n<+P ˣ`ԭ.`1nxTnep3My`7` (,# n`6%xِ㗚WqP  ,C?u^^_hq[@,vi&pІ)9Fu/܁8iIsVoA>R|BJc) hm*^BQ*_2b<R?\,*.H~khHZJX $V6 ]1 0c^amQ1ST ur㌢@~];! oj^}"x1wC8 g*3&2!8?LhZ4&` V`@SO4@8B.5j(!R@guMyRN1H. :9 ѡ!BZҘ!Q[\O'+Nz>L"N"W|}uv @Jщ&E"o% K/JM#FkX{-&ѕ9U ;(U yU1+m;M \DG}^)Y {,LkCp'j0GR#dJpk7,$CMKa&G/Iu4- #d.<9!8e,*oZ%`=$bfŐ3F|cXm|eCdM P0)ݐ5PMRIErZʺlж6QKmbH6SeLʹs9"1C6rhIF.ʉ PLWPY)Z{x/$Jld6ׂ+Uwk|ke m̫)όE)RQɆlJ|Yޥ1WuNte2Ap{ڷ&4 qj;B`ik5p(@zo@1T(y82Q ,WzwgW[1Y| ?T!'39P۝ёΈ=mvr/YE>P6Gf`va1DTؓ<Y:_Sr(0~=9ClC"Yϙ~y'X)Id YIٮçGAz;H^p5{pytB͞2:iH:4?75<ſC%H@/w}OշIwmRRe<8.ψJ d01O~OK/)AOdB}Gm6@91p=k@P]50Pp$V06Dupui0fҎm0 o jq SfHj ި M df ;p ٰ 0p0 g # P {` pư i 70f/U .`>0`0P!%(Q-ko6Q J<` Dq P IZTa^Q cf&p@1X1 L`r JJ>@v@=@q01Kwv1 1 RQ @qD `"@A} H N @`u O.Qzq=LO@:Aq":@N1 Q.&&Ӱ r"L OA8rU#@ ,rRD =Hm )w`~nJ.`r{<J<."**'@ R:sH DL4;NR`.q<414!PN#E!s## $Q .NAF䲍:`t 7D``G@ <ѓ ,3F3+v>`:J<3!>S A;ks& %R3Ցt("Tf, ?r`F .M:.&w&<T!>G>1w7w@y.MY`*AYqq4KRrM6.W%18ْ= J4p@ Ks@Eu91w JC;1IQG%54K<CE2TwQ$*k>Qq?4Rt]h`b2O.g"U$S :eCgB%A@>5 D etQrE7 qHw@T N7 &T=qV^ A%E{2TG@?FVD1'}8X-NT94Uf7ozSN.Yaww8Ow`3'N4 J[1rx_UϸoQ7vB0@ 0`y1 ۷ xnr6cp`%w }9M 0 ԰or`}?K]V_5?`07} .@D FQ!EQ&Etl뒌SDt?A@#&+ X FV 5'c#րf! ؕא"' eI3%-3'S&MPN I0߯xHPMyA" ӕ;C|Ӓ' G1ԞX@`{.:Ϲ{:qRE Tf1raX %BoL 8ZA@S/vjƓEX12;i 5kAQi?hxgxñ-Fdoڠ.!v ڪ t Iv5p2 D[i4 ק`+gve@=/z#5-M 3{`)-~)G:-0% Td!4WEG1 4;=TAI`${d`,;'<L3/}sos-!J54]qr=Q8"AooAAcqrX}0c7ѡ/"&8D|MEQcmƧ.}{y0 s|Pn@y0 -`:bʩ\ wI<Ý~ Р>~#[%~)ޜ#km-ѠbF+ 1w0k`xG\4+!xY]MO¦C%c$ p.%. 9~jA brBt?~F8˳ wPU` ZGnic ؾtZ`ZkWT@a:{l/4!F0$ &Q(F8#,̦/ڀBF+e1* ,_Mb 6*g`\DCb4NmF"ϯaelB}$ LB B=d@J6! t6e50$F"SR)5W!YbW`f PND.Q5D$xEfV6L@S `hnjDjr6EzP2ҕJViBUU&Ub"dn!K P2j,qE**2Ҩs۱Ar˾̹F^1,b;<jf\)ٳnDn0pԀ;qez(e/")/쇳e<@`P`20kJH (" HĬL,,EBYd6\kĆVj\%LF8e>u$D)En1əJCl!VIX-DHm;Djyj7!ч#n)SYubK9y o0Ip {pPLceF+A^xf^TaXEXq !{6(O] $Ł)/t^|awNIżSi {@N>!uMSg&[= #@@G 2|7}r5~(S?'"CRL/cQP'32C&p]]xrL k~r,,0h;1*1"*أ9d4R^rնB5@%#ETGܱ 9Ckɝ# C"2n1T2p)$;y!94#!k$ $+'>2lPH:drYAKK+{WaƢ^V}̆lk=I ́rj3 h(?|OY&;8Y6hqT)vkкcMiZuvkDp \ö ݭ01V:hkt@#m@po]j@:JR5%> &m:#D0;;`2\ @ `'x5~1] F0n7, + :HzK>"P(.n ׻Erp}S @ (WOqìb+Y+_߼9!ŏ#ԍÅc}j=_˞Y'Ǽo_gk|փ-~?c_u}}٪kZWñ_"'.o??_7^VJ16, bCi p`x`7_2J rm 6 F V ^C ڠ[ 5`NIJ! !3R_Zba~^CJ V z5a>`Vaa!5D.ࡈ4`H 2B| 2("C##*C$J;Pb|X1`b&&G'>*(`NCbmmb0+NbE.b/b` -m!c2*22c3:3Bc4BZ08"1C,,5bc:h#mp/x7C88 ,9afb5|"inAj&kBk&l6l&jij*Jlfo&pf'q&%)r o't"gqNqBsFrRung`guJ'wZwvfiyJz`gzf{g|gn֧j'z%}}'~~B{('mmfk&*6nΦjx6gy~x'yZhr^hb舊h艒h(*(ƨ(֨((')菮'};PK!: :PK>><<z)W^?&dVh\eX5d`Q~TiR c(1"͛s9̟>jУE췠P{F5ҪXjuc)ےȓd6 vl!p*庵ݚRRw/ݿwqʵ \~O*^̸ǐ#KL˘r|CMӨSJװcƜgͻ֋pMȥ /'_.νw)xAUoOϾ{˟O󳏯)Զ` VY*%& єB] @dt!Lpء HӁHӅfiȡ Ѕ$*"2\@ ">A#E#FM B$cSz╗ TdF!$# hih ^ER:)Vکicd!pe|v]0%%&2Ža!rHEvŗn* x^i"z#걬Jhڅ;"{c&vĂ`,`lzFө4 YfJ@!# ^ZJ蛩 " p0|I+P!zH%BP$cFK]NO|fc2 Y2/@`sH`W;Y5# L}@QF`sE1y&|.YGUfuӠh3Ebs}g<5&d;HG:|JW%`@,wT:CyrP`2%$AJ6miޢ 6p @3HlKO/jDfEæҀK| SI%խP_W4)K^` x@܀F6)MJcBtZֹ.uw(byK PPv{ , [:8tqHkZo5>j}hQ8doLŌSzX`5Elv@ث= bMkx;۽z &˓c.ȵG_Od /!-%eP+&biM!¸5L^ŪU a%NFkea*NJ%b'q* `@0v  0 Z0bW&&N&l8U["DXi# l*V#:%ҼڡqZ7wQʇ8'HV0]2>80~ ~ zA.(au|X>uT@Gox9cH!0ʘ W7%n[Lܽ | Ww1o~j6oe^gbz_|=9qwL>Ҙ4 8XX3#:DdrP X_K|nZnNU5wL_bXV(NDi ܋CcUJyYJ~:1ś64n;-y{0оU}rb.D(~Q8<8aӪ+6- 7T&7jf勲ʵʖo)RКʆ0UO_˹u68F8lFbE=CR8TXVxXZ\؅^󱄗0i_xhjljNJtXvxxz|؇~JR<s؈yH^!9[G!x`p*8Q7{ {"H(#XPhn+ha8ȡ(H!$(hH]`B-8!戎ҍXR2( 8^`" PߘbX菗@lcB )ny @ /Ꮯx^`" )>%) #9@@ɒ(^ @6' ^&ɒ T р!w!/I㸏!'YT W2,)2qIYɒ~`xY/!& ]PI)і9~9P`X"kYP闛iYY`] `NyI 韃!vɝY)w ɞ9) ɩ2p [!(. Iii"),) Y(ቕ`q0ARjX=ɏX)91GjQ&ɤr*4t 8jXXc:*JzE p aYyj]PIHzVqIuj} wzwXj:9zEZ ڨ0%) IIګ"Y*6ZX  ٬:@`ښQꭂ*|ɒw1x cy5Cwi `:^yh n:& б ҮGzҚ!ˤ|ɑ۱Qj+2i|9Qj^[ٵ_˛`k0({z_`70b@y+0^;!!;[giKf۸^89x+v x˷~ky˖0ZBrȵغzZk;K إہV~ zˎ;衔7eS [{R&+⋾;ˇߚp):<\| l˻*۫\7Q¾{*Lf. s0`g4̼6|;C,{1Ėuy³15T|VLX,?P\c y ׊PšKpLrLeXnO 'i$!+n (,<9, Оi\tvl൝I,0訖Yȗ {ZCIʻAȺ9Pv.˞0P{엯ii 01²q3l R <YRj\R<~+2\=L~ ,snֲ_\a,ɞLAqƷM>T[yƭTϽ;ړQ1~Vm?1"-ʄxȟL,y{Tɾ!Klr i˖n[D\8NX +?*? ˚7ߵ;Oɯ\C _g)$ϒo?0l*/] Р` 0[ܵ왉X.y2i]X,x?k ЬY/ ,[1Of_d{E[LB.Y^ϕ_\kΦхnЎ9|Ѓ fҐ+2⼯<='WlwB pp)C^^%MVk ^^^]^]^ ]\!]]) +^ ^]^„ ^σ ©D Ȱ 8H9JмXV6bTa 2BG(c#<|LQ#/l2dT=n"EDт &\iABY B$ v`#%#O-qAx*JQ1YΌp<0 8$pwYӛ@aWS < U>p"Ye5|h(w&ii+).L8BaGxB9`1Ez9S'lT:vj ``"*P}*k%ڋ+Zp+ ;!KS>i("Ϟ%Lj tz 2wk%ND'% \FHsO Χ,p)4\3YC3%AkdKsCr (Xg\ (ؗ,7\tQ|*tl% Irt%{&^E]n_d3d65.QSr6'0; @]=J[Wy1ucݢh*꬘p_dP*WsQ)7/XofIcծ|Dӗ=oWчPkrb}Аm=>>EOTVoJ=HD(DPY, Bd^)Mo~ L NAh" Bw6lp.c *g!0@ LmmBm!sa!mLlCpD%Vl"AѤyDA,mCD1,QT2"T1{̐H鋧q!_ hg@L!. `&/'P&IBBh$=IE`aY@V$G$+]<1"9I䒙n4ge:&P +L&e[`-qmpQ%E 'DF{6vE*ՏDFY 25|(C J0pD!n<"L[Za0f)^ vlß)D@C9 BAOJ""RmC#鼇FCB~bg)|e0: Ci-y1|H2FDT2ًl04cP GjPu4`JQ:*ZaĎUj9붴g^J LeB:c) (BbBS2}`SCD$3'uj|}jɂVP Zn"1V*qb*\Bd@+K»Ȁmi3]^U @Y MI$5B8^=MR┄UgU0)/)*L8J0DͿc<I?8 ^`mOs #1RHjzoZOveU hUs16l w/F4Ы`cS`YX,KuQ9d/lluY`W>,hcvĿ g6d~dxO{*7Vi*86@Hf QίN>ը u)hJzBI/3H.i!2gQbldp]NS׫zTt_jy~5Cv` 9}VarIu͞="() {0q}7[x&ukm7}$JrK*oT RDϹMX}@[8csa:++@ Ct9y5fqG΂pTD5S=1|=uPڤ}O`%Fw.SĦwۻWU Kl%CF)9`zkSIܩ^Y›qpX}ťqgӽ>*2 aXCgKTN(7 ڇgV}Xeau~`m5 "u53pT w]'U |mU#L%:aF2f2łs^Z"?Ǖdr_#c5am1 < 'b)W ?qJxW@1OdZ[drmn}+R'|d)Ph @ma{<6pBvx`L€g5 (ZGM8qp*rXP&eVfh2x~f2 S}Spœ]Ubhh8>XnXZDYǦg2Fv G`^ojט ʶ7H/@6׉CXЏ X}nnc+mj؏Y1ymmS*ՐdhC|D':p̘Ssxd-Òm:VG\I^yRp)9ik>"@&8{y}9=V 8 #yYBtA yٚ _pP|ڧ~zo:mꥋc;ٙ^q!pZ #yqb'D !] :FK*Q9c]`j YڪzcꩯZ ʫ=" sɪɬq' !0J Y0j %j Ϊ< J`: # &0&&0#&# ! ^  $P  " 9P#+ ^ ̪.਎Y; ۰]^L >]!PP]FбW+:j7 @5#jp`#p^T+5&& @$p%PqqY"!>˗8ksUL[ k۶= ;u{{K~[ $]&5Y˷eK鹬&<"-$PkK %` [ +5AK˽k$ $J"Zѯ /; пA@+5۽L!;ʊ`ྡྷ K3K @^˭Lj; i򫬐pXsW &4ËȬ{  aFt{2+;=)ô5'&*K :|^č(b0H(Ą`A^Y]N$=5e >ڔ/$>%*"1ꑯ큝Y6k |<Ν=i ͻxB~l 4I2NPR>T^V0mVW^`^@ߴ`EjZGqU8@]@J'⃗Z>Eǃf> 0g,h畀璮r~{^ ~ ^ l >^=XƗހN 7)Fj! ꦮT0s~ӏ@1eʤ[t/эx21Ne>q 0dX@>p|ښ r to]@/a#"kPٍU(&HЏ46 /q0' P#Pw$Pc^*A buI /]~ \ %2'wh 5E \C4v;vCkm*5T0-y14C3E-ǰ-AqV4Z'^Gk$F1wi +A7F " .G =3_YmTMT (`p`?@# eA9NaP+P$:%`IUX_ H>7/~O3C`+Qi]02:q^]\^(']^^ ]^] ^^ ^ ^^]\]Ӵ ]^^“ pg,C7B\6ȀPXb8@K: CXL2Rv2-H*1 *GV<4@pg c]hgE`I02+-f #ӛ.#&RR6QY%ƑɊ!_HI'&rXuxY %:W/)Btd*PZH׺BFu p5mDF1boKϱ(% J!4RR[HqKAwS6z?c.[ W&-~E [hTpUya[Kv8 >qO?VaQ &dȜGz$FS0^>!:Yp3Uh G%< X,^}uWK >pgYψV‚VLSbE$d n?ZJ@ӡv=N##WH/\nvQ7.=Iڙ-V|!r;yFk~]^l7iY}]p;\|,'N=W s yMoE3NsN1fW0y̍O8Ϲws+70GHOҕnULbIԧN[XϺE fY[Nvky4MP-p^,w nxϻ'.,"OwYW@:[^ӹ {2/f'O=iPNgO7w޻D}OYD#!yML.ߗo_o_/rXcvG|ֵz~~V{[k_Oa{n\p$7}}~hn7r(ihZ5~B &gp >~f~ tV, 4<6tg%i'\@e (q¥ kO LXG~} _9H+jh,g9O;#օ1`w <fs7h{Y'e!024ys 4 k#bep`4pfs=vӃOF@f `RIkjos01 nvk{;YfPL!H?~h]8 H@ 0H 1@} B8 lӠo`pxkv*&-( Gf j)!؎_@8 10rx[%/ғ"c5 cI`[)r{`;uC` ڨyTjVd) 4^0x7QLV$<5<%5x0b1D$*PR0d`!8de_Ti`f"tTGNq}%x5(Ê6YPX$P282}@!a"JҗY' 2>LU5  pUsx4 lpC KKS$2 aX7gC=EG1IRrGՆb b0/a愆) b<)-:.*DEC$~ XQZA.4Q\TklT;Xl` 2+)0@x%8P)Vah/N9 W3Xa .j2I^?CT^N6rI Dwr6p! 3y%y3GY,鈷P'qA4DՎQ>YG@ː,ж3tj$7sFeBX10YzREa/bRhJJ9ŊǚRH&\{dv3Z-5:qBad'+/R asGu/,4'ZaS! ~>s/'uAr b!Dy?m>3@ Չ zC_! + jw BQ58 G5'ҩ[p4' WA MkP2~,@A=:.KɴG$T2jk}&$z;SE۪,ю{%S{›(&_ 4-[T) T [н*9r+0S-Q;yI:IFfAp48i 0V=_Jr C± GU]7FNp$8 ˱Ki@.ʛ AtI +q'W/D:/i!*gjK$,e1%jy0(0[ijRT,C%b3[ z2\`)h +k'>!H [5 "\$x+y8)u?s꒩27f񼍌 n hs6M3P#5_Fa! DEs͵(   \; ɰ5PY7x/п=cPcR{g}|}Fю"> @XVp#l \ځ0ӘξxGe'nVH@Bpɶ3p~qfzL &vHMid8iӡ6 ,Erkr`g Vk]2|&h͈xJL5l=;nljD %vA l(Z]t"(unH3XdirwAyʻ <1+*Gk2f…ؚhFM>eW),MچnA9&kWY3|ݛvTևNE{m]baX{|JH ת.x2>zWߺFf4<>(?08B>D^F~HJLFN8N>T^VNE|LaWNI@(a^ tHlnpr>t^v~o.Ux~Y[vא芾莎2>>^d阞难@>ꖮȩK@(}>>c9mn0ƾ8Ǿni&>(՞~ʆ.n^>ɞιL!>%Y,TWNW*t PpK 0$_&(Q@().) 0_680/:@BS~W" L?q,, ql]>6^.ZtVF$-pR/}F@~Ojl@~ Pm/}fokP_ok`o? } @$gwF POP_"  ^a/@ &""lp\/oO8?aojd~ߢ_`"`&//Z^^"] ^$$]"µ]ú%!^$]]!%$^#"$ ]ȕ!$"&^ħ=s^Щc'QcJ83NzI" $7€ yvu^i8|v"%t Fa%E%CKIDjLG}UؘL $ U/-aГMˑ ЈrDs; !r%F(\u/Fh! qs YԪj[(훐Lv ]9S pć=L8!S}'+`cI# Vـcq3NWxysXG]w)U0\Q) ~-2Ō|sNu * #Ho $ۀ nGJ4Qp 哖!ppbL 96]gْ&!ŸnۈpW!-mRKdapցbedy%9 &=Km:Weu̙`P1ljDb|L3P)ڌ310)}]We %*ꨤ^Ҋ2Rc[@d) O" 5! $ra!uBPHHX7 (]ફs1 +,i%o~KZږj" M 2BZ q\ICqB"2 g>aX@R  T(n}Bh)Kwpj HE $\ZՅR Sv{3Lj("M&pb3ΘLSW]CDf&(]ɌX-73 q 6Bm4~݌g5@SqͲkpJ!TBZyUv"gl'lݕ1Kv {\)M۾Ws 7*]gKxf3"/ Eꭿ )NNxߣJ{Ŷۼ(>a ~"~?TdSifv;@A{va`D~_w#ov^%.c\wzDPi*T!Hh)P@Wa6 DGCZ!(FT9BE.PP wDN-5DFQP^GE)"c(/ \ AhPJbC":ID$5DDiEv$;FCj&P3.#"86Np5G80ʯ_;DbIL$Sa\U6Wx ٥FvRCUJ9\l%ȥ.A8}'a٭4f^HK_\Ix\1G> H" ,)G J`BI!H^^Jn:Uz& %Ad 90^G ~Pam;D<(ނN$Ce]k(1sK!9 FdR c۸VzMk41fj̋#ƩSPYEmAP9Q=rL(B5wTǂmP &Y>K{d#_ `?; P ">t2_>{?  (tļ*U`i-kDwI6*&V>@bƶZˣަr2L\$Ns^(G9Ԁ-NklYJf2*ws0E˟ Խ^mA;rZͲ".SuŸQ x}؏F\j-sBrw a%:iéK{2"vN܄t;wS,Edhe?q#B68NsWw.y&^"6{iDF1=Jj QzR)@VfϽwW 3ݏ4q-7#{"{%OG~-ȧ PϿ`%' 8 ׀8tyЁ "8$X&x(8@Pybq]0. a#3X @=;( =:8Eh0aNJ28P0C9n@Gbx`0@^sp0ctc5: u(P08,G@xp`0UH5]v{ 8R3VXGHX6L8ȍ^HɏP 4xc1؉X ЏcQIcgЋف 0x!?(I@0 WI 0(yH鐠Hዋ:+^ N+9H(68cȓ@xk 0Е^ِ\ՐЌPIp0~F)^Y~g 0 io:p0IqWsI9W Ɂv8_ @~&^ ! .XH!hvIx9Ap)  % {Iwi"?@&i^9ћ\:w'9i@# @GJY I:H!)^I(^D ؖKn IzyvWž*F*`J9JЋYb9 7+V/Z^Y$y> Y^*uQQPSzbǏgfjkI!9\0x i?oh q)њ8 qeANٮjꪥ 7ٍ)8YUI1X~RKY?o;=:0pj0p 3(s/(io @ؿ)nɾ_ Ex~ieη1/JNs)ѳVޮϡ0.zH1_i?np^]^ ^]^^ ^]\  Ȟ՟ׄ \Ӵբ۪ƆՆ]0c@˯]&_<^n _%8HBj@r0݂ hWAL&8PvFɁ?u'Iq۠w"(@z)҃IPjI2dBOy%$`*U V肈 # +[]J^ޝgMs^խVmA*hxoTWy82Jfqq#l|`!y\xVkC6!/866݌6 mYw*Q,! %3 L L`Lq O T@'*^:@h 2dWDyapAxm9OhwuLA3w^,c^D~|l]u\e!J@̸zg R 0A:9Hj(LQ_f   *7~~Pr)$#e|2&ϛp vaɜvz&inl3b7s85@w A΃@4̈IϭҀ~[z` R0Aw4"tM6d' @bʺ T?a"ޓMci׵M8mѲ~0|sϠuS#L;9OoDӊ 3Њ-9ҊNq 9& e!׵d#Laϟ՛j͐=-v5rak,8=_/߾<T-"610"؃$-G|7J9\U>.B~ţB(8Bypv-w|'1)f2 Ap/D\` Ox @^HD#"$ A ;  G|dL%.Y`.tx̣>_@BL"F:b!JZ򒘄F$' (GIRL*1%DU򕰌,C97++d10.Ib:D f:Ќ4IjZĚnzftIH:1 )g<)z̧>)OmF@JЂ4BH pІ:@9z _ͨF7щbdը@Ғ:JWҖKgJӚ4$( > .(d4xiӢtb5RJ2PJygu$U\V:ҭb5)Xӱ=*| ԎRE*\jS3-IJU+3iW;װ*-+bҴ?PMFTUMlFR4ZwE XjWֺlgKV`mwq! &#@8ЍtKZnuq;h x\U`UE:L7o2`安0.r\1/F x&ܫ;X"ϧ$wIA}1 vp{2N02` 'y ?ۅZX*!q]5^naT #Bhl\p\(p'\dIr-wI+leYn!UC2{NDk'3*ߜ$ `NcBȧBiU&èV]йWv`I;咑\Uz5kUx![ ߸ͮ˦clBˢ\sc:պ޶-28)vJm[[f7G m}"آK}Cs"٪.)e28nm\;)'%/'2cэlw:5nQ⫾7qZu(=e5}sQ2&0ҟb@e^q|@ŭou22ȃ@=HCh*WS9`2ACRUxWH0P]VX(DžiH_؄a*}`hux&#ǁzh؁H8x2 h與Xw8tH^"8V;`2 X'cu8ȊAC؊8GHć;@\C㋝~gRX~H8|Шh8ը臓8(؎LjX8Xh&#"1 S 9 i萮xIɍȑxy$ 90%')91ٍ|W{|r}7;~-/P4DɄ?FHSKM>yGP9Njlp_نXc9aeY膆\ymbY |syg:x6y w}e׷}~9|8Iv5Ǔ%y3v}'}}WiĘU~ 'uy5y zn\zV7Iy9rvކ'㙁biIIfsIF |Y|B`c_eP;P_I"m]X Y1t `fa橙doDɗө}y;#@!0o`#r Z_.f 0^f|$![f!)E&Ejm JD I)w 9h%'%\hF$uEKFfj7 \z>zmnFCڗ):ÝRTx+Zg aq\hJʦ;Acq=ie} %)-jwN ꪑ :ZbCuڠٟB 0W_%xh*,&i?lzC y*G_#P" @j&Z^$ʯj<62TEo!f66a%!`0bf'kбVw'j&N^0^ "ۭ&@ ۩oKDN9дdְlDC[춴IbT[B][Gyi+V{g˜e۞FKj\R۷~;[{id_z׸;[{2iNx _˵w_;T{kam[sֺŤۻ{J;a;Bk[;ʛJΛ`+B[+KB_AKכ{};@Y^߻A;[Ky_`kA z+|{c{`@{`ۿm[g,`|@j & =7P;$\?³ ޙxe8;"J!bn6#PEA[@&j QA[ dW>k! uWk#I%_Kǃ!U fKXTsp&;0l:c`܉}Ŝઢ,&#p}̘*LoKY̟`c|Rǔl0j؇ʲ*7^μʞ;[BaƜ^xj k"<<  9~E̠̾<phW%vd xyЗ rˬMDi}֡&ڡd_ M d¤ӂСaezaGM,\-C4 dtj\]Sld=fݸhwjEl~-@p=V<\׈MD-uKEA[ؒ=cbBFٜٞ٠ں+؃lل -ڪڬڮڰKͦ؜:űۺۼ۾]L=Œ}v;Ś{ȝʽܚw=]}؝5Lq-`m]ށ'<އm`5C; Z}mME};MnA=` dT7JD.C^7t1᣺y ~-$(>|X 2KnH<^Ѣ#9NF<,!,P"fkZ> ]N8㢫d\A^LXvasNu~m`l~>nAhfnU!/p!~&~PN鑮%~^މ>꒮Ꝏ~N롞ꮾ굎~^JÎ Ď^p֍h>jb))nؐ޾^^~ھ.Nn&)23Ѯӎ^מ>?/40?@nN x114_x:o4`9>?@8ύBD?F_IMOQSUOWox!Ec3/@a hf/kspОmBZ+@-/o1FSv>gsf}UߒgOߌlMn_rH١?ߩk_<>z/y),$wfwO t7?;>{Ư? i-\y*JPUve=>v ]^^^^%]$]^]%$^$$]׈ۜ˕] ^"]"^ ..u>|@$_!R $ MBI(M&ڗMHo]K͛Ա3q?2@KzAO @ԨW.$5QOKx @,Iݻ!Ys/4> ަ]ON@& e@"'j+^%")5kFrc˞S/f}o7vL}"SD`'2 Ƒn5rW"Bxڹ#XxGY VWAA$8QWq (GNR H Xb)e%V9r[\`B?hblJC%ʧ"m5s4 sMlT"Ie$ZmO@؉B dksk5B`4G`b̌XL(Н֞"vHV&&bׯ*֮2:;9 ?)B \< @D+xw*Y v'&3*rcvqmAb1w=b,GTn@sc8($piW9ˊFIpXȴ63m1805 l_E]p Nx-QFpփ:wxC@(`Q5#' G!!%j, X@0y7H0d,e P.-tYs FTF048bA2oii\&s%X=&))FaE٦:M|<8'Y`SE\> X] )9Mَ+r(AɉmuK](5|ATX'E3J/1M}7r3p*y@+!0a ?p a!ks T׈ X7KMmZ/ݶ\:=Pnv/,`|Fz?\g^ĩ]KWx I7-O#4=Z~}$ ^FG = z2RlQI_b:}\hK|qNGb};ԙwlxҝ/^}yοWUh}WzDgSF6Gb* VxU &fhP E Pnz hwh76)q+X -Iww1325~7 9HI;R/wX jfC|NGX=h?2}gPkFd|PQTtq%Bn {A0aw$|Մİ}kp]mm` pf,gFdhtah jlPiBu&aڀ8$7 S&ecAs6e0{Cq8K%m@bbgo&>,7S*NKI(TH8FʷXNGchcPq7sudph^dG|(}H;T( 'wnXcihqxa`^p-/yØ`d|f2%"@7a#o pt(e;qq^^3p(,狯Xu4%fZ8ak`s6iO6Ga[?cX r/ `26fyD9!؇v%(PS| &8avk @_cSs,7_bLdy1cpGc6d0{2"86xAHiy-}I,AYxiXԙOQ}r,ƀ0y i*ў.2y z܀(E5)tP_Q+*`Y6pyypG]ph`v6vec bf^m6dhF?VyN #01a ՟^`kwPapdXa+m2dI`vmXp(mhgfK p~֠7evxyJ Hݶdi E:+)RtZ:*vqbxh_gsvkix^9YkպwzdڬA`QXX Ђ#($իj%)ɓpp``ȐzvA eJ`Xg&eƩ0l`8v l+yMh)SMTE&`rt~J4FeX-h^@WkcYE& Pix8jXyDdD~.[%@`꯵;k'k c}Z@^&khз0 JLK d)Xhk96Gƒpɲw⡶k;ɑBr\tjvk"& ʙ)MzMeTaJd w),mMv 9_VWc pØUe z+iJWŶ&$ᠳO햞p~ 1<1[a/1Uk2{-_%,  Ʌ9%a& :={.*6 s¹3l5*"D !Zį֢ Eŭ[Oo]MJAmkuMZ4cBlnLy"aalE(f^fjj Rܱ@ ,bL+F%vF_^]@_ȕFp7Ʈ*60hx\_¨kjʭUȕ @Đî'‰ ʵ6nD6r kՐWaڐ7wkзpZ 0vj2O ļ +lqƬ~&i{zV2&  m+z5 y<۫^p`c K&b([|K w-K`>ԬlWfezr@_e8^?d FaKc[ImiQ) + -VSXLO3mblsZ:m0=Ȋge}u(6vh_gh;5~Y[] ˢJ]|cnZ5`>7Idcd™;KFwegه_]\ ʼ*<+ L|"}/m~1=*] m Avvq=ڐѰohx\"}P] &,|I< Xo:p`20w*%wϜ&E^>̷Z .3% >h=kd=pWfzhXet|domu2h*kۈe̮zʶb\cF1P> ]Z 5 Jzh.U9^k=cόxiWns ߈LOل>;EI[cX$NLJ:ό'Ϟ~]  )€ś\[E+ ˕HFB][I;^̻ҽn Nn . n (Vc]˨׏VìX)1~jցmVfI)ϫL-++c n 2Rߠ~zfz. W ;_"ᾆ\#ONxo Αy~1 Î)B. p)^1Pp >?|A=X~w]2հ NKi$s/ܮ,U<Kp ai˧ m@@`ɀ}iŠ_n_p oa/ w?_r  ߳&Zþ؟ڿ\˿&.9\B?_^]]\¾ʒχԢ٠]ߙ]ޚ) VKU*\ȰÇ#JHb ocyA IO HP]q&Q65h$ϟNzJW@;j2dh'A>(K`/"fXNAz,!Z5׹x e蘪RH2` 3-΅{Unޖu1}w"Sd>^ q6nSuL9dK' @@zJD| !B 2(!(C:{\t^ObAK~eT=5^J B^  &A0 `# a 0f^(R^tP "L{ԠvV|G͇_<(<@H&$% QR 1~F&煑A1bBLX%$C1%( x*+WX<壐BXV6裐J!DH@?1:=ySYCh1RI& tq`fah'a (5AKY 'haZ3ց y!tЙh`0Bi+X(PLBAɖx@€}ࡁhIP &cgeeԅsիս?/,:O@3T+,5f!ڙEYj LT-zA!}k̫W̓9Rs#=٥\\Lw`!͍ge0vw,6UjĶܦHܔD7(u!υx*l 0#jIZPkOBHw[-hEMTZf9| T@|l" epl\!*b32TŬ;Їx7ڈ pSI֨Vj*xGSӹ[44MN"s:Ռ8=5wozxNp+u Z9ֱNXdXy}ra5M$Zml^mXsCX\V6]5MHU}uZ+%MbҔ[M:(i|\w%UHv\醁E5m@MDvUZ9n2h&}/~(diqWWŤ|gWjT RRu~cf?RQc}IdcKonsGpz[[WRR5z0JcrU.xzO.eUqK舫novh׆fVdTKhRh戛dlgw#Fe(L eL#Ʉ m e'w .H~6 l~w|7Ljh/HahvzHYO?ax ]?Y~isqkCm >/]xt)~DIju 4QJ0294Y6y7YfQ8@B97HJ :"73Fo<\ٕ^\~GfyhOFizjpɢtUI3kStWlKK9):<GY٘5iimQ9 LaȌc8F𡗒×痑2/Hv9~ Qi?WB8,)7N鐪 Ù MH?eI q]\T)}9B$cxxY .)/UVYsu9wlUv yE[HR剙If evy)6Ǘb2Raz.COWhfMGnp&ʢgagpr\VדGiav}fC&c4zETjI:b,z\V|,mpfFJ%OXP7vW*% lI5W(0w\) 9>Jcҷu\&(JTf^jQ1*M&fjE=VMaeR> dGd}D{j$ox~ʀٹ_6cJcJmyAP&}nJNDq&٪?NPF[jc6Sd&}mG뤨HZ?(}$Σie@%R&ev[ ywܷb%O0ǭb ($nTV/agc z'kF]z['wv%k5%ha9[cFb2N^NUvlN|[FH (t]ʛ02Qp铩v Z$of'We]+|7@+AlM>5VDw&5puWv*%H&w+DfʓG]D0NftXC($' SXj[ln[p;>r y]b兲F{y}0U`Keka(*(lEKTv`e[R˼vF *I[[KY@f JK$7ra ʈ سL-9'eMp"g%/7cswyȷ; h,˟OZCeG+|$ ֨;c/65Lfkj l_^tzgTL;SjH *eN,л-!@3RYpZDzvslzEkNMQ,\RǜazyJ%MSOD%gsE]Dv]{\{;xOeMTWA:Y7~ǚ JYrIDlgTpG̙ O*hj\l n =p^.G@  nO>.(P~~PP߰PrcL 2m,:4!Ϡm/סA @#@Gr%N2"*C"@!!%!aʀ33dU-ber#&14mT. `^)A=2l^Cp>xS?X9C$떠(Dܮ(>,9eb&8 ,>//O n /Ȑ'P'h2b*ub&PP%01qdΆɂQSC!a6^!rϑLbQzА J%-2p2!^ 6aR _e^"A#bO_1A26/)0} yM.A)% sb+-@+1 0,r F]. ^^%!]&"]!ǰĞʠȖԼ؍]ʯ]㛾&뉹%]"$61C}M#0ӵA-ċ̭: rGl` 0VjVd !MVog+(܅v," )1ut$ MtLb[FIe0Wm}j.'vrjJ(Qtu!FqXW{p"/#/l֖aĊ{p Y24l@3!>-bWC wS"_no)E!KĿ 2,6!e ź%wb8xLxdՖy{{Y=TMu z쩗R{$|VZfWp%Ü&Mr)1#& T#K>^pӜc@#O2#%6sb^U!.NcbX"t!8FRV9׆tH7fKN4֥b-6k5S=ioy͠IBIlBpeO\~B 6]xP1kiK&Bd,&"䪆ūd?^Ta^U]Z*M"f,ho=b7&  Bzno(,v0h 6:ꬔRymZhe[4쒁7P$8YB tU!w NB qUQV72 ',\$=UROdqqpLap5)61юz 3bCwrSڝڦͷԹ n'{I?ⷅuٰ GB`w砇| 騧K=>azMNy5g%./o'6]LWo<`3pMOzvMqgPCL7l6c}p_⇊.π_&AF3@@#p @b `0 F0! `E"aK4!"t kh n?.XRi!/zaO+r!/(N.] Rc\>xDY$Ts 5h@`4 E6򑑜.x,>p_#H/~ECd%1hF4:E0!ـW " }`ΐnr"aGvD x#1@xᛆYMvF7)(x^Jޓ'%gYNܦ!6- En<4Z2LG4sk`4%Rjњ!Oz. g*OΕK;:?`Xg(KBwԞmuC+RKN}ю6iJzU5*.ȸ^TkakEڂ+/Brk(OCwejATyz HbFZ;҆niKqZH p"U+0ׁbܻ>^[^t:PҐF@W.7jEzhz70 rXhk^z#\:^PsZOLT,@I1/cwz cȁ0cd S"GTvFc!X[uDd#@dFarkN|x[.1$n}J e<2f8/* ;%:mܞG@?Uf9%=Y4ˁ-ﵰtS,Z)Pl ;Ics* u:@!ERh::ݺ+֤WVIFp8gӮc+#^νP:9 T|}&>$k(. J ^"VjRE;&G I;K{M;ʭGc^]@;g sQp,sW[A) p!bQnՐⰵeʩ09t[Qa'7y¸ @:c Q1*|RVlۭTSk8[[1!P4F`ـ;B5[  4qVbKR k+j ȠpԴ1Fjm#[?{ Ἴ mv;0{ar{ a5ʖ@-տ'9`  ;;Zl$|8 #:!v{y 8#l)| c 3/)Õ7WÇs\A-J@ ").Ć4VR,cV T>+ l4 Ib\|,-omGq s,u59ΰ)^?G#~ -N9%Nݽn㻳@D2OZS}^:f",.s'^FH>J~-L^NO~8| *Q3p"  %0`:,21 %$'C0Mp!Ma>1 e.-gNitG買!#0Rr$0"#;&00*, 0"N]δQNP]+> .웧 3]0Q%1Z~&z `.PkXXZ`*&ap0 `Ξ&{T> *{Ne2U>! 6Lkj*,$_bf, R 2 4 68:o^}r"p5q!o2qr qg$Uw|[]\B h f z?Ŏy܂פּ6Q,u;fnw♯g> TȠH.Iҕo@%ͧXbfUCAP "ʐDD;E-Qt~[F=ĶX^ 3aq!yu4J(Qmt]\_F.:uт<Xh]$ӽL DY:&"DƂF_k-B%;u/#ZuW{`ҵEuxdwͥ xvMhDw f¡bQq0mFBmI{] 5`i!l&$t 2 ݣWB $`h(w"!(ȆA HhH'%iu8Í('Hmug[N73 $xh bJdn!9]TГ|ec]"Zf*vxJ֢n]yjg&zd^ehZzDzwj$]2UI˲>j!I[r4(zѦ +4 ҁ\ƀR-tÌ-nCZ!.&<;+ '[:)]q*6Ӈ1n7N#嫇wnӮ*<(](T|*MFB=i7m}_p} y}/=,S ?˒Ns,S9xLǫ΁`9 (I.{a z| >?(L W*V aH8̡w@ aȷkCL6%:PH*#$a0Eʰ] H2QX<""76pH󕯎x̣Si IBL ӨF{d,(J~W(N6 O8ɈF&Q*IH. 0$,2%F4 ,I-l H #i߷H񔦕: lnUҦ0*L0N$&c\(7މ\5[.wa1B@2җB?S}%|D\J1l))i `C ak @ :c S.:*_hpG G6mE1M(R jPlWN;=jjڻLu _d#Ju85ZӈTt}T!(Щxyز0`wqVPz)6Tiu;"KC *hI{P!v} 0cKTvD*ڢ &\QȽ-ryctKZͮv3zElKcMz8^.׼덯|K~L8o;PK2(PK>>UUUddd666yyy:::BBB|||!,U C C ĐƍȊʇ̃ΆCCU*RO ,0U!l8kDFDI Cd$=u%ݥI{-aL}.Udy|?k┩(Ϣ6:뗱"F,B5TTM>:V[v qٳf}ve۝oM֭]w;w(߽~.DKÈ+B#KL˘3k6ԏϠCMӨSk^1`(T˞M۸s>o Nxȓ+"AˣK~|;cuKp;WedewO~5( Ah!}']~@@Y(!r Āf h`C8aw \Q!>0"4@B\`D0,6p˩X w)CJ벛)ĚmB !p߇Sp!f(Z: )"m߆kɩ*HV^2(į!X*pRW&eUkHzp < 9r/ra O+xZi)G&ψ*"Y":ȳ+{u%؋^˖4@9čIB(Dc mG=׋ 6cgn}i=dԺ&zqx9AP@^J}i׃mzm>qAկq ԝ9CĞ{KM PP0x`x +/`XSKO}!78=>sW|_r#WOy?jx v.  @+ G"P\ܼ_ JU9);kR/ c+Ƞ'[8/OHI&ǀHX/m\Ԧ^NV5OeKM3w$tHji!89]$=)50nj6Y@J>0IsCL& ! d2,)EUfO_:$6q(dg:Ymn|.D}#,s_YgEj8ta HȅgFQf!Jc6sX9;Kדʙ.4~FϦ tH1B|F)2箭lB~ϊ~hZBưX-tX "ׅVR=|$U5EC@? aw,^4smr("_zЧ)o!S}r}'}w1D,b|Q8F"QoSWDDf-bi)|Q5d)2Rj2+|1JWzrx4t;hQU%{50w;o2 00'hh*-8Rs#S5xhGAB6UUGM*58.TKUJEdZȇ, U1S27)2 v~;x !( G8D%7%| VlNx;Rnph/orvHVVxpQ(v8GxXeFg_)% EjI}Wr4w4~v,3mG{}}x/!G;Sr6m,#rJT-B@ i(&c2" Lz(b܂k(XZ4W!GXhCx\%%%7ghq4dR^n[Rĉgd\,S3w46Ageq3 \ZI3II]`^v.钹@GAy^A%H 1rs/i*,,oB71 @>6+dqfd Qfѕb9%v jɕcnI>px)LZKgq(!{eV#V]'2jIdJ1Gb!eI̙]Xhz') کj꫕Qp T/҈6B:=PxӪw*9/g 2l'2v|)5)S~2 ګk4hj?V9Wc$xZI>U08( +IT0"s1үW V'F:P!  1ZP;U訥Is:k+dPR;T[V+h3Q.ص^`b;d[f{h۵ qi ii;t[v{x;R ~sˈP!QSGh¾ \B#l5|$C %` {% P{; ;$1|@  CP&Ň XL{f<]\$CBPR|dnYǀ<ŽP[l<`,C LJPƗ|!|òa¸Ѡ<vDS\Wrlƕ,ɚ|R} ZƧ\LǍ`x\ȫ;claƌʿPȘlCaؼi$<€!l!   <|Al`CP켶lC<#0B\M=`JN,k #H,p`!}@,MM$"r!-! ,B=`'6!#H ,p,cP#QB $[}r=M"m$m%+Lz ϫ[7}9,:{lLC=֖`џ-?[|C]o͚F#= ć-A}1\A| kэ %@k M#P-CyĿ`@{ %}0ՆD3&:m9 << n ~ǧ\^=ƬMր">w{TFm,KK$Ӆ]Q= ;Іp  r? =Սڄm 1lDb@>p#}6m?! Pͺ%`#0&pnϿK< ~5&! 9,\3}!pt !ܕG>C.bl .Ϟ0mio`Kw(ϔnnW^=L~,Ճ= Pݩ~钞ښ p!mʽt; >R{_5^5~; ^,c\M޻xw;d؆쫛&C0CQO̾Ǫ˺ z½ k޺Mͺ^FB_ }2"V^;p^Uͺ 4_=6덐&=Ϻ~n$ YgVV<P C!ɼ` 9#0Vgn M-~^L\1 ]]F=| }MAղ.# W?_/ ҵXy!aOϦXo@_ʝ9RPۿ)m2pĀCBCCCB8BBB !CB$B  C%!C&&% &! CB%Cɉ!$% 90V˝:}In]l(ݧ a@Rp8sɳϟ@A!$̡Sb<5_ɈbEITXĦT\Ǧ8P ˯)1n]St/sLI'_Tb#J__lmbD H!$Ɍ}zneG.FSq6 1  A=8uqNߘsPEE.Mӫ_~%5%H*p? J}7u;uTq7]6 bB @@61W_=CG셈mހ[9AqˋD4Iz,$ 7P#}>i(0=7Fx}hSAȤ6!wdz$f8(n)映tcOS㤩\o5ۛxN:QYryեnF'6h((l-jgvhA ii"whZ饫tj%D+:6ܔӬF+jmʜ (}Ն+䖋Ia$ I+k!eu&B@l' 7G,14Hڊ8A1 ,$#\q~1.;$8 &4l8ߜX8&` Q(ADp\ Q: 0:&І8̣/v+L:|`5٨p@2C, /<N)1Yc">J+?K| AFM%ؘL-(AQL^Vf d( \  @(T.@Űs1;D`h,`S\ q.J{2,똭;R xRx| 0=P|OFɧciA>Hq2& 1`_(~a9@uZ Sz2!g6º h=9!@YqV#9LqѾvb; @PKk)L@F4 F& s?i,EDϟ,PU1P")h!PVbQ$CJ \K rH tDDKZ7 #bEIzNYJ~_7zP/O|P *x_@aV>-ືHE@7+w ]톖{7 MEݟf"H 6EYт"vo) Do)'6@Ħ*EϮX[K[m#!β xb4oyۍ,ca,!@XqƱ >Z\)9,g 5-Q@Mf'-@`m J+̿[1zԨNCDQp@4RN([; \`cO @ (|!Ԛch@7EDFDf?;>=v[抷S[~z mc #[Dħ ډh;%O7cz8NN{jb-M>8MB<.$IXULi14t,Ys{T魜b{ݦN[X&{^-<k+%3f領螸oSӝt\sl27( zСDi&8ũ`@/9#0WDY|RseSs-VhT=5H޼=ˀWyoTxT1i1'XUZ FEqTbN %=U24XQ%lrc>R)y_xϼ~|:l&gt\ҀZG+%K7P5 [J{~~Ā {dleW=wKuxZU[mf'"68679x>r葀vV Z`\fa$__ETF/$ecs\;`UyhIe![_hj+ravpH89~dmlgJ; =̑lFdVWK(crUcdVWarN"p|5Ue!vJVJxF-eCd$88js NZ4i\s?rl#`v7)# Nq/Ӊ`OxDŽfognEF s7n qNBvkDNeF+T'TV~Z'x!֍[v^z>e_C(|<oEn[1OkrgOFɦC@dtyrBdM:M5 AǒmD0Y;)Fy]ydNhk\$f"p%qyrwGY$'p_aI e5 \4FI[ g$͸"kD&5dB| @e9gpq"Rg |mIג*rt[r54|G)ؖL$h0zd'@sYMe)YqQ 0_[E("u9>R:IJ &[2+fkEYyioI8Yrؚ<-)wِl9b֞yhםyC[E 3t"@fz2+Wp`&;aEBZy!U^Y(ej ЇuluxUA' hy^Qʡ:tVGXVdFif" atgyOL̹ɨ75 c><;`"v$0'p2jZ/fT[tĊwh pxqz`; ;t &7jr+yTO~fFKF=ʤ'" A 0>P.CP>p BPqCp  @>!j> +:=v y `X_HvzU?yxVTb6wP>_> A !I$ "ĥq%p, 0B鐰_1 r # A"ybO0Bcֶv P mڔIFJ;VVƆ E@D0*z A;} % r1aBJ!$ P`ׅ%K8‰B_V6Ŷue exTC1WTb;l $ =ʠS >{Laa;C3r)B?#Zօb_(;GRn(")᪆N;PCH *sH谨#+S #8WK57JA3 $ EDY p/s Z+jּF(6&{Q >kX# P+0#ྉ¢ P 1ѪA1/ryE=yjZj}E:WF|HJ0 efLq48xL,m~"d,jsYu jȊ"<=ȡ6xGR|K| ^9\  gz6z7ĵԛ >AƔ !` <5 '`W*1$̎2 d{ ̄` Pj Y c bSP%l &>! B>@Q)5!4j="L!EjYʁKmYEY+͟ m  %6`0ڐ݇>$;l!4&jV|}ap !F %. *C QM}PR C'MCǔd ~ˬ]Έt L_q# b]~#,ʴr;~ i:1A1ERESP24FT!@A!UN7XBܿ56 W7C 0PjyQӍ 1QZNØ>ɜ^B\>% GLVk @Cm1uɞ!Ј.6k\m]>v&<)Lj,|^韜y|ҽaFdޱ2Bqz<$_&(*)e*WؠM>@B?D_FH[ѩ]}u>~52w;Cs0΃.ENlX_6~+`QbE9f?3kO@mKoSqm`?_W'vy^  {w{G}7yTݮ87*7D#C,?<Sj)C}?oAԏ)^O@_s=?&@oko4@SB'CC+(BŒɓ( ,ޭ̶й η,ڑxyO{&)Z‡ ].3BȀǏ CIɓ(G~P;0I @˘8?q̹<Y"PH'LrP312hF Ju)YNU:)fZ؋jJUܻ~u߿7ѣG+^`-JkLaFٶ}ΣS#Z\餧/A⇬gw{4dj u8sG*7廏#@سkν ֺ9t*?ol˟O ŔAXi5 Y"р`P J&U8ѥ]8!Orf^$Xч9mvX#o0h#iҩER$"SKUV)r+à0Dz\ʵ*sC`ϒ M"FCHGB="*L0,Ihhu] "df&bjmvil]f >\mߜJtш3rc&I-R>-֭Kҹ"':"s楧~竃~QnvtݶN;w>7;ڿ/{?{߻ioѳ^i`ct/:N>oz۬&~#2q.@H?P2K[ $@-l{`"D~Ȼ}/y AA0\ "RF.kVKDXvlS$p6*fa" !"b&A\C&&QH,%VuW|b"#^(t-fǶFqhGI1+ /G 4!j5E8mslV(g,#%FJFRwT]9ڱ+VP ʭ2J;PK22PK Simple Single-Source Replication Example

1 Simple Single-Source Replication Example

This chapter illustrates an example of a simple single-source replication environment that can be constructed using Oracle Streams.

This chapter contains these topics:

Overview of the Simple Single-Source Replication Example

The example in this chapter illustrates using Oracle Streams to replicate data in one table between two databases. A capture process captures data manipulation language (DML) and data definition language (DDL) changes made to the jobs table in the hr schema at the str1.example.com Oracle database, and a propagation propagates these changes to the str2.example.com Oracle database. Next, an apply process applies these changes at the str2.example.com database. This example assumes that the hr.jobs table is read-only at the str2.example.com database.

Figure 1-1 provides an overview of the environment.

Figure 1-1 Simple Example that Shares Data from a Single Source Database

Description of Figure 1-1 follows
Description of "Figure 1-1 Simple Example that Shares Data from a Single Source Database"

Prerequisites

The following prerequisites must be completed before you begin the example in this chapter.

  • Set the following initialization parameters to the values indicated:

    • GLOBAL_NAMES: This parameter must be set to TRUE at each database that is participating in your Oracle Streams environment.

    • COMPATIBLE: This parameter must be set to 10.2.0 or higher at each database that is participating in your Oracle Streams environment.

    • STREAMS_POOL_SIZE: Optionally set this parameter to an appropriate value for each database in the environment. This parameter specifies the size of the Oracle Streams pool. The Oracle Streams pool stores messages in a buffered queue and is used for internal communications during parallel capture and apply. When the MEMORY_TARGET, MEMORY_MAX_TARGET, or SGA_TARGET initialization parameter is set to a nonzero value, the Oracle Streams pool size is managed automatically.


      See Also:

      Oracle Streams Replication Administrator's Guide for information about other initialization parameters that are important in an Oracle Streams environment

  • Any database producing changes that will be captured must be running in ARCHIVELOG mode. In this example, changes are produced at str1.example.com, and so str1.example.com must be running in ARCHIVELOG mode.


    See Also:

    Oracle Database Administrator's Guide for information about running a database in ARCHIVELOG mode

  • Configure your network and Oracle Net so that the str1.example.com database can communicate with the str2.example.com database.

  • Create an Oracle Streams administrator at each database in the replication environment. In this example, the databases are str1.example.com and str2.example.com. This example assumes that the user name of the Oracle Streams administrator is strmadmin.


    See Also:

    Oracle Streams Replication Administrator's Guide for instructions about creating an Oracle Streams administrator

Create Queues and Database Links

Complete the following steps to create queues and database links for an Oracle Streams replication environment that includes two Oracle databases.

  1. Show Output and Spool Results

  2. Create the ANYDATA Queue at str1.example.com

  3. Create the Database Link at str1.example.com

  4. Set Up the ANYDATA Queue at str2.example.com

  5. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to all of the databases in the environment.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL streams_setup_simple.out

/*
Step 2   Create the ANYDATA Queue at str1.example.com

Connect as the Oracle Streams administrator at the database where you want to capture changes. In this example, that database is str1.example.com.

*/

CONNECT strmadmin@str1.example.com

/*

Run the SET_UP_QUEUE procedure to create a queue named streams_queue at str1.example.com. This queue will function as the ANYDATA queue by holding the captured changes that will be propagated to other databases.

Running the SET_UP_QUEUE procedure performs the following actions:

  • Creates a queue table named streams_queue_table. This queue table is owned by the Oracle Streams administrator (strmadmin) and uses the default storage of this user.

  • Creates a queue named streams_queue owned by the Oracle Streams administrator (strmadmin).

  • Starts the queue.

*/

EXEC DBMS_STREAMS_ADM.SET_UP_QUEUE();

/*
Step 3   Create the Database Link at str1.example.com

Create the database link from the database where changes are captured to the database where changes are propagated. In this example, the database where changes are captured is str1.example.com, and these changes are propagated to str2.example.com.

*/

ACCEPT password PROMPT 'Enter password for user: ' HIDE

CREATE DATABASE LINK str2.example.com CONNECT TO strmadmin 
   IDENTIFIED BY &password USING 'str2.example.com';

/*
Step 4   Set Up the ANYDATA Queue at str2.example.com

Connect as the Oracle Streams administrator at str2.example.com.

*/

CONNECT strmadmin@str2.example.com

/*

Run the SET_UP_QUEUE procedure to create a queue named streams_queue at str2.example.com. This queue will function as the ANYDATA queue by holding the changes that will be applied at this database.

Running the SET_UP_QUEUE procedure performs the following actions:

  • Creates a queue table named streams_queue_table. This queue table is owned by the Oracle Streams administrator (strmadmin) and uses the default storage of this user.

  • Creates a queue named streams_queue owned by the Oracle Streams administrator (strmadmin).

  • Starts the queue.

*/

EXEC  DBMS_STREAMS_ADM.SET_UP_QUEUE();

/*
Step 5   Check the Spool Results

Check the streams_setup_simple.out spool file to ensure that all actions finished successfully after this script is completed.

*/

SET ECHO OFF
SPOOL OFF

/*************************** END OF SCRIPT ******************************/

Configure Capture, Propagation, and Apply for Changes to One Table

Complete the following steps to specify the capture, propagation, and apply definitions for the hr.jobs table using the DBMS_STEAMS_ADM package.

  1. Show Output and Spool Results

  2. Configure Propagation at str1.example.com

  3. Configure the Capture Process at str1.example.com

  4. Set the Instantiation SCN for the hr.jobs Table at str2.example.com

  5. Configure the Apply Process at str2.example.com

  6. Start the Apply Process at str2.example.com

  7. Start the Capture Process at str1.example.com

  8. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to all of the databases in the environment.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL streams_share_jobs.out

/*
Step 2   Configure Propagation at str1.example.com

Connect to str1.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@str1.example.com

/*

Configure and schedule propagation of DML and DDL changes to the hr.jobs table from the queue at str1.example.com to the queue at str2.example.com.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_PROPAGATION_RULES(
    table_name              => 'hr.jobs', 
    streams_name            => 'str1_to_str2', 
    source_queue_name       => 'strmadmin.streams_queue',
    destination_queue_name  => 'strmadmin.streams_queue@str2.example.com',
    include_dml             => TRUE,
    include_ddl             => TRUE,
    source_database         => 'str1.example.com',
    inclusion_rule          => TRUE,
    queue_to_queue          => TRUE);
END;
/

/*
Step 3   Configure the Capture Process at str1.example.com

Configure the capture process to capture changes to the hr.jobs table at str1.example.com. This step specifies that changes to this table are captured by the capture process and enqueued into the specified queue.

This step also prepares the hr.jobs table for instantiation and enables supplemental logging for any primary key, unique key, bitmap index, and foreign key columns in this table. Supplemental logging places additional information in the redo log for changes made to tables. The apply process needs this extra information to perform certain operations, such as unique row identification and conflict resolution. Because str1.example.com is the only database where changes are captured in this environment, it is the only database where supplemental logging must be enabled for the hr.jobs table.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_RULES(
    table_name     => 'hr.jobs',   
    streams_type   => 'capture',
    streams_name   => 'capture_simp',
    queue_name     => 'strmadmin.streams_queue',
    include_dml    => TRUE,
    include_ddl    => TRUE,
    inclusion_rule => TRUE);
END;
/

/*
Step 4   Set the Instantiation SCN for the hr.jobs Table at str2.example.com

This example assumes that the hr.jobs table exists at both the str1.example.com database and the str2.example.com database, and that this table is synchronized at these databases. Because the hr.jobs table already exists at str2.example.com, this example uses the GET_SYSTEM_CHANGE_NUMBER function in the DBMS_FLASHBACK package at str1.example.com to obtain the current SCN for the source database. This SCN is used at str2.example.com to run the SET_TABLE_INSTANTIATION_SCN procedure in the DBMS_APPLY_ADM package. Running this procedure sets the instantiation SCN for the hr.jobs table at str2.example.com.

The SET_TABLE_INSTANTIATION_SCN procedure controls which LCRs for a table are ignored by an apply process and which LCRs for a table are applied by an apply process. If the commit SCN of an LCR for a table from a source database is less than or equal to the instantiation SCN for that table at a destination database, then the apply process at the destination database discards the LCR. Otherwise, the apply process applies the LCR.

In this example, both of the apply process at str2.example.com will apply transactions to the hr.jobs table with SCNs that were committed after SCN obtained in this step.


Note:

This example assumes that the contents of the hr.jobs table at str1.example.com and str2.example.com are consistent when you complete this step. Ensure that there is no activity on this table while the instantiation SCN is being set. You might want to lock the table at each database while you complete this step to ensure consistency. If the table does not exist at the destination database, then you can use export/import for instantiation.

*/

DECLARE
  iscn  NUMBER;         -- Variable to hold instantiation SCN value
BEGIN
  iscn := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER();
  DBMS_APPLY_ADM.SET_TABLE_INSTANTIATION_SCN@STR2.EXAMPLE.COM(
    source_object_name    => 'hr.jobs',
    source_database_name  => 'str1.example.com',
    instantiation_scn     => iscn);
END;
/

/*
Step 5   Configure the Apply Process at str2.example.com

Connect to str2.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@str2.example.com

/*

Configure str2.example.com to apply changes to the hr.jobs table.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_RULES(
    table_name      => 'hr.jobs',
    streams_type    => 'apply', 
    streams_name    => 'apply_simp',
    queue_name      => 'strmadmin.streams_queue',
    include_dml     => TRUE,
    include_ddl     => TRUE,
    source_database => 'str1.example.com',
    inclusion_rule  => TRUE);
END;
/

/*
Step 6   Start the Apply Process at str2.example.com

Set the disable_on_error parameter to n so that the apply process will not be disabled if it encounters an error, and start the apply process at str2.example.com.

*/

BEGIN
  DBMS_APPLY_ADM.SET_PARAMETER(
    apply_name  => 'apply_simp', 
    parameter   => 'disable_on_error', 
    value       => 'N');
END;
/
 
BEGIN
  DBMS_APPLY_ADM.START_APPLY(
    apply_name  => 'apply_simp');
END;
/

/*
Step 7   Start the Capture Process at str1.example.com

Connect to str1.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@str1.example.com

/*

Start the capture process at str1.example.com.

*/

BEGIN
  DBMS_CAPTURE_ADM.START_CAPTURE(
    capture_name  => 'capture_simp');
END;
/

/*
Step 8   Check the Spool Results

Check the streams_share_jobs.out spool file to ensure that all actions finished successfully after this script is completed.

*/

SET ECHO OFF
SPOOL OFF

/*************************** END OF SCRIPT ******************************/

Make Changes to the hr.jobs Table and View Results

Complete the following steps to make DML and DDL changes to the hr.jobs table at str1.example.com and then confirm that the changes were captured at str1.example.com, propagated from str1.example.com to str2.example.com, and applied to the hr.jobs table at str2.example.com.

Step 1   Make Changes to hr.jobs at str1.example.com

Make the following changes to the hr.jobs table.

CONNECT hr@str1.example.com
Enter password: password

UPDATE hr.jobs SET max_salary=9545 WHERE job_id='PR_REP';
COMMIT;

ALTER TABLE hr.jobs ADD(duties VARCHAR2(4000));
Step 2   Query and Describe the hr.jobs Table at str2.example.com

After some time passes to allow for capture, propagation, and apply of the changes performed in the previous step, run the following query to confirm that the UPDATE change was propagated and applied at str2.example.com:

CONNECT hr@str2.example.com
Enter password: password

SELECT * FROM hr.jobs WHERE job_id='PR_REP';

The value in the max_salary column should be 9545.

Next, describe the hr.jobs table to confirm that the ALTER TABLE change was propagated and applied at str2.example.com:

DESC hr.jobs

The duties column should be the last column.

PKPbKbPK Description of the illustration strex018.eps

This illustration shows an Oracle Streams replication environment that involves the following Oracle databases:

  • mult1.example.com

  • mult2.example.com

  • mult3.example.com

The mult1.example.com Oracle database has the following configuration:

  • The following three ANYDATA queues owned by the strmadmin user: captured_mult1, from_mult2, and from_mult3.

  • A capture process named capture_hrmult that captures DML and DDL changes to the tables in the hrmult schema: countries, departments, employees, job_history, jobs, locations, and regions. The capture process enqueues these changes into the captured_mult1 queue.

  • A propagation named mult1_to_mult2 that propagates changes from the local captured_mult1 queue to the from_mult1 queue at mult2.example.com.

  • A propagation named mult1_to_mult3 that propagates changes from the local captured_mult1 queue to the from_mult1 queue at mult3.example.com.

  • An apply process named apply_from_mult2 that dequeues changes that originated at mult2.example.com from the from_mult2 queue and applies them to the tables in the hrmult schema.

  • An apply process named apply_from_mult3 that dequeues changes that originated at mult3.example.com from the from_mult3 queue and applies them to the tables in the hrmult schema.

The mult2.example.com Oracle database has the following configuration:

  • The following three ANYDATA queues owned by the strmadmin user: captured_mult2, from_mult1, and from_mult3.

  • A capture process named capture_hrmult that captures DML and DDL changes to the tables in the hrmult schema: countries, departments, employees, job_history, jobs, locations, and regions. The capture process enqueues these changes into the captured_mult2 queue.

  • A propagation named mult2_to_mult1 that propagates changes from the local captured_mult2 queue to the from_mult2 queue at mult1.example.com.

  • A propagation named mult2_to_mult3 that propagates changes from the local captured_mult2 queue to the from_mult2 queue at mult3.example.com.

  • An apply process named apply_from_mult1 that dequeues changes that originated at mult1.example.com from the from_mult1 queue and applies them to the tables in the hrmult schema.

  • An apply process named apply_from_mult3 that dequeues changes that originated at mult3.example.com from the from_mult3 queue and applies them to the tables in the hrmult schema.

The mult3.example.com Oracle database has the following configuration:

  • The following three ANYDATA queues owned by the strmadmin user: captured_mult3, from_mult1, and from_mult2.

  • A capture process named capture_hrmult that captures DML and DDL changes to the tables in the hrmult schema: countries, departments, employees, job_history, jobs, locations, and regions. The capture process enqueues these changes into the captured_mult3 queue.

  • A propagation named mult3_to_mult1 that propagates changes from the local captured_mult3 queue to the from_mult3 queue at mult1.example.com.

  • A propagation named mult3_to_mult2 that propagates changes from the local captured_mult3 queue to the from_mult3 queue at mult2.example.com.

  • An apply process named apply_from_mult1 that dequeues changes that originated at mult1.example.com from the from_mult1 queue and applies them to the tables in the hrmult schema.

  • An apply process named apply_from_mult2 that dequeues changes that originated at mult2.example.com from the from_mult2 queue and applies them to the tables in the hrmult schema.

PK>PK Description of the illustration strex003.eps

This illustration shows the existing configuration, as well as the following tables added to dbs3.example.com:

  • hr.departments

  • hr.employees

  • hr.job_history

  • hr.jobs

The existing configuration includes the following databases:

  • dbs1.example.com (Oracle database)

  • dbs2.example.com (Oracle database)

  • dbs3.example.com (Oracle database)

  • dbs4.example.com (Sybase database)

The dbs1.example.com Oracle database has the following configuration:

  • One Oracle Streams queue named streams_queue and owned by the user strmadmin

  • A capture process named capture that captures DML and DDL changes to the tables in the hr schema: countries, departments, employees, job_history, jobs, locations, and regions

  • A propagation named dbs1_to_dbs2 that propagates changes from the local queue to the strmadmin.streams_queue queue at dbs2.example.com

The dbs2.example.com Oracle database has the following configuration:

  • One Oracle Streams queue named streams_queue and owned by the user strmadmin.

  • An apply process named apply_db2 that applies changes to the hr.assignments table.

  • An apply process named apply_db4 that applies changes to the hr.jobs table at dbs4.example.com, which is a Sybase database. The apply process uses a gateway to apply changes to dbs4.example.com.

  • A custom rule-based transformation specified for apply_db2 that modifies LCRs that include DML changes to the hr.jobs table to LCRs that include DML changes to the hr.assignments table. The transformation function is named to_assignments.

  • A propagation named dbs2_to_dbs3 that propagates change from the local queue to the strmadmin.streams_queue queue at dbs3.example.com. These changes originated at dbs1.example.com.

The dbs3.example.com Oracle database has the following configuration:

  • One Oracle Streams queue named streams_queue and owned by the user strmadmin

  • An apply process named apply that applies changes to the hr.countries, hr.regions, and hr.locations tables

The dbs4.example.com Sybase database contains an hr.jobs table.

PK'i d PK Description of the illustration strex036.eps

This illustration shows the following Oracle Streams configuration at the cpap.example.com database:

  • One queue named streams_queue and owned by the user strmadmin.

  • One capture process named capture_emp captures DML changes to the hr.employees table and enqueues these changes into the streams_queue.

  • An apply process named apply_emp that sends row LCRs involving the hr.employees table to a procedure DML handler, which is the emp_dml_handler PL/SQL procedure. The apply process reenqueues all messages back into the streams_queue for processing by an application.

  • The emp_dml_handler converts DELETE operations on the hr.employees table into INSERT operations on the hr.emp_del table and inserts these changes into the table.

PKTy#{PK Description of the illustration strex002.eps

This illustration shows an Oracle Streams replication environment that involves the following databases:

  • dbs1.example.com (Oracle database)

  • dbs2.example.com (Oracle database)

  • dbs3.example.com (Oracle database)

  • dbs4.example.com (Sybase database)

The dbs1.example.com Oracle database has the following configuration:

  • One Oracle Streams queue named streams_queue and owned by the user strmadmin

  • A capture process named capture that captures DML and DDL changes to the tables in the hr schema: countries, departments, employees, job_history, jobs, locations, and regions

  • A propagation named dbs1_to_dbs2 that propagates changes from the local queue to the strmadmin.streams_queue queue at dbs2.example.com

The dbs2.example.com Oracle database has the following configuration:

  • One Oracle Streams queue named streams_queue and owned by the user strmadmin.

  • An apply process named apply_db2 that applies changes to the hr.assignments table.

  • An apply process named apply_db4 that applies changes to the hr.jobs table at dbs4.example.com, which is a Sybase database. The apply process uses a gateway to apply changes to dbs4.example.com.

  • A custom rule-based transformation specified for apply_db2 that modifies LCRs that include DML changes to the hr.jobs table to LCRs that include DML changes to the hr.assignments table. The transformation function is named to_assignments.

  • A propagation named dbs2_to_dbs3 that propagates change from the local queue to the strmadmin.streams_queue queue at dbs3.example.com. These changes originated at dbs1.example.com.

The dbs3.example.com Oracle database has the following configuration:

  • One Oracle Streams queue named streams_queue and owned by the user strmadmin

  • An apply process named apply that applies changes to the hr.countries, hr.regions, and hr.locations tables

The dbs4.example.com Sybase database contains an hr.jobs table.

PK] X PK Description of the illustration strex001.eps

This illustration shows the existing configuration, as well as the added database dbs5.example.com. The dbs5.example.com Oracle database has the following configuration:

  • One Oracle Streams queue named streams_queue and owned by the user strmadmin

  • An apply process named apply that applies changes to all of the tables in the hr schema: countries, departments, employees, job_history, jobs, locations, and regions

The existing configuration includes the following databases:

  • dbs1.example.com (Oracle database)

  • dbs2.example.com (Oracle database)

  • dbs3.example.com (Oracle database)

  • dbs4.example.com (Sybase database)

The dbs1.example.com Oracle database has the following configuration:

  • One Oracle Streams queue named streams_queue and owned by the user strmadmin

  • A capture process named capture that captures DML and DDL changes to the tables in the hr schema: countries, departments, employees, job_history, jobs, locations, and regions

  • A propagation named dbs1_to_dbs2 that propagates changes from the local queue to the strmadmin.streams_queue queue at dbs2.example.com

The dbs2.example.com Oracle database has the following configuration:

  • One Oracle Streams queue named streams_queue and owned by the user strmadmin.

  • An apply process named apply_db2 that applies changes to the hr.assignments table.

  • An apply process named apply_db4 that applies changes to the hr.jobs table at dbs4.example.com, which is a Sybase database. The apply process uses a gateway to apply changes to dbs4.example.com.

  • A custom rule-based transformation specified for apply_db2 that modifies LCRs that include DML changes to the hr.jobs table to LCRs that include DML changes to the hr.assignments table. The transformation function is named to_assignments.

  • A propagation named dbs2_to_dbs3 that propagates change from the local queue to the strmadmin.streams_queue queue at dbs3.example.com. These changes originated at dbs1.example.com.

The dbs3.example.com Oracle database has the following configuration:

  • One Oracle Streams queue named streams_queue and owned by the user strmadmin

  • An apply process named apply that applies changes to all of the tables in the hr schema: countries, departments, employees, job_history, jobs, locations, and regions

The dbs4.example.com Sybase database contains an hr.jobs table.

PKh`&!PK Description of the illustration strex035.eps

This illustration shows an Oracle Streams replication environment that involves the following Oracle databases:

  • str1.example.com

  • str2.example.com

The str1.example.com Oracle database has the following configuration:

  • One Oracle Streams queue named streams_queue and owned by the user strmadmin

  • A capture process named capture_simp that captures DML and DDL changes to the hr.jobs table

  • A propagation named str1_to_str2 that propagates change from the local queue to the strmadmin.streams_queue queue at str2.example.com

The str2.example.com Oracle database has the following configuration:

  • One Oracle Streams queue named streams_queue and owned by the user strmadmin

  • An apply process named apply_simp that applies changes to the hr.jobs table

PKPK Single-Source Heterogeneous Replication Example

2 Single-Source Heterogeneous Replication Example

This chapter illustrates an example of a single-source heterogeneous replication environment that can be constructed using Oracle Streams, as well as the tasks required to add new objects and databases to such an environment.

This chapter contains these topics:

Overview of the Single-Source Heterogeneous Replication Example

This example illustrates using Oracle Streams to replicate data between four databases. The environment is heterogeneous because three of the databases are Oracle databases and one is a Sybase database. DML and DDL changes made to tables in the hr schema at the dbs1.example.com Oracle database are captured and propagated to the other two Oracle databases. Only DML changes are captured and propagated to the dbs4.example.com database, because an apply process cannot apply DDL changes to a non-Oracle database. Changes to the hr schema occur only at dbs1.example.com. The hr schema is read-only at the other databases in the environment.

Figure 2-1 provides an overview of the environment.

Figure 2-1 Sample Environment that Shares Data from a Single Source Database

Description of Figure 2-1 follows
Description of "Figure 2-1 Sample Environment that Shares Data from a Single Source Database"

As illustrated in Figure 2-1, dbs1.example.com contains the following tables in the hr schema:

  • countries

  • departments

  • employees

  • job_history

  • jobs

  • locations

  • regions

This example uses directed networks, which means that captured changes at a source database are propagated to another database through one or more intermediate databases. Here, the dbs1.example.com database propagates changes to the dbs3.example.com database through the intermediate database dbs2.example.com. This configuration is an example of queue forwarding in a directed network. Also, the dbs1.example.com database propagates changes to the dbs2.example.com database, which applies the changes directly to the dbs4.example.com database through an Oracle Database Gateway.

Some of the databases in the environment do not have certain tables. If the database is not an intermediate database for a table and the database does not contain the table, then changes to the table do not need to be propagated to that database. For example, the departments, employees, job_history, and jobs tables do not exist at dbs3.example.com. Therefore, dbs2.example.com does not propagate changes to these tables to dbs3.example.com.

In this example, Oracle Streams is used to perform the following series of actions:

  1. The capture process captures DML and DDL changes for all of the tables in the hr schema and enqueues them at the dbs1.example.com database. In this example, changes to only four of the seven tables are propagated to destination databases, but in the example that illustrates "Add Objects to an Existing Oracle Streams Replication Environment", the remaining tables in the hr schema are added to a destination database.

  2. The dbs1.example.com database propagates these changes in the form of messages to a queue at dbs2.example.com.

  3. At dbs2.example.com, DML changes to the jobs table are transformed into DML changes for the assignments table (which is a direct mapping of jobs) and then applied. Changes to other tables in the hr schema are not applied at dbs2.example.com.

  4. Because the queue at dbs3.example.com receives changes from the queue at dbs2.example.com that originated in countries, locations, and regions tables at dbs1.example.com, these changes are propagated from dbs2.example.com to dbs3.example.com. This configuration is an example of directed networks.

  5. The apply process at dbs3.example.com applies changes to the countries, locations, and regions tables.

  6. Because dbs4.example.com, a Sybase database, receives changes from the queue at dbs2.example.com to the jobs table that originated at dbs1.example.com, these changes are applied remotely from dbs2.example.com using the dbs4.example.com database link through an Oracle Database Gateway. This configuration is an example of heterogeneous support.

Prerequisites

The following prerequisites must be completed before you begin the example in this chapter.

  • Set the following initialization parameters to the values indicated for all databases in the environment:

    • GLOBAL_NAMES: This parameter must be set to TRUE at each database that is participating in your Oracle Streams environment.

    • COMPATIBLE: This parameter must be set to 10.2.0 or higher.

    • STREAMS_POOL_SIZE: Optionally set this parameter to an appropriate value for each database in the environment. This parameter specifies the size of the Oracle Streams pool. The Oracle Streams pool stores messages in a buffered queue and is used for internal communications during parallel capture and apply. When the MEMORY_TARGET, MEMORY_MAX_TARGET, or SGA_TARGET initialization parameter is set to a nonzero value, the Oracle Streams pool size is managed automatically.


    See Also:

    Oracle Streams Replication Administrator's Guide for information about other initialization parameters that are important in an Oracle Streams environment

  • Any database producing changes that will be captured must be running in ARCHIVELOG mode. In this example, changes are produced at dbs1.example.com, and so dbs1.example.com must be running in ARCHIVELOG mode.


    See Also:

    Oracle Database Administrator's Guide for information about running a database in ARCHIVELOG mode

  • Configure an Oracle Database Gateway on dbs2.example.com to communicate with the Sybase database dbs4.example.com.

  • At the Sybase database dbs4.example.com, set up the hr user.


    See Also:

    Your Sybase documentation for information about creating users and tables in your Sybase database

  • Instantiate the hr.jobs table from the dbs1.example.com Oracle database at the dbs4.example.com Sybase database.

  • Configure your network and Oracle Net so that the following databases can communicate with each other:

    • dbs1.example.com and dbs2.example.com

    • dbs2.example.com and dbs3.example.com

    • dbs2.example.com and dbs4.example.com

    • dbs3.example.com and dbs1.example.com (for optional Data Pump network instantiation)

  • Create an Oracle Streams administrator at each Oracle database in the replication environment. In this example, the databases are dbs1.example.com, dbs2.example.com, and dbs3.example.com. This example assumes that the user name of the Oracle Streams administrator is strmadmin.


    See Also:

    Oracle Streams Replication Administrator's Guide for instructions about creating an Oracle Streams administrator

Create Queues and Database Links

Complete the following steps to create queues and database links for an Oracle Streams replication environment that includes three Oracle databases and one Sybase database:

  1. Show Output and Spool Results

  2. Create the ANYDATA Queue at dbs1.example.com

  3. Create the Database Link at dbs1.example.com

  4. Create the ANYDATA Queue at dbs2.example.com

  5. Create the Database Links at dbs2.example.com

  6. Create the hr.assignments Table at dbs2.example.com

  7. Create the ANYDATA Queue at dbs3.example.com

  8. Create the Database Links at dbs2.example.com

  9. Drop All of the Tables in the hr Schema at dbs3.example.com

  10. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to all of the databases in the environment.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL streams_setup_single.out

/*
Step 2   Create the ANYDATA Queue at dbs1.example.com

Connect as the Oracle Streams administrator at the database where you want to capture changes. In this example, that database is dbs1.example.com.

*/

CONNECT strmadmin@dbs1.example.com

/*

Run the SET_UP_QUEUE procedure to create a queue named streams_queue at dbs1.example.com. This queue will function as the ANYDATA queue by holding the captured changes that will be propagated to other databases.

Running the SET_UP_QUEUE procedure performs the following actions:

  • Creates a queue table named streams_queue_table. This queue table is owned by the Oracle Streams administrator (strmadmin) and uses the default storage of this user.

  • Creates a queue named streams_queue owned by the Oracle Streams administrator (strmadmin).

  • Starts the queue.

*/

EXEC DBMS_STREAMS_ADM.SET_UP_QUEUE();

/*
Step 3   Create the Database Link at dbs1.example.com

Create the database link from the database where changes are captured to the database where changes are propagated. In this example, the database where changes are captured is dbs1.example.com, and these changes are propagated to dbs2.example.com.

*/

ACCEPT password PROMPT 'Enter password for user: ' HIDE

CREATE DATABASE LINK dbs2.example.com CONNECT TO strmadmin 
   IDENTIFIED BY &password USING 'dbs2.example.com';

/*
Step 4   Create the ANYDATA Queue at dbs2.example.com

Connect as the Oracle Streams administrator at dbs2.example.com.

*/

CONNECT strmadmin@dbs2.example.com

/*

Run the SET_UP_QUEUE procedure to create a queue named streams_queue at dbs2.example.com. This queue will function as the ANYDATA queue by holding the changes that will be applied at this database and the changes that will be propagated to other databases.

Running the SET_UP_QUEUE procedure performs the following actions:

  • Creates a queue table named streams_queue_table. This queue table is owned by the Oracle Streams administrator (strmadmin) and uses the default storage of this user.

  • Creates a queue named streams_queue owned by the Oracle Streams administrator (strmadmin).

  • Starts the queue.

*/

EXEC  DBMS_STREAMS_ADM.SET_UP_QUEUE();

/*
Step 5   Create the Database Links at dbs2.example.com

Create the database links to the databases where changes are propagated. In this example, database dbs2.example.com propagates changes to dbs3.example.com, which is another Oracle database, and to dbs4.example.com, which is a Sybase database. Notice that the database link to the Sybase database connects to the owner of the tables, not to the Oracle Streams administrator. This database link can connect to any user at dbs4.example.com that has privileges to change the hr.jobs table at that database.


Note:

On some non-Oracle databases, including Sybase, you must ensure that the characters in the user name and password are in the correct case. Therefore, double quotation marks are specified for the user name and password at the Sybase database.

*/

CREATE DATABASE LINK dbs3.example.com CONNECT TO strmadmin 
   IDENTIFIED BY &password USING 'dbs3.example.com';

CREATE DATABASE LINK dbs4.example.com CONNECT TO "hr" 
   IDENTIFIED BY "&password" USING 'dbs4.example.com';

/*
Step 6   Create the hr.assignments Table at dbs2.example.com

This example illustrates a custom rule-based transformation in which changes to the hr.jobs table at dbs1.example.com are transformed into changes to the hr.assignments table at dbs2.example.com. You must create the hr.assignments table on dbs2.example.com for the transformation portion of this example to work properly.


Note:

Instead of using a custom rule-based transformation to change the name of the table, you can use a RENAME_TABLE declarative rule-based transformation. See Oracle Streams Concepts and Administration.

Connect as hr at dbs2.example.com.

*/

CONNECT hr@dbs2.example.com

/*

Create the hr.assignments table in the dbs2.example.com database.

*/

CREATE TABLE hr.assignments AS SELECT * FROM hr.jobs;

ALTER TABLE hr.assignments ADD PRIMARY KEY (job_id);

/*
Step 7   Create the ANYDATA Queue at dbs3.example.com

Connect as the Oracle Streams administrator at dbs3.example.com.

*/

CONNECT strmadmin@dbs3.example.com

/*

Run the SET_UP_QUEUE procedure to create a queue named streams_queue at dbs3.example.com. This queue will function as the ANYDATA queue by holding the changes that will be applied at this database.

Running the SET_UP_QUEUE procedure performs the following actions:

  • Creates a queue table named streams_queue_table. This queue table is owned by the Oracle Streams administrator (strmadmin) and uses the default storage of this user.

  • Creates a queue named streams_queue owned by the Oracle Streams administrator (strmadmin).

  • Starts the queue.

*/

EXEC  DBMS_STREAMS_ADM.SET_UP_QUEUE();

/*
Step 8   Create a Database Link at dbs3.example.com to dbs1.example.com

Create a database link from dbs3.example.com to dbs1.example.com. Later in this example, this database link is used for the instantiation of some of the database objects that were dropped in Step 9. This example uses the DBMS_DATAPUMP package to perform a network import of these database objects directly from the dbs1.example.com database. Because this example performs a network import, no dump file is required.

Alternatively, you can perform an export at the source database dbs1.example.com, transfer the export dump file to the destination database dbs3.example.com, and then import the export dump file at the destination database. In this case, the database link created in this step is not required.

*/

CREATE DATABASE LINK dbs1.example.com CONNECT TO strmadmin 
   IDENTIFIED BY &password USING 'dbs1.example.com';

/*
Step 9   Drop All of the Tables in the hr Schema at dbs3.example.com

This example illustrates instantiating tables in the hr schema by importing them from dbs1.example.com into dbs3.example.com with Data Pump. You must delete these tables at dbs3.example.com for the instantiation portion of this example to work properly.

Connect as hr at dbs3.example.com.

*/

CONNECT hr@dbs3.example.com

/*

Drop all tables in the hr schema in the dbs3.example.com database.


Note:

If you complete this step and drop all of the tables in the hr schema, then you should complete the remaining sections of this example to reinstantiate the hr schema at dbs3.example.com. If the hr schema does not exist in an Oracle database, then some examples in the Oracle documentation set can fail.

*/

DROP TABLE hr.countries CASCADE CONSTRAINTS;
DROP TABLE hr.departments CASCADE CONSTRAINTS;
DROP TABLE hr.employees CASCADE CONSTRAINTS;
DROP TABLE hr.job_history CASCADE CONSTRAINTS;
DROP TABLE hr.jobs CASCADE CONSTRAINTS;
DROP TABLE hr.locations CASCADE CONSTRAINTS;
DROP TABLE hr.regions CASCADE CONSTRAINTS;

/*
Step 10   Check the Spool Results

Check the streams_setup_single.out spool file to ensure that all actions finished successfully after this script is completed.

*/

SET ECHO OFF
SPOOL OFF

/*************************** END OF SCRIPT ******************************/

Example Scripts for Sharing Data from One Database

This example illustrates two ways to accomplish the replication of the tables in the hr schema using Oracle Streams.

  • "Simple Configuration for Sharing Data from a Single Database" demonstrates a simple way to configure the environment. This example uses the DBMS_STREAMS_ADM package to create a capture process, propagations, and apply processes, as well as the rule sets associated with them. Using the DBMS_STREAMS_ADM package is the simplest way to configure an Oracle Streams environment.

  • "Flexible Configuration for Sharing Data from a Single Database" demonstrates a more flexible way to configure this environment. This example uses the DBMS_CAPTURE_ADM package to create a capture process, the DBMS_PROPAGATION_ADM package to create propagations, and the DBMS_APPLY_ADM package to create apply processes. Also, this example uses the DBMS_RULES_ADM package to create and populate the rule sets associated with these capture processes, propagations, and apply processes. Using these packages, instead of the DBMS_STREAMS_ADM package, provides more configuration options and flexibility.


    Note:

    These examples illustrate two different ways to configure the same Oracle Streams environment. Therefore, you should run only one of the examples for a particular distributed database system. Otherwise, errors stating that objects already exist will result.

Simple Configuration for Sharing Data from a Single Database

Complete the following steps to specify the capture, propagation, and apply definitions using primarily the DBMS_STEAMS_ADM package.

  1. Show Output and Spool Results

  2. Configure Propagation at dbs1.example.com

  3. Configure the Capture Process at dbs1.example.com

  4. Set the Instantiation SCN for the Existing Tables at Other Databases

  5. Instantiate the dbs1.example.com Tables at dbs3.example.com

  6. Configure the Apply Process at dbs3.example.com

  7. Specify hr as the Apply User for the Apply Process at dbs3.example.com

  8. Grant the hr User Execute Privilege on the Apply Process Rule Set

  9. Start the Apply Process at dbs3.example.com

  10. Configure Propagation at dbs2.example.com

  11. Create the Custom Rule-Based Transformation for Row LCRs at dbs2.example.com

  12. Configure the Apply Process for Local Apply at dbs2.example.com

  13. Specify hr as the Apply User for the Apply Process at dbs2.example.com

  14. Grant the hr User Execute Privilege on the Apply Process Rule Set

  15. Start the Apply Process at dbs2.example.com for Local Apply

  16. Configure the Apply Process at dbs2.example.com for Apply at dbs4.example.com

  17. Start the Apply Process at dbs2.example.com for Apply at dbs4.example.com

  18. Start the Capture Process at dbs1.example.com

  19. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to all of the databases in the environment.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL streams_share_schema1.out

/*
Step 2   Configure Propagation at dbs1.example.com

Connect to dbs1.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs1.example.com

/*

Configure and schedule propagation of DML and DDL changes in the hr schema from the queue at dbs1.example.com to the queue at dbs2.example.com.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_SCHEMA_PROPAGATION_RULES(
    schema_name             => 'hr', 
    streams_name            => 'dbs1_to_dbs2', 
    source_queue_name       => 'strmadmin.streams_queue',
    destination_queue_name  => 'strmadmin.streams_queue@dbs2.example.com',
    include_dml             => TRUE,
    include_ddl             => TRUE,
    source_database         => 'dbs1.example.com',
    inclusion_rule          => TRUE,
    queue_to_queue          => TRUE);
END;
/

/*
Step 3   Configure the Capture Process at dbs1.example.com

Configure the capture process to capture changes to the entire hr schema at dbs1.example.com. This step specifies that changes to the tables in the specified schema are captured by the capture process and enqueued into the specified queue.

This step also prepares the hr schema for instantiation and enables supplemental logging for any primary key, unique key, bitmap index, and foreign key columns in the tables in this schema. Supplemental logging places additional information in the redo log for changes made to tables. The apply process needs this extra information to perform certain operations, such as unique row identification and conflict resolution. Because dbs1.example.com is the only database where changes are captured in this environment, it is the only database where you must specify supplemental logging for the tables in the hr schema.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_SCHEMA_RULES(
    schema_name    => 'hr',   
    streams_type   => 'capture',
    streams_name   => 'capture',
    queue_name     => 'strmadmin.streams_queue',
    include_dml    => TRUE, 
    include_ddl    => TRUE,
    inclusion_rule => TRUE);
END;
/

/*
Step 4   Set the Instantiation SCN for the Existing Tables at Other Databases

In this example, the hr.jobs table already exists at dbs2.example.com and dbs4.example.com. At dbs2.example.com, this table is named assignments, but it has the same shape and data as the jobs table at dbs1.example.com. Also, in this example, dbs4.example.com is a Sybase database. All of the other tables in the Oracle Streams environment are instantiated at the other destination databases using Data Pump import.

Because the hr.jobs table already exists at dbs2.example.com and dbs4.example.com, this example uses the GET_SYSTEM_CHANGE_NUMBER function in the DBMS_FLASHBACK package at dbs1.example.com to obtain the current SCN for the database. This SCN is used at dbs2.example.com to run the SET_TABLE_INSTANTIATION_SCN procedure in the DBMS_APPLY_ADM package. Running this procedure twice sets the instantiation SCN for the hr.jobs table at dbs2.example.com and dbs4.example.com.

The SET_TABLE_INSTANTIATION_SCN procedure controls which LCRs for a table are ignored by an apply process and which LCRs for a table are applied by an apply process. If the commit SCN of an LCR for a table from a source database is less than or equal to the instantiation SCN for that table at a destination database, then the apply process at the destination database discards the LCR. Otherwise, the apply process applies the LCR.

In this example, both of the apply processes at dbs2.example.com will apply transactions to the hr.jobs table with SCNs that were committed after SCN obtained in this step.


Note:

This example assumes that the contents of the hr.jobs table at dbs1.example.com, dbs2.example.com (as hr.assignments), and dbs4.example.com are consistent when you complete this step. You might want to lock the table at each database while you complete this step to ensure consistency.

*/

DECLARE
  iscn  NUMBER;         -- Variable to hold instantiation SCN value
BEGIN
  iscn := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER();
  DBMS_APPLY_ADM.SET_TABLE_INSTANTIATION_SCN@DBS2.EXAMPLE.COM(
    source_object_name    => 'hr.jobs',
    source_database_name  => 'dbs1.example.com',
    instantiation_scn     => iscn);
  DBMS_APPLY_ADM.SET_TABLE_INSTANTIATION_SCN@DBS2.EXAMPLE.COM(
    source_object_name    => 'hr.jobs',
    source_database_name  => 'dbs1.example.com',
    instantiation_scn     => iscn,
    apply_database_link   => 'dbs4.example.com');
END;
/

/*
Step 5   Instantiate the dbs1.example.com Tables at dbs3.example.com

This example performs a network Data Pump import of the following tables:

  • hr.countries

  • hr.locations

  • hr.regions

A network import means that Data Pump imports these tables from dbs1.example.com without using an export dump file.


See Also:

Oracle Database Utilities for information about performing an import

Connect to dbs3.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs3.example.com

/*

This example will do a table import using the DBMS_DATAPUMP package. For simplicity, exceptions from any of the API calls will not be trapped. However, Oracle recommends that you define exception handlers and call GET_STATUS to retrieve more detailed error information if a failure occurs. If you want to monitor the import, then query the DBA_DATAPUMP_JOBS data dictionary view at the import database.

*/

SET SERVEROUTPUT ON
DECLARE
  h1        NUMBER;         -- Data Pump job handle
  sscn      NUMBER;         -- Variable to hold current source SCN
  job_state VARCHAR2(30);   -- To keep track of job state
  js        ku$_JobStatus;  -- The job status from GET_STATUS
  sts       ku$_Status;     -- The status object returned by GET_STATUS
  job_not_exist    exception;
  pragma exception_init(job_not_exist, -31626);
BEGIN
-- Create a (user-named) Data Pump job to do a table-level import.
  h1 := DBMS_DATAPUMP.OPEN(
          operation   => 'IMPORT',
          job_mode    => 'TABLE',
          remote_link => 'DBS1.EXAMPLE.COM',
          job_name    => 'dp_sing1');
-- A metadata filter is used to specify the schema that owns the tables 
-- that will be imported.
  DBMS_DATAPUMP.METADATA_FILTER(
    handle    => h1,
    name      => 'SCHEMA_EXPR',
    value     => '=''HR''');
-- A metadata filter is used to specify the tables that will be imported.
  DBMS_DATAPUMP.METADATA_FILTER(
    handle    => h1,
    name      => 'NAME_EXPR',
    value     => 'IN(''COUNTRIES'', ''REGIONS'', ''LOCATIONS'')');
-- Get the current SCN of the source database, and set the FLASHBACK_SCN 
-- parameter to this value to ensure consistency between all of the 
-- objects included in the import.
  sscn := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER@dbs1.example.com(); 
  DBMS_DATAPUMP.SET_PARAMETER(
    handle => h1,
    name   => 'FLASHBACK_SCN',
    value  => sscn); 
-- Start the job. 
  DBMS_DATAPUMP.START_JOB(h1);
-- The import job should be running. In the following loop, the job
-- is monitored until it completes.
  job_state := 'UNDEFINED';
  BEGIN
    WHILE (job_state != 'COMPLETED') AND (job_state != 'STOPPED') LOOP
      sts:=DBMS_DATAPUMP.GET_STATUS(
             handle  => h1,
             mask    => DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR +
                        DBMS_DATAPUMP.KU$_STATUS_JOB_STATUS +
                        DBMS_DATAPUMP.KU$_STATUS_WIP,
             timeout => -1);
      js := sts.job_status;
      DBMS_LOCK.SLEEP(10);
      job_state := js.state;
    END LOOP;
  -- Gets an exception when job no longer exists
    EXCEPTION WHEN job_not_exist THEN
      DBMS_OUTPUT.PUT_LINE('Data Pump job has completed');
      DBMS_OUTPUT.PUT_LINE('Instantiation SCN: ' ||sscn);
  END;
END;
/

/*
Step 6   Configure the Apply Process at dbs3.example.com

Connect to dbs3.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs3.example.com

/*

Configure dbs3.example.com to apply changes to the countries table, locations table, and regions table.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_RULES(
    table_name      => 'hr.countries',
    streams_type    => 'apply', 
    streams_name    => 'apply',
    queue_name      => 'strmadmin.streams_queue',
    include_dml     => TRUE,
    include_ddl     => TRUE,
    source_database => 'dbs1.example.com',
    inclusion_rule  => TRUE);
END;
/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_RULES(
    table_name      => 'hr.locations',
    streams_type    => 'apply', 
    streams_name    => 'apply',
    queue_name      => 'strmadmin.streams_queue',
    include_dml     => TRUE,
    include_ddl     => TRUE,
    source_database => 'dbs1.example.com',
    inclusion_rule  => TRUE);
END;
/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_RULES(
    table_name      => 'hr.regions',
    streams_type    => 'apply', 
    streams_name    => 'apply',
    queue_name      => 'strmadmin.streams_queue',
    include_dml     => TRUE,
    include_ddl     => TRUE,
    source_database => 'dbs1.example.com',
    inclusion_rule  => TRUE);
END;
/

/*
Step 7   Specify hr as the Apply User for the Apply Process at dbs3.example.com

In this example, the hr user owns all of the database objects for which changes are applied by the apply process at this database. Therefore, hr already has the necessary privileges to change these database objects, and it is convenient to make hr the apply user.

When the apply process was created in the previous step, the Oracle Streams administrator strmadmin was specified as the apply user by default, because strmadmin ran the procedure that created the apply process. Instead of specifying hr as the apply user, you could retain strmadmin as the apply user, but then you must grant strmadmin privileges on all of the database objects for which changes are applied and privileges to execute all user procedures used by the apply process. In an environment where an apply process applies changes to database objects in multiple schemas, it might be more convenient to use the Oracle Streams administrator as the apply user.


See Also:

Oracle Streams Replication Administrator's Guide for more information about configuring an Oracle Streams administrator

*/

BEGIN
  DBMS_APPLY_ADM.ALTER_APPLY(
    apply_name => 'apply',
    apply_user => 'hr');
END;
/

/*
Step 8   Grant the hr User Execute Privilege on the Apply Process Rule Set

Because the hr user was specified as the apply user in the previous step, the hr user requires EXECUTE privilege on the positive rule set used by the apply process

*/

DECLARE
   rs_name  VARCHAR2(64);   -- Variable to hold rule set name
BEGIN
  SELECT RULE_SET_OWNER||'.'||RULE_SET_NAME 
    INTO rs_name 
    FROM DBA_APPLY 
    WHERE APPLY_NAME='APPLY';
  DBMS_RULE_ADM.GRANT_OBJECT_PRIVILEGE(
    privilege   => SYS.DBMS_RULE_ADM.EXECUTE_ON_RULE_SET,
    object_name => rs_name,
    grantee     => 'hr');
END;
/

/*
Step 9   Start the Apply Process at dbs3.example.com

Set the disable_on_error parameter to n so that the apply process will not be disabled if it encounters an error, and start the apply process at dbs3.example.com.

*/

BEGIN
  DBMS_APPLY_ADM.SET_PARAMETER(
    apply_name  => 'apply', 
    parameter   => 'disable_on_error', 
    value       => 'N');
END;
/
 
BEGIN
  DBMS_APPLY_ADM.START_APPLY(
    apply_name  => 'apply');
END;
/

/*
Step 10   Configure Propagation at dbs2.example.com

Connect to dbs2.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs2.example.com

/*

Configure and schedule propagation from the queue at dbs2.example.com to the queue at dbs3.example.com. You must specify this propagation for each table that will apply changes at dbs3.example.com. This configuration is an example of directed networks because the changes at dbs2.example.com originated at dbs1.example.com.

*/
 
BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_PROPAGATION_RULES(
    table_name               => 'hr.countries',
    streams_name             => 'dbs2_to_dbs3',
    source_queue_name        => 'strmadmin.streams_queue',
    destination_queue_name   => 'strmadmin.streams_queue@dbs3.example.com', 
    include_dml              => TRUE,
    include_ddl              => TRUE,
    source_database          => 'dbs1.example.com',
    inclusion_rule           => TRUE,
    queue_to_queue           => TRUE);
END;
/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_PROPAGATION_RULES(
    table_name              => 'hr.locations',
    streams_name            => 'dbs2_to_dbs3',
    source_queue_name       => 'strmadmin.streams_queue',
    destination_queue_name  => 'strmadmin.streams_queue@dbs3.example.com', 
    include_dml             => TRUE,
    include_ddl             => TRUE,
    source_database         => 'dbs1.example.com',
    inclusion_rule          => TRUE);
END;
/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_PROPAGATION_RULES(
    table_name              => 'hr.regions',
    streams_name            => 'dbs2_to_dbs3',
    source_queue_name       => 'strmadmin.streams_queue',
    destination_queue_name  => 'strmadmin.streams_queue@dbs3.example.com', 
    include_dml             => TRUE,
    include_ddl             => TRUE,
    source_database         => 'dbs1.example.com',
    inclusion_rule          => TRUE);
END;
/

/*
Step 11   Create the Custom Rule-Based Transformation for Row LCRs at dbs2.example.com

Connect to dbs2.example.com as the hr user.

*/
 
CONNECT hr@dbs2.example.com

/*

Create the custom rule-based transformation function that transforms row changes resulting from DML statements to the jobs table from dbs1.example.com into row changes to the assignments table on dbs2.example.com.

The following function transforms every row LCR for the jobs table into a row LCR for the assignments table.


Note:

If DDL changes were also applied to the assignments table, then another transformation would be required for the DDL LCRs. This transformation would need to change the object name and the DDL text.

*/

CREATE OR REPLACE FUNCTION hr.to_assignments_trans_dml(
  p_in_data in ANYDATA) 
  RETURN ANYDATA IS out_data SYS.LCR$_ROW_RECORD;
  tc   pls_integer;
BEGIN
  -- Typecast AnyData to LCR$_ROW_RECORD
     tc := p_in_data.GetObject(out_data);
     IF out_data.GET_OBJECT_NAME() = 'JOBS'
     THEN
  -- Transform the in_data into the out_data
     out_data.SET_OBJECT_NAME('ASSIGNMENTS');
     END IF;
  -- Convert to AnyData
     RETURN ANYDATA.ConvertObject(out_data);
END;
/

/*
Step 12   Configure the Apply Process for Local Apply at dbs2.example.com

Connect to dbs2.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs2.example.com

/*

Configure dbs2.example.com to apply changes to the assignments table. Remember that the assignments table receives changes from the jobs table at dbs1.example.com.

*/

DECLARE
  to_assignments_rulename_dml   VARCHAR2(30);
  dummy_rule                    VARCHAR2(30);
BEGIN
--  DML changes to the jobs table from dbs1.example.com are applied 
--  to the assignments table. The to_assignments_rulename_dml variable 
--  is an out parameter in this call.
  DBMS_STREAMS_ADM.ADD_TABLE_RULES(
    table_name      => 'hr.jobs', -- jobs, not assignments, specified
    streams_type    => 'apply', 
    streams_name    => 'apply_dbs2',
    queue_name      => 'strmadmin.streams_queue',
    include_dml     => TRUE,
    include_ddl     => FALSE,
    source_database => 'dbs1.example.com',
    dml_rule_name   => to_assignments_rulename_dml,
    ddl_rule_name   => dummy_rule,
    inclusion_rule  => TRUE); 
--  Modify the rule for the hr.jobs table to use the transformation function.
  DBMS_STREAMS_ADM.SET_RULE_TRANSFORM_FUNCTION(
    rule_name          => to_assignments_rulename_dml,
    transform_function => 'hr.to_assignments_trans_dml');
END;
/

/*
Step 13   Specify hr as the Apply User for the Apply Process at dbs2.example.com

In this example, the hr user owns all of the database objects for which changes are applied by the apply process at this database. Therefore, hr already has the necessary privileges to change these database objects, and it is convenient to make hr the apply user.

When the apply process was created in the previous step, the Oracle Streams administrator strmadmin was specified as the apply user by default, because strmadmin ran the procedure that created the apply process. Instead of specifying hr as the apply user, you could retain strmadmin as the apply user, but then you must grant strmadmin privileges on all of the database objects for which changes are applied and privileges to execute all user procedures used by the apply process. In an environment where an apply process applies changes to database objects in multiple schemas, it might be more convenient to use the Oracle Streams administrator as the apply user.


See Also:

Oracle Streams Replication Administrator's Guide for more information about configuring an Oracle Streams administrator

*/

BEGIN
  DBMS_APPLY_ADM.ALTER_APPLY(
    apply_name => 'apply_dbs2',
    apply_user => 'hr');
END;
/

/*
Step 14   Grant the hr User Execute Privilege on the Apply Process Rule Set

Because the hr user was specified as the apply user in the previous step, the hr user requires EXECUTE privilege on the positive rule set used by the apply process

*/

DECLARE
   rs_name  VARCHAR2(64);   -- Variable to hold rule set name
BEGIN
  SELECT RULE_SET_OWNER||'.'||RULE_SET_NAME 
    INTO rs_name 
    FROM DBA_APPLY 
    WHERE APPLY_NAME='APPLY_DBS2';
  DBMS_RULE_ADM.GRANT_OBJECT_PRIVILEGE(
    privilege   => SYS.DBMS_RULE_ADM.EXECUTE_ON_RULE_SET,
    object_name => rs_name,
    grantee     => 'hr');
END;
/

/*
Step 15   Start the Apply Process at dbs2.example.com for Local Apply

Set the disable_on_error parameter to n so that the apply process will not be disabled if it encounters an error, and start the apply process for local apply at dbs2.example.com.

*/

BEGIN
  DBMS_APPLY_ADM.SET_PARAMETER(
    apply_name  => 'apply_dbs2', 
    parameter   => 'disable_on_error', 
    value       => 'N');
END;
/
 
BEGIN
  DBMS_APPLY_ADM.START_APPLY(
    apply_name  => 'apply_dbs2');
END;
/

/*
Step 16   Configure the Apply Process at dbs2.example.com for Apply at dbs4.example.com

Configure the apply process for dbs4.example.com, which is a Sybase database. The dbs2.example.com database is acting as a gateway to dbs4.example.com. Therefore, the apply process for dbs4.example.com must be configured at dbs2.example.com. The apply process cannot apply DDL changes to non-Oracle databases. Therefore, the include_ddl parameter is set to FALSE when the ADD_TABLE_RULES procedure is run.

*/

BEGIN
  DBMS_APPLY_ADM.CREATE_APPLY(
    queue_name          => 'strmadmin.streams_queue',
    apply_name          => 'apply_dbs4',
    apply_database_link => 'dbs4.example.com',
    apply_captured      => TRUE);
END;
/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_RULES(
    table_name      => 'hr.jobs',
    streams_type    => 'apply', 
    streams_name    => 'apply_dbs4',
    queue_name      => 'strmadmin.streams_queue',
    include_dml     => TRUE,
    include_ddl     => FALSE,
    source_database => 'dbs1.example.com',
    inclusion_rule  => TRUE);
END;
/

/*
Step 17   Start the Apply Process at dbs2.example.com for Apply at dbs4.example.com

Set the disable_on_error parameter to n so that the apply process will not be disabled if it encounters an error, and start the remote apply for Sybase using database link dbs4.example.com.

*/

BEGIN
  DBMS_APPLY_ADM.SET_PARAMETER(
    apply_name  => 'apply_dbs4', 
    parameter   => 'disable_on_error', 
    value       => 'N');
END;
/
 
BEGIN
  DBMS_APPLY_ADM.START_APPLY(
    apply_name  => 'apply_dbs4');
END;
/

/*
Step 18   Start the Capture Process at dbs1.example.com

Connect to dbs1.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs1.example.com

/*

Start the capture process at dbs1.example.com.

*/

BEGIN
  DBMS_CAPTURE_ADM.START_CAPTURE(
    capture_name  => 'capture');
END;
/

/*
Step 19   Check the Spool Results

Check the streams_share_schema1.out spool file to ensure that all actions finished successfully after this script is completed.

*/

SET ECHO OFF
SPOOL OFF

/*

You can now make DML and DDL changes to specific tables at dbs1.example.com and see these changes replicated to the other databases in the environment based on the rules you configured for the Oracle Streams processes and propagations in this environment.


See Also:

"Make DML and DDL Changes to Tables in the hr Schema" for examples of changes that are replicated in this environment

/*************************** END OF SCRIPT ******************************/

Flexible Configuration for Sharing Data from a Single Database

Complete the following steps to use a more flexible approach for specifying the capture, propagation, and apply definitions. This approach does not use the DBMS_STREAMS_ADM package. Instead, it uses the following packages:

  • The DBMS_CAPTURE_ADM package to configure capture processes

  • The DBMS_PROPAGATION_ADM package to configure propagations

  • The DBMS_APPLY_ADM package to configure apply processes

  • The DBMS_RULES_ADM package to specify capture process, propagation, and apply process rules and rule sets


    Note:

    Neither the ALL_STREAMS_TABLE_RULES nor the DBA_STREAMS_TABLE_RULES data dictionary view is populated by the rules created in this example. To view the rules created in this example, you can query the ALL_STREAMS_RULES or DBA_STREAMS_RULES data dictionary view.

This example includes the following steps:

  1. Show Output and Spool Results

  2. Configure Propagation at dbs1.example.com

  3. Configure the Capture Process at dbs1.example.com

  4. Prepare the hr Schema at dbs1.example.com for Instantiation

  5. Set the Instantiation SCN for the Existing Tables at Other Databases

  6. Instantiate the dbs1.example.com Tables at dbs3.example.com

  7. Configure the Apply Process at dbs3.example.com

  8. Grant the hr User Execute Privilege on the Apply Process Rule Set

  9. Start the Apply Process at dbs3.example.com

  10. Configure Propagation at dbs2.example.com

  11. Create the Custom Rule-Based Transformation for Row LCRs at dbs2.example.com

  12. Configure the Apply Process for Local Apply at dbs2.example.com

  13. Grant the hr User Execute Privilege on the Apply Process Rule Set

  14. Start the Apply Process at dbs2.example.com for Local Apply

  15. Configure the Apply Process at dbs2.example.com for Apply at dbs4.example.com

  16. Start the Apply Process at dbs2.example.com for Apply at dbs4.example.com

  17. Start the Capture Process at dbs1.example.com

  18. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to all of the databases in the environment.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL streams_share_schema2.out

/*
Step 2   Configure Propagation at dbs1.example.com

Connect to dbs1.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs1.example.com

/*

Configure and schedule propagation from the queue at dbs1.example.com to the queue at dbs2.example.com. This configuration specifies that the propagation propagates all changes to the hr schema. You have the option of omitting the rule set specification, but then everything in the queue will be propagated, which might not be desired if, in the future, multiple capture processes will use the streams_queue.

*/

BEGIN
  -- Create the rule set
  DBMS_RULE_ADM.CREATE_RULE_SET(
    rule_set_name       => 'strmadmin.propagation_dbs1_rules',
    evaluation_context  => 'SYS.STREAMS$_EVALUATION_CONTEXT');
  -- Create rules for all modifications to the hr schema
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name  => 'strmadmin.all_hr_dml',
    condition  => ' :dml.get_object_owner() = ''HR'' AND ' || 
                  ' :dml.is_null_tag() = ''Y'' AND ' ||
                  ' :dml.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ');
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name  => 'strmadmin.all_hr_ddl',
    condition  => ' (:ddl.get_object_owner() = ''HR'' OR ' ||
                  ' :ddl.get_base_table_owner() =   ''HR'') AND ' || 
                  ' :ddl.is_null_tag() = ''Y'' AND ' ||
                  ' :ddl.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ');
  -- Add rules to rule set
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.all_hr_dml', 
    rule_set_name  => 'strmadmin.propagation_dbs1_rules');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.all_hr_ddl', 
    rule_set_name  => 'strmadmin.propagation_dbs1_rules');
  -- Create a propagation that uses the rule set as its positive rule set
  DBMS_PROPAGATION_ADM.CREATE_PROPAGATION(
    propagation_name    => 'dbs1_to_dbs2',
    source_queue        => 'strmadmin.streams_queue',
    destination_queue   => 'strmadmin.streams_queue',
    destination_dblink  => 'dbs2.example.com',
    rule_set_name       => 'strmadmin.propagation_dbs1_rules');
END;
/

/*
Step 3   Configure the Capture Process at dbs1.example.com

Create a capture process and rules to capture the entire hr schema at dbs1.example.com.

*/

BEGIN
  -- Create the rule set
  DBMS_RULE_ADM.CREATE_RULE_SET(
    rule_set_name       => 'strmadmin.demo_rules',
    evaluation_context  => 'SYS.STREAMS$_EVALUATION_CONTEXT');
  --   Create rules that specify the entire hr schema
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name  => 'strmadmin.schema_hr_dml',
    condition  => ' :dml.get_object_owner() = ''HR''  AND ' || 
                  ' :dml.is_null_tag() = ''Y'' AND ' ||
                  ' :dml.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ');
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name  => 'strmadmin.schema_hr_ddl',
    condition  => ' (:ddl.get_object_owner() = ''HR'' OR ' ||
                  ' :ddl.get_base_table_owner() =   ''HR'') AND ' || 
                  ' :ddl.is_null_tag() = ''Y'' AND ' ||
                  ' :ddl.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ');
  --  Add the rules to the rule set
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.schema_hr_dml', 
    rule_set_name  => 'strmadmin.demo_rules');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.schema_hr_ddl', 
    rule_set_name  => 'strmadmin.demo_rules');
  --  Create a capture process that uses the rule set as its positive rule set
  DBMS_CAPTURE_ADM.CREATE_CAPTURE(
    queue_name    => 'strmadmin.streams_queue',
    capture_name  => 'capture',
    rule_set_name => 'strmadmin.demo_rules');
END;
/

/*
Step 4   Prepare the hr Schema at dbs1.example.com for Instantiation

While still connected as the Oracle Streams administrator at dbs1.example.com, prepare the hr schema at dbs1.example.com for instantiation at dbs3.example.com. This step marks the lowest SCN of the tables in the schema for instantiation. SCNs subsequent to the lowest SCN can be used for instantiation.

This step also enables supplemental logging for any primary key, unique key, bitmap index, and foreign key columns in the tables in the hr schema. Supplemental logging places additional information in the redo log for changes made to tables. The apply process needs this extra information to perform certain operations, such as unique row identification and conflict resolution. Because dbs1.example.com is the only database where changes are captured in this environment, it is the only database where you must specify supplemental logging for the tables in the hr schema.


Note:

This step is not required in the "Simple Configuration for Sharing Data from a Single Database". In that example, when the ADD_SCHEMA_RULES procedure in the DBMS_STREAMS_ADM package is run in Step 3, the PREPARE_SCHEMA_INSTANTIATION procedure in the DBMS_CAPTURE_ADM package is run automatically for the hr schema.

*/

BEGIN
  DBMS_CAPTURE_ADM.PREPARE_SCHEMA_INSTANTIATION(
    schema_name          => 'hr',
    supplemental_logging => 'keys');
END;
/

/*
Step 5   Set the Instantiation SCN for the Existing Tables at Other Databases

In this example, the hr.jobs table already exists at dbs2.example.com and dbs4.example.com. At dbs2.example.com, this table is named assignments, but it has the same shape and data as the jobs table at dbs1.example.com. Also, in this example, dbs4.example.com is a Sybase database. All of the other tables in the Oracle Streams environment are instantiated at the other destination databases using Data Pump import.

Because the hr.jobs table already exists at dbs2.example.com and dbs4.example.com, this example uses the GET_SYSTEM_CHANGE_NUMBER function in the DBMS_FLASHBACK package at dbs1.example.com to obtain the current SCN for the database. This SCN is used at dbs2.example.com to run the SET_TABLE_INSTANTIATION_SCN procedure in the DBMS_APPLY_ADM package. Running this procedure twice sets the instantiation SCN for the hr.jobs table at dbs2.example.com and dbs4.example.com.

The SET_TABLE_INSTANTIATION_SCN procedure controls which LCRs for a table are ignored by an apply process and which LCRs for a table are applied by an apply process. If the commit SCN of an LCR for a table from a source database is less than or equal to the instantiation SCN for that table at a destination database, then the apply process at the destination database discards the LCR. Otherwise, the apply process applies the LCR.

In this example, both of the apply processes at dbs2.example.com will apply transactions to the hr.jobs table with SCNs that were committed after SCN obtained in this step.


Note:

This example assumes that the contents of the hr.jobs table at dbs1.example.com, dbs2.example.com (as hr.assignments), and dbs4.example.com are consistent when you complete this step. You might want to lock the table at each database while you complete this step to ensure consistency.

*/

DECLARE
  iscn  NUMBER;         -- Variable to hold instantiation SCN value
BEGIN
  iscn := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER();
  DBMS_APPLY_ADM.SET_TABLE_INSTANTIATION_SCN@DBS2.EXAMPLE.COM(
    source_object_name    => 'hr.jobs',
    source_database_name  => 'dbs1.example.com',
    instantiation_scn     => iscn);
  DBMS_APPLY_ADM.SET_TABLE_INSTANTIATION_SCN@DBS2.EXAMPLE.COM(
    source_object_name    => 'hr.jobs',
    source_database_name  => 'dbs1.example.com',
    instantiation_scn     => iscn,
    apply_database_link   =>       'dbs4.example.com');
END;
/

/*

Step 6   Instantiate the dbs1.example.com Tables at dbs3.example.com

This example performs a network Data Pump import of the following tables:

  • hr.countries

  • hr.locations

  • hr.regions

A network import means that Data Pump imports these tables from dbs1.example.com without using an export dump file.


See Also:

Oracle Database Utilities for information about performing an import

Connect to dbs3.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs3.example.com

/*

This example will do a table import using the DBMS_DATAPUMP package. For simplicity, exceptions from any of the API calls will not be trapped. However, Oracle recommends that you define exception handlers and call GET_STATUS to retrieve more detailed error information if a failure occurs. If you want to monitor the import, then query the DBA_DATAPUMP_JOBS data dictionary view at the import database.

*/

SET SERVEROUTPUT ON
DECLARE
  h1        NUMBER;         -- Data Pump job handle
  sscn      NUMBER;         -- Variable to hold current source SCN
  job_state VARCHAR2(30);   -- To keep track of job state
  js        ku$_JobStatus;  -- The job status from GET_STATUS
  sts       ku$_Status;     -- The status object returned by GET_STATUS
  job_not_exist    exception;
  pragma exception_init(job_not_exist, -31626);
BEGIN
-- Create a (user-named) Data Pump job to do a table-level import.
  h1 := DBMS_DATAPUMP.OPEN(
          operation   => 'IMPORT',
          job_mode    => 'TABLE',
          remote_link => 'DBS1.EXAMPLE.COM',
          job_name    => 'dp_sing2');
-- A metadata filter is used to specify the schema that owns the tables 
-- that will be imported.
  DBMS_DATAPUMP.METADATA_FILTER(
    handle    => h1,
    name      => 'SCHEMA_EXPR',
    value     => '=''HR''');
-- A metadata filter is used to specify the tables that will be imported.
  DBMS_DATAPUMP.METADATA_FILTER(
    handle    => h1,
    name      => 'NAME_EXPR',
    value     => 'IN(''COUNTRIES'', ''REGIONS'', ''LOCATIONS'')');
-- Get the current SCN of the source database, and set the FLASHBACK_SCN 
-- parameter to this value to ensure consistency between all of the 
-- objects included in the import.
  sscn := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER@dbs1.example.com();
  DBMS_DATAPUMP.SET_PARAMETER(
    handle => h1,
    name   => 'FLASHBACK_SCN',
    value  => sscn); 
-- Start the job. 
  DBMS_DATAPUMP.START_JOB(h1);
-- The import job should be running. In the following loop, the job
-- is monitored until it completes.
  job_state := 'UNDEFINED';
  BEGIN
    WHILE (job_state != 'COMPLETED') AND (job_state != 'STOPPED') LOOP
      sts:=DBMS_DATAPUMP.GET_STATUS(
             handle  => h1,
             mask    => DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR +
                        DBMS_DATAPUMP.KU$_STATUS_JOB_STATUS +
                        DBMS_DATAPUMP.KU$_STATUS_WIP,
             timeout => -1);
      js := sts.job_status;
      DBMS_LOCK.SLEEP(10);
      job_state := js.state;
    END LOOP;
  -- Gets an exception when job no longer exists
    EXCEPTION WHEN job_not_exist THEN
      DBMS_OUTPUT.PUT_LINE('Data Pump job has completed');
      DBMS_OUTPUT.PUT_LINE('Instantiation SCN: ' ||sscn);
  END;
END;
/

/*
Step 7   Configure the Apply Process at dbs3.example.com

Connect to dbs3.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs3.example.com

/*

Configure dbs3.example.com to apply DML and DDL changes to the countries table, locations table, and regions table.

*/

BEGIN
  -- Create the rule set
  DBMS_RULE_ADM.CREATE_RULE_SET(
    rule_set_name       => 'strmadmin.apply_rules',
    evaluation_context  => 'SYS.STREAMS$_EVALUATION_CONTEXT');
  -- Rules for hr.countries
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name    => 'strmadmin.all_countries_dml',
    condition    => ' :dml.get_object_owner() = ''HR'' AND ' || 
                    ' :dml.get_object_name() = ''COUNTRIES''  AND ' || 
                    ' :dml.is_null_tag() = ''Y'' AND ' ||
                    ' :dml.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ');
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name    => 'strmadmin.all_countries_ddl',
    condition    => ' (:ddl.get_object_owner() = ''HR'' OR ' ||
                    ' :ddl.get_base_table_owner() =         ''HR'') AND ' || 
                    ' :ddl.get_object_name() = ''COUNTRIES'' AND ' || 
                    ' :ddl.is_null_tag() = ''Y'' AND ' ||
                    ' :ddl.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ');
  -- Rules for hr.locations
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name    => 'strmadmin.all_locations_dml',
    condition    => ' :dml.get_object_owner() = ''HR'' AND ' ||
                    ' :dml.get_object_name() = ''LOCATIONS'' AND ' || 
                    ' :dml.is_null_tag() = ''Y'' AND ' ||
                    ' :dml.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ');
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name    => 'strmadmin.all_locations_ddl',
    condition    => ' (:ddl.get_object_owner() = ''HR'' OR ' ||
                    ' :ddl.get_base_table_owner() =         ''HR'') AND ' ||
                    ' :ddl.get_object_name() = ''LOCATIONS'' AND ' || 
                    ' :ddl.is_null_tag() = ''Y'' AND ' ||
                    ' :ddl.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ');
  -- Rules for hr.regions
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name    => 'strmadmin.all_regions_dml',
    condition    => ' :dml.get_object_owner() = ''HR'' AND ' ||
                    ' :dml.get_object_name() = ''REGIONS'' AND ' || 
                    ' :dml.is_null_tag() = ''Y'' AND ' ||
                    ' :dml.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ');
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name    => 'strmadmin.all_regions_ddl',
    condition    => ' (:ddl.get_object_owner() = ''HR'' OR ' ||
                    ' :ddl.get_base_table_owner() =         ''HR'') AND ' ||
                    ' :ddl.get_object_name() = ''REGIONS'' AND ' || 
                    ' :ddl.is_null_tag() = ''Y'' AND ' ||
                    ' :ddl.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ');
  -- Add rules to rule set
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.all_countries_dml', 
    rule_set_name  => 'strmadmin.apply_rules');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.all_countries_ddl', 
    rule_set_name  => 'strmadmin.apply_rules');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.all_locations_dml', 
    rule_set_name  => 'strmadmin.apply_rules');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.all_locations_ddl', 
    rule_set_name  => 'strmadmin.apply_rules');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.all_regions_dml', 
    rule_set_name  => 'strmadmin.apply_rules');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.all_regions_ddl', 
    rule_set_name  => 'strmadmin.apply_rules');
  -- Create an apply process that uses the rule set as its positive rule set
  DBMS_APPLY_ADM.CREATE_APPLY(
    queue_name      => 'strmadmin.streams_queue',
    apply_name      => 'apply',
    rule_set_name   => 'strmadmin.apply_rules',
    apply_user      => 'hr',
    apply_captured  => TRUE,
    source_database => 'dbs1.example.com');
END;
/

/*
Step 8   Grant the hr User Execute Privilege on the Apply Process Rule Set

Because the hr user was specified as the apply user in the previous step, the hr user requires EXECUTE privilege on the positive rule set used by the apply process

*/

BEGIN
  DBMS_RULE_ADM.GRANT_OBJECT_PRIVILEGE(
    privilege   => SYS.DBMS_RULE_ADM.EXECUTE_ON_RULE_SET,
    object_name => 'strmadmin.apply_rules',
    grantee     => 'hr');
END;
/

/*
Step 9   Start the Apply Process at dbs3.example.com

Set the disable_on_error parameter to n so that the apply process will not be disabled if it encounters an error, and start the apply process at dbs3.example.com.

*/

BEGIN
  DBMS_APPLY_ADM.SET_PARAMETER(
    apply_name  => 'apply', 
    parameter   => 'disable_on_error', 
    value       => 'N');
END;
/
 
BEGIN
  DBMS_APPLY_ADM.START_APPLY(
    apply_name  => 'apply');
END;
/

/*
Step 10   Configure Propagation at dbs2.example.com

Connect to dbs2.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs2.example.com

/*

Configure and schedule propagation from the queue at dbs2.example.com to the queue at dbs3.example.com. This configuration is an example of directed networks because the changes at dbs2.example.com originated at dbs1.example.com.

*/

BEGIN
  -- Create the rule set
  DBMS_RULE_ADM.CREATE_RULE_SET(
    rule_set_name       => 'strmadmin.propagation_dbs3_rules',
    evaluation_context  => 'SYS.STREAMS$_EVALUATION_CONTEXT');
  -- Create rules for all modifications to the countries table
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name  => 'strmadmin.all_countries_dml',
    condition  => ' :dml.get_object_owner() = ''HR'' AND ' || 
                  ' :dml.get_object_name() = ''COUNTRIES'' AND ' || 
                  ' :dml.is_null_tag() = ''Y'' AND ' ||
                  ' :dml.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ');
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name  => 'strmadmin.all_countries_ddl',
    condition  => ' (:ddl.get_object_owner() = ''HR'' OR ' ||
                  ' :ddl.get_base_table_owner() =   ''HR'') AND ' || 
                  ' :ddl.get_object_name() = ''COUNTRIES'' AND ' || 
                  ' :ddl.is_null_tag() = ''Y'' AND ' ||
                  ' :ddl.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ');
  -- Create rules for all modifications to the locations table
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name   => 'strmadmin.all_locations_dml',
    condition  => ' :dml.get_object_owner() = ''HR'' AND ' ||
                  ' :dml.get_object_name() = ''LOCATIONS'' AND ' || 
                  ' :dml.is_null_tag() = ''Y'' AND ' ||
                  ' :dml.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ');
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name   => 'strmadmin.all_locations_ddl',
    condition  => ' (:ddl.get_object_owner() = ''HR'' OR ' ||
                  ' :ddl.get_base_table_owner() =   ''HR'') AND ' ||
                  ' :ddl.get_object_name() = ''LOCATIONS'' AND ' || 
                  ' :ddl.is_null_tag() = ''Y'' AND ' ||
                  ' :ddl.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ');
  -- Create rules for all modifications to the regions table
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name   => 'strmadmin.all_regions_dml',
    condition  => ' :dml.get_object_owner() = ''HR'' AND ' ||
                  ' :dml.get_object_name() = ''REGIONS'' AND ' || 
                  ' :dml.is_null_tag() = ''Y'' AND ' ||
                  ' :dml.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ');
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name   => 'strmadmin.all_regions_ddl',
    condition  => ' (:ddl.get_object_owner() = ''HR'' OR ' ||
                  ' :ddl.get_base_table_owner() =   ''HR'') AND ' ||
                  ' :ddl.get_object_name() = ''REGIONS'' AND ' || 
                  ' :ddl.is_null_tag() = ''Y'' AND ' ||
                  ' :ddl.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ');
  -- Add rules to rule set
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.all_countries_dml', 
    rule_set_name  => 'strmadmin.propagation_dbs3_rules');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.all_countries_ddl', 
    rule_set_name  => 'strmadmin.propagation_dbs3_rules');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.all_locations_dml',  
    rule_set_name  => 'strmadmin.propagation_dbs3_rules');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.all_locations_ddl',  
    rule_set_name  => 'strmadmin.propagation_dbs3_rules');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.all_regions_dml',  
    rule_set_name  => 'strmadmin.propagation_dbs3_rules');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.all_regions_ddl',  
    rule_set_name  => 'strmadmin.propagation_dbs3_rules');
  -- Create a propagation that uses the rule set as its positive rule set
  DBMS_PROPAGATION_ADM.CREATE_PROPAGATION(
    propagation_name    => 'dbs2_to_dbs3',
    source_queue        => 'strmadmin.streams_queue',
    destination_queue   => 'strmadmin.streams_queue',
    destination_dblink  => 'dbs3.example.com',
    rule_set_name       => 'strmadmin.propagation_dbs3_rules');
END;
/

/*
Step 11   Create the Custom Rule-Based Transformation for Row LCRs at dbs2.example.com

Connect to dbs2.example.com as the hr user.

*/
 
CONNECT hr@dbs2.example.com

/*

Create the custom rule-based transformation function that transforms row changes resulting from DML statements to the jobs table from dbs1.example.com into row changes to the assignments table on dbs2.example.com.

The following function transforms every row LCR for the jobs table into a row LCR for the assignments table.


Note:

If DDL changes were also applied to the assignments table, then another transformation would be required for the DDL LCRs. This transformation would need to change the object name and the DDL text.

*/

CREATE OR REPLACE FUNCTION hr.to_assignments_trans_dml(
  p_in_data in ANYDATA) 
  RETURN ANYDATA IS out_data SYS.LCR$_ROW_RECORD;
  tc   pls_integer;
BEGIN
  -- Typecast AnyData to LCR$_ROW_RECORD
     tc := p_in_data.GetObject(out_data);
     IF out_data.GET_OBJECT_NAME() = 'JOBS'
     THEN
  -- Transform the in_data into the out_data
     out_data.SET_OBJECT_NAME('ASSIGNMENTS');
     END IF;
  -- Convert to AnyData
     RETURN ANYDATA.ConvertObject(out_data);
END;
/

/*
Step 12   Configure the Apply Process for Local Apply at dbs2.example.com

Connect to dbs2.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs2.example.com

/*

Configure dbs2.example.com to apply changes to the local assignments table. Remember that the assignments table receives changes from the jobs table at dbs1.example.com. This step specifies a custom rule-based transformation without using the SET_RULE_TRANSFORM_FUNCTION procedure in the DBMS_STREAMS_ADM package. Instead, a name-value pair is added manually to the action context of the rule. The name-value pair specifies STREAMS$_TRANSFORM_FUNCTION for the name and hr.to_assignments_trans_dml for the value.

*/

DECLARE
  action_ctx_dml       SYS.RE$NV_LIST;
  action_ctx_ddl       SYS.RE$NV_LIST;
  ac_name              VARCHAR2(30) := 'STREAMS$_TRANSFORM_FUNCTION';
BEGIN
  -- Specify the name-value pair in the action context
  action_ctx_dml := SYS.RE$NV_LIST(SYS.RE$NV_ARRAY());
  action_ctx_dml.ADD_PAIR(
    ac_name, 
    ANYDATA.CONVERTVARCHAR2('hr.to_assignments_trans_dml'));
  --  Create the rule set strmadmin.apply_rules
  DBMS_RULE_ADM.CREATE_RULE_SET(
    rule_set_name       => 'strmadmin.apply_rules',
    evaluation_context  => 'SYS.STREAMS$_EVALUATION_CONTEXT');
  --  Create a rule that transforms all DML changes to the jobs table into 
  --  DML changes for assignments table
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name       => 'strmadmin.all_jobs_dml',
    condition       => ' :dml.get_object_owner() = ''HR'' AND ' ||
                       ' :dml.get_object_name() = ''JOBS'' AND ' || 
                       ' :dml.is_null_tag() = ''Y'' AND ' ||
                       ' :dml.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ',
    action_context  => action_ctx_dml);
  --  Add the rule to the rule set
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.all_jobs_dml', 
    rule_set_name  => 'strmadmin.apply_rules');
  -- Create an apply process that uses the rule set as its positive rule set
  DBMS_APPLY_ADM.CREATE_APPLY(
    queue_name      => 'strmadmin.streams_queue',
    apply_name      => 'apply_dbs2',
    rule_set_name   => 'strmadmin.apply_rules',
    apply_user      => 'hr',
    apply_captured  => TRUE,
    source_database => 'dbs1.example.com');
END;
/

/*
Step 13   Grant the hr User Execute Privilege on the Apply Process Rule Set

Because the hr user was specified as the apply user in the previous step, the hr user requires EXECUTE privilege on the positive rule set used by the apply process

*/

BEGIN
  DBMS_RULE_ADM.GRANT_OBJECT_PRIVILEGE(
    privilege   => SYS.DBMS_RULE_ADM.EXECUTE_ON_RULE_SET,
    object_name => 'strmadmin.apply_rules',
    grantee     => 'hr');
END;
/

/*
Step 14   Start the Apply Process at dbs2.example.com for Local Apply

Set the disable_on_error parameter to n so that the apply process will not be disabled if it encounters an error, and start the apply process for local apply at dbs2.example.com.

*/

BEGIN
  DBMS_APPLY_ADM.SET_PARAMETER(
    apply_name  => 'apply_dbs2', 
    parameter   => 'disable_on_error', 
    value       => 'N');
END;
/
 
BEGIN
  DBMS_APPLY_ADM.START_APPLY(
    apply_name  => 'apply_dbs2');
END;
/

/*
Step 15   Configure the Apply Process at dbs2.example.com for Apply at dbs4.example.com

Configure dbs2.example.com to apply DML changes to the jobs table at dbs4.example.com, which is a Sybase database. Remember that these changes originated at dbs1.example.com.

*/

BEGIN
  -- Create the rule set
  DBMS_RULE_ADM.CREATE_RULE_SET(
    rule_set_name      => 'strmadmin.apply_dbs4_rules',
    evaluation_context => 'SYS.STREAMS$_EVALUATION_CONTEXT');
  -- Create rule strmadmin.all_jobs_remote for all modifications 
  -- to the jobs table
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name    => 'strmadmin.all_jobs_remote',
    condition    => ' :dml.get_object_owner() = ''HR'' AND ' ||
                    ' :dml.get_object_name() = ''JOBS'' AND ' || 
                    ' :dml.is_null_tag() = ''Y'' AND ' ||
                    ' :dml.get_source_database_name() = ''DBS1.EXAMPLE.COM'' ');
  -- Add the rule to the rule set
  DBMS_RULE_ADM.ADD_RULE(
    rule_name      => 'strmadmin.all_jobs_remote', 
    rule_set_name  => 'strmadmin.apply_dbs4_rules');
  -- Create an apply process that uses the rule set as its positive rule set
  DBMS_APPLY_ADM.CREATE_APPLY(
    queue_name          => 'strmadmin.streams_queue',
    apply_name          => 'apply_dbs4',
    rule_set_name       => 'strmadmin.apply_dbs4_rules',
    apply_database_link => 'dbs4.example.com',
    apply_captured      => TRUE,
    source_database     => 'dbs1.example.com');
END;
/

/*
Step 16   Start the Apply Process at dbs2.example.com for Apply at dbs4.example.com

Set the disable_on_error parameter to n so that the apply process will not be disabled if it encounters an error, and start the remote apply for Sybase using database link dbs4.example.com.

*/

BEGIN
  DBMS_APPLY_ADM.SET_PARAMETER(
    apply_name  => 'apply_dbs4', 
    parameter   => 'disable_on_error', 
    value       => 'N');
END;
/

BEGIN
  DBMS_APPLY_ADM.START_APPLY(
    apply_name  => 'apply_dbs4');
END;
/

/*
Step 17   Start the Capture Process at dbs1.example.com

Connect to dbs1.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs1.example.com

/*

Start the capture process at dbs1.example.com.

*/

BEGIN
  DBMS_CAPTURE_ADM.START_CAPTURE(
    capture_name  => 'capture');
END;
/

/*
Step 18   Check the Spool Results

Check the streams_share_schema2.out spool file to ensure that all actions finished successfully after this script is completed.

*/

SET ECHO OFF
SPOOL OFF

/*

You can now make DML and DDL changes to specific tables at dbs1.example.com and see these changes replicated to the other databases in the environment based on the rules you configured for the Oracle Streams processes and propagations in this environment.


<table class="NoteAlso oac_no_warn" summary="" width="80%" border="1" frame="hsides" rules="groups" cellspacing="0" cellpadding="3">

See Also:

"Make DML and DDL Changes to Tables in the hr Schema" for examples of changes that are replicated in this environment
/*************************** END OF SCRIPT ******************************/

Make DML and DDL Changes to Tables in the hr Schema

After completing either of the examples described in "Example Scripts for Sharing Data from One Database", you can make DML and DDL changes to the tables in the hr schema at the dbs1.example.com database. These changes will be replicated to the other databases in the environment based on the rules you configured for Oracle Streams processes and propagations. You can check the other databases to see that the changes have been replicated.

For example, complete the following steps to make DML changes to the hr.jobs and hr.locations tables at dbs1.example.com. You can also make a DDL change to the hr.locations table at dbs1.example.com.

After you make these changes, you can query the hr.assignments table at dbs2.example.com to see that the DML change you made to this table at dbs1.example.com has been replicated. Remember that a custom rule-based transformation configured for the apply process at dbs2.example.com transforms DML changes to the hr.jobs table into DML changes to the hr.assignments table. You can also query the hr.locations table at dbs3.example.com to see that the DML and DDL changes you made to this table at dbs1.example.com have been replicated.

Step 1   Make DML and DDL Changes to Tables in the hr Schema

Make the following changes:

CONNECT hr@dbs1.example.com
Enter password: password

UPDATE hr.jobs SET max_salary=10000 WHERE job_id='MK_REP';
COMMIT;

INSERT INTO hr.locations VALUES(
  3300, '521 Ralston Avenue', '94002', 'Belmont', 'CA', 'US');
COMMIT;

ALTER TABLE hr.locations RENAME COLUMN state_province TO state_or_province;
Step 2   Query the hr.assignments Table at dbs2.example.com

After some time passes to allow for capture, propagation, and apply of the changes performed the previous step, run the following query to confirm that the UPDATE change made to the hr.jobs table at dbs1.example.com has been applied to the hr.assignments table at dbs2.example.com.

CONNECT hr@dbs2.example.com
Enter password: password

SELECT max_salary FROM hr.assignments WHERE job_id='MK_REP';

You should see 10000 for the value of the max_salary.

Step 3   Query and Describe the hr.locations Table at dbs3.example.com

Run the following query to confirm that the INSERT change made to the hr.locations table at dbs1.example.com has been applied at dbs3.example.com.

CONNECT hr@dbs3.example.com
Enter password: password

SELECT * FROM hr.locations WHERE location_id=3300;

You should see the row inserted into the hr.locations table at dbs1.example.com in the previous step.

Next, describe the hr.locations table at to confirm that the ALTER TABLE change was propagated and applied correctly.

DESC hr.locations

The fifth column in the table should be state_or_province.

Add Objects to an Existing Oracle Streams Replication Environment

This example extends the Oracle Streams environment configured in the previous sections by adding replicated objects to an existing database. To complete this example, you must have completed the tasks in one of the previous examples in this chapter.

This example will add the following tables to the hr schema in the dbs3.example.com database:

  • departments

  • employees

  • job_history

  • jobs

When you complete this example, Oracle Streams processes changes to these tables with the following series of actions:

  1. The capture process captures changes at dbs1.example.com and enqueues them at dbs1.example.com.

  2. A propagation propagates changes from the queue at dbs1.example.com to the queue at dbs2.example.com.

  3. A propagation propagates changes from the queue at dbs2.example.com to the queue at dbs3.example.com.

  4. The apply process at dbs3.example.com applies the changes at dbs3.example.com.

When you complete this example, the hr schema at the dbs3.example.com database will have all of its original tables, because the countries, locations, and regions tables were instantiated at dbs3.example.com in the previous section.

Figure 2-2 provides an overview of the environment with the added tables.

Figure 2-2 Adding Objects to dbs3.example.com in the Environment

Description of Figure 2-2 follows
Description of "Figure 2-2 Adding Objects to dbs3.example.com in the Environment"

Complete the following steps to replicate these tables to the dbs3.example.com database.

  1. Show Output and Spool Results

  2. Stop the Apply Process at dbs3.example.com

  3. Configure the Apply Process for the Added Tables at dbs3.example.com

  4. Specify the Table Propagation Rules for the Added Tables at dbs2.example.com

  5. Prepare the Four Added Tables for Instantiation at dbs1.example.com

  6. Instantiate the dbs1.example.com Tables at dbs3.example.com

  7. Start the Apply Process at dbs3.example.com

  8. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to all of the databases in the environment.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL streams_addobjs.out

/*
Step 2    Stop the Apply Process at dbs3.example.com

Until you finish adding objects to dbs3.example.com, you must ensure that the apply process that will apply changes for the added objects does not try to apply changes for these objects. You can do this by stopping the capture process at the source database. Or, you can do this by stopping propagation of changes from dbs2.example.com to dbs3.example.com. Yet another alternative is to stop the apply process at dbs3.example.com. This example stops the apply process at dbs3.example.com.

Connect to dbs3.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs3.example.com

/*

Stop the apply process at dbs3.example.com.

*/

BEGIN
  DBMS_APPLY_ADM.STOP_APPLY(
    apply_name  => 'apply');
END;
/

/*
Step 3   Configure the Apply Process for the Added Tables at dbs3.example.com

Configure the apply process at dbs3.example.com to apply changes to the tables you are adding.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_RULES(
    table_name      => 'hr.departments',
    streams_type    => 'apply', 
    streams_name    => 'apply',
    queue_name      => 'strmadmin.streams_queue',
    include_dml     => TRUE,
    include_ddl     => TRUE,
    source_database => 'dbs1.example.com',
    inclusion_rule  => TRUE);
END;
/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_RULES(
    table_name      => 'hr.employees',
    streams_type    => 'apply', 
    streams_name    => 'apply',
    queue_name      => 'strmadmin.streams_queue',
    include_dml     => TRUE,
    include_ddl     => TRUE,
    source_database => 'dbs1.example.com',
    inclusion_rule  => TRUE);
END;
/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_RULES(
    table_name      => 'hr.job_history',
    streams_type    => 'apply', 
    streams_name    => 'apply',
    queue_name      => 'strmadmin.streams_queue',
    include_dml     => TRUE,
    include_ddl     => TRUE,
    source_database => 'dbs1.example.com',
    inclusion_rule  => TRUE);
END;
/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_RULES(
    table_name      => 'hr.jobs',
    streams_type    => 'apply', 
    streams_name    => 'apply',
    queue_name      => 'strmadmin.streams_queue',
    include_dml     => TRUE,
    include_ddl     => TRUE,
    source_database => 'dbs1.example.com',
    inclusion_rule  => TRUE);
END;
/

/*
Step 4   Specify the Table Propagation Rules for the Added Tables at dbs2.example.com

Connect to dbs2.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs2.example.com

/*

Add the tables to the rules for propagation from the queue at dbs2.example.com to the queue at dbs3.example.com.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_PROPAGATION_RULES(
    table_name               => 'hr.departments',
    streams_name             => 'dbs2_to_dbs3',
    source_queue_name        => 'strmadmin.streams_queue',
    destination_queue_name   => 'strmadmin.streams_queue@dbs3.example.com', 
    include_dml              => TRUE,
    include_ddl              => TRUE,
    source_database          => 'dbs1.example.com',
    inclusion_rule           => TRUE);
END;
/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_PROPAGATION_RULES(
    table_name              => 'hr.employees',
    streams_name            => 'dbs2_to_dbs3',
    source_queue_name       => 'strmadmin.streams_queue',
    destination_queue_name  => 'strmadmin.streams_queue@dbs3.example.com', 
    include_dml             => TRUE,
    include_ddl             => TRUE,
    source_database         => 'dbs1.example.com',
    inclusion_rule          => TRUE);
END;
/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_PROPAGATION_RULES(
    table_name              => 'hr.job_history',
    streams_name            => 'dbs2_to_dbs3',
    source_queue_name       => 'strmadmin.streams_queue',
    destination_queue_name  => 'strmadmin.streams_queue@dbs3.example.com', 
    include_dml             => TRUE,
    include_ddl             => TRUE,
    source_database         => 'dbs1.example.com',
    inclusion_rule          => TRUE);
END;
/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_PROPAGATION_RULES(
    table_name              => 'hr.jobs',
    streams_name            => 'dbs2_to_dbs3',
    source_queue_name       => 'strmadmin.streams_queue',
    destination_queue_name  => 'strmadmin.streams_queue@dbs3.example.com', 
    include_dml             => TRUE,
    include_ddl             => TRUE,
    source_database         => 'dbs1.example.com',
    inclusion_rule          => TRUE);
END;
/

/*
Step 5   Prepare the Four Added Tables for Instantiation at dbs1.example.com

Connect to dbs1.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs1.example.com

/*

Prepare the tables for instantiation. These tables will be instantiated at dbs3.example.com. This step marks the lowest SCN of the tables for instantiation. SCNs subsequent to the lowest SCN can be used for instantiation. Also, this preparation is necessary so that the Oracle Streams data dictionary for the relevant propagations and the apply process at dbs3.example.com contain information about these tables.


Note:

When the PREPARE_TABLE_INSTANTIATION procedure is run in this step, the supplemental_logging parameter is not specified. Therefore, the default value (keys) is used for this parameter. Supplemental logging already was enabled for any primary key, unique key, bitmap index, and foreign key columns in these tables in Step 3.

*/

BEGIN
  DBMS_CAPTURE_ADM.PREPARE_TABLE_INSTANTIATION(
    table_name  => 'hr.departments');
END;
/
 
BEGIN
  DBMS_CAPTURE_ADM.PREPARE_TABLE_INSTANTIATION(
    table_name  => 'hr.employees');
END;
/

BEGIN
  DBMS_CAPTURE_ADM.PREPARE_TABLE_INSTANTIATION(
    table_name  => 'hr.job_history');
END;
/

BEGIN
  DBMS_CAPTURE_ADM.PREPARE_TABLE_INSTANTIATION(
    table_name  => 'hr.jobs');
END;
/

/*
Step 6   Instantiate the dbs1.example.com Tables at dbs3.example.com

This example performs a network Data Pump import of the following tables:

  • hr.departments

  • hr.employees

  • hr.job_history

  • hr.jobs

A network import means that Data Pump imports these tables from dbs1.example.com without using an export dump file.


See Also:

Oracle Database Utilities for information about performing an import

Connect to dbs3.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs3.example.com

/*

This example will do a table import using the DBMS_DATAPUMP package. For simplicity, exceptions from any of the API calls will not be trapped. However, Oracle recommends that you define exception handlers and call GET_STATUS to retrieve more detailed error information if a failure occurs. If you want to monitor the import, then query the DBA_DATAPUMP_JOBS data dictionary view at the import database.

*/

SET SERVEROUTPUT ON
DECLARE
  h1        NUMBER;         -- Data Pump job handle
  sscn      NUMBER;         -- Variable to hold current source SCN
  job_state VARCHAR2(30);   -- To keep track of job state
  js        ku$_JobStatus;  -- The job status from GET_STATUS
  sts       ku$_Status;     -- The status object returned by GET_STATUS
  job_not_exist    exception;
  pragma exception_init(job_not_exist, -31626);
BEGIN
-- Create a (user-named) Data Pump job to do a table-level import.
  h1 := DBMS_DATAPUMP.OPEN(
          operation   => 'IMPORT',
          job_mode    => 'TABLE',
          remote_link => 'DBS1.EXAMPLE.COM',
          job_name    => 'dp_sing3');
-- A metadata filter is used to specify the schema that owns the tables 
-- that will be imported.
  DBMS_DATAPUMP.METADATA_FILTER(
    handle    => h1,
    name      => 'SCHEMA_EXPR',
    value     => '=''HR''');
-- A metadata filter is used to specify the tables that will be imported.
  DBMS_DATAPUMP.METADATA_FILTER(
    handle    => h1,
    name      => 'NAME_EXPR',
    value     => 'IN(''DEPARTMENTS'', ''EMPLOYEES'', 
                     ''JOB_HISTORY'', ''JOBS'')');
-- Get the current SCN of the source database, and set the FLASHBACK_SCN 
-- parameter to this value to ensure consistency between all of the 
-- objects included in the import.
  sscn := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER@dbs1.example.com();
  DBMS_DATAPUMP.SET_PARAMETER(
    handle => h1,
    name   => 'FLASHBACK_SCN',
    value  => sscn); 
-- Start the job. 
  DBMS_DATAPUMP.START_JOB(h1);
-- The import job should be running. In the following loop, the job
-- is monitored until it completes.
  job_state := 'UNDEFINED';
  BEGIN
    WHILE (job_state != 'COMPLETED') AND (job_state != 'STOPPED') LOOP
      sts:=DBMS_DATAPUMP.GET_STATUS(
             handle  => h1,
             mask    => DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR +
                        DBMS_DATAPUMP.KU$_STATUS_JOB_STATUS +
                        DBMS_DATAPUMP.KU$_STATUS_WIP,
             timeout => -1);
      js := sts.job_status;
      DBMS_LOCK.SLEEP(10);
      job_state := js.state;
    END LOOP;
  -- Gets an exception when job no longer exists
    EXCEPTION WHEN job_not_exist THEN
      DBMS_OUTPUT.PUT_LINE('Data Pump job has completed');
      DBMS_OUTPUT.PUT_LINE('Instantiation SCN: ' ||sscn);
  END;
END;
/

/*
Step 7    Start the Apply Process at dbs3.example.com

Start the apply process at dbs3.example.com. This apply process was stopped in Step 2.

Connect to dbs3.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs3.example.com

/*

Start the apply process at dbs3.example.com.

*/

BEGIN
  DBMS_APPLY_ADM.START_APPLY(
    apply_name  => 'apply');
END;
/

/*
Step 8   Check the Spool Results

Check the streams_addobjs.out spool file to ensure that all actions finished successfully after this script is completed.

*/

SET ECHO OFF
SPOOL OFF

/*************************** END OF SCRIPT ******************************/

Make a DML Change to the hr.employees Table

After completing the examples described in the "Add Objects to an Existing Oracle Streams Replication Environment" section, you can make DML and DDL changes to the tables in the hr schema at the dbs1.example.com database. These changes will be replicated to dbs3.example.com. You can check these tables at dbs3.example.com to see that the changes have been replicated.

For example, complete the following steps to make a DML change to the hr.employees table at dbs1.example.com. Next, query the hr.employees table at dbs3.example.com to see that the change has been replicated.

Step 1   Make a DML Change to the hr.employees Table

Make the following change:

CONNECT hr@dbs1.example.com
Enter password: password

UPDATE hr.employees SET job_id='ST_MAN' WHERE employee_id=143;
COMMIT;
Step 2   Query the hr.employees Table at dbs3.example.com

After some time passes to allow for capture, propagation, and apply of the change performed in the previous step, run the following query to confirm that the UPDATE change made to the hr.employees table at dbs1.example.com has been applied to the hr.employees table at dbs3.example.com.

CONNECT hr@dbs3.example.com
Enter password: password

SELECT job_id FROM hr.employees WHERE employee_id=143;

You should see ST_MAN for the value of the job_id.

Add a Database to an Existing Oracle Streams Replication Environment

This example extends the Oracle Streams environment configured in the previous sections by adding an additional database to the existing configuration. In this example, an existing Oracle database named dbs5.example.com is added to receive changes to the entire hr schema from the queue at dbs2.example.com.

Figure 2-3 provides an overview of the environment with the added database.

Figure 2-3 Adding the dbs5.example.com Oracle Database to the Environment

Description of Figure 2-3 follows
Description of "Figure 2-3 Adding the dbs5.example.com Oracle Database to the Environment"

To complete this example, you must meet the following prerequisites:

  • The dbs5.example.com database must exist.

  • The dbs2.example.com and dbs5.example.com databases must be able to communicate with each other through Oracle Net.

  • The dbs5.example.com and dbs1.example.com databases must be able to communicate with each other through Oracle Net (for optional Data Pump network instantiation).

  • You must have completed the tasks in the previous examples in this chapter.

  • The "Prerequisites" must be met if you want the entire Oracle Streams environment to work properly.

  • This examples creates a new user to function as the Oracle Streams administrator (strmadmin) at the dbs5.example.com database and prompts you for the tablespace you want to use for this user's data. Before you start this example, either create a new tablespace or identify an existing tablespace for the Oracle Streams administrator to use at the dbs5.example.com database. The Oracle Streams administrator should not use the SYSTEM tablespace.

Complete the following steps to add dbs5.example.com to the Oracle Streams environment.

  1. Show Output and Spool Results

  2. Drop All of the Tables in the hr Schema at dbs5.example.com

  3. Set Up Users at dbs5.example.com

  4. Create the ANYDATA Queue at dbs5.example.com

  5. Create a Database Link at dbs5.example.com to dbs1.example.com

  6. Configure the Apply Process at dbs5.example.com

  7. Specify hr as the Apply User for the Apply Process at dbs5.example.com

  8. Grant the hr User Execute Privilege on the Apply Process Rule Set

  9. Create the Database Link Between dbs2.example.com and dbs5.example.com

  10. Configure Propagation Between dbs2.example.com and dbs5.example.com

  11. Prepare the hr Schema for Instantiation at dbs1.example.com

  12. Instantiate the dbs1.example.com Tables at dbs5.example.com

  13. Start the Apply Process at dbs5.example.com

  14. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to all of the databases in the environment.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL streams_adddb.out

/*
Step 2   Drop All of the Tables in the hr Schema at dbs5.example.com

This example illustrates instantiating the tables in the hr schema by importing them from dbs1.example.com into dbs5.example.com using Data Pump. You must delete these tables at dbs5.example.com for the instantiation portion of this example to work properly.

Connect as hr at dbs5.example.com.

*/

CONNECT hr@dbs5.example.com

/*

Drop all tables in the hr schema in the dbs5.example.com database.


Note:

If you complete this step and drop all of the tables in the hr schema, then you should complete the remaining sections of this example to reinstantiate the hr schema at dbs5.example.com. If the hr schema does not exist in an Oracle database, then some examples in the Oracle documentation set can fail.

*/

DROP TABLE hr.countries CASCADE CONSTRAINTS;
DROP TABLE hr.departments CASCADE CONSTRAINTS;
DROP TABLE hr.employees CASCADE CONSTRAINTS;
DROP TABLE hr.job_history CASCADE CONSTRAINTS;
DROP TABLE hr.jobs CASCADE CONSTRAINTS;
DROP TABLE hr.locations CASCADE CONSTRAINTS;
DROP TABLE hr.regions CASCADE CONSTRAINTS;

/*
Step 3   Set Up Users at dbs5.example.com

Connect to dbs5.example.com as SYSTEM user.

*/
 
CONNECT system@dbs5.example.com

/*

Create the Oracle Streams administrator named strmadmin and grant this user the necessary privileges. These privileges enable the user to manage queues, execute subprograms in packages related to Oracle Streams, create rule sets, create rules, and monitor the Oracle Streams environment by querying data dictionary views and queue tables. You can choose a different name for this user.


Note:

The ACCEPT command must appear on a single line in the script.


See Also:

Oracle Streams Replication Administrator's Guide for more information about configuring an Oracle Streams administrator

*/

ACCEPT password PROMPT 'Enter password for user: ' HIDE

GRANT DBA TO strmadmin IDENTIFIED BY &password;

ACCEPT streams_tbs PROMPT 'Enter Oracle Streams administrator tablespace on dbs5.example.com: ' HIDE

ALTER USER strmadmin DEFAULT TABLESPACE &streams_tbs
                     QUOTA UNLIMITED ON &streams_tbs;

/*
Step 4   Create the ANYDATA Queue at dbs5.example.com

Connect as the Oracle Streams administrator at the database you are adding. In this example, that database is dbs5.example.com.

*/

CONNECT strmadmin@dbs5.example.com

/*

Run the SET_UP_QUEUE procedure to create a queue named streams:=_queue at dbs5.example.com. This queue will function as the ANYDATA queue by holding the changes that will be applied at this database.

Running the SET_UP_QUEUE procedure performs the following actions:

  • Creates a queue table named streams_queue_table. This queue table is owned by the Oracle Streams administrator (strmadmin) and uses the default storage of this user.

  • Creates a queue named streams_queue owned by the Oracle Streams administrator (strmadmin).

  • Starts the queue.

*/

EXEC  DBMS_STREAMS_ADM.SET_UP_QUEUE();

/*
Step 5   Create a Database Link at dbs5.example.com to dbs1.example.com

Create a database link from dbs5.example.com to dbs1.example.com. Later in this example, this database link is used for the instantiation of the database objects that were dropped in Step 2. This example uses the DBMS_DATAPUMP package to perform a network import of these database objects directly from the dbs1.example.com database. Because this example performs a network import, no dump file is required.

Alternatively, you can perform an export at the source database dbs1.example.com, transfer the export dump file to the destination database dbs5.example.com, and then import the export dump file at the destination database. In this case, the database link created in this step is not required.

*/

CREATE DATABASE LINK dbs1.example.com CONNECT TO strmadmin 
   IDENTIFIED BY &password USING 'dbs1.example.com';

/*
Step 6   Configure the Apply Process at dbs5.example.com

While still connected as the Oracle Streams administrator at dbs5.example.com, configure the apply process to apply changes to the hr schema.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_SCHEMA_RULES(
    schema_name     => 'hr',   
    streams_type    => 'apply',
    streams_name    => 'apply',
    queue_name      => 'strmadmin.streams_queue',
    include_dml     => TRUE,
    include_ddl     => TRUE,
    source_database => 'dbs1.example.com',
    inclusion_rule  => TRUE);
END;
/

/*
Step 7   Specify hr as the Apply User for the Apply Process at dbs5.example.com

In this example, the hr user owns all of the database objects for which changes are applied by the apply process at this database. Therefore, hr already has the necessary privileges to change these database objects, and it is convenient to make hr the apply user.

When the apply process was created in the previous step, the Oracle Streams administrator strmadmin was specified as the apply user by default, because strmadmin ran the procedure that created the apply process. Instead of specifying hr as the apply user, you could retain strmadmin as the apply user, but then you must grant strmadmin privileges on all of the database objects for which changes are applied and privileges to execute all user procedures used by the apply process. In an environment where an apply process applies changes to database objects in multiple schemas, it might be more convenient to use the Oracle Streams administrator as the apply user.


See Also:

Oracle Streams Replication Administrator's Guide for more information about configuring an Oracle Streams administrator

*/

BEGIN
  DBMS_APPLY_ADM.ALTER_APPLY(
    apply_name => 'apply',
    apply_user => 'hr');
END;
/

/*
Step 8   Grant the hr User Execute Privilege on the Apply Process Rule Set

Because the hr user was specified as the apply user in the previous step, the hr user requires EXECUTE privilege on the positive rule set used by the apply process

*/

DECLARE
   rs_name  VARCHAR2(64);   -- Variable to hold rule set name
BEGIN
  SELECT RULE_SET_OWNER||'.'||RULE_SET_NAME 
    INTO rs_name 
    FROM DBA_APPLY 
    WHERE APPLY_NAME='APPLY';
  DBMS_RULE_ADM.GRANT_OBJECT_PRIVILEGE(
    privilege   => SYS.DBMS_RULE_ADM.EXECUTE_ON_RULE_SET,
    object_name => rs_name,
    grantee     => 'hr');
END;
/

/*
Step 9   Create the Database Link Between dbs2.example.com and dbs5.example.com

Connect to dbs2.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs2.example.com

/*

Create the database links to the databases where changes are propagated. In this example, database dbs2.example.com propagates changes to dbs5.example.com.

*/

CREATE DATABASE LINK dbs5.example.com CONNECT TO strmadmin 
   IDENTIFIED BY &password USING 'dbs5.example.com';

/*
Step 10   Configure Propagation Between dbs2.example.com and dbs5.example.com

While still connected as the Oracle Streams administrator at dbs2.example.com, configure and schedule propagation from the queue at dbs2.example.com to the queue at dbs5.example.com. Remember, changes to the hr schema originated at dbs1.example.com.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_SCHEMA_PROPAGATION_RULES(
    schema_name             => 'hr', 
    streams_name            => 'dbs2_to_dbs5', 
    source_queue_name       => 'strmadmin.streams_queue',
    destination_queue_name  => 'strmadmin.streams_queue@dbs5.example.com',
    include_dml             => TRUE,
    include_ddl             => TRUE,
    source_database         => 'dbs1.example.com',
    inclusion_rule          => TRUE,
    queue_to_queue          => TRUE);
END;
/

/*
Step 11   Prepare the hr Schema for Instantiation at dbs1.example.com

Connect to dbs1.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs1.example.com

/*

Prepare the hr schema for instantiation. These tables in this schema will be instantiated at dbs5.example.com. This preparation is necessary so that the Oracle Streams data dictionary for the relevant propagations and the apply process at dbs5.example.com contain information about the hr schema and the objects in the schema.

*/

BEGIN
  DBMS_CAPTURE_ADM.PREPARE_SCHEMA_INSTANTIATION(
    schema_name          => 'hr',
    supplemental_logging => 'keys');
END;
/

/*
Step 12   Instantiate the dbs1.example.com Tables at dbs5.example.com

This example performs a network Data Pump import of the following tables:

  • hr.countries

  • hr.departments

  • hr.employees

  • hr.job_history

  • hr.jobs

  • hr.locations

  • hr.regions

A network import means that Data Pump imports these tables from dbs1.example.com without using an export dump file.


See Also:

Oracle Database Utilities for information about performing an import

Connect to dbs5.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@dbs5.example.com

/*

This example will do a table import using the DBMS_DATAPUMP package. For simplicity, exceptions from any of the API calls will not be trapped. However, Oracle recommends that you define exception handlers and call GET_STATUS to retrieve more detailed error information if a failure occurs. If you want to monitor the import, then query the DBA_DATAPUMP_JOBS data dictionary view at the import database.

*/

SET SERVEROUTPUT ON
DECLARE
  h1        NUMBER;         -- Data Pump job handle
  sscn      NUMBER;         -- Variable to hold current source SCN
  job_state VARCHAR2(30);   -- To keep track of job state
  js        ku$_JobStatus;  -- The job status from GET_STATUS
  sts       ku$_Status;     -- The status object returned by GET_STATUS
  job_not_exist    exception;
  pragma exception_init(job_not_exist, -31626);
BEGIN
-- Create a (user-named) Data Pump job to do a table-level import.
  h1 := DBMS_DATAPUMP.OPEN(
          operation   => 'IMPORT',
          job_mode    => 'TABLE',
          remote_link => 'DBS1.EXAMPLE.COM',
          job_name    => 'dp_sing4');
-- A metadata filter is used to specify the schema that owns the tables 
-- that will be imported.
  DBMS_DATAPUMP.METADATA_FILTER(
    handle    => h1,
    name      => 'SCHEMA_EXPR',
    value     => '=''HR''');
-- Get the current SCN of the source database, and set the FLASHBACK_SCN 
-- parameter to this value to ensure consistency between all of the 
-- objects included in the import.
  sscn := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER@dbs1.example.com();
  DBMS_DATAPUMP.SET_PARAMETER(
    handle => h1,
    name   => 'FLASHBACK_SCN',
    value  => sscn); 
-- Start the job. 
  DBMS_DATAPUMP.START_JOB(h1);
-- The import job should be running. In the following loop, the job
-- is monitored until it completes.
  job_state := 'UNDEFINED';
  BEGIN
    WHILE (job_state != 'COMPLETED') AND (job_state != 'STOPPED') LOOP
      sts:=DBMS_DATAPUMP.GET_STATUS(
             handle  => h1,
             mask    => DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR +
                        DBMS_DATAPUMP.KU$_STATUS_JOB_STATUS +
                        DBMS_DATAPUMP.KU$_STATUS_WIP,
             timeout => -1);
      js := sts.job_status;
      DBMS_LOCK.SLEEP(10);
      job_state := js.state;
    END LOOP;
  -- Gets an exception when job no longer exists
    EXCEPTION WHEN job_not_exist THEN
      DBMS_OUTPUT.PUT_LINE('Data Pump job has completed');
      DBMS_OUTPUT.PUT_LINE('Instantiation SCN: ' ||sscn);
  END;
END;
/

/*
Step 13   Start the Apply Process at dbs5.example.com

Connect as the Oracle Streams administrator at dbs5.example.com.

*/

CONNECT strmadmin@dbs5.example.com

/*

Set the disable_on_error parameter to n so that the apply process will not be disabled if it encounters an error, and start apply process at dbs5.example.com.

*/

BEGIN
  DBMS_APPLY_ADM.SET_PARAMETER(
    apply_name  => 'apply', 
    parameter   => 'disable_on_error', 
    value       => 'N');
END;
/

BEGIN
  DBMS_APPLY_ADM.START_APPLY(
    apply_name  => 'apply');
END;
/

/*
Step 14   Check the Spool Results

Check the streams_adddb.out spool file to ensure that all actions finished successfully after this script is completed.

*/

SET ECHO OFF
SPOOL OFF

/*************************** END OF SCRIPT ******************************/

Make a DML Change to the hr.departments Table

After completing the examples described in the "Add a Database to an Existing Oracle Streams Replication Environment" section, you can make DML and DDL changes to the tables in the hr schema at the dbs1.example.com database. These changes will be replicated to dbs5.example.com. You can check these tables at dbs5.example.com to see that the changes have been replicated.

For example, complete the following steps to make a DML change to the hr.departments table at dbs1.example.com. Next, query the hr.departments table at dbs5.example.com to see that the change has been replicated.

Step 1   Make a DML Change to the hr.departments Table

Make the following change:

CONNECT hr@dbs1.example.com
Enter password: password

UPDATE hr.departments SET location_id=2400 WHERE department_id=270;
COMMIT;
Step 2   Query the hr.departments Table at dbs5.example.com

After some time passes to allow for capture, propagation, and apply of the change performed in the previous step, run the following query to confirm that the UPDATE change made to the hr.departments table at dbs1.example.com has been applied to the hr.departments table at dbs5.example.com.

CONNECT hr@dbs5.example.com
Enter password: password

SELECT location_id FROM hr.departments WHERE department_id=270;

You should see 2400 for the value of the location_id.

PKwS=:=PK Oracle® Streams Extended Examples, 11g Release 2 (11.2) Cover Table of Contents Oracle Streams Extended Examples, 11g Release 2 (11.2) Preface Simple Single-Source Replication Example Single-Source Heterogeneous Replication Example N-Way Replication Example Single-Database Capture and Apply Example Logical Change Records with LOBs Example Rule-Based Application Example Index Copyright PK* PK Oracle® Streams Extended Examples, 11g Release 2 (11.2) en-US E12862-03 Oracle Corporation Oracle Corporation Oracle® Streams Extended Examples, 11g Release 2 (11.2) 2010-07-26T10:29:21Z Contains extended examples that configure various types of Oracle Streams environments. PKAXSPKV%ȣOΏ9??:a"\fSrğjAsKJ:nOzO=}E1-I)3(QEQEQEQEQEQEQE֝Hza<["2"pO#f8M[RL(,?g93QSZ uy"lx4h`O!LŏʨXZvq& c՚]+: ǵ@+J]tQ]~[[eϸ (]6A&>ܫ~+כzmZ^(<57KsHf妬Ϧmnẁ&F!:-`b\/(tF*Bֳ ~V{WxxfCnMvF=;5_,6%S>}cQQjsOO5=)Ot [W9 /{^tyNg#ЄGsֿ1-4ooTZ?K Gc+oyڙoNuh^iSo5{\ܹ3Yos}$.nQ-~n,-zr~-|K4R"8a{]^;I<ȤL5"EԤP7_j>OoK;*U.at*K[fym3ii^#wcC'IIkIp$󿉵|CtĈpW¹l{9>⪦׺*ͯj.LfGߍԁw] |WW18>w.ӯ! VӃ :#1~ +މ=;5c__b@W@ +^]ևՃ7 n&g2I8Lw7uҭ$"&"b eZ":8)D'%{}5{; w]iu;_dLʳ4R-,2H6>½HLKܹR ~foZKZ࿷1[oZ7׫Z7R¢?«'y?A}C_iG5s_~^ J5?œ tp]X/c'r%eܺA|4ծ-Ե+ْe1M38Ǯ `|Kյ OVڅu;"d56, X5kYR<̭CiطXԮ];Oy)OcWj֩}=܅s۸QZ*<~%뺃ȶp f~Bðzb\ݳzW*y{=[ C/Ak oXCkt_s}{'y?AmCjޓ{ WRV7r. g~Q"7&͹+c<=,dJ1V߁=T)TR՜*N4 ^Bڥ%B+=@fE5ka}ędܤFH^i1k\Sgdk> ֤aOM\_\T)8靠㡮3ģR: jj,pk/K!t,=ϯZ6(((((((49 xn_kLk&f9sK`zx{{y8H 8b4>ÇНE|7v(z/]k7IxM}8!ycZRQ pKVr(RPEr?^}'ðh{x+ՀLW154cK@Ng C)rr9+c:׹b Жf*s^ fKS7^} *{zq_@8# pF~ [VPe(nw0MW=3#kȵz晨cy PpG#W:%drMh]3HH<\]ԁ|_W HHҡb}P>k {ZErxMX@8C&qskLۙOnO^sCk7ql2XCw5VG.S~H8=(s1~cV5z %v|U2QF=NoW]ո?<`~׮}=ӬfԵ,=;"~Iy7K#g{ñJ?5$y` zz@-~m7mG宝Gٱ>G&K#]؃y1$$t>wqjstX.b̐{Wej)Dxfc:8)=$y|L`xV8ߙ~E)HkwW$J0uʟk>6Sgp~;4֌W+חc"=|ř9bc5> *rg {~cj1rnI#G|8v4wĿhFb><^ pJLm[Dl1;Vx5IZ:1*p)إ1ZbAK(1ׅ|S&5{^ KG^5r>;X׻K^? s fk^8O/"J)3K]N)iL?5!ƾq:G_=X- i,vi2N3 |03Qas ! 7}kZU781M,->e;@Qz T(GK(ah(((((((Y[×j2F}o־oYYq $+]%$ v^rϭ`nax,ZEuWSܽ,g%~"MrsrY~Ҿ"Fت;8{ѰxYEfP^;WPwqbB:c?zp<7;SBfZ)dϛ; 7s^>}⍱x?Bix^#hf,*P9S{w[]GF?1Z_nG~]kk)9Sc5Ո<<6J-ϛ}xUi>ux#ţc'{ᛲq?Oo?x&mѱ'#^t)ϲbb0 F«kIVmVsv@}kҡ!ˍUTtxO̧]ORb|2yԵk܊{sPIc_?ħ:Ig)=Z~' "\M2VSSMyLsl⺿U~"C7\hz_ Rs$~? TAi<lO*>U}+'f>7_K N s8g1^CeКÿE ;{+Y\ O5|Y{/o+ LVcO;7Zx-Ek&dpzbӱ+TaB0gNy׭ 3^c T\$⫫?F33?t._Q~Nln:U/Ceb1-im WʸQM+VpafR3d׫é|Aү-q*I P7:y&]hX^Fbtpܩ?|Wu󭏤ʫxJ3ߴm"(uqA}j.+?S wV ~ [B&<^U?rϜ_OH\'.;|.%pw/ZZG'1j(#0UT` Wzw}>_*9m>󑓀F?EL3"zpubzΕ$+0܉&3zڶ+jyr1QE ( ( ( ( ( ( ( (UIdC0EZm+]Y6^![ ԯsmܶ捆?+me+ZE29)B[;я*wGxsK7;5w)}gH~.Ɣx?X\ߚ}A@tQ(:ͧ|Iq(CT?v[sKG+*רqҍck <#Ljα5݈`8cXP6T5i.K!xX*p&ќZǓϘ7 *oƽ:wlຈ:Q5yIEA/2*2jAҐe}k%K$N9R2?7ýKMV!{W9\PA+c4w` Wx=Ze\X{}yXI Ү!aOÎ{]Qx)#D@9E:*NJ}b|Z>_k7:d$z >&Vv󃏽WlR:RqJfGإd9Tm(ҝEtO}1O[xxEYt8,3v bFF )ǙrPNE8=O#V*Cc𹾾&l&cmCh<.P{ʦ&ۣY+Gxs~k5$> ӥPquŽўZt~Tl>Q.g> %k#ú:Kn'&{[yWQGqF}AЅ׮/}<;VYZa$wQg!$;_ $NKS}“_{MY|w7G!"\JtRy+贾d|o/;5jz_6fHwk<ѰJ#]kAȎ J =YNu%dxRwwbEQEQEQEQEQEQEQEQEQE'fLQZ(1F)hQ@X1KEQE-Q@ 1KE3h=iPb(((1GjZ(-ʹRPbR@ 1KE7`bڒyS0(-&)P+ ڎԴP11F)h&:LRmQ@Q@Š(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((s;jMb9&%6'iH$.qRI=83u^7WҴ "S8 v IpOR'[ƳaOWwk`C& c n'{eW|@ֶ>GDu39((((((((((?>5Լ[]SKiKv[fP6Z<_Sӭ#kuM nF#{Es3CLh2QJToQ6GS4r(+X?^,tߴ2B$o.uw =B((((+y|-o,.紺4s<`# qEp/5?5wRyC#' ? (((?NXug[?:Hr۔Q^@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@KIxvcͬ2 | uFXǍ?8f5(%HִiO|y g# tsK5;hld?FpA"Yȁ|nߜFrN_2nZ.~*x|C4њK6"1vIvVhOk}ZW}u2]qw` 1ZTRqƒ)f|^/~3(%*_Cdv$w$IZx?qx2pFvp>r_U YWkjro$ 䒠0uڧ]yzeCqv&,j H+!Bf(O|@x^5$( f 6w\X|A^c o'qp$\ݰp$H MOĿK enbڱ$ebO=#X w^ z%ܻnFݵrUbXfH mwl|=soEy܇#T/9s^_G<+O i?.X/t(mh  H#8><-gމm+Eo!Nõ YV@ؼk'DjkNZޗiwB#Xijz.&91yr6dm' A88?{h#j sLHCrN0l(<yV?-t m| {lG%J%[ +iEKV<'t; ̺88OJ?; z aSU-5 4LEt$&v!b= qнr>i/PYF\FQ1n.HҀ6>:x^>y߹M|}N1z{8Cq1fM>$ޱG1U`İ;Ac_+ѯ|mmⷷ8`GjQ@PYx > h^'$y{)QJ(SI'G[֤ KT Zf$UԱy s^'M̭oFO*ysjK"$ڜI*| 7*n??|bsoH+llY_T8fQ噎ŷ3Cx~ι&IjrC*%*Icmeo}6|6EHtw*h1bI=&0C7M]7x^A(r.WfO_Ht/x҉+wO9o XzdXH*XuwwAKIoQ%xG)ztkf_/7 p"[J-lỵ}.L`, m9͌3¯5W^%u/0!leCJ$?LEc[>>i/PYF\FQ1n.HҀ:_o<%-!D6MR<\ >S=_7q_u%%ٷ8*90~Ay |=~b( {]2xJ?}YnnYn7 08>T!h=ʠoNQھ(((((((((((?Ɵ4j:D:3QG!1^p2F,~#+PiI{ çq^i:nni}uC`s\|>\K;v09v̊ 9UQq$hZLJ&7ƥKd<::"Fb\`Gï\xW6nwfAXARhe;H'W.2ʄӶG%S]%|#V|!Ii^6Li@Ì׽:3 .N3?~xGWX uw ݜvXǂ|;`icV;Inv8@?Nƿi_@W?x'ú'ou;Ou;36RFXP7WvPNtMJ]^X%K>|T6v00~? 8Fp'X\`9 :w]$Cuo-QRHPF Aby |>war#s«jжMCӵX ܨjm!c'# 88iqSl}"w Tg\<O ˣvN]g`e ]lK09>/,;9, 1"6#*x8 €9X w%Wy~Ҿfg^wvxx/WqO֞{{5 H@>BόrBy8$N{#Ð< d9rO^㎽kR>57Vcf%G#ưlYE`ǷJO[}d[7m`Õ @<4MN \[bǖ$I<;IaXTxkRFGj #AeCq"bbHO h.ӣ,keO5À@9BF?\JѢ3n-VOLAv;msoj=Er]L-8i#_C^IY7U Jf"oܝDY *M7|y%$|dA,F :vZ<)wYt} ˬ +`1' i /#txfMuC =+b(h'cRIxV. i$r_Ĭ"PA`n߻svWICC|5oII#D[Pma`X  ?Jؽٖ=ߊ,*t> 7 |s1}HGskT%F6A^xsH𶖺ncr,ǩfbK$ciwfm͝l'#@#Pk>) 'YH%YH # ׆x:??Lj.Ӗ^S |Rdt\<׊ xCCNK"؀qHp3T~$?]sHHlr3ބ6ߘNqAnK vI+!I8>'º<ʐ7g7¾d]Iɔ$0GUtzw]ɍ<@p|AmͼS` u^ƌX؀GzDx]Fm1I,mGD/\9R_?^ۏ9]&xYt]7J[q #egzA@7M̭oF ~Ig;Wt52ϊ~Ӭ]B'K0bFzdu ?|;ol.'Gv7}]x=DžPMGr$Q8L1 j<d"XLJ&|#˸ԯ-$)a7s`@j@:汢A=233!8!u\GmB#8yv ; -NP?/$:D~0Η)<^7pTh$嶖 |>hw4x4&0nϷs<$IՋ =NK;H.dgHʞ 8> 6)Z0I:/cFAS@#G|B׉BόrBy8$N_PtZIbDm±d+(((((((((((k]7z4:Kp궨deGx?Q^? ;^P:5n,F+` 4r(((((((+G> lk[X#":fQEQEQEQEQEW| yv3.g݈hx>6>ֈ߻I xG`8i$ m)g4Q^_azmk;mVI.g r+vQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEw?±k< $_ K^G<+5Zt͜Pb] !&t: J}kOYG}ynD@821dT0~S |Yu_ihlq/SH9B36xKoϛq&VpQ|/<7¿7PKpv!VP`,Un˚ŏ>9x[ı=G4PQ%.s>A@_j.Ưd[̓js6.^;mk1,D2S$1bFv3&?|B#tm\VmF1O5B1H&rUĐqSj ?q:Ʈ ;Dyڀ|^g2nZ=s~9| ,!cv@$u ;ůu/-2Mz; GNr.9N r|If-,shZ/[udk/(i2Q᣼@sChS:~״kE ^6 !d$n`1$ mę[[Dl=ÎFF[{}^! _Yzvwv3)#緣5L <*r菳>0 1kqG4fKo>L?I۝)|L_8`,8P.8}_?SW[k6~u{kiJ E\ y$ ;?~?ǁ!i.nQO$|Pv%F䜊KjSEhZ~EķUUcx:wMFeX&[H$:`Xq$|}C\Đx #BL[p^rHfǮhZ߇[~iϕvnp38fu͛oo qsCO<5`_QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEw?±k< $_ KY O<|78G]ܿsڸ> +{0D#4U ((A+:7Q5[V$m㷛Uh p̿8*Fwdp>"xTؼѦGu*|Hܻ8aOr|N~|R'K 8[0׀rRzÿZ_9LR}S"ka)Uڼy9<XRhI &900A X..2]ZGԑdq0{^O9iZ,cn򀖔dcv97 -Ƴiş%IہcdFoےf'ILjmd*0(s,|9SHRܾBk0ۏn oqyVO\Ϯ=֑]߷ur$|8/+\i??o!5m㳺ڬx2ۨ*k;$rjD!ĎF(#;Hz_;}S:rzΕj.e'dĥUe!$ے $dž f5jʲ<хdQYvۃf:OZ$ogl$f6c!BTà^W2nZ?eM>˷f9Ҁz&D* h C$4-3fya۷~ 83Т(f_kWY|,V^NlุXY10V X<쟈v<ϲZ[gl8h(o^Լ/Mex]U s*O_;dO=()+ Zy M֮G]f$t5x[lj4jw]Zl(3XpH=Ji k i^c%*,eY@ٿWqϯxG>1զa$dJMn'FԿ [q 29'[񞫠Z`oЕn#NhEZ9o^\~` 2Bza\' *E-xGůh^mKu=u=@\O}v I*m`]+`İm;3~]ծo ɸalQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQT-[MѭT-,`g]L)lXqk/ס*: *iͻ\iz KYU pJ3=\(+tꚤsA-۪ڢ*͓c{PIESu(uTY x#Y 0@Q@Q@p|x¶^=3z NʥB:mV9ˎ޵Q@~fѼ+iw Oee %K"$dAZQ@Q@Q@V>{x)#74$dS;hb(Q)dPI,r@봱㼰L $0? M'MTTO}BBT^xW@{ZEQEqW}[k|ϴ\lE6`B~4QEe^%tk56v@DrF@b2ϱ J*8'+yc Ppq8=} OI󼟷ZKmݳz݌9Ey\>ѦΛ5n).|<d<ß'U|k95}J̩o.9L9 @'A+~#x;@5X)O:DqH2h7׀[ĶɺW\dEPO=3J(??:um|˻\pݿ NA' vQEQEQEQEQEQEQEQEQEQEQEQEQEQEw?±jDŽxSnyyWZ}?R2IUh'cRՏ _&]txfONvơB`Ҁ8 ~+f>o4l+|m/zNX"{ok S^ oXo(Uhw`j䑖bIaѴk.ݤh,㷍 TP3(=g\==oNYdp+ ı%:#Yc+~ 8=y\g /),6]y*,mky,Uq#N1r~^ (Y´[U?x퐜}D4?|AXլJE):FY& 11'}g_w95}s×kNAʶ  tVcivs͝oTAAU}zGԵ-f=X8 x+x]7OI˪ :T"/rϠm$JX+3yʜpA#᣼@sCk_/Y>$K}hx<[SZo<my=u)$LrFÍ$|h?sjU|*/+Ȓ@10U, ˝Ӻ`So1HCEICxCz P|;APA Tprɮg1w? D~qa9imJhmKUȸ؃4jsbVNѤ5n4VX(PN_ -52pt֤ 0n*k>,. kwגi\Mf';P9z+:;IaXTyZ襮<RA@OcB=Cĺ_瘢MU3 r/? e?}EP^oᏂ>Pue|2Wi/9 0b3c(|!=-m4 b1,0݉Rs\gZ}$ %䈜6#)x`ۮHAL O 63:ÞVn5*d8`&2p8cWȠOԴ#6 7"@RݑEs((((((((((((((h'cRIV֍4$XDbRI%rIP6~!{kwʙ UlFx [t#|9G#;X)yѼa}.C&$j}9Bd$ $M;BЧV[Ά6n j$ı!Σнr|kqiiڌnb`RYs?OChѵ3Lqae@:|q:5k= ˺&9AS8 u_yV?-hxZ⶧MW7:v dҠu~H)rl3|khvkPI<ԏ# uaƧ*?2I ) (dּ< ;R4҉T̑j`rq=v7gt; ]>p;]cP#J|*t^x-N{g6 lHGCsp?I|<{'n?lxo߅|#_d# ?%(H$a؎?*!Bf+yxמ7 (0ypʪ.v ˛Wѵ]/GI e$b;I$pz_)//o gG>s?7^}ki)M֬c.cXt*ASd#5  ]Xm緳x_9 ]G>$GG.LguqAW!Ԑhf5h:39~*F Pg98n8O^0-ωx{bwWUx"/08|0yko t  vba;;BЧV[Ά6n j$ı!Σнr|kqiiڌnb`RYs?OChѵ3Lqae@:|q:5k= ˺&9AS8 u⼿ֿ?Zj)Ӵes&[,AMۗsg 'QpV?OGCWUyy\DD$lWϟ<7{+ /4xQPRVpr_9$dv17bږXvb3NFV,q#iV:oiYۦȢN?$y$rMxyC3~MDNI'$$kZvx:U<)t1\3umQE46+G[KnIZEVR81gohu(uTY x#Y 0$]_=KJ8`r)>j)$,M{dW7/4ۆА|@?w?±k<ѡO +ZFX+.c{zPI熿k ='Pۣh:]f;xIбTP\A^@|2nZ[]}=}=.$[YdCOgdVoy~gbll݌mz}Es~ ]40-4@\2S;q`Ѽsc-~W_lٜր=B?,|Aεp<1@{ub0:| }NJ? ռ5v%-*ʿ( 0co|3g}P&KOoop9=Aڝ&h">9&5'7ө⟌SO[!\ *_t  Exr%*_}!#U #4 & ֩3|b]L ]t b+Da&R_2lEٱZ`aC)/яmvUkS r(-iPE Vv_{z GLt\2s!F A#葡JY r|AA,hB}q|B`du }00(䡆<pb,G+oB C0p/x$…– ]7 @2HFc ) @AD \0 LHG',(A` `@SC)_" PH`}Y+_|1.K8pAKMA @?3҄$[JPA)+NH I ,@8G0/@R T,`pF8Ѓ)$^$ DDTDlA@ s;PKPKz'TQuw7Ŀ KX߁M2=S'TQt?.5w'97;~pq=" ~k?`'9q6 E|yayM^Om'fkC&<5x' ?A?Zx'jß={=SßM gVC.5+Hd֪xc^)Җufz{Cީ|D Vkznq|+Xa+{50rx{|OG.OϞ~f/ xxX[2H )c+#jpUOZYX\=SG ߨC|K@;_߆'e?LT?]:?>w ڔ`D^So~xo[Ӡ3i7B:Q8 Vc-ďoi:FM292~y_*_闱YN\Fr=xZ3鳎OwW_QEzW~c]REeaSM}}Hӏ4&.E]u=gMѠ+mF`rNn$w9gMa꺢nTuhf2Xv>އ a(Û6߭?<=>z'TQuw7Ŀ KX߁M2=S'TQt?.5Kko\.8S$TOX߀Gw?Zx汴X)C7~.i6(Щ=+4{mGӭ¸-]&'t_kV*I<1)4thtIsqpQJ+> \m^[aJ5)ny:4o&QEnyAEPEEss 72,PDۢ׃K W{Wjr+wگ iM/;pd?~&?@;7E4gv8 $l'z'TQuw7Ŀ Gֱ=ɿ&G?. iR(5W*$|?w᫼gkmIbHe/_t>tg%y.l}N5[]+Mk0ĠeHdPrsst'UiC,y8`V%9ZIia|ܪvi מYG,o}+kk{YbyIeb*sAtի82zWoEK5z*o-eo;n(P u-I)4Š(HQEQEQEQEhz(X/Đ?}Bk˩ ݏrk0]4>8XzV? }6$}d^F>nU K ?Bտk_9׾x~w'ߞ  uDŽtL ؈5c-E/"|_Oo.IH쐍=i*Iw5(ںw?t5s.)+tQ2dUt5Vĺ.jZ"@IRrZƅY4ߡ_;}ų(KyQf1Aǵt?sZg+?F5_oQR&Dg߿]6FuRD u>ڿxl7?IT8'shj^=.=J1rj1Wl$얲cPx;E,p$֟ˏkw qg"45(ǛkV/=+ũ)bYl~K#˝J_כ5&\F'I#8/|wʾ_Xj Q:os^T1.M_|TO.;?_  jF?g N 8nA2F%i =qW,G=5OU u8]Rq?wr'˻S+۾.ܼ 87Q^elo/T*?L|ۚ<%<,/v_OKs B5f/29n0=zqQq(ª=VX@*J(э(f5qJN_EVǞQEOuoѕOuoa5}gO?:߂8Wא|cڽ~]N&O( (<]>͠@VQ=^~U ̴m&\խ5i:}|}r~9՝f}_>'vVֲ$~^f30^in{\_.O F8to}?${φ|#x^#^n~w=~k~?'KRtO.㌡h![3Zu*ٷճ(ԟ]z_/W1(ԟ]v~g|Yq<ז0 ; b8֮s,w9\?uEyStKaª@\,)) (!EPEPEPEPEPzѧts{v>C/"N6`d*J2gGӧWqBq_1ZuΓ\X]r?=Ey88Mp&pKtO-"wR2 K^-Z< \c>V0^@O7x2WFjs<׻kZ(<Т(OFw/6$1[:ޯԯ#q~4|,LVPem=@=YLUxӃV}AUbcUB.Ds5*kٸAeG>PJxt͝ b88?*$~@ׯD VkraiJs}Q.20x&mXξ,Z]“A-J#`+-E/"<]\a'tZGy.(|lދ~gMK OZdxDŽU9T6ϯ^<Ϡt5CZ]].t۫S=s`ڳ%8iVK:nqe+#<.T6U>zWoy3^I {F?J~=G}k)K$$;$de8*G Uӟ4Ocºw}|]4=ݣ\x$ʠms?q^ipw\"ȿPs^Z Q_0GڼU.t}ROM[G#]8wٞ ӫ87}Cgw vHȩBM55vof =A_٭`Ygx[6 P,5}>蚊(0(+?>+?> k|TuXq6_ +szk :u_ Z߶Ak_U}Jc2u/1[_»ݸG41-bሬ۴}}Eȹפ_c?5gi @cL\L<68hF_Ih>X4K7UТ sMj =J7CKo>Օ5s:߀t ~ηaٿ?|gdL8+gG%o?x`دOqȱwc¨&TW_V_aI=dpG!wu۞սZ1yL50$(l3(:~'ַo A}a3N*[0ǭ HKQV}G@֜$ 9of$ArNqUOgË05#m?D)^_h//5_/<?4}Jį+GkpG4"$ r| >S4Ђ"S 1%R:ȝ 8;PKPz PK >@,4`H.|`a (Q 9:&[|ځ,4p Y&BDb,!2@, $wPA'ܠǃ@CO~/d.`I @8ArHx9H75j L 3B/` P#qD*s 3A:3,H70P,R@ p!(F oԥ D;"0 ,6QBRɄHhI@@VDLCk8@NBBL2&pClA?DAk%$`I2 #Q+l7 "=&dL&PRSLIP)PɼirqМ'N8[_}w;PK-PK Oracle Legal Notices

Oracle Legal Notices

Copyright Notice

Copyright © 1994-2012, Oracle and/or its affiliates. All rights reserved.

Trademark Notice

Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners.

Intel and Intel Xeon are trademarks or registered trademarks of Intel Corporation. All SPARC trademarks are used under license and are trademarks or registered trademarks of SPARC International, Inc. AMD, Opteron, the AMD logo, and the AMD Opteron logo are trademarks or registered trademarks of Advanced Micro Devices. UNIX is a registered trademark of The Open Group.

License Restrictions Warranty/Consequential Damages Disclaimer

This software and related documentation are provided under a license agreement containing restrictions on use and disclosure and are protected by intellectual property laws. Except as expressly permitted in your license agreement or allowed by law, you may not use, copy, reproduce, translate, broadcast, modify, license, transmit, distribute, exhibit, perform, publish, or display any part, in any form, or by any means. Reverse engineering, disassembly, or decompilation of this software, unless required by law for interoperability, is prohibited.

Warranty Disclaimer

The information contained herein is subject to change without notice and is not warranted to be error-free. If you find any errors, please report them to us in writing.

Restricted Rights Notice

If this is software or related documentation that is delivered to the U.S. Government or anyone licensing it on behalf of the U.S. Government, the following notice is applicable:

U.S. GOVERNMENT RIGHTS Programs, software, databases, and related documentation and technical data delivered to U.S. Government customers are "commercial computer software" or "commercial technical data" pursuant to the applicable Federal Acquisition Regulation and agency-specific supplemental regulations. As such, the use, duplication, disclosure, modification, and adaptation shall be subject to the restrictions and license terms set forth in the applicable Government contract, and, to the extent applicable by the terms of the Government contract, the additional rights set forth in FAR 52.227-19, Commercial Computer Software License (December 2007). Oracle America, Inc., 500 Oracle Parkway, Redwood City, CA 94065.

Hazardous Applications Notice

This software or hardware is developed for general use in a variety of information management applications. It is not developed or intended for use in any inherently dangerous applications, including applications that may create a risk of personal injury. If you use this software or hardware in dangerous applications, then you shall be responsible to take all appropriate fail-safe, backup, redundancy, and other measures to ensure its safe use. Oracle Corporation and its affiliates disclaim any liability for any damages caused by use of this software or hardware in dangerous applications.

Third-Party Content, Products, and Services Disclaimer

This software or hardware and documentation may provide access to or information on content, products, and services from third parties. Oracle Corporation and its affiliates are not responsible for and expressly disclaim all warranties of any kind with respect to third-party content, products, and services. Oracle Corporation and its affiliates will not be responsible for any loss, costs, or damages incurred due to your access to or use of third-party content, products, or services.

Alpha and Beta Draft Documentation Notice

If this document is in prerelease status:

This documentation is in prerelease status and is intended for demonstration and preliminary use only. It may not be specific to the hardware on which you are using the software. Oracle Corporation and its affiliates are not responsible for and expressly disclaim all warranties of any kind with respect to this documentation and will not be responsible for any loss, costs, or damages incurred due to the use of this documentation.

Oracle Logo

PKN61PK p { display: none; } /* Class Selectors */ .ProductTitle { font-family: sans-serif; } .BookTitle { font-family: sans-serif; } .VersionNumber { font-family: sans-serif; } .PrintDate { font-family: sans-serif; font-size: small; } .PartNumber { font-family: sans-serif; font-size: small; } PKeӺ1,PKꑈ53=Z]'yuLG*)g^!8C?-6(29K"Ĩ0Яl;U+K9^u2,@@ (\Ȱ Ë $P`lj 8x I$4H *(@͉0dа8tA  DсSP v"TUH PhP"Y1bxDǕ̧_=$I /& .)+ 60D)bB~=0#'& *D+l1MG CL1&+D`.1qVG ( "D2QL,p.;u. |r$p+5qBNl<TzB"\9e0u )@D,¹ 2@C~KU 'L6a9 /;<`P!D#Tal6XTYhn[p]݅ 7}B a&AƮe{EɲƮiEp#G}D#xTIzGFǂEc^q}) Y# (tۮNeGL*@/%UB:&k0{ &SdDnBQ^("@q #` @1B4i@ aNȅ@[\B >e007V[N(vpyFe Gb/&|aHZj@""~ӎ)t ? $ EQ.սJ$C,l]A `8A o B C?8cyA @Nz|`:`~7-G|yQ AqA6OzPbZ`>~#8=./edGA2nrBYR@ W h'j4p'!k 00 MT RNF6̙ m` (7%ꑀ;PKl-OJPKxAܽ[G.\rQC wr}BŊQ A9ᾑ#5Y0VȒj0l-GqF>ZpM rb ;=.ސW-WѻWo ha!}~ْ ; t 53 :\ 4PcD,0 4*_l0K3-`l.j!c Aa|2L4/1C`@@md;(H*80L0L(h*҇҆o#N84pC (xO@ A)J6rVlF r  fry†$r_pl5xhA+@A=F rGU a 1х4s&H Bdzt x#H%Rr (Ѐ7P`#Rщ'x" #0`@~i `HA'Tk?3!$`-A@1l"P LhʖRG&8A`0DcBH sq@AXB4@&yQhPAppxCQ(rBW00@DP1E?@lP1%T` 0 WB~nQ@;PKGC PK!/;xP` (Jj"M6 ;PK枰pkPK 1) collapsible = false; for (var k = 0; k < p.length; k++) { if ( getTextContent(p[k]).split(" ").length > 12 ) collapsible = false; c.push(p[k]); } } if (collapsible) { for (var j = 0; j < c.length; j++) { c[j].style.margin = "0"; } } } function getTextContent(e) { if (e.textContent) return e.textContent; if (e.innerText) return e.innerText; } } addLoadEvent(compactLists); function processIndex() { try { if (!/\/index.htm(?:|#.*)$/.test(window.location.href)) return false; } catch(e) {} var shortcut = []; lastPrefix = ""; var dd = document.getElementsByTagName("dd"); for (var i = 0; i < dd.length; i++) { if (dd[i].className != 'l1ix') continue; var prefix = getTextContent(dd[i]).substring(0, 2).toUpperCase(); if (!prefix.match(/^([A-Z0-9]{2})/)) continue; if (prefix == lastPrefix) continue; dd[i].id = prefix; var s = document.createElement("a"); s.href = "#" + prefix; s.appendChild(document.createTextNode(prefix)); shortcut.push(s); lastPrefix = prefix; } var h2 = document.getElementsByTagName("h2"); for (var i = 0; i < h2.length; i++) { var nav = document.createElement("div"); nav.style.position = "relative"; nav.style.top = "-1.5ex"; nav.style.left = "1.5em"; nav.style.width = "90%"; while (shortcut[0] && shortcut[0].toString().charAt(shortcut[0].toString().length - 2) == getTextContent(h2[i])) { nav.appendChild(shortcut.shift()); nav.appendChild(document.createTextNode("\u00A0 ")); } h2[i].parentNode.insertBefore(nav, h2[i].nextSibling); } function getTextContent(e) { if (e.textContent) return e.textContent; if (e.innerText) return e.innerText; } } addLoadEvent(processIndex); PKo"nR M PK*1$#"%+ ( E' n7Ȇ(,҅(L@(Q$\x 8=6 'נ9tJ&"[Epljt p#ѣHb :f F`A =l|;&9lDP2ncH R `qtp!dȐYH›+?$4mBA9 i@@ ]@ꃤFxAD*^Ŵ#,(ε  $H}F.xf,BD Z;PK1FAPK"p`ƒFF "a"E|ժOC&xCRz OBtX>XE*O>tdqAJ +,WxP!CYpQ HQzDHP)T njJM2ꔀJ2T0d#+I:<жk 'ꤱF AB @@nh Wz' H|-7f\A#yNR5 /PM09u UjćT|q~Yq@&0YZAPa`EzI /$AD Al!AAal 2H@$ PVAB&c*ؠ p @% p-`@b`uBa l&`3Ap8槖X~ vX$Eh`.JhAepA\"Bl, :Hk;PKx[?:PK_*OY0J@pw'tVh;PKp*c^PK#Sb(clhUԂ̗4DztSԙ9ZQҀEPEPEPEPEPEPEPM=iԍP Gii c*yF 1׆@\&o!QY00_rlgV;)DGhCq7~..p&1c:u֫{fI>fJL$}BBP?JRWc<^j+χ5b[hֿ- 5_j?POkeQ^hֿ1L^ H ?Qi?z?+_xɔŪ\썽O]χ>)xxV/s)e6MI7*ߊޛv֗2J,;~E4yi3[nI`Ѱe9@zXF*W +]7QJ$$=&`a۾?]N T䏟'X)Ɣkf:j |>NBWzYx0t!* _KkoTZ?K Gc+UyڹgNuh^iSo5{\ܹ3Yos}.>if FqR5\/TӮ#]HS0DKu{($"2xִ{SBJ8=}Y=.|Tsц2UЫ%.InaegKo z ݎ3ֹxxwM&2S%';+I',kW&-"_¿_ Vq^ܫ6pfT2RV A^6RKetto^[{w\jPZ@ޢN4/XN#\42j\(z'j =~-I#:q[Eh|X:sp* bifp$TspZ-}NM*B-bb&*xUr#*$M|QWY ~p~- fTED6O.#$m+t$˙H"Gk=t9r娮Y? CzE[/*-{c*[w~o_?%ƔxZ:/5𨴟q}/]22p qD\H"K]ZMKR&\C3zĽ[PJm]AS)Ia^km M@dК)fT[ijW*hnu Ͳiw/bkExG£@f?Zu.s0(<`0ֹoxOaDx\zT-^ѧʧ_1+CP/p[w 9~U^[U<[tĽwPv[yzD1W='u$Oeak[^ |Gk2xv#2?¹TkSݕ| rݞ[Vi _Kz*{\c(Ck_܏|?u jVڔ6f t?3nmZ6f%QAjJf9Rq _j7Z-y.pG$Xb]0')[_k;$̭?&"0FOew7 z-cIX岛;$u=\an$ zmrILu uٞ% _1xcUW%dtÀx885Y^gn;}ӭ)場QEQ@Q@Q@Q@Q@Q@!4xPm3w*]b`F_931˜[ן+(> E ly;<;MF-qst+}DH @YKlLmؤciN<|]IU)Lw(8t9FS(=>og<\Z~u_+X1ylsj'eՃ*U3`C!N9Q_WܱhKc93^ua>H ƕGk=8~e#_?{ǀe-[2ٔ7;=&K挑5zsLdx(e8#{1wS+ΝVkXq9>&yஏh$zq^0~/j@:/«Vnce$$uoPp}MC{$-akH@ɫ1O !8R9s5ԦYmϧ'OUṡ5T,!Ԛ+s#1Veo=[)g>#< s)ƽُA^䠮ωFUj(ǩ|N3Jڷ睁ϱuږZYGOTsI<&drav?A^_f׻B$,O__ԿC`it{6>G׈C~&$y؎v1q9Sc1fH[ѽ>,gG'0'@Vw,BO [#>ﱺg5ΒFVD%Yr:O5 Tu+O멃]ی38Ze}R&ѝ_xzc1DXgس;<,_,{ƽY'AS#oF.M#~cBuEx7G+Y)(5q+GCV;qF+CLQ)qEC&6z𿊘z}?&w=+)??&\g{;V??׻xGœdٿ׼-Nc')3K]N)iLTӿCdb7Q^a N sd>Fz[0S^s'Zi 77D}kWus ab~~H(>.fif9,~|Jk;YN3H8Y(t6Q݉k͇_÷Z+2߄&[ +Tr^藺97~c܎=[f1RrBǓ^kEMhxYVm<[џ6| kqbѱ| YA{G8p?\UM7Z66 g1U1igU69 u5Pƪ:VVZC=[@ҹ¨$kSmɳО\vFz~i3^a Osŧυ9Q}_3 όO{/wgoet39 vO2ea;Ύ7$U#?k+Ek&dpzbӱ+TaB0gN{[N7Gי}U7&@?>Fz~E!a@s ?'67XxO*!?qi]֏TQN@tI+\^s8l0)2k!!iW8F$(yOּT.k,/#1:}8uT˾+5=O/`IW G֯b.-<= HOm;~so~hW5+kS8s.zwE| ?4ӿw/K N 9?j(#0UT` Wzw}:_*9m>󑓀F?ELzv=8q:=WgJ`nDr Zе<ֹ](Q@Q@Q@Q@Q@Q@Q@Q@ 'IdC0EYJVcMty_~u+Sw-aO n<[YJgL#6i g5ЖDZ14cʝ!!\/M}/_AYR__>oC? _?7_G#RERW쏞KB}JxGSkǕA pƱơP m]hwB7U$Zq M95"3q1ioATߚ{g.t uu2k=;h#YB= fgS :TdLԃ!44mFK{Hrd^7oz|BVr<{)6AXգV»|>*/hS܏z͆OM=Εq (s|s׊LKQI :9NJ)P+!ʣoAF>+=@I}"x/}۠1aנc¹4emC:>p_xWKX` >R3_S½èųp3޺u3N e یbmͺ<_ mnݮ1Op?Gm)Qb%N585'%Ahs\6yw!"&Ɨ._wk)}GP;Z!#\"< *oƾ\)}N>"լ/~]Lg}pBG X?<zZ#x69S=6) jzx=y9O&>+e!!? ?s~k5Gʏ)?*ce7Ox~k5􇔾Q/e7/Ԑ#3OgNC0] ;_FiRl>Q.g>!%k#ú:Kn'&}?U@\pџPtp)v<{_i}Oվֲ3XIYIx~b<D?(=_JXH=bbi=Oh?_ C_O)}oW쏜? %Ƶ;-RYFi`wۭ{ϖZMtQ$"c_+ԃx1*0b;ԕ݋ESQEQEQEQEQEQEQEQEQEQZ(1F)h1K@XLRE&9P (bf{RӨ&)PEPEPbԴPGKZ(iإbn(:A%S0(-&)P+ ڎԴP11F)h&:LRmQ@Q@Š(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((PKje88PK Table of Contents

Contents

Title and Copyright Information

Preface

1 Simple Single-Source Replication Example

2 Single-Source Heterogeneous Replication Example

3 N-Way Replication Example

4 Single-Database Capture and Apply Example

5 Logical Change Records with LOBs Example

6 Rule-Based Application Example

Index

PK23PK N-Way Replication Example

3 N-Way Replication Example

This chapter illustrates an example of an n-way replication environment that can be constructed using Oracle Streams.

This chapter contains these topics:

Overview of the N-Way Replication Example

This example illustrates using Oracle Streams to replicate data for a schema among three Oracle databases. DML and DDL changes made to tables in the hrmult schema are captured at all databases in the environment and propagated to each of the other databases in the environment.

This type of environment is called an n-way replication environment. An n-way replication environment is a type of multiple-source replication environment because more than one source database captures and replicates changes.

Figure 3-1 provides an overview of the environment.

Figure 3-1 Sample N-Way Replication Environment

Description of Figure 3-1 follows
Description of "Figure 3-1 Sample N-Way Replication Environment"

As illustrated in Figure 3-1, all of the databases will contain the hrmult schema when the example is complete. However, at the beginning of the example, the hrmult schema exists only at mult1.example.com. During the example, you instantiate the hrmult schema at mult2.example.com and mult3.example.com.

In this example, Oracle Streams is used to perform the following series of actions:

  1. After instantiation, the capture process at each database captures DML and DDL changes for all of the tables in the hrmult schema and enqueues them into a local queue.

  2. Propagations at each database propagate these changes to all of the other databases in the environment.

  3. The apply processes at each database apply changes in the hrmult schema received from the other databases in the environment.

This example avoids sending changes back to their source database by using the default apply tag for the apply processes. When you create an apply process, the changes applied by the apply process have redo entries with a tag of '00' (double zero) by default. These changes are not recaptured because, by default, rules created by the DBMS_STREAMS_ADM package have an is_null_tag()='Y' condition by default, and this condition ensures that each capture process captures a change in a redo entry only if the tag for the redo entry is NULL.


See Also:


Prerequisites

The following prerequisites must be completed before you begin the example in this chapter.

  • Set the following initialization parameters to the values indicated at each database in the Oracle Streams environment:

    • GLOBAL_NAMES: This parameter must be set to TRUE. Ensure that the global names of the databases are mult1.example.com, mult2.example.com, and mult3.example.com.

    • COMPATIBLE: This parameter must be set to 10.2.0 or higher.

    • Ensure that the PROCESSES and SESSIONS initialization parameters are set high enough for all of the Oracle Streams clients used in this example. This example configures one capture process, two propagations, and two apply processes at each database.

    • STREAMS_POOL_SIZE: Optionally set this parameter to an appropriate value for each database in the environment. This parameter specifies the size of the Oracle Streams pool. The Oracle Streams pool stores messages in a buffered queue and is used for internal communications during parallel capture and apply. When the MEMORY_TARGET, MEMORY_MAX_TARGET, or SGA_TARGET initialization parameter is set to a nonzero value, the Oracle Streams pool size is managed automatically.


      Note:

      You might need to modify other initialization parameter settings for this example to run properly.


    See Also:

    Oracle Streams Replication Administrator's Guide for information about other initialization parameters that are important in an Oracle Streams environment

  • Any database producing changes that will be captured must be running in ARCHIVELOG mode. In this example, all databases are capturing changes, and so all databases must be running in ARCHIVELOG mode.


    See Also:

    Oracle Database Administrator's Guide for information about running a database in ARCHIVELOG mode

  • Configure your network and Oracle Net so that all three databases can communicate with each other.

  • Create an Oracle Streams administrator at each database in the replication environment. In this example, the databases are mult1.example.com, mult2.example.com, and mult3.example.com. This example assumes that the user name of the Oracle Streams administrator is strmadmin.


    See Also:

    Oracle Streams Replication Administrator's Guide for instructions about creating an Oracle Streams administrator

Create the hrmult Schema at the mult1.example.com Database

For the purposes of this example, create a new schema named hrmult at the mult1.example.com database. The n-way environment will replicate this new schema.

Complete the following steps to use Data Pump export/import to create an hrmult schema that is a copy of the hr schema:

  1. In SQL*Plus, connect to the mult1.example.com database as an administrative user.

    See Oracle Database Administrator's Guide for instructions about connecting to a database in SQL*Plus.

  2. Create a directory object to hold the export dump file and export log file. The directory object can point to any accessible directory on the computer system. For example, the following statement creates a directory object named dp_hrmult_dir that points to the /usr/tmp directory:

    CREATE DIRECTORY dp_hrmult_dir AS '/usr/tmp';
    

    Substitute an appropriate directory on your computer system.

  3. Determine the current system change number (SCN) of the source database:

    SELECT DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER FROM DUAL;
    

    The SCN value returned by this query is specified for the FLASHBACK_SCN Data Pump export parameter in Step 5. Because the hr schema includes foreign key constraints between tables, the FLASHBACK_SCN export parameter, or a similar export parameter, must be specified during export.

  4. Exit SQL*Plus.

  5. On a command line at the mult1.example.com database site, use Data Pump to export the hr schema at the mult1.example.com database. Ensure that you specify the SCN value returned in Step 3 for the FLASHBACK_SCN parameter:

    expdp system SCHEMAS=hr DIRECTORY=dp_hrmult_dir
    DUMPFILE=hrmult_schema.dmp FLASHBACK_SCN=flashback_scn_value
    
  6. On a command line at the mult1.example.com database site, use Data Pump to import the import dump file hrmult_schema.dmp:

    impdp system SCHEMAS=hr DIRECTORY=dp_hrmult_dir
    DUMPFILE=hrmult_schema.dmp REMAP_SCHEMA=hr:hrmult
    
  7. In SQL*Plus, connect to the mult1.example.com database as an administrative user.

  8. Assign a password to the new hrmult user at the mult1.example.com database using the ALTER USER statement.

    Remember the password that you assign to the hrmult user so that you can log in as the user in the future.

Create Queues and Database Links

This section illustrates how to create queues and database links for an Oracle Streams replication environment that includes three Oracle databases. The remaining parts of this example depend on the queues and database links that you configure in this section.

Complete the following steps to create the queues and database links at all of the databases.

  1. Show Output and Spool Results

  2. Create the ANYDATA Queue at mult1.example.com

  3. Create the Database Links at mult1.example.com

  4. Prepare the Tables at mult1.example.com for Latest Time Conflict Resolution

  5. Create the ANYDATA Queue at mult2.example.com

  6. Create the Database Links at mult2.example.com

  7. Create the ANYDATA Queue at mult3.example.com

  8. Create the Database Links at mult3.example.com

  9. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to all of the databases in the environment.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL streams_setup_mult.out

/*
Step 2   Create the ANYDATA Queue at mult1.example.com

Connect as the Oracle Streams administrator at mult1.example.com.

*/

CONNECT strmadmin@mult1.example.com

/*

Run the SET_UP_QUEUE procedure to create the following queues:

  • The captured_mult1 queue to hold changes captured at the mult1.example.com database and propagated to other databases.

  • The from_mult2 queue to hold changes captured at the mult2.example.com database and propagated to the mult1.example.com database to be applied.

  • The from_mult3 queue to hold changes captured at the mult3.example.com database and propagated to the mult1.example.com database to be applied.

Running the SET_UP_QUEUE procedure performs the following actions for each queue:

  • Creates a queue table that is owned by the Oracle Streams administrator (strmadmin) and that uses the default storage of this user.

  • Creates an ANYDATA queue that is owned by the Oracle Streams administrator (strmadmin).

  • Starts the queue.

*/

BEGIN
  DBMS_STREAMS_ADM.SET_UP_QUEUE(
    queue_table  => 'strmadmin.captured_mult1_table',
    queue_name   => 'strmadmin.captured_mult1');
END;
/

BEGIN
  DBMS_STREAMS_ADM.SET_UP_QUEUE(
    queue_table  => 'strmadmin.from_mult2_table',
    queue_name   => 'strmadmin.from_mult2');
END;
/

BEGIN
  DBMS_STREAMS_ADM.SET_UP_QUEUE(
    queue_table  => 'strmadmin.from_mult3_table',
    queue_name   => 'strmadmin.from_mult3');
END;
/

/*
Step 3   Create the Database Links at mult1.example.com

Create database links from the current database to the other databases in the environment.

*/

ACCEPT password PROMPT 'Enter password for user: ' HIDE

CREATE DATABASE LINK mult2.example.com CONNECT TO strmadmin 
   IDENTIFIED BY &password USING 'mult2.example.com';

CREATE DATABASE LINK mult3.example.com CONNECT TO strmadmin 
   IDENTIFIED BY &password USING 'mult3.example.com';

/*
Step 4   Prepare the Tables at mult1.example.com for Latest Time Conflict Resolution

This example will configure the tables in the hrmult schema for conflict resolution based on the latest time for a transaction.

Connect to mult1.example.com as the hrmult user.

*/
 
CONNECT hrmult@mult1.example.com

/*

Add a time column to each table in the hrmult schema.

*/
 
ALTER TABLE hrmult.countries ADD (time TIMESTAMP WITH TIME ZONE);
ALTER TABLE hrmult.departments ADD (time TIMESTAMP WITH TIME ZONE);
ALTER TABLE hrmult.employees ADD (time TIMESTAMP WITH TIME ZONE);
ALTER TABLE hrmult.job_history ADD (time TIMESTAMP WITH TIME ZONE);
ALTER TABLE hrmult.jobs ADD (time TIMESTAMP WITH TIME ZONE);
ALTER TABLE hrmult.locations ADD (time TIMESTAMP WITH TIME ZONE);
ALTER TABLE hrmult.regions ADD (time TIMESTAMP WITH TIME ZONE);

/*

Create a trigger for each table in the hrmult schema to insert the time of a transaction for each row inserted or updated by the transaction.

*/

CREATE OR REPLACE TRIGGER hrmult.insert_time_countries
BEFORE
  INSERT OR UPDATE ON hrmult.countries FOR EACH ROW
BEGIN
   -- Consider time synchronization problems. The previous update to this 
   -- row might have originated from a site with a clock time ahead of the 
   -- local clock time.
   IF :OLD.TIME IS NULL OR :OLD.TIME < SYSTIMESTAMP THEN
     :NEW.TIME := SYSTIMESTAMP;
   ELSE
     :NEW.TIME := :OLD.TIME + 1 / 86400;
   END IF;
END;
/

CREATE OR REPLACE TRIGGER hrmult.insert_time_departments
BEFORE
  INSERT OR UPDATE ON hrmult.departments FOR EACH ROW
BEGIN
   IF :OLD.TIME IS NULL OR :OLD.TIME < SYSTIMESTAMP THEN
     :NEW.TIME := SYSTIMESTAMP;
   ELSE
     :NEW.TIME := :OLD.TIME + 1 / 86400;
   END IF;
END;
/

CREATE OR REPLACE TRIGGER hrmult.insert_time_employees
BEFORE
  INSERT OR UPDATE ON hrmult.employees FOR EACH ROW
BEGIN
   IF :OLD.TIME IS NULL OR :OLD.TIME < SYSTIMESTAMP THEN
     :NEW.TIME := SYSTIMESTAMP;
   ELSE
     :NEW.TIME := :OLD.TIME + 1 / 86400;
   END IF;
END;
/

CREATE OR REPLACE TRIGGER hrmult.insert_time_job_history
BEFORE
  INSERT OR UPDATE ON hrmult.job_history FOR EACH ROW
BEGIN
   IF :OLD.TIME IS NULL OR :OLD.TIME < SYSTIMESTAMP THEN
     :NEW.TIME := SYSTIMESTAMP;
   ELSE
     :NEW.TIME := :OLD.TIME + 1 / 86400;
   END IF;
END;
/

CREATE OR REPLACE TRIGGER hrmult.insert_time_jobs
BEFORE
  INSERT OR UPDATE ON hrmult.jobs FOR EACH ROW
BEGIN
   IF :OLD.TIME IS NULL OR :OLD.TIME < SYSTIMESTAMP THEN
     :NEW.TIME := SYSTIMESTAMP;
   ELSE
     :NEW.TIME := :OLD.TIME + 1 / 86400;
   END IF;
END;
/

CREATE OR REPLACE TRIGGER hrmult.insert_time_locations
BEFORE
  INSERT OR UPDATE ON hrmult.locations FOR EACH ROW
BEGIN
   IF :OLD.TIME IS NULL OR :OLD.TIME < SYSTIMESTAMP THEN
     :NEW.TIME := SYSTIMESTAMP;
   ELSE
     :NEW.TIME := :OLD.TIME + 1 / 86400;
   END IF;
END;
/

CREATE OR REPLACE TRIGGER hrmult.insert_time_regions
BEFORE
  INSERT OR UPDATE ON hrmult.regions FOR EACH ROW
BEGIN
   IF :OLD.TIME IS NULL OR :OLD.TIME < SYSTIMESTAMP THEN
     :NEW.TIME := SYSTIMESTAMP;
   ELSE
     :NEW.TIME := :OLD.TIME + 1 / 86400;
   END IF;
END;
/

/*
Step 5   Create the ANYDATA Queue at mult2.example.com

Connect as the Oracle Streams administrator at mult2.example.com.

*/

CONNECT strmadmin@mult2.example.com

/*

Run the SET_UP_QUEUE procedure to create the following queues:

  • The captured_mult2 queue to hold changes captured at the mult2.example.com database and propagated to other databases.

  • The from_mult1 queue to hold changes captured at the mult1.example.com database and propagated to the mult2.example.com database to be applied.

  • The from_mult3 queue to hold changes captured at the mult3.example.com database and propagated to the mult2.example.com database to be applied.

Running the SET_UP_QUEUE procedure performs the following actions for each queue:

  • Creates a queue table that is owned by the Oracle Streams administrator (strmadmin) and that uses the default storage of this user.

  • Creates an ANYDATA queue that is owned by the Oracle Streams administrator (strmadmin).

  • Starts the queue.

*/

BEGIN
  DBMS_STREAMS_ADM.SET_UP_QUEUE(
    queue_table  => 'strmadmin.captured_mult2_table',
    queue_name   => 'strmadmin.captured_mult2');
END;
/

BEGIN
  DBMS_STREAMS_ADM.SET_UP_QUEUE(
    queue_table  => 'strmadmin.from_mult1_table',
    queue_name   => 'strmadmin.from_mult1');
END;
/

BEGIN
  DBMS_STREAMS_ADM.SET_UP_QUEUE(
    queue_table  => 'strmadmin.from_mult3_table',
    queue_name   => 'strmadmin.from_mult3');
END;
/

/*
Step 6   Create the Database Links at mult2.example.com

Create database links from the current database to the other databases in the environment.

*/

CREATE DATABASE LINK mult1.example.com CONNECT TO strmadmin 
   IDENTIFIED BY &password USING 'mult1.example.com';

CREATE DATABASE LINK mult3.example.com CONNECT TO strmadmin 
   IDENTIFIED BY &password USING 'mult3.example.com';

/*
Step 7   Create the ANYDATA Queue at mult3.example.com

Connect as the Oracle Streams administrator at mult3.example.com.

*/

CONNECT strmadmin@mult3.example.com

/*

Run the SET_UP_QUEUE procedure to create the following queues:

  • The captured_mult3 queue to hold changes captured at the mult3.example.com database and propagated to other databases.

  • The from_mult1 queue to hold changes captured at the mult1.example.com database and propagated to the mult3.example.com database to be applied.

  • The from_mult2 queue to hold changes captured at the mult2.example.com database and propagated to the mult3.example.com database to be applied.

Running the SET_UP_QUEUE procedure performs the following actions for each queue:

  • Creates a queue table that is owned by the Oracle Streams administrator (strmadmin) and that uses the default storage of this user.

  • Creates an ANYDATA queue that is owned by the Oracle Streams administrator (strmadmin).

  • Starts the queue.

*/

BEGIN
  DBMS_STREAMS_ADM.SET_UP_QUEUE(
    queue_table  => 'strmadmin.captured_mult3_table',
    queue_name   => 'strmadmin.captured_mult3');
END;
/

BEGIN
  DBMS_STREAMS_ADM.SET_UP_QUEUE(
    queue_table  => 'strmadmin.from_mult1_table',
    queue_name   => 'strmadmin.from_mult1');
END;
/

BEGIN
  DBMS_STREAMS_ADM.SET_UP_QUEUE(
    queue_table  => 'strmadmin.from_mult2_table',
    queue_name   => 'strmadmin.from_mult2');
END;
/

/*
Step 8   Create the Database Links at mult3.example.com

Create database links from the current database to the other databases in the environment.

*/

CREATE DATABASE LINK mult1.example.com CONNECT TO strmadmin 
   IDENTIFIED BY &password USING 'mult1.example.com';

CREATE DATABASE LINK mult2.example.com CONNECT TO strmadmin 
   IDENTIFIED BY &password USING 'mult2.example.com';

/*
Step 9   Check the Spool Results

Check the streams_setup_mult.out spool file to ensure that all actions finished successfully after this script is completed.

*/

SET ECHO OFF
SPOOL OFF

/*************************** END OF SCRIPT ******************************/

Example Script for Configuring N-Way Replication

Complete the following steps to configure an Oracle Streams n-way replication environment.

  1. Show Output and Spool Results

  2. Specify Supplemental Logging at mult1.example.com

  3. Create the Capture Process at mult1.example.com

  4. Create One Apply Process at mult1.example.com for Each Source Database

  5. Configure Latest Time Conflict Resolution at mult1.example.com

  6. Configure Propagation at mult1.example.com

  7. Create the Capture Process at mult2.example.com.

  8. Set the Instantiation SCN for mult2.example.com at the Other Databases

  9. Create One Apply Process at mult2.example.com for Each Source Database

  10. Configure Propagation at mult2.example.com

  11. Create the Capture Process at mult3.example.com

  12. Set the Instantiation SCN for mult3.example.com at the Other Databases

  13. Create One Apply Process at mult3.example.com for Each Source Database

  14. Configure Propagation at mult3.example.com

  15. Instantiate the hrmult Schema at mult2.example.com

  16. Instantiate the hrmult Schema at mult3.example.com

  17. Configure Latest Time Conflict Resolution at mult2.example.com

  18. Start the Apply Processes at mult2.example.com

  19. Configure Latest Time Conflict Resolution at mult3.example.com

  20. Start the Apply Processes at mult3.example.com

  21. Start the Apply Processes at mult1.example.com

  22. Start the Capture Process at mult1.example.com

  23. Start the Capture Process at mult2.example.com

  24. Start the Capture Process at mult3.example.com

  25. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to all of the databases in the environment.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL streams_mult.out

/*
Step 2   Specify Supplemental Logging at mult1.example.com

Connect to mult1.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@mult1.example.com

/*

Specify an unconditional supplemental log group that includes the primary key for each table and the column list for each table, as specified in "Configure Latest Time Conflict Resolution at mult1.example.com". Because the column list for each table includes all of the columns of each table except for its primary key, this step creates a supplemental log group for each table that includes all of the columns in the table.


Note:

  • For convenience, this example includes the primary key column(s) for each table and the columns used for update conflict resolution in a single unconditional log group. You can choose to place the primary key column(s) for each table in an unconditional log group and the columns used for update conflict resolution in a conditional log group.

  • You do not need to specify supplemental logging explicitly at mult2.example.com and mult3.example.com in this example. When you use Data Pump to instantiate the tables in the hrmult schema at these databases later in this example, the supplemental logging specifications at mult1.example.com are retained at mult2.example.com and mult3.example.com.


*/

ALTER TABLE hrmult.countries ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS;

ALTER TABLE hrmult.departments ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS;

ALTER TABLE hrmult.employees ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS;

ALTER TABLE hrmult.jobs ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS;

ALTER TABLE hrmult.job_history ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS;

ALTER TABLE hrmult.locations ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS;

ALTER TABLE hrmult.regions ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS;

/*
Step 3   Create the Capture Process at mult1.example.com

Create the capture process to capture changes to the entire hrmult schema at mult1.example.com. This step also prepares the hrmult schema at mult1.example.com for instantiation. After this step is complete, users can modify tables in the hrmult schema at mult1.example.com.

*/

BEGIN   
  DBMS_STREAMS_ADM.ADD_SCHEMA_RULES(
    schema_name    => 'hrmult',
    streams_type   => 'capture',
    streams_name   => 'capture_hrmult', 
    queue_name     => 'strmadmin.captured_mult1',
    include_dml    => TRUE,
    include_ddl    => TRUE,
    inclusion_rule => TRUE);
END;
/

/*
Step 4   Create One Apply Process at mult1.example.com for Each Source Database

Configure mult1.example.com to apply changes to the hrmult schema at mult2.example.com.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_SCHEMA_RULES(
    schema_name     => 'hrmult',   
    streams_type    => 'apply',
    streams_name    => 'apply_from_mult2',
    queue_name      => 'strmadmin.from_mult2',
    include_dml     => TRUE,
    include_ddl     => TRUE,
    source_database => 'mult2.example.com',
    inclusion_rule  => TRUE);
END;
/

/*

Configure mult1.example.com to apply changes to the hrmult schema at mult3.example.com.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_SCHEMA_RULES(
    schema_name     => 'hrmult',   
    streams_type    => 'apply',
    streams_name    => 'apply_from_mult3',
    queue_name      => 'strmadmin.from_mult3',
    include_dml     => TRUE,
    include_ddl     => TRUE,
    source_database => 'mult3.example.com',
    inclusion_rule  => TRUE);
END;
/

/*
Step 5   Configure Latest Time Conflict Resolution at mult1.example.com

Specify an update conflict handler for each table in the hrmult schema. For each table, designate the time column as the resolution column for a MAXIMUM conflict handler. When an update conflict occurs, such an update conflict handler applies the transaction with the latest (or greater) time and discards the transaction with the earlier (or lesser) time. The column lists include all columns for each table, except for the primary key, because this example assumes that primary key values are never updated.

*/
 
DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'country_name';
  cols(2) := 'region_id';
  cols(3) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.countries',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'department_name';
  cols(2) := 'manager_id';
  cols(3) := 'location_id';
  cols(4) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.departments',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1)  := 'first_name';
  cols(2)  := 'last_name';
  cols(3)  := 'email';
  cols(4)  := 'phone_number';
  cols(5)  := 'hire_date';
  cols(6)  := 'job_id';
  cols(7)  := 'salary';
  cols(8)  := 'commission_pct';
  cols(9)  := 'manager_id';
  cols(10) := 'department_id';
  cols(11) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.employees',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'job_title';
  cols(2) := 'min_salary';
  cols(3) := 'max_salary';
  cols(4) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.jobs',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'employee_id';
  cols(2) := 'start_date';
  cols(3) := 'end_date';
  cols(4) := 'job_id';
  cols(5) := 'department_id';
  cols(6) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.job_history',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'street_address';
  cols(2) := 'postal_code';
  cols(3) := 'city';
  cols(4) := 'state_province';
  cols(5) := 'country_id';
  cols(6) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.locations',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'region_name';
  cols(2) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.regions',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

/*
Step 6   Configure Propagation at mult1.example.com

Configure and schedule propagation of DML and DDL changes in the hrmult schema from the queue at mult1.example.com to the queue at mult2.example.com.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_SCHEMA_PROPAGATION_RULES(
    schema_name               => 'hrmult', 
    streams_name              => 'mult1_to_mult2',    
    source_queue_name         => 'strmadmin.captured_mult1',
    destination_queue_name    => 'strmadmin.from_mult1@mult2.example.com',
    include_dml               => TRUE,
    include_ddl               => TRUE,
    source_database           => 'mult1.example.com',
    inclusion_rule            => TRUE,
    queue_to_queue            => TRUE);
END;
/

/*

Configure and schedule propagation of DML and DDL changes in the hrmult schema from the queue at mult1.example.com to the queue at mult3.example.com.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_SCHEMA_PROPAGATION_RULES(
    schema_name               => 'hrmult', 
    streams_name              => 'mult1_to_mult3',    
    source_queue_name         => 'strmadmin.captured_mult1',
    destination_queue_name    => 'strmadmin.from_mult1@mult3.example.com',
    include_dml               => TRUE,
    include_ddl               => TRUE,
    source_database           => 'mult1.example.com',
    inclusion_rule            => TRUE,
    queue_to_queue            => TRUE);
END;
/

/*
Step 7   Create the Capture Process at mult2.example.com.

Connect to mult2.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@mult2.example.com

/*

Create the capture process to capture changes to the entire hrmult schema at mult2.example.com. This step also prepares the hrmult schema at mult2.example.com for instantiation.

*/

BEGIN   
  DBMS_STREAMS_ADM.ADD_SCHEMA_RULES(
    schema_name    => 'hrmult',
    streams_type   => 'capture',
    streams_name   => 'capture_hrmult', 
    queue_name     => 'strmadmin.captured_mult2',
    include_dml    => TRUE,
    include_ddl    => TRUE,
    inclusion_rule => TRUE);
END;
/

/*
Step 8   Set the Instantiation SCN for mult2.example.com at the Other Databases

In this example, the hrmult schema already exists at all of the databases. The tables in the schema exist only at mult1.example.com until they are instantiated at mult2.example.com and mult3.example.com in Step 16. The instantiation is done using an import of the tables from mult1.example.com. These import operations set the schema instantiation SCNs for mult1.example.com at mult2.example.com and mult3.example.com automatically.

However, the instantiation SCNs for mult2.example.com and mult3.example.com are not set automatically at the other sites in the environment. This step sets the schema instantiation SCN for mult2.example.com manually at mult1.example.com and mult3.example.com. The current SCN at mult2.example.com is obtained by using the GET_SYSTEM_CHANGE_NUMBER function in the DBMS_FLASHBACK package at mult2.example.com. This SCN is used at mult1.example.com and mult3.example.com to run the SET_SCHEMA_INSTANTIATION_SCN procedure in the DBMS_APPLY_ADM package.

The SET_SCHEMA_INSTANTIATION_SCN procedure controls which DDL LCRs for a schema are ignored by an apply process and which DDL LCRs for a schema are applied by an apply process. If the commit SCN of a DDL LCR for a database object in a schema from a source database is less than or equal to the instantiation SCN for that database object at some destination database, then the apply process at the destination database disregards the DDL LCR. Otherwise, the apply process applies the DDL LCR.

Because you are running the SET_SCHEMA_INSTANTIATION_SCN procedure before the tables are instantiated at mult2.example.com, and because the local capture process is configured already, you do not need to run the SET_TABLE_INSTANTIATION_SCN for each table after the instantiation. In this example, an apply process at both mult1.example.com and mult3.example.com will apply transactions to the tables in the hrmult schema with SCNs that were committed after the SCN obtained in this step.


Note:

  • In a case where you are instantiating a schema that does not exist, you can set the global instantiation SCN instead of the schema instantiation SCN.

  • In a case where the tables are instantiated before you set the instantiation SCN, you must set the schema instantiation SCN and the instantiation SCN for each table in the schema.


*/

DECLARE
  iscn  NUMBER;         -- Variable to hold instantiation SCN value
BEGIN
  iscn := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER();
  DBMS_APPLY_ADM.SET_SCHEMA_INSTANTIATION_SCN@MULT1.EXAMPLE.COM(
    source_schema_name    => 'hrmult',
    source_database_name  => 'mult2.example.com',
    instantiation_scn     => iscn);
  DBMS_APPLY_ADM.SET_SCHEMA_INSTANTIATION_SCN@MULT3.EXAMPLE.COM(
    source_schema_name    => 'hrmult',
    source_database_name  => 'mult2.example.com',
    instantiation_scn     => iscn);
END;
/

/*

Step 9   Create One Apply Process at mult2.example.com for Each Source Database

Configure mult2.example.com to apply changes to the hrmult schema at mult1.example.com.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_SCHEMA_RULES(
    schema_name     => 'hrmult',   
    streams_type    => 'apply',
    streams_name    => 'apply_from_mult1',
    queue_name      => 'strmadmin.from_mult1',
    include_dml     => TRUE,
    include_ddl     => TRUE,
    source_database => 'mult1.example.com',
    inclusion_rule  => TRUE);
END;
/

/*

Configure mult2.example.com to apply changes to the hrmult schema at mult3.example.com.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_SCHEMA_RULES(
    schema_name     => 'hrmult',   
    streams_type    => 'apply',
    streams_name    => 'apply_from_mult3',
    queue_name      => 'strmadmin.from_mult3',
    include_dml     => TRUE,
    include_ddl     => TRUE,
    source_database => 'mult3.example.com',
    inclusion_rule  => TRUE);
END;
/

/*
Step 10   Configure Propagation at mult2.example.com

Configure and schedule propagation of DML and DDL changes in the hrmult schema from the queue at mult2.example.com to the queue at mult1.example.com.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_SCHEMA_PROPAGATION_RULES(
    schema_name             => 'hrmult',
    streams_name            => 'mult2_to_mult1',
    source_queue_name       => 'strmadmin.captured_mult2',
    destination_queue_name  => 'strmadmin.from_mult2@mult1.example.com',
    include_dml             => TRUE,
    include_ddl             => TRUE, 
    source_database         => 'mult2.example.com',
    inclusion_rule          => TRUE,
    queue_to_queue          => TRUE);
END;
/

/*

Configure and schedule propagation of DML and DDL changes in the hrmult schema from the queue at mult2.example.com to the queue at mult3.example.com.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_SCHEMA_PROPAGATION_RULES(
    schema_name             => 'hrmult',
    streams_name            => 'mult2_to_mult3',
    source_queue_name       => 'strmadmin.captured_mult2',
    destination_queue_name  => 'strmadmin.from_mult2@mult3.example.com', 
    include_dml             => TRUE,
    include_ddl             => TRUE,
    source_database         => 'mult2.example.com',
    inclusion_rule          => TRUE,
    queue_to_queue          => TRUE);
END;
/

/*
Step 11   Create the Capture Process at mult3.example.com

Connect to mult3.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@mult3.example.com

/*

Create the capture process to capture changes to the entire hrmult schema at mult3.example.com. This step also prepares the hrmult schema at mult3.example.com for instantiation.

*/

BEGIN   
  DBMS_STREAMS_ADM.ADD_SCHEMA_RULES(
    schema_name    => 'hrmult',
    streams_type   => 'capture',
    streams_name   => 'capture_hrmult', 
    queue_name     => 'strmadmin.captured_mult3',
    include_dml    => TRUE,
    include_ddl    => TRUE,
    inclusion_rule => TRUE);
END;
/

/*
Step 12   Set the Instantiation SCN for mult3.example.com at the Other Databases

In this example, the hrmult schema already exists at all of the databases. The tables in the schema exist only at mult1.example.com until they are instantiated at mult2.example.com and mult3.example.com in Step 16. The instantiation is done using an import of the tables from mult1.example.com. These import operations set the schema instantiation SCNs for mult1.example.com at mult2.example.com and mult3.example.com automatically.

However, the instantiation SCNs for mult2.example.com and mult3.example.com are not set automatically at the other sites in the environment. This step sets the schema instantiation SCN for mult3.example.com manually at mult1.example.com and mult2.example.com. The current SCN at mult3.example.com is obtained by using the GET_SYSTEM_CHANGE_NUMBER function in the DBMS_FLASHBACK package at mult3.example.com. This SCN is used at mult1.example.com and mult2.example.com to run the SET_SCHEMA_INSTANTIATION_SCN procedure in the DBMS_APPLY_ADM package.

The SET_SCHEMA_INSTANTIATION_SCN procedure controls which DDL LCRs for a schema are ignored by an apply process and which DDL LCRs for a schema are applied by an apply process. If the commit SCN of a DDL LCR for a database object in a schema from a source database is less than or equal to the instantiation SCN for that database object at some destination database, then the apply process at the destination database disregards the DDL LCR. Otherwise, the apply process applies the DDL LCR.

Because you are running the SET_SCHEMA_INSTANTIATION_SCN procedure before the tables are instantiated at mult3.example.com, and because the local capture process is configured already, you do not need to run the SET_TABLE_INSTANTIATION_SCN for each table after the instantiation. In this example, an apply process at both mult1.example.com and mult2.example.com will apply transactions to the tables in the hrmult schema with SCNs that were committed after the SCN obtained in this step.


Note:

  • In a case where you are instantiating a schema that does not exist, you can set the global instantiation SCN instead of the schema instantiation SCN.

  • In a case where the tables are instantiated before you set the instantiation SCN, you must set the schema instantiation SCN and the instantiation SCN for each table in the schema.


*/

DECLARE
  iscn  NUMBER;         -- Variable to hold instantiation SCN value
BEGIN
  iscn := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER();
  DBMS_APPLY_ADM.SET_SCHEMA_INSTANTIATION_SCN@MULT1.EXAMPLE.COM(
    source_schema_name    => 'hrmult',
    source_database_name  => 'mult3.example.com',
    instantiation_scn     => iscn);
  DBMS_APPLY_ADM.SET_SCHEMA_INSTANTIATION_SCN@MULT2.EXAMPLE.COM(
    source_schema_name    => 'hrmult',
    source_database_name  => 'mult3.example.com',
    instantiation_scn     => iscn);
END;
/

/*
Step 13   Create One Apply Process at mult3.example.com for Each Source Database

Configure mult3.example.com to apply changes to the hrmult schema at mult1.example.com.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_SCHEMA_RULES(
    schema_name     => 'hrmult',   
    streams_type    => 'apply',
    streams_name    => 'apply_from_mult1',
    queue_name      => 'strmadmin.from_mult1',
    include_dml     => TRUE,
    include_ddl     => TRUE,
    source_database => 'mult1.example.com',
    inclusion_rule  => TRUE);
END;
/

/*

Configure mult3.example.com to apply changes to the hrmult schema at mult2.example.com.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_SCHEMA_RULES(
    schema_name     => 'hrmult',   
    streams_type    => 'apply',
    streams_name    => 'apply_from_mult2',
    queue_name      => 'strmadmin.from_mult2',
    include_dml     => TRUE,
    include_ddl     => TRUE,
    source_database => 'mult2.example.com',
    inclusion_rule  => TRUE);
END;
/

/*
Step 14   Configure Propagation at mult3.example.com

Configure and schedule propagation of DML and DDL changes in the hrmult schema from the queue at mult3.example.com to the queue at mult1.example.com.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_SCHEMA_PROPAGATION_RULES(
    schema_name             => 'hrmult',
    streams_name            => 'mult3_to_mult1',
    source_queue_name       => 'strmadmin.captured_mult3',
    destination_queue_name  => 'strmadmin.from_mult3@mult1.example.com', 
    include_dml             => TRUE,
    include_ddl             => TRUE,
    source_database         => 'mult3.example.com',
    inclusion_rule          => TRUE,
    queue_to_queue          => TRUE);
END;
/

/*

Configure and schedule propagation of DML and DDL changes in the hrmult schema from the queue at mult3.example.com to the queue at mult2.example.com.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_SCHEMA_PROPAGATION_RULES(
    schema_name             => 'hrmult',
    streams_name            => 'mult3_to_mult2',
    source_queue_name       => 'strmadmin.captured_mult3',
    destination_queue_name  => 'strmadmin.from_mult3@mult2.example.com', 
    include_dml             => TRUE,
    include_ddl             => TRUE,
    source_database         => 'mult3.example.com',
    inclusion_rule          => TRUE,
    queue_to_queue          => TRUE);
END;
/

/*
Step 15   Instantiate the hrmult Schema at mult2.example.com

This example performs a network Data Pump import of the hrmult schema from mult1.example.com to mult2.example.com. A network import means that Data Pump imports the database objects in the schema from mult1.example.com without using an export dump file.


See Also:

Oracle Database Utilities for information about performing an import

Connect to mult2.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@mult2.example.com

/*

This example will do a schema-level import using the DBMS_DATAPUMP package. For simplicity, exceptions from any of the API calls will not be trapped. However, Oracle recommends that you define exception handlers and call GET_STATUS to retrieve more detailed error information if a failure occurs. If you want to monitor the import, then query the DBA_DATAPUMP_JOBS data dictionary view at the import database.

*/

SET SERVEROUTPUT ON
DECLARE
  h1                 NUMBER;         -- Data Pump job handle
  mult2_instantscn   NUMBER;         -- Variable to hold current source SCN
  job_state          VARCHAR2(30);   -- To keep track of job state
  js                 ku$_JobStatus;  -- The job status from GET_STATUS
  sts                ku$_Status;     -- The status object returned by GET_STATUS
  job_not_exist    exception;
  pragma exception_init(job_not_exist, -31626);
BEGIN
-- Create a (user-named) Data Pump job to do a schema-level import.
  h1 := DBMS_DATAPUMP.OPEN(
          operation   => 'IMPORT',
          job_mode    => 'SCHEMA',
          remote_link => 'MULT1.EXAMPLE.COM',
          job_name    => 'dp_mult2');
-- A metadata filter is used to specify the schema that owns the tables 
-- that will be imported.
  DBMS_DATAPUMP.METADATA_FILTER(
    handle    => h1,
    name      => 'SCHEMA_EXPR',
    value     => '=''HRMULT''');
-- Get the current SCN of the source database, and set the FLASHBACK_SCN 
-- parameter to this value to ensure consistency between all of the 
-- objects in the schema.
  mult2_instantscn := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER@mult1.example.com();  
  DBMS_DATAPUMP.SET_PARAMETER(
    handle => h1,
    name   => 'FLASHBACK_SCN',
    value  => mult2_instantscn); 
-- Start the job. 
  DBMS_DATAPUMP.START_JOB(h1);
-- The import job should be running. In the following loop, the job
-- is monitored until it completes.
  job_state := 'UNDEFINED';
  BEGIN
    WHILE (job_state != 'COMPLETED') AND (job_state != 'STOPPED') LOOP
      sts:=DBMS_DATAPUMP.GET_STATUS(
             handle  => h1,
             mask    => DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR +
                        DBMS_DATAPUMP.KU$_STATUS_JOB_STATUS +
                        DBMS_DATAPUMP.KU$_STATUS_WIP,
             timeout => -1);
      js := sts.job_status;
      DBMS_LOCK.SLEEP(10);
      job_state := js.state;
    END LOOP;
  -- Gets an exception when job no longer exists
    EXCEPTION WHEN job_not_exist THEN
      DBMS_OUTPUT.PUT_LINE('Data Pump job has completed');
      DBMS_OUTPUT.PUT_LINE('Instantiation SCN: ' ||mult2_instantscn);
  END;
END;
/

/*
Step 16   Instantiate the hrmult Schema at mult3.example.com

This example performs a network Data Pump import of the hrmult schema from mult1.example.com to mult3.example.com. A network import means that Data Pump imports the database objects in the schema from mult1.example.com without using an export dump file.


See Also:

Oracle Database Utilities for information about performing an import

Connect to mult3.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@mult3.example.com

/*

This example will do a table import using the DBMS_DATAPUMP package. For simplicity, exceptions from any of the API calls will not be trapped. However, Oracle recommends that you define exception handlers and call GET_STATUS to retrieve more detailed error information if a failure occurs. If you want to monitor the import, then query the DBA_DATAPUMP_JOBS data dictionary view at the import database.

*/

SET SERVEROUTPUT ON
DECLARE
  h1                 NUMBER;         -- Data Pump job handle
  mult3_instantscn   NUMBER;         -- Variable to hold current source SCN
  job_state          VARCHAR2(30);   -- To keep track of job state
  js                 ku$_JobStatus;  -- The job status from GET_STATUS
  sts                ku$_Status;     -- The status object returned by GET_STATUS
  job_not_exist    exception;
  pragma exception_init(job_not_exist, -31626);
BEGIN
-- Create a (user-named) Data Pump job to do a schema-level import.
  h1 := DBMS_DATAPUMP.OPEN(
          operation   => 'IMPORT',
          job_mode    => 'SCHEMA',
          remote_link => 'MULT1.EXAMPLE.COM',
          job_name    => 'dp_mult3');
-- A metadata filter is used to specify the schema that owns the tables 
-- that will be imported.
  DBMS_DATAPUMP.METADATA_FILTER(
    handle    => h1,
    name      => 'SCHEMA_EXPR',
    value     => '=''HRMULT''');
-- Get the current SCN of the source database, and set the FLASHBACK_SCN 
-- parameter to this value to ensure consistency between all of the 
-- objects in the schema.
  mult3_instantscn := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER@mult1.example.com();
  DBMS_DATAPUMP.SET_PARAMETER(
    handle => h1,
    name   => 'FLASHBACK_SCN',
    value  => mult3_instantscn); 
-- Start the job. 
  DBMS_DATAPUMP.START_JOB(h1);
-- The import job should be running. In the following loop, the job
-- is monitored until it completes.
  job_state := 'UNDEFINED';
  BEGIN
    WHILE (job_state != 'COMPLETED') AND (job_state != 'STOPPED') LOOP
      sts:=DBMS_DATAPUMP.GET_STATUS(
             handle  => h1,
             mask    => DBMS_DATAPUMP.KU$_STATUS_JOB_ERROR +
                        DBMS_DATAPUMP.KU$_STATUS_JOB_STATUS +
                        DBMS_DATAPUMP.KU$_STATUS_WIP,
             timeout => -1);
      js := sts.job_status;
      DBMS_LOCK.SLEEP(10);
      job_state := js.state;
    END LOOP;
  -- Gets an exception when job no longer exists
    EXCEPTION WHEN job_not_exist THEN
      DBMS_OUTPUT.PUT_LINE('Data Pump job has completed');
      DBMS_OUTPUT.PUT_LINE('Instantiation SCN: ' ||mult3_instantscn);
  END;
END;
/

/*
Step 17   Configure Latest Time Conflict Resolution at mult2.example.com

Connect to mult2.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@mult2.example.com

/*

Specify an update conflict handler for each table in the hrmult schema. For each table, designate the time column as the resolution column for a MAXIMUM conflict handler. When an update conflict occurs, such an update conflict handler applies the transaction with the latest (or greater) time and discards the transaction with the earlier (or lesser) time.

*/
 
DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'country_name';
  cols(2) := 'region_id';
  cols(3) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.countries',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DE9DCLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'department_name';
  cols(2) := 'manager_id';
  cols(3) := 'location_id';
  cols(4) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.departments',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1)  := 'first_name';
  cols(2)  := 'last_name';
  cols(3)  := 'email';
  cols(4)  := 'phone_number';
  cols(5)  := 'hire_date';
  cols(6)  := 'job_id';
  cols(7)  := 'salary';
  cols(8)  := 'commission_pct';
  cols(9)  := 'manager_id';
  cols(10) := 'department_id';
  cols(11) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.employees',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'job_title';
  cols(2) := 'min_salary';
  cols(3) := 'max_salary';
  cols(4) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.jobs',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'employee_id';
  cols(2) := 'start_date';
  cols(3) := 'end_date';
  cols(4) := 'job_id';
  cols(5) := 'department_id';
  cols(6) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.job_history',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'street_address';
  cols(2) := 'postal_code';
  cols(3) := 'city';
  cols(4) := 'state_province';
  cols(5) := 'country_id';
  cols(6) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.locations',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'region_name';
  cols(2) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.regions',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

/*
Step 18   Start the Apply Processes at mult2.example.com

Start both of the apply processes at mult2.example.com.

*/

BEGIN
  DBMS_APPLY_ADM.START_APPLY(
    apply_name  => 'apply_from_mult1');
END;
/

BEGIN
  DBMS_APPLY_ADM.START_APPLY(
    apply_name  => 'apply_from_mult3');
END;
/

/*
Step 19   Configure Latest Time Conflict Resolution at mult3.example.com

Connect to mult3.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@mult3.example.com

/*

Specify an update conflict handler for each table in the hrmult schema. For each table, designate the time column as the resolution column for a MAXIMUM conflict handler. When an update conflict occurs, such an update conflict handler applies the transaction with the latest (or greater) time and discards the transaction with the earlier (or lesser) time.

*/
 
DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'country_name';
  cols(2) := 'region_id';
  cols(3) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.countries',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'department_name';
  cols(2) := 'manager_id';
  cols(3) := 'location_id';
  cols(4) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.departments',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1)  := 'first_name';
  cols(2)  := 'last_name';
  cols(3)  := 'email';
  cols(4)  := 'phone_number';
  cols(5)  := 'hire_date';
  cols(6)  := 'job_id';
  cols(7)  := 'salary';
  cols(8)  := 'commission_pct';
  cols(9)  := 'manager_id';
  cols(10) := 'department_id';
  cols(11) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.employees',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'job_title';
  cols(2) := 'min_salary';
  cols(3) := 'max_salary';
  cols(4) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.jobs',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'employee_id';
  cols(2) := 'start_date';
  cols(3) := 'end_date';
  cols(4) := 'job_id';
  cols(5) := 'department_id';
  cols(6) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.job_history',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'street_address';
  cols(2) := 'postal_code';
  cols(3) := 'city';
  cols(4) := 'state_province';
  cols(5) := 'country_id';
  cols(6) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.locations',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

DECLARE
  cols  DBMS_UTILITY.NAME_ARRAY;
BEGIN
  cols(1) := 'region_name';
  cols(2) := 'time';
  DBMS_APPLY_ADM.SET_UPDATE_CONFLICT_HANDLER(
    object_name           =>  'hrmult.regions',
    method_name           =>  'MAXIMUM',
    resolution_column     =>  'time',
    column_list           =>  cols);
END;
/

/*
Step 20   Start the Apply Processes at mult3.example.com

Start both of the apply processes at mult3.example.com.

*/

BEGIN
  DBMS_APPLY_ADM.START_APPLY(
    apply_name  => 'apply_from_mult1');
END;
/

BEGIN
  DBMS_APPLY_ADM.START_APPLY(
    apply_name  => 'apply_from_mult2');
END;
/

/*
Step 21   Start the Apply Processes at mult1.example.com

Connect to mult1.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@mult1.example.com

/*

Start both of the apply processes at mult1.example.com.

*/

BEGIN
  DBMS_APPLY_ADM.START_APPLY(
    apply_name  => 'apply_from_mult2');
END;
/

BEGIN
  DBMS_APPLY_ADM.START_APPLY(
    apply_name  => 'apply_from_mult3');
END;
/

/*
Step 22   Start the Capture Process at mult1.example.com

Start the capture process at mult1.example.com.

*/

BEGIN
  DBMS_CAPTURE_ADM.START_CAPTURE(
    capture_name  => 'capture_hrmult');
END;
/

/*
Step 23   Start the Capture Process at mult2.example.com

Connect to mult2.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@mult2.example.com

/*

Start the capture process at mult2.example.com.

*/

BEGIN
  DBMS_CAPTURE_ADM.START_CAPTURE(
    capture_name  => 'capture_hrmult');
END;
/

/*
Step 24   Start the Capture Process at mult3.example.com

Connect to mult3.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@mult3.example.com

/*

Start the capture process at mult3.example.com.

*/

BEGIN
  DBMS_CAPTURE_ADM.START_CAPTURE(
    capture_name  => 'capture_hrmult');
END;
/

SET ECHO OFF

/*
Step 25   Check the Spool Results

Check the streams_mult.out spool file to ensure that all actions finished successfully after this script is completed.

*/

SET ECHO OFF
SPOOL OFF

/*************************** END OF SCRIPT ******************************/

See Also:

Oracle Streams Replication Administrator's Guide for general instructions that explain how to add database objects or databases to the replication environment

Make DML and DDL Changes to Tables in the hrmult Schema

You can make DML and DDL changes to the tables in the hrmult schema at any of the databases in the environment. These changes will be replicated to the other databases in the environment, and you can run queries to view the replicated data.

For example, complete the following steps to make DML changes to the hrmult.employees table at mult1.example.com and mult2.example.com. To see the update conflict handler you configured earlier resolve an update conflict, you can make a change to the same row in these two databases and commit the changes at nearly the same time. You can query the changed row at each database in the environment to confirm that the changes were captured, propagated, and applied correctly.

You can also make a DDL change to the hrmult.jobs table at mult3.example.com and then confirm that the change was captured at mult3.example.com, propagated to the other databases in the environment, and applied at these databases.

Step 1   Make a DML Change to hrmult.employees at mult.example.com and mult2.example.com

Make the following changes. To simulate a conflict, try to commit them at nearly the same time, but commit the change at mult2.example.com after you commit the change at mult1.example.com. The update conflict handler at each database will resolve the conflict.

CONNECT hrmult@mult1.example.com
Enter password: password

UPDATE hrmult.employees SET salary=9000 WHERE employee_id=206;
COMMIT;

CONNECT hrmult@mult2.example.com
Enter password: password

UPDATE hrmult.employees SET salary=10000 WHERE employee_id=206;
COMMIT;
Step 2   Alter the hrmult.jobs Table at mult3.example.com

Alter the hrmult.jobs table by renaming the job_title column to job_name:

CONNECT hrmult@mult3.example.com
Enter password: password

ALTER TABLE hrmult.jobs RENAME COLUMN job_title TO job_name;
Step 3   Query the hrmult.employees Table at Each Database

After some time passes to allow for capture, propagation, and apply of the changes performed in Step 1, run the following query to confirm that the UPDATE changes have been applied at each database.

CONNECT hrmult@mult1.example.com
Enter password: password

SELECT salary FROM hrmult.employees WHERE employee_id=206;

CONNECT hrmult@mult2.example.com
Enter password: password

SELECT salary FROM hrmult.employees WHERE employee_id=206;

CONNECT hrmult@mult3.example.com
Enter password: password

SELECT salary FROM hrmult.employees WHERE employee_id=206;

All of the queries should show 10000 for the value of the salary. The update conflict handler at each database has resolved the conflict by using the latest change to the row. In this case, the latest change to the row was made at the mult2.example.com database in Step 1.

Step 4   Describe the hrmult.jobs Table at Each Database

After some time passes to allow for capture, propagation, and apply of the change performed in Step 2, describe the hrmult.jobs table at each database to confirm that the ALTER TABLE change was propagated and applied correctly.

CONNECT hrmult@mult1.example.com
Enter password: password

DESC hrmult.jobs

CONNECT hrmult@mult2.example.com
Enter password: password

DESC hrmult.jobs

CONNECT hrmult@mult3.example.com
Enter password: password

DESC hrmult.jobs

Each database should show job_name as the second column in the table.

PK99PK Logical Change Records with LOBs Example

5 Logical Change Records with LOBs Example

This chapter illustrates an example that creates a PL/SQL procedure for constructing and enqueuing LCRs that contain LOBs.

This chapter contains this topic:

Example Script for Constructing and Enqueuing LCRs Containing LOBs

  1. Show Output and Spool Results

  2. Grant the Oracle Streams Administrator EXECUTE Privilege on DBMS_STREAMS_MESSAGING

  3. Connect as the Oracle Streams Administrator

  4. Create an ANYDATA Queue

  5. Create and Start an Apply Process

  6. Create a Schema with Tables Containing LOB Columns

  7. Grant the Oracle Streams Administrator Necessary Privileges on the Tables

  8. Create a PL/SQL Procedure to Enqueue LCRs Containing LOBs

  9. Create the do_enq_clob Function to Enqueue CLOB Data

  10. Enqueue CLOB Data Using the do_enq_clob Function

  11. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to all of the databases in the environment.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL lob_construct.out

/*
Step 2   Grant the Oracle Streams Administrator EXECUTE Privilege on DBMS_STREAMS_MESSAGING

Explicit EXECUTE privilege on the package is required because a procedure in the package is called in within a PL/SQL procedure in Step 8.

*/

CONNECT / AS SYSDBA;

GRANT EXECUTE ON DBMS_STREAMS_MESSAGING TO strmadmin;

/*
Step 3   Connect as the Oracle Streams Administrator
*/
SET ECHO ON
SET FEEDBACK 1
SET NUMWIDTH 10
SET LINESIZE 80
SET TRIMSPOOL ON
SET TAB OFF
SET PAGESIZE 100
SET SERVEROUTPUT ON SIZE 100000

CONNECT strmadmin

/*
Step 4   Create an ANYDATA Queue
*/
BEGIN
  DBMS_STREAMS_ADM.SET_UP_QUEUE( 
    queue_table => 'lobex_queue_table', 
    queue_name  => 'lobex_queue');
END;
/

/*
Step 5   Create and Start an Apply Process
*/
BEGIN
  DBMS_APPLY_ADM.CREATE_APPLY(
    queue_name      => 'strmadmin.lobex_queue',
    apply_name      => 'apply_lob',
    apply_captured  => FALSE);
END;
/

BEGIN
  DBMS_APPLY_ADM.SET_PARAMETER(
    apply_name => 'apply_lob', 
    parameter  => 'disable_on_error',
    value      => 'N');
END;
/

BEGIN
  DBMS_APPLY_ADM.START_APPLY(
    'apply_lob');
END;
/

/*
Step 6   Create a Schema with Tables Containing LOB Columns
*/
CONNECT system

CREATE TABLESPACE lob_user_tbs DATAFILE 'lob_user_tbs.dbf' 
  SIZE 5M REUSE AUTOEXTEND ON MAXSIZE UNLIMITED;

ACCEPT password PROMPT 'Enter password for user: ' HIDE

CREATE USER lob_user
IDENTIFIED BY &password
  DEFAULT TABLESPACE lob_user_tbs
  QUOTA UNLIMITED ON lob_user_tbs;

GRANT ALTER SESSION, CREATE CLUSTER, CREATE DATABASE LINK, CREATE SEQUENCE,
  CREATE SESSION, CREATE SYNONYM, CREATE TABLE, CREATE VIEW, CREATE INDEXTYPE, 
  CREATE OPERATOR, CREATE PROCEDURE, CREATE TRIGGER, CREATE TYPE
TO lob_user;

CONNECT lob_user/lob_user_pw

CREATE TABLE with_clob (a  NUMBER PRIMARY KEY,
                        c1 CLOB,
                        c2 CLOB,
                        c3 CLOB);

CREATE TABLE with_blob (a NUMBER PRIMARY KEY,
                        b BLOB);

/*
Step 7   Grant the Oracle Streams Administrator Necessary Privileges on the Tables

Granting these privileges enables the Oracle Streams administrator to get the LOB length for offset and to perform DML operations on the tables.

*/

GRANT ALL ON with_clob TO strmadmin;
GRANT ALL ON with_blob TO strmadmin;
COMMIT;

/*
Step 8   Create a PL/SQL Procedure to Enqueue LCRs Containing LOBs
*/
CONNECT strmadmin

CREATE OR REPLACE PROCEDURE enq_row_lcr(source_dbname  VARCHAR2,
                                            cmd_type       VARCHAR2,
                                            obj_owner      VARCHAR2,
                                            obj_name       VARCHAR2,
                                            old_vals       SYS.LCR$_ROW_LIST,
                                            new_vals       SYS.LCR$_ROW_LIST) AS
  xr_lcr         SYS.LCR$_ROW_RECORD;
BEGIN
  xr_lcr := SYS.LCR$_ROW_RECORD.CONSTRUCT(
              source_database_name => source_dbname,
              command_type         => cmd_type,
              object_owner         => obj_owner,
              object_name          => obj_name,
              old_values           => old_vals,
              new_values           => new_vals);
  -- Enqueue a row lcr
  DBMS_STREAMS_MESSAGING.ENQUEUE(
        queue_name         => 'lobex_queue', 
        payload            => ANYDATA.ConvertObject(xr_lcr));
END enq_row_lcr;
/
SHOW ERRORS

/*
Step 9   Create the do_enq_clob Function to Enqueue CLOB Data
*/
-- Description of each variable:
-- src_dbname  : Source database name
-- tab_owner   : Table owner
-- tab_name    : Table name
-- col_name    : Name of the CLOB column
-- new_vals    : SYS.LCR$_ROW_LIST containing primary key and supplementally  
--               logged colums
-- clob_data   : CLOB that contains data to be sent
-- offset      : Offset from which data should be sent, default is 1
-- lsize       : Size of data to be sent, default is 0
-- chunk_size  : Size used for creating LOB chunks, default is 2048

CREATE OR REPLACE FUNCTION do_enq_clob(src_dbname     VARCHAR2,
                                       tab_owner      VARCHAR2,
                                       tab_name       VARCHAR2,
                                       col_name       VARCHAR2,
                                       new_vals       SYS.LCR$_ROW_LIST,
                                       clob_data      CLOB,
                                       offset         NUMBER default 1,
                                       lsize          NUMBER default 0,
                                       chunk_size     NUMBER default 2048) 
RETURN NUMBER IS
  lob_offset NUMBER; -- maintain lob offset
  newunit    SYS.LCR$_ROW_UNIT;
  tnewvals   SYS.LCR$_ROW_LIST;
  lob_flag   NUMBER;
  lob_data   VARCHAR2(32767);
  lob_size   NUMBER;
  unit_pos   NUMBER;
  final_size NUMBER;
  exit_flg   BOOLEAN;
  c_size     NUMBER;
  i          NUMBER;
BEGIN
  lob_size := DBMS_LOB.GETLENGTH(clob_data);
  unit_pos := new_vals.count + 1;
  tnewvals := new_vals;
  c_size   := chunk_size;
  i := 0;
  -- validate parameters
  IF (unit_pos <= 1) THEN
    DBMS_OUTPUT.PUT_LINE('Invalid new_vals list');
    RETURN 1;
  END IF;

  IF (c_size < 1) THEN
    DBMS_OUTPUT.PUT_LINE('Invalid LOB chunk size');
    RETURN 1;
  END IF;

  IF (lsize < 0 OR lsize > lob_size) THEN
    DBMS_OUTPUT.PUT_LINE('Invalid LOB size');
    RETURN 1;
  END IF;

  IF (offset < 1 OR offset >= lob_size) THEN
    DBMS_OUTPUT.PUT_LINE('Invalid lob offset');
    RETURN 1;
  ELSE
    lob_offset := offset;
  END IF;

  -- calculate final size
  IF (lsize = 0) THEN
    final_size := lob_size;
  ELSE
    final_size := lob_offset + lsize;
  END IF;

  --  The following output lines are for debugging purposes only.
  -- DBMS_OUTPUT.PUT_LINE('Final size: ' || final_size);
  -- DBMS_OUTPUT.PUT_LINE('Lob size: ' || lob_size);

  IF (final_size < 1 OR final_size > lob_size) THEN
    DBMS_OUTPUT.PUT_LINE('Invalid lob size');
    RETURN 1;
  END IF;

  -- expand new_vals list for LOB column
  tnewvals.extend();

  exit_flg := FALSE;

  -- Enqueue all LOB chunks
  LOOP
    --  The following output line is for debugging purposes only.
    DBMS_OUTPUT.PUT_LINE('About to write chunk#' || i);
    i := i + 1;
 
    -- check if last LOB chunk
    IF ((lob_offset + c_size) < final_size) THEN
      lob_flag := DBMS_LCR.LOB_CHUNK;
    ELSE
      lob_flag := DBMS_LCR.LAST_LOB_CHUNK;
      exit_flg := TRUE;
      --  The following output line is for debugging purposes only.
      DBMS_OUTPUT.PUT_LINE('Last LOB chunk');
    END IF;

    --  The following output lines are for debugging purposes only.
    DBMS_OUTPUT.PUT_LINE('lob offset: ' || lob_offset);
    DBMS_OUTPUT.PUT_LINE('Chunk size: ' || to_char(c_size));

    lob_data := DBMS_LOB.SUBSTR(clob_data, c_size, lob_offset); 

    -- create row unit for clob
    newunit := SYS.LCR$_ROW_UNIT(col_name,
                                 ANYDATA.ConvertVarChar2(lob_data), 
                                 lob_flag, 
                                 lob_offset, 
                                 NULL);

    -- insert new LCR$_ROW_UNIT
    tnewvals(unit_pos) := newunit;  

    -- enqueue lcr
    enq_row_lcr(
          source_dbname => src_dbname,
          cmd_type      => 'LOB WRITE',
          obj_owner     => tab_owner,
          obj_name      => tab_name,
          old_vals      => NULL,
          new_vals      => tnewvals);

    -- calculate next chunk size 
    lob_offset := lob_offset + c_size;
    
    IF ((final_size - lob_offset) < c_size) THEN
      c_size := final_size - lob_offset + 1;
    END IF;

    --  The following output line is for debugging purposes only.
    DBMS_OUTPUT.PUT_LINE('Next chunk size : ' || TO_CHAR(c_size));

    IF (c_size < 1) THEN
      exit_flg := TRUE;
    END IF;

    EXIT WHEN exit_flg;

  END LOOP;

  RETURN 0;
END do_enq_clob;
/

SHOW ERRORS

/*
Step 10   Enqueue CLOB Data Using the do_enq_clob Function

The DBMS_OUTPUT lines in the following example can be used for debugging purposes if necessary. If they are not needed, then they can be commented out or deleted.

*/

SET SERVEROUTPUT ON SIZE 100000
DECLARE
  c1_data CLOB;
  c2_data CLOB;
  c3_data CLOB;
  newunit1 SYS.LCR$_ROW_UNIT;
  newunit2 SYS.LCR$_ROW_UNIT;
  newunit3 SYS.LCR$_ROW_UNIT;
  newunit4 SYS.LCR$_ROW_UNIT;
  newvals  SYS.LCR$_ROW_LIST;
  big_data VARCHAR(22000);
  n        NUMBER;
BEGIN
  -- Create primary key for LCR$_ROW_UNIT
  newunit1 := SYS.LCR$_ROW_UNIT('A',
                                ANYDATA.ConvertNumber(3), 
                                NULL, 
                                NULL, 
                                NULL);
  -- Create empty CLOBs
  newunit2 := sys.lcr$_row_unit('C1',
                                ANYDATA.ConvertVarChar2(NULL),
                                DBMS_LCR.EMPTY_LOB, 
                                NULL, 
                                NULL);
  newunit3 := SYS.LCR$_ROW_UNIT('C2',
                                ANYDATA.ConvertVarChar2(NULL),
                                DBMS_LCR.EMPTY_LOB, 
                                NULL, 
                                NULL);
  newunit4 := SYS.LCR$_ROW_UNIT('C3',
                                ANYDATA.ConvertVarChar2(NULL),
                                DBMS_LCR.EMPTY_LOB, 
                                NULL, 
                                NULL);
  newvals := SYS.LCR$_ROW_LIST(newunit1,newunit2,newunit3,newunit4);

  -- Perform an insert
  enq_row_lcr(
    source_dbname => 'MYDB.EXAMPLE.COM',
    cmd_type      => 'INSERT',
    obj_owner     => 'LOB_USER',
    obj_name      => 'WITH_CLOB',
    old_vals      => NULL,
    new_vals      => newvals);

  -- construct clobs
  big_data := RPAD('Hello World', 1000, '_');
  big_data := big_data || '#';
  big_data := big_data || big_data || big_data || big_data || big_data;
  DBMS_LOB.CREATETEMPORARY(
    lob_loc => c1_data, 
    cache   => TRUE);
  DBMS_LOB.WRITEAPPEND(
    lob_loc => c1_data, 
    amount  => length(big_data), 
    buffer  => big_data);

  big_data := RPAD('1234567890#', 1000, '_');
  big_data := big_data || big_data || big_data || big_data;
  DBMS_LOB.CREATETEMPORARY(
    lob_loc => c2_data, 
    cache   => TRUE);
  DBMS_LOB.WRITEAPPEND(
    lob_loc => c2_data, 
    amount  => length(big_data), 
    buffer  => big_data);

  big_data := RPAD('ASDFGHJKLQW', 2000, '_');
  big_data := big_data || '#';
  big_data := big_data || big_data || big_data || big_data || big_data;
  DBMS_LOB.CREATETEMPORARY(
    lob_loc => c3_data, 
    cache   => TRUE);
  DBMS_LOB.WRITEAPPEND(
    lob_loc => c3_data, 
    amount  => length(big_data), 
    buffer  => big_data);

  -- pk info
  newunit1 := SYS.LCR$_ROW_UNIT('A',
                                ANYDATA.ConvertNumber(3), 
                                NULL, 
                                NULL, 
                                NULL);
  newvals  := SYS.LCR$_ROW_LIST(newunit1); 

  -- write c1 clob
  n := do_enq_clob(
         src_dbname => 'MYDB.EXAMPLE.COM',
         tab_owner  => 'LOB_USER',
         tab_name   => 'WITH_CLOB',
         col_name   => 'C1',
         new_vals   => newvals,
         clob_data  => c1_data,
         offset     => 1,
         chunk_size => 1024);
  DBMS_OUTPUT.PUT_LINE('n=' || n);
 
  -- write c2 clob
  newvals  := SYS.LCR$_ROW_LIST(newunit1); 
  n := do_enq_clob(
         src_dbname => 'MYDB.EXAMPLE.COM',
         tab_owner  => 'LOB_USER',
         tab_name   => 'WITH_CLOB',
         col_name   => 'C2',
         new_vals   => newvals,
         clob_data  => c2_data,
         offset     => 1,
         chunk_size => 2000);
  DBMS_OUTPUT.PUT_LINE('n=' || n);
 
  -- write c3 clob
  newvals  := SYS.LCR$_ROW_LIST(newunit1); 
  n := do_enq_clob(src_dbname=>'MYDB.EXAMPLE.COM',
         tab_owner  => 'LOB_USER',
         tab_name   => 'WITH_CLOB',
         col_name   => 'C3',
         new_vals   => newvals,
         clob_data  => c3_data,
         offset     => 1,
         chunk_size => 500);
  DBMS_OUTPUT.PUT_LINE('n=' || n);
 
  COMMIT;

END;
/

/*
Step 11   Check the Spool Results

Check the lob_construct.out spool file to ensure that all actions completed successfully after this script completes.

*/

SET ECHO OFF
SPOOL OFF

/*************************** END OF SCRIPT ******************************/

After you run the script, you can check the lob_user.with_clob table to list the rows applied by the apply process. The DBMS_LOCK.SLEEP statement is used to give the apply process time to apply the enqueued rows.

CONNECT lob_user/lob_user_pw

EXECUTE DBMS_LOCK.SLEEP(10);

SELECT a, c1, c2, c3 FROM with_clob ORDER BY a;

SELECT a, LENGTH(c1), LENGTH(c2), LENGTH(c3) FROM with_clob ORDER BY a;
PKHHPK Single-Database Capture and Apply Example

4 Single-Database Capture and Apply Example

This chapter illustrates an example of a single database that captures changes to a table with a capture process, reenqueues the captured changes into a queue, and then uses a procedure DML handler during apply to insert a subset of the changes into a different table.

The following topics describe configuring an example single-database capture and apply example:

Overview of the Single-Database Capture and Apply Example

The example in this chapter illustrates using Oracle Streams to capture and apply data manipulation language (DML) changes at a single database named cpap.example.com. Specifically, this example captures DML changes to the employees table in the hr schema, placing row logical change records (LCRs) into a queue named streams_queue. Next, an apply process dequeues these row LCRs from the same queue, reenqueues them into this queue, and sends them to a procedure DML handler.

When the row LCRs are captured, they reside in the buffered queue and cannot be dequeued explicitly. After the row LCRs are reenqueued during apply, they are available for explicit dequeue by an application. This example does not create the application that dequeues these row LCRs.

This example illustrates a procedure DML handler that inserts records of deleted employees into an emp_del table in the hr schema. This example assumes that the emp_del table is used to retain the records of all deleted employees. The procedure DML handler is used to determine whether each row LCR contains a DELETE statement. When the procedure DML handler finds a row LCR containing a DELETE statement, it converts the DELETE into an INSERT on the emp_del table and then inserts the row.

Figure 4-1 provides an overview of the environment.

Figure 4-1 Single Database Capture and Apply Example

Description of Figure 4-1 follows
Description of "Figure 4-1 Single Database Capture and Apply Example"

Prerequisites

The following prerequisites must be completed before you begin the example in this chapter.

  • Optionally set the STREAMS_POOL_SIZE initialization parameter to an appropriate value. This parameter specifies the size of the Oracle Streams pool. The Oracle Streams pool stores messages in a buffered queue and is used for internal communications during parallel capture and apply. When the MEMORY_TARGET, MEMORY_MAX_TARGET, or SGA_TARGET initialization parameter is set to a nonzero value, the Oracle Streams pool size is managed automatically.


    See Also:

    Oracle Streams Replication Administrator's Guide for information about setting initialization parameters that are relevant to Oracle Streams

  • Set the database to run in ARCHIVELOG mode. Any database producing changes that will be captured must run in ARCHIVELOG mode.


    See Also:

    Oracle Database Administrator's Guide for information about running a database in ARCHIVELOG mode

  • Create an Oracle Streams administrator at the database. This example assumes that the user name of the Oracle Streams administrator is strmadmin.

    This example executes a subprogram in an Oracle Streams packages within a stored procedure. Specifically, the emp_dq procedure created in Step 8 runs the DEQUEUE procedure in the DBMS_STREAMS_MESSAGING package. Therefore, the Oracle Streams administrator must be granted EXECUTE privilege explicitly on the package. In this case, EXECUTE privilege cannot be granted through a role. The DBMS_STREAMS_AUTH.GRANT_ADMIN_PRIVILEGE procedure grants EXECUTE on all Oracle Streams packages, as well as other privileges relevant to Oracle Streams. You can either grant the EXECUTE privilege on the package directly, or use the GRANT_ADMIN_PRIVILEGE procedure to grant it.


    See Also:

    Oracle Streams Replication Administrator's Guide for information about creating an Oracle Streams administrator

Set Up the Environment

Complete the following steps to create the hr.emp_del table, set up the Oracle Streams administrator, and create the queue.

  1. Show Output and Spool Results

  2. Create the hr.emp_del Table

  3. Grant Additional Privileges to the Oracle Streams Administrator

  4. Create the ANYDATA Queue at cpap.example.com

  5. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to the database.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL streams_setup_capapp.out

/*
Step 2   Create the hr.emp_del Table

Connect to cpap.example.com as the hr user.

*/
 
CONNECT hr@cpap.example.com

/*

Create the hr.emp_del table. The columns in the emp_del table is the same as the columns in the employees table, except for one added timestamp column that will record the date when a row is inserted into the emp_del table.

*/

CREATE TABLE emp_del( 
  employee_id    NUMBER(6), 
  first_name     VARCHAR2(20), 
  last_name      VARCHAR2(25), 
  email          VARCHAR2(25), 
  phone_number   VARCHAR2(20), 
  hire_date      DATE, 
  job_id         VARCHAR2(10), 
  salary         NUMBER(8,2), 
  commission_pct NUMBER(2,2), 
  manager_id     NUMBER(6), 
  department_id  NUMBER(4),
  timestamp      DATE);

CREATE UNIQUE INDEX emp_del_id_pk ON emp_del (employee_id);

ALTER TABLE emp_del ADD (CONSTRAINT emp_del_id_pk PRIMARY KEY (employee_id));

/*
Step 3   Grant Additional Privileges to the Oracle Streams Administrator

Connect to cpap.example.com as SYSTEM user.

*/
 
CONNECT SYSTEM@cpap.example.com

/*

Grant the Oracle Streams administrator all privileges on the emp_del table, because the Oracle Streams administrator will be the apply user and must be able to insert records into this table. Alternatively, you can alter the apply process to specify that hr is the apply user.

*/

GRANT ALL ON hr.emp_del TO STRMADMIN;

/*
Step 4   Create the ANYDATA Queue at cpap.example.com

Connect to cpap.example.com as the strmadmin user.

*/

CONNECT strmadmin@cpap.example.com

/*

Run the SET_UP_QUEUE procedure to create a queue named streams_queue at cpap.example.com. This queue is an ANYDATA queue that will stage the captured changes to be dequeued by an apply process and the user-constructed changes to be dequeued by a dequeue procedure.

Running the SET_UP_QUEUE procedure performs the following actions:

  • Creates a queue table named streams_queue_table. This queue table is owned by the Oracle Streams administrator (strmadmin) and uses the default storage of this user.

  • Creates a queue named streams_queue owned by the Oracle Streams administrator (strmadmin).

  • Starts the queue.

*/

BEGIN
  DBMS_STREAMS_ADM.SET_UP_QUEUE(
    queue_table  => 'strmadmin.streams_queue_table',
    queue_name   => 'strmadmin.streams_queue');
END;
/

/*
Step 5   Check the Spool Results

Check the streams_setup_capapp.out spool file to ensure that all actions finished successfully after this script is completed.

*/

SET ECHO OFF
SPOOL OFF

/*************************** END OF SCRIPT ******************************/

Configure Capture and Apply

Complete the following steps to capture changes to the hr.employees table and apply these changes on single database in a customized way using a procedure DML handler.

  1. Show Output and Spool Results

  2. Configure the Capture Process at cpap.example.com

  3. Set the Instantiation SCN for the hr.employees Table

  4. Create the Procedure DML Handler handler Procedure

  5. Set the Procedure DML Handler for the hr.employees Table

  6. Create a Messaging Client for the Queue

  7. Configure the Apply Process at cpap.example.com

  8. Create a Procedure to Dequeue the Messages

  9. Start the Apply Process at cpap.example.com

  10. Start the Capture Process at cpap.example.com

  11. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect the database.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL streams_config_capapp.out

/*
Step 2   Configure the Capture Process at cpap.example.com

Connect to cpap.example.com as the strmadmin user.

*/
 
CONNECT strmadmin@cpap.example.com

/*

Configure the capture process to capture DML changes to the hr.employees table at cpap.example.com. This step creates the capture process and adds a rule to its positive rule set that instructs the capture process to capture DML changes to this table. This step also prepares the hr.employees table for instantiation and enables supplemental logging for any primary key, unique key, bitmap index, and foreign key columns in the table.

Supplemental logging places additional information in the redo log for changes made to tables. The apply process needs this extra information to perform some operations, such as unique row identification.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_RULES(
    table_name     => 'hr.employees',   
    streams_type   => 'capture',
    streams_name   => 'capture_emp',
    queue_name     => 'strmadmin.streams_queue',
    include_dml    =>  TRUE,
    include_ddl    =>  FALSE,
    inclusion_rule =>  TRUE);
END;
/

/*
Step 3   Set the Instantiation SCN for the hr.employees Table

Because this example captures and applies changes in a single database, no instantiation is necessary. However, the apply process at the cpap.example.com database still must be instructed to apply changes that were made to the hr.employees table after a specific system change number (SCN).

This example uses the GET_SYSTEM_CHANGE_NUMBER function in the DBMS_FLASHBACK package to obtain the current SCN for the database. This SCN is used to run the SET_TABLE_INSTANTIATION_SCN procedure in the DBMS_APPLY_ADM package.

The SET_TABLE_INSTANTIATION_SCN procedure controls which LCRs for a table are ignored by an apply process and which LCRs for a table are applied by an apply process. If the commit SCN of an LCR for a table from a source database is less than or equal to the instantiation SCN for that table at a destination database, then the apply process at the destination database discards the LCR. Otherwise, the apply process applies the LCR. In this example, the cpap.example.com database is both the source database and the destination database.

The apply process will apply transactions to the hr.employees table with SCNs that were committed after SCN obtained in this step.


Note:

The hr.employees table also must be prepared for instantiation. This preparation was done automatically when the capture process was configured with a rule to capture DML changes to the hr.employees table in Step 2.

*/

DECLARE
  iscn  NUMBER;         -- Variable to hold instantiation SCN value
BEGIN
  iscn := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER();
  DBMS_APPLY_ADM.SET_TABLE_INSTANTIATION_SCN(
    source_object_name    => 'hr.employees',
    source_database_name  => 'cpap.example.com',
    instantiation_scn     => iscn);
END;
/

/*
Step 4   Create the Procedure DML Handler handler Procedure

This step creates the emp_dml_handler procedure. This procedure will be the procedure DML handler for DELETE changes to the hr.employees table. It converts any row LCR containing a DELETE command type into an INSERT row LCR and then inserts the converted row LCR into the hr.emp_del table by executing the row LCR.

*/

CREATE OR REPLACE PROCEDURE emp_dml_handler(in_any IN ANYDATA) IS
  lcr          SYS.LCR$_ROW_RECORD;
  rc           PLS_INTEGER;
  command      VARCHAR2(30);
  old_values   SYS.LCR$_ROW_LIST;
BEGIN    
  -- Access the LCR
  rc := in_any.GETOBJECT(lcr);
  -- Get the object command type
  command := lcr.GET_COMMAND_TYPE();
  -- Check for DELETE command on the hr.employees table
  IF command = 'DELETE' THEN
    -- Set the command_type in the row LCR to INSERT
    lcr.SET_COMMAND_TYPE('INSERT');
    -- Set the object_name in the row LCR to EMP_DEL
    lcr.SET_OBJECT_NAME('EMP_DEL');
    -- Get the old values in the row LCR
    old_values := lcr.GET_VALUES('old');
    -- Set the old values in the row LCR to the new values in the row LCR
    lcr.SET_VALUES('new', old_values);
    -- Set the old values in the row LCR to NULL
    lcr.SET_VALUES('old', NULL);
    -- Add a SYSDATE value for the timestamp column
    lcr.ADD_COLUMN('new', 'TIMESTAMP', ANYDATA.ConvertDate(SYSDATE));
    -- Apply the row LCR as an INSERT into the hr.emp_del table
    lcr.EXECUTE(TRUE);
  END IF;
END;
/

/*
Step 5   Set the Procedure DML Handler for the hr.employees Table

Set the procedure DML handler for the hr.employees table to the procedure created in Step 4. Notice that the operation_name parameter is set to DEFAULT so that the procedure DML handler is used for each possible operation on the table, including INSERT, UPDATE, and DELETE.

*/

BEGIN
  DBMS_APPLY_ADM.SET_DML_HANDLER(
    object_name         => 'hr.employees',
    object_type         => 'TABLE',
    operation_name      => 'DEFAULT',
    error_handler       => FALSE,
    user_procedure      => 'strmadmin.emp_dml_handler',
    apply_database_link => NULL,
    apply_name          => NULL);
END;
/

/*
Step 6   Create a Messaging Client for the Queue

Create a messaging client that can be used by an application to dequeue the reenqueued messages. A messaging client must be specified before the messages can be reenqueued into the queue.

*/

BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_RULES(
    table_name     => 'hr.employees',   
    streams_type   => 'dequeue',
    streams_name   => 'hr',
    queue_name     => 'strmadmin.streams_queue',
    include_dml    =>  TRUE,
    include_ddl    =>  FALSE,
    inclusion_rule =>  TRUE);
END;
/

/*
Step 7   Configure the Apply Process at cpap.example.com

Create an apply process to apply DML changes to the hr.employees table. Although the procedure DML handler for the apply process causes deleted employees to be inserted into the emp_del table, this rule specifies the employees table, because the row LCRs in the queue contain changes to the employees table, not the emp_del table. When you run the ADD_TABLE_RULES procedure to create the apply process, the out parameter dml_rule_name contains the name of the DML rule created. This rule name is then passed to the SET_ENQUEUE_DESTINATION procedure.

The SET_ENQUEUE_DESTINATION procedure in the DBMS_APPLY_ADM package specifies that any apply process using the DML rule generated by ADD_TABLE_RULES will enqueue messages that satisfy this rule into streams_queue. In this case, the DML rule is for row LCRs with DML changes to the hr.employees table. A local queue other than the apply process queue can be specified if appropriate.

*/

DECLARE
          emp_rule_name_dml  VARCHAR2(30);
          emp_rule_name_ddl  VARCHAR2(30);
BEGIN
  DBMS_STREAMS_ADM.ADD_TABLE_RULES(
    table_name      => 'hr.employees',
    streams_type    => 'apply', 
    streams_name    => 'apply_emp',
    queue_name      => 'strmadmin.streams_queue',
    include_dml     =>  TRUE,
    include_ddl     =>  FALSE,
    source_database => 'cpap.example.com',
    dml_rule_name   => emp_rule_name_dml,
    ddl_rule_name   => emp_rule_name_ddl);
  DBMS_APPLY_ADM.SET_ENQUEUE_DESTINATION(
    rule_name               =>  emp_rule_name_dml,
    destination_queue_name  =>  'strmadmin.streams_queue');
END;
/

/*
Step 8   Create a Procedure to Dequeue the Messages

The emp_dq procedure created in this step can be used to dequeue the messages that are reenqueued by the apply process. In Step 7, the SET_ENQUEUE_DESTINATION procedure was used to instruct the apply process to enqueue row LCRs containing changes to the hr.employees table into streams_queue. When the emp_dq procedure is executed, it dequeues each row LCR in the queue and displays the type of command in the row LCR, either INSERT, UPDATE, or DELETE. Any information in the row LCRs can be accessed and displayed, not just the command type.


See Also:

Oracle Streams Concepts and Administration for more information about displaying information in LCRs

*/

CREATE OR REPLACE PROCEDURE emp_dq (consumer IN VARCHAR2) AS
  msg            ANYDATA;
  row_lcr        SYS.LCR$_ROW_RECORD;
  num_var        pls_integer;
  more_messages  BOOLEAN := TRUE;
  navigation     VARCHAR2(30);
BEGIN
  navigation := 'FIRST MESSAGE';
  WHILE (more_messages) LOOP
    BEGIN
      DBMS_STREAMS_MESSAGING.DEQUEUE(
        queue_name   => 'strmadmin.streams_queue',
        streams_name => consumer,
        payload      => msg,
        navigation   => navigation,
        wait         => DBMS_STREAMS_MESSAGING.NO_WAIT);
      IF msg.GETTYPENAME() = 'SYS.LCR$_ROW_RECORD' THEN
        num_var := msg.GetObject(row_lcr);   
        DBMS_OUTPUT.PUT_LINE(row_lcr.GET_COMMAND_TYPE || ' row LCR dequeued');
      END IF;
      navigation := 'NEXT MESSAGE';
    COMMIT;
    EXCEPTION WHEN SYS.DBMS_STREAMS_MESSAGING.ENDOFCURTRANS THEN
                navigation := 'NEXT TRANSACTION';
              WHEN DBMS_STREAMS_MESSAGING.NOMOREMSGS THEN
                more_messages := FALSE;
                DBMS_OUTPUT.PUT_LINE('No more messages.');
              WHEN OTHERS THEN
                RAISE; 
    END;
  END LOOP;
END;
/

/*
Step 9   Start the Apply Process at cpap.example.com

Set the disable_on_error parameter to n so that the apply process will not be disabled if it encounters an error, and start the apply process at cpap.example.com.

*/

BEGIN
  DBMS_APPLY_ADM.SET_PARAMETER(
    apply_name  => 'apply_emp', 
    parameter   => 'disable_on_error', 
    value       => 'N');
END;
/
 
BEGIN
  DBMS_APPLY_ADM.START_APPLY(
    apply_name  => 'apply_emp');
END;
/

/*
Step 10   Start the Capture Process at cpap.example.com

Start the capture process at cpap.example.com.

*/

BEGIN
  DBMS_CAPTURE_ADM.START_CAPTURE(
    capture_name  => 'capture_emp');
END;
/

/*
Step 11   Check the Spool Results

Check the streams_config_capapp.out spool file to ensure that all actions finished successfully after this script is completed.

*/

SET ECHO OFF
SPOOL OFF

/*************************** END OF SCRIPT ******************************/

Make DML Changes, Query for Results, and Dequeue Messages

Complete the following steps to confirm that apply process is configured correctly, make DML changes to the hr.employees table, query for the resulting inserts into the hr.emp_del table and the reenqueued messages in the streams_queue_table, and dequeue the messages that were reenqueued by the apply process:

  1. Confirm the Rule Action Context

  2. Perform an INSERT, UPDATE, and DELETE on hr.employees

  3. Query the hr.emp_del Table and the streams_queue_table

  4. Dequeue Messages Reenqueued by the Procedure DML Handler

Step 1   Confirm the Rule Action Context

Step 7 creates an apply process rule that specifies a destination queue into which LCRs that satisfy the rule are enqueued. In this case, LCRs that satisfy the rule are row LCRs with changes to the hr.employees table.

Complete the following steps to confirm that the rule specifies a destination queue:

  1. Run the following query to determine the name of the rule for DML changes to the hr.employees table used by the apply process apply_emp:

    CONNECT strmadmin@cpap.example.com
    Enter password: password
    
    SELECT RULE_OWNER, RULE_NAME FROM DBA_STREAMS_RULES 
      WHERE STREAMS_NAME = 'APPLY_EMP' AND
            STREAMS_TYPE = 'APPLY' AND
            SCHEMA_NAME  = 'HR' AND
            OBJECT_NAME  = 'EMPLOYEES' AND
            RULE_TYPE    = 'DML'
      ORDER BY RULE_NAME;
    

    Your output looks similar to the following:

    RULE_OWNER                     RULE_NAME
    ------------------------------ ------------------------------
    STRMADMIN                      EMPLOYEES3
    
  2. View the action context for the rule returned by the query in Step 1:

    COLUMN RULE_OWNER HEADING 'Rule Owner' FORMAT A15
    COLUMN DESTINATION_QUEUE_NAME HEADING 'Destination Queue' FORMAT A30
    
    SELECT RULE_OWNER, DESTINATION_QUEUE_NAME
      FROM DBA_APPLY_ENQUEUE
      WHERE RULE_NAME = 'EMPLOYEES3'
      ORDER BY DESTINATION_QUEUE_NAME;
    

    Ensure that you substitute the rule name returned in Step 1 in the WHERE clause. Your output looks similar to the following:

    Rule Ownper      Destination Queue
    --------------- ------------------------------
    STRMADMIN       "STRMADMIN"."STREAMS_QUEUE"
    

    The output should show that LCRs that satisfy the apply process rule are enqueued into streams_queue.

Step 2   Perform an INSERT, UPDATE, and DELETE on hr.employees

Make the following DML changes to the hr.employees table.

CONNECT hr@cpap.example.com
Enter password: password

INSERT INTO hr.employees VALUES(207, 'JOHN', 'SMITH', 'JSMITH@EXAMPLE.COM', 
  NULL, '07-JUN-94', 'AC_ACCOUNT', 777, NULL, NULL, 110);
COMMIT;

UPDATE hr.employees SET salary=5999 WHERE employee_id=207;
COMMIT;

DELETE FROM hr.employees WHERE employee_id=207;
COMMIT;
Step 3   Query the hr.emp_del Table and the streams_queue_table

After some time passes to allow for capture and apply of the changes performed in the previous step, run the following queries to see the results:

CONNECT strmadmin@cpap.example.com
Enter password: password

SELECT employee_id, first_name, last_name, timestamp 
  FROM hr.emp_del ORDER BY employee_id;

SELECT MSG_ID, MSG_STATE, CONSUMER_NAME 
  FROM AQ$STREAMS_QUEUE_TABLE ORDER BY MSG_ID;

When you run the first query, you should see a record for the employee with an employee_id of 207. This employee was deleted in the previous step. When you run the second query, you should see the reenqueued messages resulting from all of the changes in the previous step, and the MSG_STATE should be READY for these messages.

Step 4   Dequeue Messages Reenqueued by the Procedure DML Handler

Use the emp_dq procedure to dequeue the messages that were reenqueued by the procedure DML handler.

SET SERVEROUTPUT ON SIZE 100000

EXEC emp_dq('HR');

For each row changed by a DML statement, one line is returned, and each line states the command type of the change (either INSERT, UPDATE, or DELETE). If you repeat the query on the queue table in Step 3 after the messages are dequeued, then the dequeued messages should have been consumed. That is, either the MSG_STATE should be PROCESSED for these messages, or the messages should no longer be in the queue.

SELECT MSG_ID, MSG_STATE, CONSUMER_NAME 
  FROM AQ$STREAMS_QUEUE_TABLE ORDER BY MSG_ID;
PKՎ>PK Rule-Based Application Example

6 Rule-Based Application Example

This chapter illustrates a rule-based application that uses the Oracle rules engine.

The examples in this chapter are independent of Oracle Streams. That is, no Oracle Streams capture processes, propagations, apply processes, or messaging clients are clients of the rules engine in these examples, and no queues are used.

The following topics describe configuring examples of rules-based applications:

Overview of the Rule-Based Application

Each example in this chapter creates a rule-based application that handles customer problems. The application uses rules to determine actions that must be completed based on the problem priority when a new problem is reported. For example, the application assigns each problem to a particular company center based on the problem priority.

The application enforces these rules using the rules engine. An evaluation context named evalctx is created to define the information surrounding a support problem. Rules are created based on the requirements described previously, and they are added to a rule set named rs.

The task of assigning problems is done by a user-defined procedure named problem_dispatch, which calls the rules engine to evaluate rules in the rule set rs and then takes appropriate action based on the rules that evaluate to TRUE.

Using Rules on Nontable Data Stored in Explicit Variables

This example illustrates how to use rules to evaluate data stored in explicit variables. This example handles customer problems based on priority and uses the following rules for handling customer problems:

  • Assign all problems with priority greater than 2 to the San Jose Center.

  • Assign all problems with priority less than or equal to 2 to the New York Center.

  • Send an alert to the vice president of support for a problem with priority equal to 1.

The evaluation context contains only one explicit variable named priority, which refers to the priority of the problem being dispatched. The value for this variable is passed to DBMS_RULE.EVALUATE procedure by the problem_dispatch procedure.

Complete the following steps:

  1. Show Output and Spool Results

  2. Create the support User

  3. Grant the support User the Necessary System Privileges on Rules

  4. Create the evalctx Evaluation Context

  5. Create the Rules that Correspond to Problem Priority

  6. Create the rs Rule Set

  7. Add the Rules to the Rule Set

  8. Query the Data Dictionary

  9. Create the problem_dispatch PL/SQL Procedure

  10. Dispatch Sample Problems

  11. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to all of the databases in the environment.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL rules_stored_variables.out

/*
Step 2   Create the support User
*/
CONNECT SYSTEM

ACCEPT password PROMPT 'Enter password for user: ' HIDE

GRANT ALTER SESSION, CREATE CLUSTER, CREATE DATABASE LINK, CREATE SEQUENCE,
  CREATE SESSION, CREATE SYNONYM, CREATE TABLE, CREATE VIEW, CREATE INDEXTYPE, 
  CREATE OPERATOR, CREATE PROCEDURE, CREATE TRIGGER, CREATE TYPE
TO support IDENTIFIED BY &password;

/*
Step 3   Grant the support User the Necessary System Privileges on Rules
*/
BEGIN
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_RULE_SET_OBJ, 
    grantee      => 'support', 
    grant_option => FALSE);
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_RULE_OBJ,
    grantee      => 'support', 
    grant_option => FALSE);
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_EVALUATION_CONTEXT_OBJ, 
    grantee      => 'support', 
    grant_option => FALSE);
END;
/

/*
Step 4   Create the evalctx Evaluation Context
*/
CONNECT support

SET FEEDBACK 1
SET NUMWIDTH 10
SET LINESIZE 80
SET TRIMSPOOL ON
SET TAB OFF
SET PAGESIZE 100
SET SERVEROUTPUT ON
DECLARE
  vt SYS.RE$VARIABLE_TYPE_LIST;
BEGIN
  vt := SYS.RE$VARIABLE_TYPE_LIST(
    SYS.RE$VARIABLE_TYPE('priority', 'NUMBER', NULL, NULL));
  DBMS_RULE_ADM.CREATE_EVALUATION_CONTEXT(
    evaluation_context_name    => 'evalctx',
    variable_types             => vt,
    evaluation_context_comment => 'support problem definition');
END;
/

/*
Step 5   Create the Rules that Correspond to Problem Priority

The following code creates one action context for each rule, and one name-value pair in each action context.

*/

DECLARE
  ac  SYS.RE$NV_LIST;
BEGIN
  ac := SYS.RE$NV_LIST(NULL);
  ac.ADD_PAIR('CENTER', ANYDATA.CONVERTVARCHAR2('San Jose'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name      => 'r1',
    condition      => ':priority > 2',
    action_context => ac,
    rule_comment   => 'Low priority problems');
  ac := SYS.RE$NV_LIST(NULL);
  ac.ADD_PAIR('CENTER', ANYDATA.CONVERTVARCHAR2('New York'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name      => 'r2',
    condition      => ':priority <= 2',
    action_context => ac,
    rule_comment   => 'High priority problems');
  ac := SYS.RE$NV_LIST(NULL);
  ac.ADD_PAIR('ALERT', ANYDATA.CONVERTVARCHAR2('John Doe'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name      => 'r3',
    condition      => ':priority = 1',
    action_context => ac,
    rule_comment   => 'Urgent problems');
END;
/

/*
Step 6   Create the rs Rule Set
*/
BEGIN
  DBMS_RULE_ADM.CREATE_RULE_SET(
    rule_set_name      => 'rs',
    evaluation_context => 'evalctx',
    rule_set_comment   => 'support rules');
END;
/

/*
Step 7   Add the Rules to the Rule Set
*/
BEGIN
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r1', 
    rule_set_name => 'rs');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r2', 
    rule_set_name => 'rs');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r3', 
    rule_set_name => 'rs');
END;
/

/*
Step 8   Query the Data Dictionary

At this point, you can view the evaluation context, rules, and rule set you created in the previous steps.

*/

COLUMN EVALUATION_CONTEXT_NAME HEADING 'Eval Context Name' FORMAT A30
COLUMN EVALUATION_CONTEXT_COMMENT HEADING 'Eval Context Comment' FORMAT A40

SELECT EVALUATION_CONTEXT_NAME, EVALUATION_CONTEXT_COMMENT
  FROM USER_EVALUATION_CONTEXTS
  ORDER BY EVALUATION_CONTEXT_NAME;

SET LONGCHUNKSIZE 4000
SET LONG 4000
COLUMN RULE_NAME HEADING 'Rule|Name' FORMAT A5
COLUMN RULE_CONDITION HEADING 'Rule Condition' FORMAT A35
COLUMN ACTION_CONTEXT_NAME HEADING 'Action|Context|Name' FORMAT A10
COLUMN ACTION_CONTEXT_VALUE HEADING 'Action|Context|Value' FORMAT A10

SELECT RULE_NAME, 
       RULE_CONDITION,
       AC.NVN_NAME ACTION_CONTEXT_NAME, 
       AC.NVN_VALUE.ACCESSVARCHAR2() ACTION_CONTEXT_VALUE
  FROM USER_RULES R, TABLE(R.RULE_ACTION_CONTEXT.ACTX_LIST) AC
  ORDER BY RULE_NAME;

COLUMN RULE_SET_NAME HEADING 'Rule Set Name' FORMAT A20
COLUMN RULE_SET_EVAL_CONTEXT_OWNER HEADING 'Eval Context|Owner' FORMAT A12
COLUMN RULE_SET_EVAL_CONTEXT_NAME HEADING 'Eval Context Name' FORMAT A25
COLUMN RULE_SET_COMMENT HEADING 'Rule Set|Comment' FORMAT A15

SELECT RULE_SET_NAME, 
       RULE_SET_EVAL_CONTEXT_OWNER,
       RULE_SET_EVAL_CONTEXT_NAME,
       RULE_SET_COMMENT
  FROM USER_RULE_SETS
  ORDER BY RULE_SET_NAME;

/*
Step 9   Create the problem_dispatch PL/SQL Procedure
*/
CREATE OR REPLACE PROCEDURE problem_dispatch (priority NUMBER) 
IS
    vv        SYS.RE$VARIABLE_VALUE;
    vvl       SYS.RE$VARIABLE_VALUE_LIST;
    truehits  SYS.RE$RULE_HIT_LIST;
    maybehits SYS.RE$RULE_HIT_LIST;
    ac        SYS.RE$NV_LIST;
    namearray SYS.RE$NAME_ARRAY;
    name      VARCHAR2(30);
    cval      VARCHAR2(100);
    rnum      INTEGER;
    i         INTEGER;
    status    PLS_INTEGER;
BEGIN
  vv  := SYS.RE$VARIABLE_VALUE('priority',
                               ANYDATA.CONVERTNUMBER(priority));
  vvl := SYS.RE$VARIABLE_VALUE_LIST(vv);
  truehits := SYS.RE$RULE_HIT_LIST();
  maybehits := SYS.RE$RULE_HIT_LIST();
  DBMS_RULE.EVALUATE(
      rule_set_name        => 'support.rs',
      evaluation_context   => 'evalctx',
      variable_values      => vvl,
      true_rules           => truehits,
      maybe_rules          => maybehits);
  FOR rnum IN 1..truehits.COUNT LOOP
    DBMS_OUTPUT.PUT_LINE('Using rule '|| truehits(rnum).rule_name);
    ac := truehits(rnum).rule_action_context;
    namearray := ac.GET_ALL_NAMES;
      FOR i IN 1..namearray.count loop
        name := namearray(i);
        status := ac.GET_VALUE(name).GETVARCHAR2(cval);
        IF (name = 'CENTER') then
          DBMS_OUTPUT.PUT_LINE('Assigning problem to ' || cval);
        ELSIF (name = 'ALERT') THEN
          DBMS_OUTPUT.PUT_LINE('Sending alert to: '|| cval);
        END IF;
      END LOOP;
  END LOOP;
END;
/

/*
Step 10   Dispatch Sample Problems
*/
EXECUTE problem_dispatch(1);
EXECUTE problem_dispatch(2);
EXECUTE problem_dispatch(3);
EXECUTE problem_dispatch(5);

/*
Step 11   Check the Spool Results

Check the rules_stored_variables.out spool file to ensure that all actions completed successfully after this script completes.

*/

SET ECHO OFF
SPOOL OFF

/*************************** END OF SCRIPT ******************************/

Using Rules on Data in Explicit Variables with Iterative Results

This example is the same as the previous example "Using Rules on Nontable Data Stored in Explicit Variables", except that this example returns evaluation results iteratively instead of all at once.

Complete the following steps:

  1. Show Output and Spool Results

  2. Ensure That You Have Completed the Preliminary Steps

  3. Replace the problem_dispatch PL/SQL Procedure

  4. Dispatch Sample Problems

  5. Clean Up the Environment (Optional)

  6. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to all of the databases in the environment.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL rules_stored_variables_iterative.out

/*
Step 2   Ensure That You Have Completed the Preliminary Steps

Ensure that you have completed Steps 1 to 8 in the "Using Rules on Nontable Data Stored in Explicit Variables". If you have not completed these steps, then complete them before you continue.

*/ 

PAUSE Press <RETURN> to continue when the preliminary steps have been completed.

/*
Step 3   Replace the problem_dispatch PL/SQL Procedure

Replace the problem_dispatch procedure created in Step 9 with the procedure in this step. The difference between the two procedures is that the procedure created in Step 9 returns all evaluation results at once while the procedure in this step returns evaluation results iteratively.

*/

CONNECT support

SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE problem_dispatch (priority NUMBER) 
IS
    vv          SYS.RE$VARIABLE_VALUE;
    vvl         SYS.RE$VARIABLE_VALUE_LIST;
    truehits    BINARY_INTEGER;
    maybehits   BINARY_INTEGER;
    hit         SYS.RE$RULE_HIT;
    ac          SYS.RE$NV_LIST;
    namearray   SYS.RE$NAME_ARRAY;
    name        VARCHAR2(30);
    cval        VARCHAR2(100);
    i           INTEGER;
    status      PLS_INTEGER;
    iter_closed EXCEPTION;
    pragma exception_init(iter_closed, -25453);
BEGIN
  vv  := SYS.RE$VARIABLE_VALUE('priority',
                               ANYDATA.CONVERTNUMBER(priority));
  vvl := SYS.RE$VARIABLE_VALUE_LIST(vv);
  DBMS_RULE.EVALUATE(
      rule_set_name        => 'support.rs',
      evaluation_context   => 'evalctx',
      variable_values      => vvl,
      true_rules_iterator  => truehits,
      maybe_rules_iterator => maybehits);
  LOOP
    hit := DBMS_RULE.GET_NEXT_HIT(truehits);
    EXIT WHEN hit IS NULL;
    DBMS_OUTPUT.PUT_LINE('Using rule '|| hit.rule_name);
    ac := hit.rule_action_context;
    namearray := ac.GET_ALL_NAMES;
      FOR i IN 1..namearray.COUNT LOOP
        name := namearray(i);
        status := ac.GET_VALUE(name).GETVARCHAR2(cval);
        IF (name = 'CENTER') then
          DBMS_OUTPUT.PUT_LINE('Assigning problem to ' || cval);
        ELSIF (name = 'ALERT') THEN
          DBMS_OUTPUT.PUT_LINE('Sending alert to: '|| cval);
        END IF;
      END LOOP;
  END LOOP;
  -- Close iterators
  BEGIN
    DBMS_RULE.CLOSE_ITERATOR(truehits);
  EXCEPTION
    WHEN iter_closed THEN
      NULL;
  END;
  BEGIN
    DBMS_RULE.CLOSE_ITERATOR(maybehits);
  EXCEPTION
    WHEN iter_closed THEN
      NULL;
  END;
END;
/

/*
Step 4   Dispatch Sample Problems
*/
EXECUTE problem_dispatch(1);
EXECUTE problem_dispatch(2);
EXECUTE problem_dispatch(3);
EXECUTE problem_dispatch(5);

/*
Step 5   Clean Up the Environment (Optional)

You can clean up the sample environment by dropping the support user.

*/

CONNECT SYSTEM

DROP USER support CASCADE;

/*
Step 6   Check the Spool Results

Check the rules_stored_variables_iterative.out spool file to ensure that all actions completed successfully after this script completes.

*/

SET ECHO OFF
SPOOL OFF

/*************************** END OF SCRIPT ******************************/

Using Partial Evaluation of Rules on Data in Explicit Variables

This example illustrates how to use partial evaluation when an event causes rules to evaluate to MAYBE instead of TRUE or FALSE. This example handles customer problems based on priority and problem type, and uses the following rules for handling customer problems:

  • Assign all problems whose problem type is HARDWARE to the San Jose Center.

  • Assign all problems whose problem type is SOFTWARE to the New York Center.

  • Assign all problems whose problem type is NULL (unknown) to the Texas Center.

  • Send an alert to the vice president of support for a problem with priority equal to 1.

Problems whose problem type is NULL evaluate to MAYBE. This example uses partial evaluation to take an action when MAYBE rules are returned to the rules engine client. In this case, the action is to assign the problem to the Texas Center.

The evaluation context contains an explicit variable named priority, which refers to the priority of the problem being dispatched. The evaluation context also contains an explicit variable named problem_type, which refers to the type of problem being dispatched (either HARDWARE or SOFTWARE). The values for these variables are passed to DBMS_RULE.EVALUATE procedure by the problem_dispatch procedure.

Complete the following steps:

  1. Show Output and Spool Results

  2. Create the support User

  3. Grant the support User the Necessary System Privileges on Rules

  4. Create the evalctx Evaluation Context

  5. Create the Rules that Correspond to Problem Priority

  6. Create the rs Rule Set

  7. Add the Rules to the Rule Set

  8. Query the Data Dictionary

  9. Create the problem_dispatch PL/SQL Procedure

  10. Dispatch Sample Problems

  11. Clean Up the Environment (Optional)

  12. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to all of the databases in the environment.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL rules_stored_variables_partial.out

/*
Step 2   Create the support User
*/
CONNECT SYSTEM

ACCEPT password PROMPT 'Enter password for user: ' HIDE

GRANT ALTER SESSION, CREATE CLUSTER, CREATE DATABASE LINK, CREATE SEQUENCE,
  CREATE SESSION, CREATE SYNONYM, CREATE TABLE, CREATE VIEW, CREATE INDEXTYPE, 
  CREATE OPERATOR, CREATE PROCEDURE, CREATE TRIGGER, CREATE TYPE
TO support IDENTIFIED BY &password;

/*
Step 3   Grant the support User the Necessary System Privileges on Rules
*/
BEGIN
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_RULE_SET_OBJ, 
    grantee      => 'support', 
    grant_option => FALSE);
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_RULE_OBJ,
    grantee      => 'support', 
    grant_option => FALSE);
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_EVALUATION_CONTEXT_OBJ, 
    grantee      => 'support', 
    grant_option => FALSE);
END;
/

/*
Step 4   Create the evalctx Evaluation Context
*/
CONNECT support

SET FEEDBACK 1
SET NUMWIDTH 10
SET LINESIZE 80
SET TRIMSPOOL ON
SET TAB OFF
SET PAGESIZE 100
SET SERVEROUTPUT ON
DECLARE
  vt  SYS.RE$VARIABLE_TYPE_LIST;
BEGIN
  vt := SYS.RE$VARIABLE_TYPE_LIST(
        SYS.RE$VARIABLE_TYPE('priority', 'NUMBER', NULL, NULL),
        SYS.RE$VARIABLE_TYPE('problem_type', 'VARCHAR2(30)', NULL, NULL));
  DBMS_RULE_ADM.CREATE_EVALUATION_CONTEXT(
    evaluation_context_name    => 'evalctx',
    variable_types             => vt,
    evaluation_context_comment => 'support problem definition');
end;
/

/*
Step 5   Create the Rules that Correspond to Problem Priority

The following code creates one action context for each rule, and one name-value pair in each action context.

*/

DECLARE
  ac  SYS.RE$NV_LIST;
begin
  ac := SYS.RE$NV_LIST(NULL);
  ac.ADD_PAIR('ALERT', ANYDATA.CONVERTVARCHAR2('John Doe'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name      => 'r1',
    condition      => ':priority = 1',
    action_context => ac,
    rule_comment   => 'Urgent problems');
  ac := sys.re$nv_list(NULL);
  ac.ADD_PAIR('TRUE CENTER', ANYDATA.CONVERTVARCHAR2('San Jose'));
  ac.ADD_PAIR('MAYBE CENTER', ANYDATA.CONVERTVARCHAR2('Texas'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name       => 'r2',
    condition       => ':problem_type = ''HARDWARE''',
    action_context  => ac,
    rule_comment    => 'Hardware problems');
  ac := sys.re$nv_list(NULL);
  ac.ADD_PAIR('TRUE CENTER', ANYDATA.CONVERTVARCHAR2('New York'));
  ac.ADD_PAIR('MAYBE CENTER', ANYDATA.CONVERTVARCHAR2('Texas'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name       => 'r3',
    condition       => ':problem_type = ''SOFTWARE''',
    action_context  => ac,
    rule_comment    => 'Software problems');
END;
/

/*
Step 6   Create the rs Rule Set
*/
BEGIN
  DBMS_RULE_ADM.CREATE_RULE_SET(
    rule_set_name      => 'rs',
    evaluation_context => 'evalctx',
    rule_set_comment   => 'support rules');
END;
/

/*
Step 7   Add the Rules to the Rule Set
*/
BEGIN
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r1', 
    rule_set_name => 'rs');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r2', 
    rule_set_name => 'rs');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r3', 
    rule_set_name => 'rs');
END;
/

/*
Step 8   Query the Data Dictionary

At this point, you can view the evaluation context, rules, and rule set you created in the previous steps.

*/

COLUMN EVALUATION_CONTEXT_NAME HEADING 'Eval Context Name' FORMAT A30
COLUMN EVALUATION_CONTEXT_COMMENT HEADING 'Eval Context Comment' FORMAT A40

SELECT EVALUATION_CONTEXT_NAME, EVALUATION_CONTEXT_COMMENT
  FROM USER_EVALUATION_CONTEXTS
  ORDER BY EVALUATION_CONTEXT_NAME;

SET LONGCHUNKSIZE 4000
SET LONG 4000
COLUMN RULE_NAME HEADING 'Rule|Name' FORMAT A5
COLUMN RULE_CONDITION HEADING 'Rule Condition' FORMAT A35
COLUMN ACTION_CONTEXT_NAME HEADING 'Action|Context|Name' FORMAT A10
COLUMN ACTION_CONTEXT_VALUE HEADING 'Action|Context|Value' FORMAT A10

SELECT RULE_NAME, 
       RULE_CONDITION,
       AC.NVN_NAME ACTION_CONTEXT_NAME, 
       AC.NVN_VALUE.ACCESSVARCHAR2() ACTION_CONTEXT_VALUE
  FROM USER_RULES R, TABLE(R.RULE_ACTION_CONTEXT.ACTX_LIST) AC
  ORDER BY RULE_NAME;

COLUMN RULE_SET_NAME HEADING 'Rule Set Name' FORMAT A20
COLUMN RULE_SET_EVAL_CONTEXT_OWNER HEADING 'Eval Context|Owner' FORMAT A12
COLUMN RULE_SET_EVAL_CONTEXT_NAME HEADING 'Eval Context Name' FORMAT A25
COLUMN RULE_SET_COMMENT HEADING 'Rule Set|Comment' FORMAT A15

SELECT RULE_SET_NAME, 
       RULE_SET_EVAL_CONTEXT_OWNER,
       RULE_SET_EVAL_CONTEXT_NAME,
       RULE_SET_COMMENT
  FROM USER_RULE_SETS
  ORDER BY RULE_SET_NAME;

/*
Step 9   Create the problem_dispatch PL/SQL Procedure
*/
CREATE OR REPLACE PROCEDURE problem_dispatch (priority     NUMBER,
                                              problem_type VARCHAR2 := NULL) 
IS
    vvl       SYS.RE$VARIABLE_VALUE_LIST;
    truehits  SYS.RE$RULE_HIT_LIST;
    maybehits SYS.RE$RULE_HIT_LIST;
    ac        SYS.RE$NV_LIST;
    namearray SYS.RE$NAME_ARRAY;
    name      VARCHAR2(30);
    cval      VARCHAR2(100);
    rnum      INTEGER;
    i         INTEGER;
    status    PLS_INTEGER;
BEGIN
  IF (problem_type IS NULL) THEN 
    vvl  := SYS.RE$VARIABLE_VALUE_LIST(
            SYS.RE$VARIABLE_VALUE('priority',
                                  ANYDATA.CONVERTNUMBER(priority)));
  ELSE
    vvl  := SYS.RE$VARIABLE_VALUE_LIST(
            SYS.RE$VARIABLE_VALUE('priority',
                                  ANYDATA.CONVERTNUMBER(priority)),
            SYS.RE$VARIABLE_VALUE('problem_type',
                                  ANYDATA.CONVERTVARCHAR2(problem_type)));
  END IF;
  truehits := SYS.RE$RULE_HIT_LIST();
  maybehits := SYS.RE$RULE_HIT_LIST();
  DBMS_RULE.EVALUATE(
      rule_set_name        => 'support.rs',
      evaluation_context   => 'evalctx',
      variable_values      => vvl,
      true_rules           => truehits,
      maybe_rules          => maybehits);
  FOR rnum IN 1..truehits.COUNT LOOP
    DBMS_OUTPUT.PUT_LINE('Using rule '|| truehits(rnum).rule_name);
    ac := truehits(rnum).rule_action_context;
    namearray := ac.GET_ALL_NAMES;
      FOR i IN 1..namearray.count LOOP
        name := namearray(i);
        status := ac.GET_VALUE(name).GETVARCHAR2(cval);
        IF (name = 'TRUE CENTER') then
          DBMS_OUTPUT.PUT_LINE('Assigning problem to ' || cval);
        ELSIF (name = 'ALERT') THEN
          DBMS_OUTPUT.PUT_LINE('Sending alert to: '|| cval);
        END IF;
      END LOOP;
  END LOOP;
  FOR rnum IN 1..maybehits.COUNT LOOP
    DBMS_OUTPUT.PUT_LINE('Using rule '|| maybehits(rnum).rule_name);
    ac := maybehits(rnum).rule_action_context;
    namearray := ac.GET_ALL_NAMES;
      FOR i IN 1..namearray.count loop
        name := namearray(i);
        status := ac.GET_VALUE(name).GETVARCHAR2(cval);
        IF (name = 'MAYBE CENTER') then
          DBMS_OUTPUT.PUT_LINE('Assigning problem to ' || cval);
        END IF;
      END LOOP;
  END LOOP;
END;
/

/*
Step 10   Dispatch Sample Problems

The first problem dispatch in this step uses partial evaluation and takes an action based on the partial evaluation. Specifically, the first problem dispatch specifies that the priority is 1 and the problem_type is NULL. In this case, the rules engine returns a MAYBE rule for the event, and the problem_dispatch procedure assigns the problem to the Texas center.

The second and third problem dispatches do not use partial evaluation. Each of these problems evaluate to TRUE for a rule, and the problem is assigned accordingly by the problem_dispatch procedure.

*/

EXECUTE problem_dispatch(1, NULL);
EXECUTE problem_dispatch(2, 'HARDWARE');
EXECUTE problem_dispatch(3, 'SOFTWARE');

/*
Step 11   Clean Up the Environment (Optional)

You can clean up the sample environment by dropping the support user.

*/

CONNECT SYSTEM

DROP USER support CASCADE;

/*
Step 12   Check the Spool Results

Check the rules_stored_variables_partial.out spool file to ensure that all actions completed successfully after this script completes.

*/

SET ECHO OFF
SPOOL OFF

/*************************** END OF SCRIPT ******************************/

Using Rules on Data Stored in a Table

This example illustrates how to use rules to evaluate data stored in a table. This example is similar to the example described in "Using Rules on Nontable Data Stored in Explicit Variables". In both examples, the application routes customer problems based on priority. However, in this example, the problems are stored in a table instead of variables.

The application uses the problems table in the support schema, into which customer problems are inserted. This example uses the following rules for handling customer problems:

  • Assign all problems with priority greater than 2 to the San Jose Center.

  • Assign all problems with priority less than or equal to 2 to the New York Center.

  • Send an alert to the vice president of support for a problem with priority equal to 1.

The evaluation context consists of the problems table. The relevant row of the table, which corresponds to the problem being routed, is passed to the DBMS_RULE.EVALUATE procedure as a table value.

Complete the following steps:

  1. Show Output and Spool Results

  2. Create the support User

  3. Grant the support User the Necessary System Privileges on Rules

  4. Create the problems Table

  5. Create the evalctx Evaluation Context

  6. Create the Rules that Correspond to Problem Priority

  7. Create the rs Rule Set

  8. Add the Rules to the Rule Set

  9. Create the problem_dispatch PL/SQL Procedure

  10. Log Problems

  11. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to all of the databases in the environment.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL rules_table.out

/*
Step 2   Create the support User
*/
CONNECT SYSTEM

CREATE TABLESPACE support_tbs1 DATAFILE 'support_tbs1.dbf' 
  SIZE 5M REUSE AUTOEXTEND ON MAXSIZE UNLIMITED;

ACCEPT password PROMPT 'Enter password for user: ' HIDE

CREATE USER support
IDENTIFIED BY &password
  DEFAULT TABLESPACE support_tbs1
  QUOTA UNLIMITED ON support_tbs1;

GRANT ALTER SESSION, CREATE CLUSTER, CREATE DATABASE LINK, CREATE SEQUENCE,
  CREATE SESSION, CREATE SYNONYM, CREATE TABLE, CREATE VIEW, CREATE INDEXTYPE, 
  CREATE OPERATOR, CREATE PROCEDURE, CREATE TRIGGER, CREATE TYPE
TO support;

/*
Step 3   Grant the support User the Necessary System Privileges on Rules
*/
BEGIN
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_RULE_SET_OBJ, 
    grantee      => 'support', 
    grant_option => FALSE);
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_RULE_OBJ,
    grantee      => 'support', 
    grant_option => FALSE);
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_EVALUATION_CONTEXT_OBJ, 
    grantee      => 'support', 
    grant_option => FALSE);
END;
/

/*
Step 4   Create the problems Table
*/
CONNECT support

SET FEEDBACK 1
SET NUMWIDTH 10
SET LINESIZE 80
SET TRIMSPOOL ON
SET TAB OFF
SET PAGESIZE 100
SET SERVEROUTPUT ON

CREATE TABLE problems(
  probid          NUMBER PRIMARY KEY,
  custid          NUMBER,
  priority        NUMBER,
  description     VARCHAR2(4000),
  center          VARCHAR2(100));

/*
Step 5   Create the evalctx Evaluation Context
*/
DECLARE
  ta  SYS.RE$TABLE_ALIAS_LIST;
BEGIN
  ta := SYS.RE$TABLE_ALIAS_LIST(SYS.RE$TABLE_ALIAS('prob', 'problems'));
  DBMS_RULE_ADM.CREATE_EVALUATION_CONTEXT(
    evaluation_context_name    => 'evalctx',
    table_aliases              => ta,
    evaluation_context_comment => 'support problem definition');
END;
/

/*
Step 6   Create the Rules that Correspond to Problem Priority

The following code creates one action context for each rule, and one name-value pair in each action context.

*/

DECLARE
  ac  SYS.RE$NV_LIST;
BEGIN
  ac := SYS.RE$NV_LIST(NULL);
  ac.ADD_PAIR('CENTER', ANYDATA.CONVERTVARCHAR2('San Jose'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name      => 'r1',
    condition      => 'prob.priority > 2',
    action_context => ac,
    rule_comment   => 'Low priority problems');
  ac := SYS.RE$NV_LIST(NULL);
  ac.ADD_PAIR('CENTER', ANYDATA.CONVERTVARCHAR2('New York'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name      => 'r2',
    condition      => 'prob.priority <= 2',
    action_context => ac,
    rule_comment   => 'High priority problems');
  ac := sys.RE$NV_LIST(NULL);
  ac.ADD_PAIR('ALERT', ANYDATA.CONVERTVARCHAR2('John Doe'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name      => 'r3',
    condition      => 'prob.priority = 1',
    action_context => ac,
    rule_comment   => 'Urgent problems');
END;
/

/*
Step 7   Create the rs Rule Set
*/
BEGIN
  DBMS_RULE_ADM.CREATE_RULE_SET(
    rule_set_name      => 'rs',
    evaluation_context => 'evalctx',
    rule_set_comment   => 'support rules');
END;
/

/*
Step 8   Add the Rules to the Rule Set
*/
BEGIN
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r1', 
    rule_set_name => 'rs');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r2', 
    rule_set_name => 'rs');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r3', 
    rule_set_name => 'rs');
END;
/

/*
Step 9   Create the problem_dispatch PL/SQL Procedure
*/
CREATE OR REPLACE PROCEDURE problem_dispatch 
IS
    cursor c IS SELECT probid, rowid FROM problems WHERE center IS NULL;
    tv        SYS.RE$TABLE_VALUE;
    tvl       SYS.RE$TABLE_VALUE_LIST;
    truehits  SYS.RE$RULE_HIT_LIST;
    maybehits SYS.RE$RULE_HIT_LIST;
    ac        SYS.RE$NV_LIST;
    namearray SYS.RE$NAME_ARRAY;
    name      VARCHAR2(30);
    cval      VARCHAR2(100);
    rnum      INTEGER;
    i         INTEGER;
    status    PLS_INTEGER;
BEGIN
  FOR r IN c LOOP
    tv  := SYS.RE$TABLE_VALUE('prob', rowidtochar(r.rowid));
    tvl := SYS.RE$TABLE_VALUE_LIST(tv);
    truehits := SYS.RE$RULE_HIT_LIST();
    maybehits := SYS.RE$RULE_HIT_LIST();
    DBMS_RULE.EVALUATE(
      rule_set_name        => 'support.rs',
      evaluation_context   => 'evalctx',
      table_values         => tvl,
      true_rules           => truehits,
      maybe_rules          => maybehits);
    FOR rnum IN 1..truehits.COUNT LOOP
      DBMS_OUTPUT.PUT_LINE('Using rule '|| truehits(rnum).rule_name);
      ac := truehits(rnum).rule_action_context;
      namearray := ac.GET_ALL_NAMES;
      FOR i IN 1..namearray.COUNT LOOP
        name := namearray(i);
        status := ac.GET_VALUE(name).GETVARCHAR2(cval);
        IF (name = 'CENTER') THEN
          UPDATE PROBLEMS SET center = cval WHERE rowid = r.rowid;
          DBMS_OUTPUT.PUT_LINE('Assigning '|| r.probid || ' to ' || cval);
        ELSIF (name = 'ALERT') THEN
          DBMS_OUTPUT.PUT_LINE('Alert: '|| cval || ' Problem:' || r.probid);
        END IF;
       END LOOP;
    END LOOP;
  END LOOP;
END;
/

/*
Step 10   Log Problems
*/
INSERT INTO problems(probid, custid, priority, description)
  VALUES(10101, 11, 1, 'no dial tone');

INSERT INTO problems(probid, custid, priority, description)
  VALUES(10102, 21, 2, 'noise on local calls');

INSERT INTO problems(probid, custid, priority, description)
  VALUES(10103, 31, 3, 'noise on long distance calls');

COMMIT;

/*
Step 11   Check the Spool Results

Check the rules_table.out spool file to ensure that all actions completed successfully after this script completes.

*/

SET ECHO OFF
SPOOL OFF

/*************************** END OF SCRIPT ******************************/

See Also:

"Dispatching Problems and Checking Results for the Table Examples" for the steps to complete to dispatch the problems logged in this example and check the results of the problem dispatch

Using Rules on Both Explicit Variables and Table Data

This example illustrates how to use rules to evaluate data stored in explicit variables and in a table. The application uses the problems table in the support schema, into which customer problems are inserted. This example uses the following rules for handling customer problems:

  • Assign all problems with priority greater than 2 to the San Jose Center.

  • Assign all problems with priority equal to 2 to the New York Center.

  • Assign all problems with priority equal to 1 to the Tampa Center from 8 AM to 8 PM.

  • Assign all problems with priority equal to 1 to the Bangalore Center from 8 PM to 8 AM.

  • Send an alert to the vice president of support for a problem with priority equal to 1.

The evaluation context consists of the problems table. The relevant row of the table, which corresponds to the problem being routed, is passed to the DBMS_RULE.EVALUATE procedure as a table value.

Some of the rules in this example refer to the current time, which is represented as an explicit variable named current_time. The current time is treated as additional data in the evaluation context. It is represented as a variable for the following reasons:

  • It is not practical to store the current time in a table because it would have to be updated very often.

  • The current time can be accessed by inserting calls to SYSDATE in every rule that requires it, but that would cause repeated invocations of the same SQL function SYSDATE, which might slow down rule evaluation. Different values of the current time in different rules might lead to incorrect behavior.

Complete the following steps:

  1. Show Output and Spool Results

  2. Create the support User

  3. Grant the support User the Necessary System Privileges on Rules

  4. Create the problems Table

  5. Create the evalctx Evaluation Context

  6. Create the Rules that Correspond to Problem Priority

  7. Create the rs Rule Set

  8. Add the Rules to the Rule Set

  9. Create the problem_dispatch PL/SQL Procedure

  10. Log Problems

  11. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to all of the databases in the environment.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL rules_var_tab.out

/*
Step 2   Create the support User
*/
CONNECT SYSTEM

CREATE TABLESPACE support_tbs2 DATAFILE 'support_tbs2.dbf' 
  SIZE 5M REUSE AUTOEXTEND ON MAXSIZE UNLIMITED;

ACCEPT password PROMPT 'Enter password for user: ' HIDE

CREATE USER support
IDENTIFIED BY &password
  DEFAULT TABLESPACE support_tbs2
  QUOTA UNLIMITED ON support_tbs2;

GRANT ALTER SESSION, CREATE CLUSTER, CREATE DATABASE LINK, CREATE SEQUENCE,
  CREATE SESSION, CREATE SYNONYM, CREATE TABLE, CREATE VIEW, CREATE INDEXTYPE, 
  CREATE OPERATOR, CREATE PROCEDURE, CREATE TRIGGER, CREATE TYPE
TO support;

/*
Step 3   Grant the support User the Necessary System Privileges on Rules
*/
BEGIN
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_RULE_SET_OBJ, 
    grantee      => 'support', 
    grant_option => FALSE);
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_RULE_OBJ,
    grantee      => 'support', 
    grant_option => FALSE);
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_EVALUATION_CONTEXT_OBJ, 
    grantee      => 'support', 
    grant_option => FALSE);
END;
/

/*
Step 4   Create the problems Table
*/
CONNECT support

SET FEEDBACK 1
SET NUMWIDTH 10
SET LINESIZE 80
SET TRIMSPOOL ON
SET TAB OFF
SET PAGESIZE 100
SET SERVEROUTPUT ON

CREATE TABLE problems(
  probid          NUMBER PRIMARY KEY,
  custid          NUMBER,
  priority        NUMBER,
  description     VARCHAR2(4000),
  center          VARCHAR2(100));

/*
Step 5   Create the evalctx Evaluation Context
*/
DECLARE
  ta SYS.RE$TABLE_ALIAS_LIST;
  vt SYS.RE$VARIABLE_TYPE_LIST;
BEGIN
  ta := SYS.RE$TABLE_ALIAS_LIST(SYS.RE$TABLE_ALIAS('prob', 'problems'));
  vt := SYS.RE$VARIABLE_TYPE_LIST(
          SYS.RE$VARIABLE_TYPE('current_time', 'DATE', NULL, NULL));
  DBMS_RULE_ADM.CREATE_EVALUATION_CONTEXT(
    evaluation_context_name    => 'evalctx',
    table_aliases              => ta,
    variable_types             => vt,
    evaluation_context_comment => 'support problem definition');
END;
/

/*
Step 6   Create the Rules that Correspond to Problem Priority

The following code creates one action context for each rule, and one name-value pair in each action context.

*/

DECLARE
  ac SYS.RE$NV_LIST;
BEGIN
  ac := SYS.RE$NV_LIST(NULL);
  ac.ADD_PAIR('CENTER', ANYDATA.CONVERTVARCHAR2('San Jose'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name      => 'r1',
    condition      => 'prob.priority > 2',
    action_context => ac,
    rule_comment   => 'Low priority problems');
  ac := SYS.RE$NV_LIST(NULL);
  ac.ADD_PAIR('CENTER', ANYDATA.CONVERTVARCHAR2('New York'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name      => 'r2',
    condition      => 'prob.priority = 2',
    action_context => ac,
    rule_comment   => 'High priority problems');
  ac := SYS.RE$NV_LIST(NULL);
  ac.ADD_PAIR('ALERT', ANYDATA.CONVERTVARCHAR2('John Doe'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name      => 'r3',
    condition      => 'prob.priority = 1',
    action_context => ac,
    rule_comment   => 'Urgent problems');
  ac := SYS.RE$NV_LIST(NULL);
  ac.ADD_PAIR('CENTER', ANYDATA.CONVERTVARCHAR2('Tampa'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name => 'r4',
    condition => '(prob.priority = 1) and ' ||
                 '(TO_NUMBER(TO_CHAR(:current_time, ''HH24'')) >= 8) and ' ||
                 '(TO_NUMBER(TO_CHAR(:current_time, ''HH24'')) <= 20)',
    action_context => ac,
    rule_comment => 'Urgent daytime problems');
  ac := sys.RE$NV_LIST(NULL);
  ac.add_pair('CENTER', ANYDATA.CONVERTVARCHAR2('Bangalore'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name => 'r5',
    condition => '(prob.priority = 1) and ' ||
                 '((TO_NUMBER(TO_CHAR(:current_time, ''HH24'')) < 8) or ' ||
                 ' (TO_NUMBER(TO_CHAR(:current_time, ''HH24'')) > 20))',
    action_context => ac,
    rule_comment => 'Urgent nighttime problems');
END;
/

/*
Step 7   Create the rs Rule Set
*/
BEGIN
  DBMS_RULE_ADM.CREATE_RULE_SET(
    rule_set_name      => 'rs',
    evaluation_context => 'evalctx',
    rule_set_comment   => 'support rules');
END;
/

/*
Step 8   Add the Rules to the Rule Set
*/
BEGIN
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r1', 
    rule_set_name => 'rs');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r2', 
    rule_set_name => 'rs');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r3', 
    rule_set_name => 'rs');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r4', 
    rule_set_name => 'rs');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r5', 
    rule_set_name => 'rs');
END;
/

/*
Step 9   Create the problem_dispatch PL/SQL Procedure
*/
CREATE OR REPLACE PROCEDURE problem_dispatch
IS
    cursor c  is SELECT probid, rowid FROM PROBLEMS WHERE center IS NULL;
    tv        SYS.RE$TABLE_VALUE;
    tvl       SYS.RE$TABLE_VALUE_LIST;
    vv1       SYS.RE$VARIABLE_VALUE;
    vvl       SYS.RE$VARIABLE_VALUE_LIST;
    truehits  SYS.RE$RULE_HIT_LIST;
    maybehits SYS.RE$RULE_HIT_LIST;
    ac        SYS.RE$NV_LIST;
    namearray SYS.RE$NAME_ARRAY;
    name      VARCHAR2(30);
    cval      VARCHAR2(100);
    rnum      INTEGER;
    i         INTEGER;
    status    PLS_INTEGER;
BEGIN
  FOR r IN c LOOP
    tv  := sYS.RE$TABLE_VALUE('prob', ROWIDTOCHAR(r.rowid));
    tvl := SYS.RE$TABLE_VALUE_LIST(tv);
    vv1 := SYS.RE$VARIABLE_VALUE('current_time',
                                 ANYDATA.CONVERTDATE(SYSDATE));
    vvl := SYS.RE$VARIABLE_VALUE_LIST(vv1);
    truehits := SYS.RE$RULE_HIT_LIST();
    maybehits := SYS.RE$RULE_HIT_LIST();
    DBMS_RULE.EVALUATE(
        rule_set_name        => 'support.rs',
        evaluation_context   => 'evalctx',
        table_values         => tvl,
        variable_values      => vvl,
        true_rules           => truehits,
        maybe_rules          => maybehits);
    FOR rnum IN 1..truehits.COUNT loop
      DBMS_OUTPUT.PUT_LINE('Using rule '|| truehits(rnum).rule_name);
      ac := truehits(rnum).rule_action_context;
      namearray := ac.GET_ALL_NAMES;
      FOR i in 1..namearray.COUNT LOOP
        name := namearray(i);
        status := ac.GET_VALUE(name).GETVARCHAR2(cval);
        IF (name = 'CENTER') THEN
          UPDATE problems SET center = cval
          WHERE rowid = r.rowid;
          DBMS_OUTPUT.PUT_LINE('Assigning '|| r.probid || ' to ' || cval);
        ELSIF (name = 'ALERT') THEN
          DBMS_OUTPUT.PUT_LINE('Alert: '|| cval || ' Problem:' || r.probid);
        END IF;
      END LOOP;
    END LOOP;  
  END LOOP;
END;
/

/*
Step 10   Log Problems
*/
INSERT INTO problems(probid, custid, priority, description)
  VALUES(10201, 12, 1, 'no dial tone');

INSERT INTO problems(probid, custid, priority, description)
  VALUES(10202, 22, 2, 'noise on local calls');

INSERT INTO PROBLEMS(probid, custid, priority, description)
  VALUES(10203, 32, 3, 'noise on long distance calls');

COMMIT;

/*
Step 11   Check the Spool Results

Check the rules_var_tab.out spool file to ensure that all actions completed successfully after this script completes.

*/

SET ECHO OFF
SPOOL OFF

/*************************** END OF SCRIPT ******************************/

See Also:

"Dispatching Problems and Checking Results for the Table Examples" for the steps to complete to dispatch the problems logged in this example and check the results of the problem dispatch

Using Rules on Implicit Variables and Table Data

This example illustrates how to use rules to evaluate implicit variables and data stored in a table. The application uses the problems table in the support schema, into which customer problems are inserted. This example uses the following rules for handling customer problems:

  • Assign all problems with priority greater than 2 to the San Jose Center.

  • Assign all problems with priority equal to 2 to the New York Center.

  • Assign all problems with priority equal to 1 to the Tampa Center from 8 AM to 8 PM.

  • Assign all problems with priority equal to 1 to the Bangalore Center after 8 PM and before 8 AM.

  • Send an alert to the vice president of support for a problem with priority equal to 1.

The evaluation context consists of the problems table. The relevant row of the table, which corresponds to the problem being routed, is passed to the DBMS_RULE.EVALUATE procedure as a table value.

As in the example illustrated in "Using Rules on Both Explicit Variables and Table Data", the current time is represented as a variable named current_time. However, this variable value is not specified during evaluation by the caller. That is, current_time is an implicit variable in this example. A PL/SQL function named timefunc is specified for current_time, and this function is invoked once during evaluation to get its value.

Using implicit variables can be useful in other cases if one of the following conditions is true:

  • The caller does not have access to the variable value.

  • The variable is referenced infrequently in rules. Because it is implicit, its value can be retrieved only when necessary, and does not need to be passed in for every evaluation.

Complete the following steps:

  1. Show Output and Spool Results

  2. Create the support User

  3. Grant the support User the Necessary System Privileges on Rules

  4. Create the problems Table

  5. Create the timefunc Function to Return the Value of current_time

  6. Create the evalctx Evaluation Context

  7. Create the Rules that Correspond to Problem Priority

  8. Create the rs Rule Set

  9. Add the Rules to the Rule Set

  10. Create the problem_dispatch PL/SQL Procedure

  11. Log Problems

  12. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to all of the databases in the environment.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL rules_implicit_var.out

/*
Step 2   Create the support User
*/
CONNECT SYSTEM

CREATE TABLESPACE support_tbs3 DATAFILE 'support_tbs3.dbf' 
  SIZE 5M REUSE AUTOEXTEND ON MAXSIZE UNLIMITED;

ACCEPT password PROMPT 'Enter password for user: ' HIDE

CREATE USER support
IDENTIFIED BY &password
  DEFAULT TABLESPACE support_tbs3
  QUOTA UNLIMITED ON support_tbs3;

GRANT ALTER SESSION, CREATE CLUSTER, CREATE DATABASE LINK, CREATE SEQUENCE,
  CREATE SESSION, CREATE SYNONYM, CREATE TABLE, CREATE VIEW, CREATE INDEXTYPE, 
  CREATE OPERATOR, CREATE PROCEDURE, CREATE TRIGGER, CREATE TYPE
TO support;

/*
Step 3   Grant the support User the Necessary System Privileges on Rules
*/
BEGIN
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_RULE_SET_OBJ, 
    grantee      => 'support', 
    grant_option => FALSE);
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_RULE_OBJ,
    grantee      => 'support', 
    grant_option => FALSE);
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_EVALUATION_CONTEXT_OBJ, 
    grantee      => 'support', 
    grant_option => FALSE);
END;
/

/*
Step 4   Create the problems Table
*/
CONNECT support

SET FEEDBACK 1
SET NUMWIDTH 10
SET LINESIZE 80
SET TRIMSPOOL ON
SET TAB OFF
SET PAGESIZE 100
SET SERVEROUTPUT ON

CREATE TABLE problems(
  probid          NUMBER PRIMARY KEY,
  custid          NUMBER,
  priority        NUMBER,
  description     VARCHAR2(4000),
  center          VARCHAR2(100));

/*
Step 5   Create the timefunc Function to Return the Value of current_time
*/
CREATE OR REPLACE FUNCTION timefunc(
  eco    VARCHAR2, 
  ecn    VARCHAR2, 
  var    VARCHAR2,
  evctx  SYS.RE$NV_LIST)
RETURN SYS.RE$VARIABLE_VALUE
IS
BEGIN
  IF (var = 'CURRENT_TIME') THEN
    RETURN(SYS.RE$VARIABLE_VALUE('current_time',
                                 ANYDATA.CONVERTDATE(SYSDATE)));
  ELSE
    RETURN(NULL);
  END IF;
END;
/

/*
Step 6   Create the evalctx Evaluation Context
*/
DECLARE
  ta SYS.RE$TABLE_ALIAS_LIST;
  vt SYS.RE$VARIABLE_TYPE_LIST;
BEGIN
  ta := SYS.RE$TABLE_ALIAS_LIST(SYS.RE$TABLE_ALIAS('prob', 'problems'));
  vt := SYS.RE$VARIABLE_TYPE_LIST(
          SYS.RE$VARIABLE_TYPE('current_time', 'DATE', 'timefunc', NULL));
  DBMS_RULE_ADM.CREATE_EVALUATION_CONTEXT(
    evaluation_context_name    => 'evalctx',
    table_aliases              => ta,
    variable_types             => vt,
    evaluation_context_comment => 'support problem definition');
END;
/

/*
Step 7   Create the Rules that Correspond to Problem Priority

The following code creates one action context for each rule, and one name-value pair in each action context.

*/

DECLARE
  ac SYS.RE$NV_LIST;
BEGIN
  ac := SYS.RE$NV_LIST(NULL);
  ac.ADD_PAIR('CENTER', ANYDATA.CONVERTVARCHAR2('San Jose'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name      => 'r1',
    condition      => 'prob.priority > 2',
    action_context => ac,
    rule_comment   => 'Low priority problems');
  ac := SYS.RE$NV_LIST(NULL);
  ac.ADD_PAIR('CENTER', ANYDATA.CONVERTVARCHAR2('New York'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name      => 'r2',
    condition      => 'prob.priority = 2',
    action_context => ac,
    rule_comment   => 'High priority problems');
  ac := SYS.RE$NV_LIST(NULL);
  ac.ADD_PAIR('ALERT', ANYDATA.CONVERTVARCHAR2('John Doe'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name      => 'r3',
    condition      => 'prob.priority = 1',
    action_context => ac,
    rule_comment   => 'Urgent problems');
  ac := SYS.RE$NV_LIST(NULL);
  ac.ADD_PAIR('CENTER', ANYDATA.CONVERTVARCHAR2('Tampa'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name => 'r4',
    condition => '(prob.priority = 1) and ' ||
                 '(TO_NUMBER(TO_CHAR(:current_time, ''HH24'')) >= 8) and ' ||
                 '(TO_NUMBER(TO_CHAR(:current_time, ''HH24'')) <= 20)',
    action_context => ac,
    rule_comment   => 'Urgent daytime problems');
  ac := SYS.RE$NV_LIST(NULL);
  ac.add_pair('CENTER', ANYDATA.CONVERTVARCHAR2('Bangalore'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name => 'r5',
    condition => '(prob.priority = 1) and ' ||
                 '((TO_NUMBER(TO_CHAR(:current_time, ''HH24'')) < 8) or ' ||
                 ' (TO_NUMBER(TO_CHAR(:current_time, ''HH24'')) > 20))',
    action_context => ac,
    rule_comment => 'Urgent nighttime problems');
END;
/

/*
Step 8   Create the rs Rule Set
*/
BEGIN
  DBMS_RULE_ADM.CREATE_RULE_SET(
    rule_set_name      => 'rs',
    evaluation_context => 'evalctx',
    rule_set_comment   => 'support rules');
END;
/

/*
Step 9   Add the Rules to the Rule Set
*/
BEGIN
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r1', 
    rule_set_name => 'rs');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r2', 
    rule_set_name => 'rs');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r3', 
    rule_set_name => 'rs');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r4', 
    rule_set_name => 'rs');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r5', 
    rule_set_name => 'rs');
END;
/

/*
Step 10   Create the problem_dispatch PL/SQL Procedure
*/
CREATE OR REPLACE PROCEDURE problem_dispatch
IS
    cursor c  IS SELECT probid, rowid FROM problems WHERE center IS NULL;
    tv        SYS.RE$TABLE_VALUE;
    tvl       SYS.RE$TABLE_VALUE_LIST;
    truehits  SYS.RE$RULE_HIT_LIST;
    maybehits SYS.RE$RULE_HIT_LIST;
    ac        SYS.RE$NV_LIST;
    namearray SYS.RE$NAME_ARRAY;
    name      VARCHAR2(30);
    cval      VARCHAR2(100);
    rnum      INTEGER;
    i         INTEGER;
    status    PLS_INTEGER;
BEGIN
  FOR r IN c LOOP
    tv  := SYS.RE$TABLE_VALUE('prob', rowidtochar(r.rowid));
    tvl := SYS.RE$TABLE_VALUE_LIST(tv);
    truehits := SYS.RE$RULE_HIT_LIST();
    maybehits := SYS.RE$RULE_HIT_LIST();
    DBMS_RULE.EVALUATE(
        rule_set_name        => 'support.rs',
        evaluation_context   => 'evalctx',
        table_values         => tvl,
        true_rules           => truehits,
        maybe_rules          => maybehits);
    FOR rnum IN 1..truehits.COUNT LOOP
      DBMS_OUTPUT.PUT_LINE('Using rule '|| truehits(rnum).rule_name);
      ac := truehits(rnum).rule_action_context;
      namearray := ac.GET_ALL_NAMES;
      FOR i IN 1..namearray.COUNT LOOP
        name := namearray(i);
        status := ac.GET_VALUE(name).GETVARCHAR2(cval);
        IF (name = 'CENTER') THEN
          UPDATE problems SET center = cval
            WHERE rowid = r.rowid;
          DBMS_OUTPUT.PUT_LINE('Assigning '|| r.probid || ' to ' || cval);
        ELSIF (name = 'ALERT') THEN
          DBMS_OUTPUT.PUT_LINE('Alert: '|| cval || ' Problem:' || r.probid);
        END IF;
      END LOOP;
    END LOOP;
  END LOOP;
END;
/

/*
Step 11   Log Problems
*/
INSERT INTO problems(probid, custid, priority, description)
  VALUES(10301, 13, 1, 'no dial tone');

INSERT INTO problems(probid, custid, priority, description)
  VALUES(10302, 23, 2, 'noise on local calls');

INSERT INTO problems(probid, custid, priority, description)
  VALUES(10303, 33, 3, 'noise on long distance calls');

COMMIT;

/*
Step 12   Check the Spool Results

Check the rules_implicit_var.out spool file to ensure that all actions completed successfully after this script completes.

*/

SET ECHO OFF
SPOOL OFF

/*************************** END OF SCRIPT ******************************/

See Also:

"Dispatching Problems and Checking Results for the Table Examples" for the steps to complete to dispatch the problems logged in this example and check the results of the problem dispatch

Using Event Contexts and Implicit Variables with Rules

An event context is a varray of type SYS.RE$NV_LIST that contains name-value pairs that contain information about the event. This optional information is not directly used or interpreted by the rules engine. Instead, it is passed to client callbacks such as an evaluation function, a variable value function (for implicit variables), or a variable method function.

In this example, assume every customer has a primary contact person, and the goal is to assign the problem reported by a customer to the support center to which the customer's primary contact person belongs. The customer name is passed in the event context.

This example illustrates how to use event contexts with rules to evaluate implicit variables. Specifically, when an event is evaluated using the DBMS_RULE.EVALUATE procedure, the event context is passed to the variable value function for implicit variables in the evaluation context. The name of the variable value function is find_contact, and this PL/SQL function returns the contact person based on the name of the company specified in the event context. The rule set is evaluated based on the contact person name and the priority for an event.

This example uses the following rules for handling customer problems:

  • Assign all problems that belong to Jane to the San Jose Center.

  • Assign all problems that belong to Fred to the New York Center.

  • Assign all problems whose primary contact is unknown to George at the Texas Center.

  • Send an alert to the vice president of support for a problem with priority equal to 1.

Complete the following steps:

  1. Show Output and Spool Results

  2. Create the support User

  3. Grant the support User the Necessary System Privileges on Rules

  4. Create the find_contact Function to Return a Customer's Contact

  5. Create the evalctx Evaluation Context

  6. Create the Rules that Correspond to Problem Priority and Contact

  7. Create the rs Rule Set

  8. Add the Rules to the Rule Set

  9. Query the Data Dictionary

  10. Create the problem_dispatch PL/SQL Procedure

  11. Dispatch Sample Problems

  12. Clean Up the Environment (Optional)

  13. Check the Spool Results


Note:

If you are viewing this document online, then you can copy the text from the "BEGINNING OF SCRIPT" line after this note to the next "END OF SCRIPT" line into a text editor and then edit the text to create a script for your environment. Run the script with SQL*Plus on a computer that can connect to all of the databases in the environment.

/************************* BEGINNING OF SCRIPT ******************************
Step 1   Show Output and Spool Results

Run SET ECHO ON and specify the spool file for the script. Check the spool file for errors after you run this script.

*/

SET ECHO ON
SPOOL rules_event_context.out

/*
Step 2   Create the support User
*/
CONNECT SYSTEM

ACCEPT password PROMPT 'Enter password for user: ' HIDE

GRANT ALTER SESSION, CREATE CLUSTER, CREATE DATABASE LINK, CREATE SEQUENCE,
  CREATE SESSION, CREATE SYNONYM, CREATE TABLE, CREATE VIEW, CREATE INDEXTYPE, 
  CREATE OPERATOR, CREATE PROCEDURE, CREATE TRIGGER, CREATE TYPE
TO support IDENTIFIED BY &support;

/*
Step 3   Grant the support User the Necessary System Privileges on Rules
*/
BEGIN
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_RULE_SET_OBJ, 
    grantee      => 'support', 
    grant_option => FALSE);
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_RULE_OBJ,
    grantee      => 'support', 
    grant_option => FALSE);
  DBMS_RULE_ADM.GRANT_SYSTEM_PRIVILEGE(
    privilege    => DBMS_RULE_ADM.CREATE_EVALUATION_CONTEXT_OBJ, 
    grantee      => 'support', 
    grant_option => FALSE);
END;
/

/*
Step 4   Create the find_contact Function to Return a Customer's Contact
*/
CONNECT support

SET FEEDBACK 1
SET NUMWIDTH 10
SET LINESIZE 80
SET TRIMSPOOL ON
SET TAB OFF
SET PAGESIZE 100
SET SERVEROUTPUT ON
CREATE OR REPLACE FUNCTION find_contact(
  eco       VARCHAR2, 
  ecn       VARCHAR2, 
  var       VARCHAR2,
  evctx     SYS.RE$NV_LIST)
RETURN SYS.RE$VARIABLE_VALUE IS
  cust      VARCHAR2(30);
  contact   VARCHAR2(30);
  status    PLS_INTEGER;
BEGIN  
  IF (var = 'CUSTOMER_CONTACT') THEN
    status := evctx.GET_VALUE('CUSTOMER').GETVARCHAR2(cust);    
    IF (cust = 'COMPANY1') THEN     -- COMPANY1's contact person is Jane
      contact := 'JANE';
    ELSIF (cust = 'COMPANY2') THEN  -- COMPANY2's contact person is Fred
      contact := 'FRED';
    ELSE        -- Assign customers without primary contact person to George
      contact := 'GEORGE';
    END IF;
    RETURN SYS.RE$VARIABLE_VALUE('customer_contact',
                                 ANYDATA.CONVERTVARCHAR2(contact));
  ELSE
    RETURN NULL;
  END IF;
END;
/

/*
Step 5   Create the evalctx Evaluation Context
*/
DECLARE
  vt  SYS.RE$VARIABLE_TYPE_LIST;
BEGIN
  vt := SYS.RE$VARIABLE_TYPE_LIST(
        SYS.RE$VARIABLE_TYPE('priority', 'NUMBER', NULL, NULL),
        SYS.RE$VARIABLE_TYPE('customer_contact', 'VARCHAR2(30)', 
                             'find_contact', NULL));
  DBMS_RULE_ADM.CREATE_EVALUATION_CONTEXT(
    evaluation_context_name    => 'evalctx',
    variable_types             => vt,
    evaluation_context_comment => 'support problem definition');
END;
/

/*
Step 6   Create the Rules that Correspond to Problem Priority and Contact

The following code creates one action context for each rule, and one name-value pair in each action context.

*/

DECLARE
  ac  SYS.RE$NV_LIST;
BEGIN
  ac := SYS.RE$NV_LIST(NULL);
  ac.ADD_PAIR('CENTER', ANYDATA.CONVERTVARCHAR2('San Jose'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name       => 'r1',
    condition       => ':customer_contact = ''JANE''',
    action_context  => ac,
    rule_comment    => 'Jane''s customer problems');
  ac := sys.re$nv_list(NULL);
  ac.ADD_PAIR('CENTER', ANYDATA.CONVERTVARCHAR2('New York'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name       => 'r2',
    condition       => ':customer_contact = ''FRED''',
    action_context  => ac,
    rule_comment    => 'Fred''s customer problems');
  ac := sys.re$nv_list(NULL);
  ac.ADD_PAIR('CENTER', ANYDATA.CONVERTVARCHAR2('Texas'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name       => 'r3',
    condition       => ':customer_contact = ''GEORGE''',
    action_context  => ac,
    rule_comment    => 'George''s customer problems');
  ac := sys.re$nv_list(NULL);
  ac.ADD_PAIR('ALERT', ANYDATA.CONVERTVARCHAR2('John Doe'));
  DBMS_RULE_ADM.CREATE_RULE(
    rule_name       => 'r4',
    condition       => ':priority=1',
    action_context  => ac,
    rule_comment    => 'Urgent problems');
END;
/

/*
Step 7   Create the rs Rule Set
*/
BEGIN
  DBMS_RULE_ADM.CREATE_RULE_SET(
    rule_set_name      => 'rs',
    evaluation_context => 'evalctx',
    rule_set_comment   => 'support rules');
END;
/

/*
Step 8   Add the Rules to the Rule Set
*/
BEGIN
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r1', 
    rule_set_name => 'rs');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r2', 
    rule_set_name => 'rs');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r3', 
    rule_set_name => 'rs');
  DBMS_RULE_ADM.ADD_RULE(
    rule_name     => 'r4', 
    rule_set_name => 'rs');
END;
/

/*
Step 9   Query the Data Dictionary

At this point, you can view the evaluation context, rules, and rule set you created in the previous steps.

*/

COLUMN EVALUATION_CONTEXT_NAME HEADING 'Eval Context Name' FORMAT A30
COLUMN EVALUATION_CONTEXT_COMMENT HEADING 'Eval Context Comment' FORMAT A40

SELECT EVALUATION_CONTEXT_NAME, EVALUATION_CONTEXT_COMMENT
  FROM USER_EVALUATION_CONTEXTS
  ORDER BY EVALUATION_CONTEXT_NAME;

SET LONGCHUNKSIZE 4000
SET LONG 4000
COLUMN RULE_NAME HEADING 'Rule|Name' FORMAT A5
COLUMN RULE_CONDITION HEADING 'Rule Condition' FORMAT A35
COLUMN ACTION_CONTEXT_NAME HEADING 'Action|Context|Name' FORMAT A10
COLUMN ACTION_CONTEXT_VALUE HEADING 'Action|Context|Value' FORMAT A10

SELECT RULE_NAME, 
       RULE_CONDITION,
       AC.NVN_NAME ACTION_CONTEXT_NAME, 
       AC.NVN_VALUE.ACCESSVARCHAR2() ACTION_CONTEXT_VALUE
  FROM USER_RULES R, TABLE(R.RULE_ACTION_CONTEXT.ACTX_LIST) AC
  ORDER BY RULE_NAME;

COLUMN RULE_SET_NAME HEADING 'Rule Set Name' FORMAT A20
COLUMN RULE_SET_EVAL_CONTEXT_OWNER HEADING 'Eval Context|Owner' FORMAT A12
COLUMN RULE_SET_EVAL_CONTEXT_NAME HEADING 'Eval Context Name' FORMAT A25
COLUMN RULE_SET_COMMENT HEADING 'Rule Set|Comment' FORMAT A15

SELECT RULE_SET_NAME, 
       RULE_SET_EVAL_CONTEXT_OWNER,
       RULE_SET_EVAL_CONTEXT_NAME,
       RULE_SET_COMMENT
  FROM USER_RULE_SETS
  ORDER BY RULE_SET_NAME;

/*
Step 10   Create the problem_dispatch PL/SQL Procedure
*/
CREATE OR REPLACE PROCEDURE problem_dispatch (priority  NUMBER,
                                              customer  VARCHAR2) 
IS
    vvl       SYS.RE$VARIABLE_VALUE_LIST;
    truehits  SYS.RE$RULE_HIT_LIST;
    maybehits SYS.RE$RULE_HIT_LIST;
    ac        SYS.RE$NV_LIST;
    namearray SYS.RE$NAME_ARRAY;
    name      VARCHAR2(30);
    cval      VARCHAR2(100);
    rnum      INTEGER;
    i         INTEGER;
    status    PLS_INTEGER;
    evctx     SYS.RE$NV_LIST;
BEGIN
  vvl  := SYS.RE$VARIABLE_VALUE_LIST(
            SYS.RE$VARIABLE_VALUE('priority',
                                  ANYDATA.CONVERTNUMBER(priority)));
  evctx := SYS.RE$NV_LIST(NULL);
  evctx.ADD_PAIR('CUSTOMER', ANYDATA.CONVERTVARCHAR2(customer));
  truehits  := SYS.RE$RULE_HIT_LIST();
  maybehits := SYS.RE$RULE_HIT_LIST();
  DBMS_RULE.EVALUATE(
      rule_set_name        => 'support.rs',
      evaluation_context   => 'evalctx',
      event_context        => evctx,
      variable_values      => vvl,
      true_rules           => truehits,
      maybe_rules          => maybehits);
  FOR rnum IN 1..truehits.COUNT LOOP
    DBMS_OUTPUT.PUT_LINE('Using rule '|| truehits(rnum).rule_name);
    ac := truehits(rnum).rule_action_context;
    namearray := ac.GET_ALL_NAMES;
      FOR i IN 1..namearray.count LOOP
        name := namearray(i);
        status := ac.GET_VALUE(name).GETVARCHAR2(cval);
        IF (name = 'CENTER') THEN
          DBMS_OUTPUT.PUT_LINE('Assigning problem to ' || cval);
        ELSIF (name = 'ALERT') THEN
          DBMS_OUTPUT.PUT_LINE('Sending alert to: '|| cval);
        END IF;
      END LOOP;
  END LOOP;
END;
/

/*
Step 11   Dispatch Sample Problems

The first problem dispatch in this step uses the event context and the variable value function to determine the contact person for COMPANY1. The event context is passed to the find_contact variable value function, and this function returns the contact name JANE. Therefore, rule r1 evaluates to TRUE. The problem_dispatch procedure sends the problem to the San Jose office because JANE belongs to that office. In addition, the priority for this event is 1, which causes rule r4 to evaluate to TRUE. As a result, the problem_dispatch procedure sends an alert to John Doe.

The second problem dispatch in this step uses the event context and the variable value function to determine the contact person for COMPANY2. The event context is passed to the find_contact variable value function, and this function returns the contact name FRED. Therefore, rule r2 evaluates to TRUE. The problem_dispatch procedure sends the problem to the New York office because FRED belongs to that office.

The third problem dispatch in this step uses the event context and the variable value function to determine the contact person for COMPANY3. This company does not have a dedicated contact person. The event context is passed to the find_contact variable value function, and this function returns the contact name GEORGE, because GEORGE is the default contact when no contact person is found. Therefore, rule r3 evaluates to TRUE. The problem_dispatch procedure sends the problem to the Texas office because GEORGE belongs to that office.

*/

EXECUTE problem_dispatch(1, 'COMPANY1');
EXECUTE problem_dispatch(2, 'COMPANY2');
EXECUTE problem_dispatch(5, 'COMPANY3');

/*
Step 12   Clean Up the Environment (Optional)

You can clean up the sample environment by dropping the support user.

*/

CONNECT SYSTEM

DROP USER support CASCADE;

/*
Step 13   Check the Spool Results

Check the rules_event_context.out spool file to ensure that all actions completed successfully after this script completes.

*/

SET ECHO OFF
SPOOL OFF

/*************************** END OF SCRIPT ******************************/

Dispatching Problems and Checking Results for the Table Examples

The following sections configure a problem_dispatch procedure that updates information in the problems table:

Complete the following steps to dispatch the problems by running the problem_dispatch procedure and display the results in the problems table:

  1. Query the Data Dictionary

  2. List the Problems in the problems Table

  3. Dispatch the Problems by Running the problem_dispatch Procedure

  4. List the Problems in the problems Table

  5. Clean Up the Environment (Optional)

Step 1   Query the Data Dictionary

View the evaluation context, rules, and rule set you created in the example:

CONNECT support
Enter password: password

COLUMN EVALUATION_CONTEXT_NAME HEADING 'Eval Context Name' FORMAT A30
COLUMN EVALUATION_CONTEXT_COMMENT HEADING 'Eval Context Comment' FORMAT A40

SELECT EVALUATION_CONTEXT_NAME, EVALUATION_CONTEXT_COMMENT
  FROM USER_EVALUATION_CONTEXTS
  ORDER BY EVALUATION_CONTEXT_NAME;

SET LONGCHUNKSIZE 4000
SET LONG 4000
COLUMN RULE_NAME HEADING 'Rule|Name' FORMAT A5
COLUMN RULE_CONDITION HEADING 'Rule Condition' FORMAT A35
COLUMN ACTION_CONTEXT_NAME HEADING 'Action|Context|Name' FORMAT A10
COLUMN ACTION_CONTEXT_VALUE HEADING 'Action|Context|Value' FORMAT A10

SELECT RULE_NAME, 
       RULE_CONDITION,
       AC.NVN_NAME ACTION_CONTEXT_NAME, 
       AC.NVN_VALUE.ACCESSVARCHAR2() ACTION_CONTEXT_VALUE
  FROM USER_RULES R, TABLE(R.RULE_ACTION_CONTEXT.ACTX_LIST) AC
  ORDER BY RULE_NAME;

COLUMN RULE_SET_NAME HEADING 'Rule Set Name' FORMAT A20
COLUMN RULE_SET_EVAL_CONTEXT_OWNER HEADING 'Eval Context|Owner' FORMAT A12
COLUMN RULE_SET_EVAL_CONTEXT_NAME HEADING 'Eval Context Name' FORMAT A25
COLUMN RULE_SET_COMMENT HEADING 'Rule Set|Comment' FORMAT A15

SELECT RULE_SET_NAME, 
       RULE_SET_EVAL_CONTEXT_OWNER,
       RULE_SET_EVAL_CONTEXT_NAME,
       RULE_SET_COMMENT
  FROM USER_RULE_SETS
  ORDER BY RULE_SET_NAME;
Step 2   List the Problems in the problems Table

This SELECT statement should show the problems logged previously.

COLUMN probid HEADING 'Problem ID' FORMAT 99999
COLUMN custid HEADING 'Customer ID' FORMAT 99
COLUMN priority HEADING 'Priority' FORMAT 9
COLUMN description HEADING 'Problem Description' FORMAT A30
COLUMN center HEADING 'Center' FORMAT A10

SELECT probid, custid, priority, description, center FROM problems
  ORDER BY probid;

Your output looks similar to the following:

Problem ID Customer ID Priority Problem Description            Center
---------- ----------- -------- ------------------------------ ----------
     10301          13        1 no dial tone
     10302          23        2 noise on local calls
     10303          33        3 noise on long distance calls

Notice that the Center column is NULL for each new row inserted.

Step 3   Dispatch the Problems by Running the problem_dispatch Procedure

Execute the problem_dispatch procedure.

SET SERVEROUTPUT ON
EXECUTE problem_dispatch;
Step 4   List the Problems in the problems Table

If the problems were dispatched successfully in Step 3, then this SELECT statement should show the center to which each problem was dispatched in the Center column.

SELECT probid, custid, priority, description, center FROM problems
  ORDER BY probid;

Your output looks similar to the following:

Problem ID Customer ID Priority Problem Description            Center
---------- ----------- -------- ------------------------------ ----------
     10201          12        1 no dial tone                   Tampa
     10202          22        2 noise on local calls           New York
     10203          32        3 noise on long distance calls   San Jose

Note:

The output will vary depending on which example you used to create the problem_dispatch procedure.

Step 5   Clean Up the Environment (Optional)

You can clean up the sample environment by dropping the support user.

CONNECT SYSTEM
Enter password: password

DROP USER support CASCADE;
PK&(zzPK 4}OEBPS/img/strex003.gifPKROEBPS/img_text/strex018.htmPK OEBPS/capappdemo.htmPK