From a3a8dbca5cc190caea36c5029d83f02977b6d149 Mon Sep 17 00:00:00 2001 From: Carl Yeksigian Date: Tue, 11 Aug 2015 12:01:48 -0400 Subject: [PATCH] Improve MV schema representation patch by Carl Yeksigian; reviewed by Aleksey Yeschenko for CASSANDRA-9921 --- CHANGES.txt | 1 + ...-3.0.0-alpha3-8bd064d-SNAPSHOT-shaded.jar} | Bin 2209303 -> 2218913 bytes ...r-internal-only-3.0.0a2.post0-96883eb.zip} | Bin 229078 -> 230630 bytes .../apache/cassandra/concurrent/Stage.java | 4 +- .../cassandra/concurrent/StageManager.java | 2 +- .../apache/cassandra/config/CFMetaData.java | 55 +-- .../cassandra/config/DatabaseDescriptor.java | 2 +- .../config/MaterializedViewDefinition.java | 93 ----- .../org/apache/cassandra/config/Schema.java | 111 +++++- .../cassandra/config/ViewDefinition.java | 114 ++++++ src/java/org/apache/cassandra/cql3/Cql.g | 12 +- .../cql3/statements/AlterTableStatement.java | 99 +++--- .../cql3/statements/AlterTypeStatement.java | 10 + ...Statement.java => AlterViewStatement.java} | 23 +- .../statements/CreateTriggerStatement.java | 2 +- ...tatement.java => CreateViewStatement.java} | 95 +++-- .../cql3/statements/DropTableStatement.java | 21 +- .../cql3/statements/DropTypeStatement.java | 2 +- ...wStatement.java => DropViewStatement.java} | 45 +-- .../statements/ModificationStatement.java | 38 +- .../cql3/statements/SelectStatement.java | 6 +- .../cql3/statements/TruncateStatement.java | 2 +- .../cassandra/db/ColumnFamilyStore.java | 18 +- .../org/apache/cassandra/db/Keyspace.java | 20 +- .../apache/cassandra/db/SystemKeyspace.java | 72 ++-- .../org/apache/cassandra/db/WriteType.java | 2 +- .../db/compaction/CompactionManager.java | 4 +- .../db/compaction/OperationType.java | 2 +- .../db/view/MaterializedViewManager.java | 241 ------------- .../view/{MaterializedView.java => View.java} | 177 +++------- ...lizedViewBuilder.java => ViewBuilder.java} | 31 +- .../apache/cassandra/db/view/ViewManager.java | 271 ++++++++++++++ ...erializedViewUtils.java => ViewUtils.java} | 4 +- .../cassandra/dht/ByteOrderedPartitioner.java | 2 +- .../dht/OrderPreservingPartitioner.java | 2 +- ...riteMetrics.java => ViewWriteMetrics.java} | 4 +- .../cassandra/schema/KeyspaceMetadata.java | 52 ++- .../cassandra/schema/SchemaKeyspace.java | 288 +++++++++------ .../{MaterializedViews.java => Views.java} | 88 +++-- .../cassandra/service/CassandraDaemon.java | 11 +- .../cassandra/service/MigrationListener.java | 14 + .../cassandra/service/MigrationManager.java | 62 +++- .../cassandra/service/StartupChecks.java | 2 +- .../cassandra/service/StorageProxy.java | 60 ++-- .../cassandra/service/StorageService.java | 18 +- .../streaming/StreamReceiveTask.java | 15 +- .../cassandra/thrift/CassandraServer.java | 19 +- .../cassandra/thrift/ThriftConversion.java | 8 +- .../utils/NativeSSTableLoaderClient.java | 82 +++-- ...zedViewLongTest.java => ViewLongTest.java} | 14 +- .../org/apache/cassandra/SchemaLoader.java | 2 +- .../config/DatabaseDescriptorTest.java | 2 +- ...aterializedViewTest.java => ViewTest.java} | 332 ++++++++++++------ ...dViewUtilsTest.java => ViewUtilsTest.java} | 14 +- .../cassandra/dht/LengthPartitioner.java | 2 +- .../org/apache/cassandra/hints/HintTest.java | 2 +- .../schema/LegacySchemaMigratorTest.java | 3 + 57 files changed, 1560 insertions(+), 1117 deletions(-) rename lib/{cassandra-driver-core-3.0.0-alpha3-55db84c-SNAPSHOT-shaded.jar => cassandra-driver-core-3.0.0-alpha3-8bd064d-SNAPSHOT-shaded.jar} (87%) rename lib/{cassandra-driver-internal-only-3.0.0a2.post0-2429ba3.zip => cassandra-driver-internal-only-3.0.0a2.post0-96883eb.zip} (78%) delete mode 100644 src/java/org/apache/cassandra/config/MaterializedViewDefinition.java create mode 100644 src/java/org/apache/cassandra/config/ViewDefinition.java rename src/java/org/apache/cassandra/cql3/statements/{AlterMaterializedViewStatement.java => AlterViewStatement.java} (79%) rename src/java/org/apache/cassandra/cql3/statements/{CreateMaterializedViewStatement.java => CreateViewStatement.java} (78%) rename src/java/org/apache/cassandra/cql3/statements/{DropMaterializedViewStatement.java => DropViewStatement.java} (58%) delete mode 100644 src/java/org/apache/cassandra/db/view/MaterializedViewManager.java rename src/java/org/apache/cassandra/db/view/{MaterializedView.java => View.java} (81%) rename src/java/org/apache/cassandra/db/view/{MaterializedViewBuilder.java => ViewBuilder.java} (87%) create mode 100644 src/java/org/apache/cassandra/db/view/ViewManager.java rename src/java/org/apache/cassandra/db/view/{MaterializedViewUtils.java => ViewUtils.java} (98%) rename src/java/org/apache/cassandra/metrics/{MVWriteMetrics.java => ViewWriteMetrics.java} (93%) rename src/java/org/apache/cassandra/schema/{MaterializedViews.java => Views.java} (51%) rename test/long/org/apache/cassandra/cql3/{MaterializedViewLongTest.java => ViewLongTest.java} (94%) rename test/unit/org/apache/cassandra/cql3/{MaterializedViewTest.java => ViewTest.java} (78%) rename test/unit/org/apache/cassandra/db/view/{MaterializedViewUtilsTest.java => ViewUtilsTest.java} (91%) diff --git a/CHANGES.txt b/CHANGES.txt index c688615d10..b2132609a2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 3.0.0-rc1 + * Improve MV schema representation (CASSANDRA-9921) * Add flag to enable/disable coordinator batchlog for MV writes (CASSANDRA-10230) * Update cqlsh COPY for new internal driver serialization interface (CASSANDRA-10318) * Give index implementations more control over rebuild operations (CASSANDRA-10312) diff --git a/lib/cassandra-driver-core-3.0.0-alpha3-55db84c-SNAPSHOT-shaded.jar b/lib/cassandra-driver-core-3.0.0-alpha3-8bd064d-SNAPSHOT-shaded.jar similarity index 87% rename from lib/cassandra-driver-core-3.0.0-alpha3-55db84c-SNAPSHOT-shaded.jar rename to lib/cassandra-driver-core-3.0.0-alpha3-8bd064d-SNAPSHOT-shaded.jar index 385d9470023f215535fa2597c3718cae6282f91b..fc5d2f0c561a6963cebd835f03ff7e55e8cb32cf 100644 GIT binary patch delta 124230 zcmaI7by!qi_dYB`H_{;8-Hnt=cQ?|~C0!#e(jkMCbaxHi-QCjNAp!#W8-~vl-{-pC z_YXPld#$}{uYJzUz0ZJ-Lt(POVKC?_^008YFOZOsUKD-gh(i~GeY(iKhF-$ep_eu5 zr%Qr7j17<+k40cVn~f=V-8XfmJ|H2ZxI7m41_24`nUh-;kWar9 z%Ey+JAPjbEinl|RGU$u}{BURt%P_8g!hyJ<$3j$iLRi)8D)NX(-QT?c2`^r}D1`j` zKPl8`0rJqG@&3W!gkHHYp-F*abI`~@9AW^(ghWgdsH6r{>gftn<;I0d&=Rn`kZRbl zFz}w>8Zp`;N~pYw8I|ct1oBrUM*@+llE19sleT&ZRm#$-L4qoE_98zkt*Mez))bqd zz(V1d(kM_k;oJ%*;lX?X3aY|k{Kw&wq=v-uI{+&Bm#><0t6@|q!r=q+WC0VDp-zre z^VfCz*|6w5B2x{PS24oBl|Zr@{0Q@J z%?cfIG?1|lqy%(hIM4_Z#Aum8l{%2s@SL!OD%NaF5p(z|a<=pTqd~ro2S&L!7gKjNg+ZfPlv2_~3L47&>_9jHiUa5G!Aw zy+OKI2;5}{qx&4`#StVK4s&u$u#O`P)&F8(XBfeM%>P@D)$Lyx)Krcq%yU13A;S|y zs7t6oF9=5DzlJkkU<{tkKS@Di=8)34M!~%OFD(5DyN`kC_zwoYjfb&-?kuqJH<*vl zu{d0#LwgDx6l+QT64YoxP6p;khbeiMsu(}tM4(bikhmo|5|}FshVQwioH(dVN$D`y zAP!4%EHF3^#_oC6Q?0=&MKGjdPZ@xn_F+z;YY#TLgGqXh2f7?sHst7F&Ig#p|G_|? ztRQ|6|H6no3;wmaUxG-i$dSQ77y#3=Bh`%O@SX)AacfBM0C)i6Khl5p@_(erU_~TA z>+@1VI*%Rv8Uuhx^b{my+cANi=m9>@3kwnQfyQkhC1hpC_y5I?BkMs9#BRO3I81uww*e;@?@f6PJy%oO;?`@iRa zlpG*I>Iwaq*0cG)!T_89Yb(hSqFE>c`0#&Wu(a4efq)#j;i18yfG@=XoX|%B2(o0% zASVj|JP1n*fC|ENB7X^XQ3KdQOAKDu1av&#P@ireAQd$L0*Kuia!*?`2A~7=)%7)T z|F~njU|cu}<1VRG5~#{lDh6zZsL;>_NC7(2XlOGxDbxCD>FKX&I>&B{b7l$Hxvwu( z(%XL4DcT<_RQ+09+lVy=Z|_f8uno|DG{Y*n5QN5wY=bB`(L_Fc3f4lPm*i{^U=OX&Q{lps@5PR= zy_#g{URbT;bPd_EX2nzN!{3|h3itFjiIb3%+MBviiI)q;L6y&PvSGymeksU7b6~}j z>+M2|9_2)Elm1xu#jx^H!kSz`QEE?RdK5>%na`s*Nimp zMaYnL%ahd_$2u%eMZ8-T0cuvFe6-L%-QP)bE>dIA#isinCyVs$Qj_8{_q*TOo^;2^ zn>-np8e?{@(}Kst16Etk-3AI860epTj|GWHe_ndDV0v3{t(u&_?lbSfbDe?3w{T_J zFLhk~I@$Yzh!Nhd{kcb(84yF)fZU&$Ljz?mb;Qt1H{m{|^|6(yZ z^u%tVJXEuhHqlaYfy_bp{UxUj&+ zP17lbldbTyD)5pLGYwnr_KR(inL(7ix%MDnP35cO1)z=kacv7W&Md0j-8J>_+IkVZZk0bz2aI1%QC!;t%4}C(^egej=i9FJ2TO{m&;-5ae0MlH^1V_q5%y zL17kA-qU4jy=Tu~@canx2fg2oz06Hx6LIJx_j>>Q0+zCjlo)I^Gh7uYkqFP$N4|XFeBZmfI2U5NqIM#f*1#Q z*3HwsVrO>8aJ<;o301^!Opcjj=OPmS${YPnCeS8g(RL0`p`gZLwvSaybOH-spfSxm zng(0IHnEGh2d-;|g2Myt*GR*NyATfz92NHFX0lbXAz*gejoG>e3(;Vxr+Ol%`dRpZF$J%%Sw=!-33}Ir8j2VT=v( zELTLQWhzXn2);HUzJ0=;dtF0m#oNUVU0EQJU3NTW#DyzXHvc6=I$UX>=f&tFzuV+F#iwk%i2O+Mk$zt%U^t$qo-umJ zg?4XWNAtBlsWhH_-cghiM`RhAB=ek%{(SX?wKVuhN2yXmF<7A7ILUi7Q1PUkK)Si3 zbi|}E&ijr>;{kK9g<&Ll`*(c#9R>~<144O3q$9r{y3x90^H4Q z2Wz0&IBac&_(*rRPTA}i+L_f43f*pK`J_4-3ZWgr#}_M6w6AGzk<`v7sAEZi_IA7m zVrp-{_G)*#2NeA!GDYXVJbh)88JfE2nyT2oxP)KmZr;A~Wm&BOUU)mNXkx2|_iawz z7RCzW!6_BYaMJ>*e5vAPQk)l7a>C=dgHUt^_E~3Z_3twr4yo~i4gY+qGob)$;kIZ* z3wV)FVLAjB1bLy-`z9Wkq)imUz?ttRM_DTe{pX%2F`LL3i`3d8K4F1=*EideTo&6t z!7tBPEmI^pm*hiWvzl8J2y-XF%t3KtZ4Od12CJQDKRxYwKj(fO zXX1CnUs4$72g}|5wUOZ@q0Au$1WsCX$>A4@2JZ0U3N*5i zf2h2s_6_gjP-=rmLcYUZ7gB12of*>pq<4p)!u%dHcS%toIQ)U)0}`hX(uqWJ^(Q^E z7YR|Y0MQjh;i0>swl$}E*BEIg^DZObsg>qI4@r4V*JlzD>t_`g1tA_3XV>`Xp>OxHT_z|qA0+sflQ#et_c*JKJp`1m1dUCk*-s}o(!z= zq!i<`x%pO33Fbu#$m@^9kz#RNpst!Z=j1-JDSWA;#oDNgf?PW~?KCX0l^d!TK#UF0 z$IqN|>8Sold{ad@Om>l1&VK-E~Vr(2HYP-UE2 zpGpb1(m*PKd-GTn@J=XBE}jKnF3A?}80!>o0z-wByTL8qJ9sWeoimshU}C|&MM|u- z%e6Xb3xC);s?lC6EWWzTj+=L(hQyliJL9UYZEsg9U@3b8E3YGTwOLBQ9u2rRn#gVS zjh?6|a|Tbx!{biGKmms$8BQ&$VM&ExevIE-kIKcAH9=K z{Y~Gbq%#CW8`f$qTQ4Rx%TwTd`)-CR=rGmUT^7VLA#;>DrhjEcC}D@AqxmamzB(qg z(NF4tL1gM&hfuguQ5sGE5UT@-Bc%88t42XI-;BHsD0*jsrihL7WPzoK-lB}Oj<9a7 z*{rw?A@Y-Z4`XC;^GAm^N}UeUqhzr}AuD65H-cg|LfBJp(eL5w*|rtjDui82B`P$| z%#skNejL0rkd|W$nPi{fBG=|6DLvFP>f_CYpBww;hR*aOCiok%(e)=`;1IEsdFCikrV6JxJjxBPlN=>Y%s?srr*nT4mE zQ{_FZmHvErU!=O_2mWAZXB@%<(&G+1EjcxRNLVrlU2fNSuB&#B?To^Ojf4_mrq$nKHOkjF*0DF`8)k^H{^P-!P0B}s=(;J+rKKl1F zHM#YL=EBopbW#z+Fa;Va}+KES6?aSwZ4@OBv)+EbA3PBm$vZMGd?v52CgF5e<$k3GZws>+h$0-OSmGb6XDzVX8( z$op{ve;U*Cf#5*}#h+*AGm#6o;gwm@T{z^^2hBCnQFCK%z9|yu+hzPwCH`jGe7Q+; z5LZWwAne>{`vx-952glzvlFVA0Tl!Axb3xpjx_D1 zf$t^!E#TWz+VfHVh+f_(#{n|~fw8_H;s0XYx#2x{U%q0xMSAHp74MMvQL9zZVm6gK z=`7EMPW{i9cpzalxTg2x4wE)p7DB|W5qWFC$>j0^rz>nsOb(EGi7z^RQgLg6)Sg`0 z!&TCWYX86N$ky@vVAF?aCf5G~0f@GUhMr13j>RT1*1~)e$_gd=_)1*#Z=X8#U!KYr0HdFj zrvNQ|#0U~&5KU7A-L;cTc7RJJNzR01D{C!{pL)_NT9~MDP;QjvA z(iYITiTMYWRY9ve!Kygri3?N&Z?W?U;xHrJZK9PqS(j-ot(sUUKMa zgXjO$?vx$bHSE}J{)6`xhBcMt+zY#a!9#PWQL2n@rSBW1^)LJ}ZG>Gp(lwz zvo>lEe=c?czeq#y1%AcQGM5Oy^M;V9&XO-F$b6)HYx?N6MO$heCB?UqPmagG6A`Ra zXRj__KP(26V-@ZIC9s9euIiR&s9Tg!pJTdLVKm?Ft=^z)P(5Ux`iL8Xuz)6N5zQ%o zV4t2WzMlr;ULm}A@#}vdEYEzRmI0ABJ0 zgg*1dkjGI{B82=&_5(cpn-78_BcA}IuSYiQTfdI&0$}qs;Gt>4T>}@1~4$8ee zQ9w^KK@P4@yiN>&`Pl^ou1^GP{2%Ke3O9%j1qk5&Zyl)9u~YyflvezgF)(`$VCUcF zK@{%f*kJVnK>Gik#!w2#d7k!9KA<1&5MB%Yt%s^YaHi+EXHq^(=ebl!-Vknf`T+0>8YUQ`Rqz}hgvVk5WqOgL zfCjzEQNb&hfS3QQ>p??b z_kb<>PbMJ95ECID3)4uJiK)qi4!lY$_`Ffasm z=h?--p^PxtEhv<*G5Mr~1MUom<$pfkfYvMsvUnT`u#3-rS?5rho?<})izdP5p#Lj- zCJ<&YgeYdog}wT}7$Rsr7}6l7)v!0utq3Ka#j9bdK*mCd2;l5`SRk~aKoHuU39Qry zoA^&FLrx;Vm_x8V&+$rNqA}Hvz&=y&&`?KUQNTB2u-~BF_Jn|2XJONxdk;d7Gl7i5 zAoq#L71)Po3((buVEEwSRam&^m3`vk!RK4B@1Hj##K`lkzDd{S^8c3r1;h~n$=DeO?(V-b_KSdI zT!sf%|DWR{a4Z!Z?SG{Q8KQuXY2g^47?cNxKu922Mz~sNuuu{kOA-za%*F&~``if7 z!aQLh2!;TjV1dJfdIj&Z!ZkqK2Xc}JIZKj+!vU}J!;wN~{Ra{ef(wJ@@NWn-77e+f zqKd-}LGAnpXODs4ByZsQ|8e{d(kBqJmCr*6nGW@W3;Gp9jseD$fpdAz{$BxNgF9s5 zU@4yxc}fj()C6btJoTx1;7|5&zn>>Sw-jWE0kV&Sv}}(foX>wef}R9mZx=WlDE8EE zPY^nY%nc3&m^!cPFQz}mKHQ$L5T0IWjRS66j3?z+n#v!`lO+*$rXE%_7uHPKWUSw0 zh@;hxU(U}_AqCiI8L;WyWroYgWtAmmaljyoF)Lu>fDst9X;~(SJCe zRTyQ%%aiPWUU2WP!`}VsmEYQ(nA=qjIpPExQD@%<@TbnQ3=6^VuIYGflsUF?FSmX7 zfkI0eJufyepFbm_2#o)(@>t=xNJpP5iP-)~*o#gytwx2hkeOrm-k!p-#^?R@>TnLvUcOd2!u_d`Ow1N7SsM=%i^3f-GBe#b{jQ>AnH z>(DmbBpKkJVzV$>S|E9MAvTe{LODA+$&QVU=SoqEq1!SidnI46q|{e^QGXhqxVy;c zxv9m|{AMS_&%>4Xoiu8T+@tHCO~+(mN+~IDT&K zdX)CrRhu72A^Vaib!jKj$3r0eO=G}df&TFe)frBJ{K-3GbXx@rc=@-II+U`7o)+`I zV3tx~IbreAmS*@O^^B3_xutRzibVN3p(Cqy*YRQj60=BoW%r3g`}MBeW?v0+oZR*= zq;k;7amc$pL%;j6+xK?<(PO(3bTRq660a~#3NYD@t0%vVY8G~oy|Qo6j{3FKK(0Sy z)gD#0kS~R3XV(vFGh@FcarF!9B2hSffbB~Ma8rFyCv7OVKRW+gTi>w9PM|ApZB3)^ z&KM(azaUG44`(Mn8LjVu4320LIRB2*KBL`yi-BrZAkg3P9qcOQUW*r}j>a8YSV;e8 zjy#4896RQ$JmWld+FYVKq4MqQu`N%7gV&Tgg7-{`GU-t%9Vb3Gjh=K#@--9uJb1(tTcy)q;Kmq!)kIta zB&+H;`euaRCx!4-xd-iluK#QtUzNnLW8Y-BX4u@9VeBR_M3EU z*R_mB)E%@}j-5Am>)rbBvlzrt_2gJ2EjXm`x>Af@&6xQ96A_5$Nyq{c+JZVpxGPN7 zi=G6lWgU-iQKCsj^E1ddIJr_eO;C=icW1|D1}#Q61>dR&gVGE2w0(=^G!QjWlYnD_ zKCd-Gsu@0T)ONgVnKnIqFH<>W&r}t>MLgGI2NP6-g;{>RKOAx zw)rzwd7Qi>LUd*`J|Xu@^Qg&mS3U4LGD>m(kKBi`_c$84UJTt98F!c4Q%S?Ov6$pN zVSb;P#2VCa26G9tJwrE@m3rdg)cqI>IEajT6HQa_;ZhvrH+b{T=ix_DL%HG!Un%#I zhyM&AkFuzCrrV0CNsKC~a?TcAIl^ZPgO|P$GWxZ_e~z3G!N{Irnwy$}FA)!9!}9yq z`R>6Q<(9@@Uh)X9DYu55XW~`;#d0xD)w!?%v3tDFU3i-DaN)B0kv+`&I-w?5-pA5T zQ2LpDqaPiD$O*SfE}BHkNW+S?krfmFpOHAG52-$H*OMd|oR+$@I~LXce82^!fD+AH zKJ@gxr`YWNzQz761tr1EQ1uK8C}*yTz}hf$I&5ybH&Y$bZt`i=FN~m=s(vwo?&T%? zL)j1lu>WB}Bc+dr>+gE^-bD=Bt5L>=1xNi6bksF@n_B7*6?DtP1nRDWMY86?^wo_& zlk-hXQmT{)-oo>|dap)Q0{H8I5hsxx#mGTVy06|#NXb6rM^&Uu+VZtoN>!xx*!*fTscIeb#WpryH9l5l z&PG2P%l#fzU{|C8(2j^WGhG~`&Z5S-K#S=7aISm!GQu9^y6_~jKeVXCjnk0Zyv9C7 zBRmH3hdpV1w^~jE^mn$`XUgY#gJ&33*t;GB9J1W7hLn?{y zwZ=rHG)TB2|8J$jy{rWOp zfQ?fBI+qQNIk!7k+Ak1jw`hUyQS%ebx;TH=k%p#NXlscR|i0Y79M$6S-_9i;l=dvP6R!tvo|4?v4S zGM?@Y@YmqM*iz(QVAz?a1(k;1Y;pLuY4?Z0ty|}t7Cy5$<$hW{ruQ@=aYWG`^obnJ z^k2VxaFvMu;LJXpg(}YbfP#!gv{MSO6?ELzkDK;xi@v&szlut8WJ>1$NXC#1&Q$V? z+-gwr`!d~rVI-aV9jkarxRW?Y$vm) zz=R9@J)lf`g6O=tJd=6d<9%o6r?rppBIHySd1)J0u^U~~0%-ej)p9=z70-;K5-q-Z z=i0d(#8nTKNe{8br0q0NDzBYRh{?!{#BbkCL}9IlndZ$Gi~>KTGSm>St@aXf*zDV-v1^`v=3L^3;B8o0K%e_=oUO zw%j0Q^Mr}vTS=aYH}2rIEpYj!2*^4{{{9T^raH#wPW3l%D5umlyI4vrl)Qkxug1O)d^Dmc$Xb$M+WGM! zl|A$O5`v6~Op{sd5$Df~B~RS`qCICD0f};Y8p$EvLQhx;PnJsB*QS&<0g8{YAZApfE>NLcwR7~E<_kn2 z@e_eCvF$?a`-5Gv%>l_h$@CZLG**9g!UV&^kT1k`CcQ8>N@(}qTDYEMvc0{g_hKqP zP;tXz6DQ%b*AG-Z6jLlh4n0O=6_EcSYon;XCLm4JMIYf|9H^Etmt`ATw~e61VOe38 z5N+`D6FhKDvG%HHkG$5QaDdP0<8iGbZn(tc-V&)NGUod^M8&&)z6d%PtNYj9J6@t< z;^d^pNN*fhvp7UcUT;Vx<-$QM#isw$3y$ZVMXgXUA$z;OU2R1@D0$9^}K(-G6uo{Y3qP_nxKG_k3POd z6^*+(S2TVp;}zSZg6oy+=ri^-_iqqb)9H6>xDjTpg4*=tOt0)^AKp~;RD6wJaM8zZ z*I~H;9(jecJLO|7r1wwpv}KVsxzB_WraBsma|0a=I{Y3Qy(ZpxcI<@${&ODBm+@p-GxxI#$ zjO+M$xt8AIAaUl}r6H}|dfzdmUA&>|zI6R%98ks8~r?p^lj zoJ``50CpYjKDDajdU0vE!k`~Pl}TeMST3$6qLC>Tck+0(o@gtrweg#;;!QQ$xNL$F z@Suc)a#SeKvDaCZG}(6Ut~!vmglsq$ro=Ly{39%P~gSQlDu<$OZ zI8l|XxcSPVxFA(SJF%ILb+TlN7=;Z~FT3ci&}*Sb=Cm@syje?ouTP>}hLqqErhf)@ z(_4se=_fhf_#~`JW$9Tg+o;WI;JZZ(47;C=Rozd4yn47a5$%4E^tS1KNYLMeF`mpT z*k3mSMv$eo)-PY5`zNBT<7&vQ(%lDcVWacvv#(J<$LUJ*#8USM-G6gXNu;f_`oGiT zWJ--o;ybCxEA%C`WwkaTxFE*B$~v?S2wJ!)GAkG>7n0jk1snV}@LrZ6RSVqt3^a5#tb26<<^5mIh>rYCCm`zH-z#x}InQ*I{ zMJL_WS4daE91>m@$svn&&gmFvXNvPG-8h_=F9q}1NJH@aaK1(}kE{Mjc(51W6N2w- zp0BthE56gu3GqZO)2nYbs;@`aIVdv4)*s!{y|#a%p7;E}1b@bS@gk1yf6g>PP;zK4 z7>)p_gY1htOZsPZcVW~L$%L}=MHiKJwO~@J%p2nbJBnD?YDz|{S7D=ATKvsN$96Ab z>VHktFGbANFTJ(3t(wcOrNb(kTV-lxnd)AeGx}BitFrDo_b)%Uu+J#ltBv{h{@?u0 zM*sSq0o!gyeU9r8$pDq{*IYBV-8rWwsxNcC06jZISP=VY7EdsT<9$+HIf!3As)2Jq z8+4%bsrWYv&@}~_TJ`*B-g6nO&{6GBiDO&t=q0o)p5mm%a`lLa>s^hdBCpL(HqjeG zWwt|;Jk&=x;#*URDOes}-+ThXa4&S< z3KS(Kj2_&~$mFSgkHMfe6Y4^Ib$#RYyEWAVz0S?c*MOmTAe$s+uLpKowOUYPa04FaK7A#-GKH#JZq z{FAhFHzk-5B?OOmG5a(?oJZW`1ZntDO=HrgJhRDCT%2^p9oJN_dkU^pjO4Q4To;A$ zlrN`IGV)Y?*gKE3q|%^6(kZDUkQab&TjmBxvMGyz*RU<4gJCPHljtx&YN=E0cx^DC zPX?p8w9FUURD>IiR|~?uw+Qz}dj%|+PUos)!mT9IKi(HF_;jb!fwv?DUvZ48;%i%D zmUkArt#y>4Lm}|GGQPM{C=7e!vC);MGaZCF{Dm~}f;v8X2MK1|WF?t~)2LISM-QEN5c63MZmQYJjw1J+H zNC{zzqFL%5a(^YqeUU|MuE%8Heb0oG${`87~!h~#q zXtcgy7d;_ULQoz4G9ayx~0{ouR;6RLSM&;_w8uQ}a zc7@R)Y3@+VZ^`0qta%$i0b~;$e%=VQU-8V;TW_>{10-bhqP5tk(PS8mBZw5sb<+kp zjz^jz%tN_>rtdPAUZEmvZ7t=SYNZNt(_ef3#%>#7KKWySDBeV+rGO<*uoJhyFp2*| za5sYQ1Aj$20y>xOw=(kwx?RI~#Qm9vCW?d~cf71Ch5?0d?hmvXfUn+6`NyzCAl_jG zmHdt`O^vq+qm*~>M|xkkCGnbNU6f>JZ*iUof6pu|DVP+wxQyO}YToWOeXYTIHo`{U zS@>04$TzqYh4J@GU-}6{$AMVfv)JcIWIN@LWV9cusRyj5c?)Ro@)d-zkcYp}6Vtoa zO!M09LQ} zqz`L!wU0EeHjV)MN=X9N?rsG*5rWVWz){g8GtMG)ID zlTdlT-B6@Ov6LpUHp%*ml<`O20k3z# zY4buNA}3=rK6KRseY;m_Yw6s3-x9b&SZShZt@#ZUiwrb@&G^IjhxuK0e1ByK!irpN zug2K^IJ@ADZ@w)}F|m*K(nZ!7{Y2_`9f&DXO9PNtfyXx=$;w8p=>n`|h zBp-u>04>z6p>N_#jS^&3S!l*IkTTIU8A4w4r4)V0`a`0id^n!jMqMNp@f%O}adD=d zqG&vCPmTPiFeX89`&E_rV_9IrHeaC&Y7tOX4fyh!fc_e!0<=~G5)Iu+mP}S;-IVA0 zv(|N<0Si?+^a|aG;fwu5=Q&HA(mRR``zyUkY;i{W-hK4TUQDy9Z?NgF8sv2(DJ%c& zT-$sYV|FCa6pd{Mvn-1Xtt7q3M^fOD98<a;JSb$st+r4|sLm@i8WoL{nU_>kWJrUNz4a`UKlMOl z63Ne=Kbbh2YW{H{@x|PxR&K8@JfoJhjDx~|sYna#cj);6Fn zxFN{g`TX_3p@sVyO>n22i=-aErDUcCsg=Zf5*PWYXaNp$teVFcem zTK@Y))M7Tfy~pfS_WRTh{waQv=Bf9P?t^fE3KKP}alXvFO{c(`Jx*5FMIV*58tB-Fd@ARm-yT7j(UmKsgLd^f;Fu1nH zzR%>a)h6cn$3Ohxk!wSYOw429WqMSrFQ8yQ)<3&sjyK@D!usCMFXRW835FqA;6rrV zi$l5#`nGho?7sIz><<}wnw9#LNHqryqaH!-zdtc|Ob5wD}=f1{!dN_e{~NSl8rzWi&@>-+kqY z{=rqcq~!~8Wfhl3wTr0|_e8fP6|l>4!&@u7dBN^=PcZ}Ho=WUq6~e{YX)hyl zh8OZ|MJ9`Wkg3PgXTypy9&#R2{shkIX!XxqrLlV4x||}2)+_l@VByf8reB=-S*Fca zL{MmqI5`zPQ^THt%b#$QV&6JP~j$Qjy4^SMyU>jKx$zfse) zice>{1sd|`=&$y??`eH+HM3x`Fo0i0TMQ)Uc@QqY=Qo;8nb|a_8y*OYEEFhti(1le zQ3|8Y#gAK00|z__xExsFUF7GGWoD=P8L;lNd>8i zX5SW;G1&wm+i+)<55T(7u1UtLv<5F_WtVeF*i+8TdS#Ypb;(Jc(9D!E9qQrCTnBqQ zllixTz6cW4rK)%x&(+Z3{9bYUrE->e^TPl|lH{{7QR%zGlU~}tkoSXLAtDXlRc4u^ zzj#5x@IT+upl_b8#gpTL0P%49(39zZ{WcK|B$NO-v!zRbt9^c#1S(h`heHP=rNNm# zpW{M*oA`tQO z31lwUF0x=QEMG!n*SO%^$XLqow&o)TDcN~8zmBkRlg;@F0*svSi#qZlPa&qwK3uyg zA4kXW$Mh?)z1lUP?;jLnm>5EQx8|(5hJM2sV7r=av@->XCYUtK!9`gBfj^2ryWaLQ zD79-Vz@&*HfRm@r&%Z-6LEp`-q>0_Nq7c5c$$nl@;wNYkl)(}#t*FLTqn zgM5#n*Ta$*aLt>7p_=UFctqIgWVG*y=>BeKzKJx+2BkE98dMSRAg1XdGCxhKud{zI z0IVRA;$pA<5y0H=B7}1Hgvh}=1AA2%xz!-<=fW2gqE+H34<2|1UB)CY(Kvrv z1n$9wTKqH%rX&vTt912uY7)=7zOtOAxhnyNm*%t-5I%ynA>zd)Iv!Z%3Y8|8^TDG&@jj`s}H>8wV-qd1)+cax838;|+6;F|6yyMh%r7Re5`rI5^O3LTQKmbC}Yc=#7A! zhxj+XVRJ6Mzeo*BU)fI=BsnaxS!X)^vXhIgHqq^3{I=p2B5(0UB~?wN6FWRJ;$VDM z+5-Txz7Z{(8=!hqlQs1$y(#bRc@~a*boLt96Yb z&z=lZl|CpUd!hcYTh529gK1P1+a)d-F;M4~+`RG=k1@oPNDKnJcrg$EKRe|4-KhWH z7U%k>#nlWQu_bUt2&@R4UKY=))#{xr1!`(@ip~G_hgmM9Cl(EtFB*2- zhWTpYm_;67o=X#*sflIC^qB9i`dIF!-B;Z|_Rp)o&|IN?UHh1I@L47}&eK*X07yLC zQTr&KqWE%999g?HVJsj%AwTln!$hGC#hZW%DJT0GT3vLRj0k<$e6oI z1txKoA_-boKPD&c)%B$ncyf{T|H@3n($ajBY!H*iFjMx54ke+)lMW`$5mt>8vHHS~ za75Vd7RgOYJ;!OekA)qEPx-ju{uNk|O>dym=pxSWnC>)u7Jb}V9$&7G2@sp8RL~(o z^`Lp%mE*Eg$?144EW~C;8*#;O$Y9JE1Sc1~67eBDW430gET{E1^P6j6aD^#rch@{p z3c0`#&S@>Ze7}%G52GV?l1foYewI zLiwkm=%5?5N&7D6->$`SF%-MT64TfUpZcS)A>rL(1W{!z@M~oRUhUv4G}NONvoC*r znQ*s~wvenLB*HKK$lo$9aEa~4*FW`t;gtJE&R>r&$keS}fit=kLs8Xl1FpGr`cT-i zKBL=JD`$?NM5vP@;?)bOvGcxZv+fJ!UfHdI!>=3_g^xNRU8QZ4t(2x5P1#HSx~pm) z3vBw0^VCe_!t160NHbN!{%O%px6`$KVea_QkV31P8^7j+qSbntxZgaLBT}{t_~^VfpfA`+vl^( zdsUkU%k7#!ZDA?oQ&0p~R$1Ol+rc{3?CUr7X40q*&dpTxGv-UUV_N77zxUV_! zb@$-Vky%yAEJ|q(YaY=W_gjxU_>cW%;yWbhV=WyTC7;&Jy z0^IE4q?GVzVJJW%gBN!uQf{6Q`Pw~9ovHq}b-igPkuJ6s1KxJ59ET4H7n3$ZcRKM9 ze4`DGQz(_8q1^P;u1swS8~&2}Ty&GAt$kca>nu5U84gNuN`JkC8rq;-;D&f1c-jsr z#179&V)30xM9_w@Z;{Lf;fPj>j~P(pPX|T2-3}jfe=+J^x1pQ;g%!ujSDk|>#J6ro zb9YIY>HLUh7gf6@4W4t2Y>^2N2rdE` z2kXK|Q<&v?Hh95%$A}SQGWo=288;~6$CQhEE3oM`F{HO!s7hgN(dS1KpUO$b;>$#* zOrJSABZLvgJY<`kimS%aoR1QJcZ^-k+(kw+NNIJJ`c!29{gW8}RhV$jpWwGq$WjMN z(hRFhuh1^=zdxFD6!2CH-=7l!@n4V+S11#;tjw)CyICtu{_DGO$!j%w}Aw z7V=I{H%&E&h4Q^Lx0Y8WHmi_QXqK^g)Ox%WP=F-d2h7>+^|jteUjM8XnCziXb9h2# zYFoiYMm~~)Uvk5@lp!23L`$nNs#3E}RGhCAp;)IiL%PU)c52M99hGviYPIm{6g|Yi zDsLDg!}@xT)RLk+E8RqL2p`psPQ#-1(%Vj@U@^I`!@pR9R^?Fn8XlEf$NuA$Q`WbN z%QwD5L8G@kA!y2X#G>7wfZ@`txUA!@+&$-INZZ|*p))^^%VoRy2y9Nam>#{6 zNwSj}l)bbc8~9c8+cjZ(A(1F4Xk866Xp_q_v2WMS{zS2?E$OPBr`*8^{Drb+O=|~b zN!zPDrdOAW4Oi)7-4MT-r^D2Rps&$Ii=5Nc#D|S^%e}il3+}_U+BE`Y)i{6Z$P9vx z;0PiE+EFz63+uUNUjq3(V`EzDafiQ=6k7gOR%wKOvg)Ze5#q(Nu2HTLkW&H5SaV`H zU5RZ|s0wztLiAij>=r>A&G!d=uIqjCK{aK32fGQa-+arG5oQFUG03d{`MozgAcB`ruu zOE*ZTfYcxo(jYN(cZV?2(jW~gNOyM&2>&oV&-;G=dtGy>XRWo@UVERl_ZjzD_dRkA z+pAz^tarD>5-xez5(QEi4pGAXR-^YJZeZ)p>MyZBVjPe8@A#=yR}K?YVpz#|wl~hs zPF`ADXZN2(-Uk>neYuhENrCYSpkI>IuV_3ygYOYV$|(DQnf*W}A`&NI^{DlxTSOV0YrKiy3Puh4$te60u^d0r46+F<5^ z$xgl}xfgssoM%yr=a3OOPF&WePLI+M9(U!zle)9AlQx-c@0($!CVZT0ab0^>B=0cg z94YW)f3fRKZT+<5^&NJf*j0dM=v=@%DrFM1$Z`S&!_MxZU{rdIwwRovvKjj$fciTv z@UM4bT%A;Q2D2>sK~+x?6Zs1RbnLh`}Nm9V+g~-orq&sbBqiq8?>baaCCt7;Fa!qkL0MD*+yhmK;~*f|dgN&$iy`bCKuo04beJ9C-Mn*7zF zf0|RUhk9e%1$t9x8~76CxdlIg8f&GAAl^K&tT&&oHZn6`@-AS%N?kI$8K*}Y?kJSv zyrEt8m~Iv^d>%hYQ1rK(%GDOE1db~jO#Ck9y6*Nxd1h6{adMr-r;F%EpUsYWMTLfK z*Sm|5-F?XGs4z5r?lzqlT@el<`mYOX~Rfdj{<}hmOkX zEWbkLldgLriOxtyCJB-WyyrJ6d!9KiZFiKTRWAV!E?^VJQOMVQiQ59B_gP*T_Ygnp zHjdL*Uygr$rP|CO!>MAQ0Fz-Pvj*;XPpgR+zDl z+_GA!tg}(8<=8&!-jv0CUaOWX_bB;D`qM0_AWmo9c}cl=Ga$JA0PfyrXZSt-VqaB zuAclkSld(NJ56Z5IS#(Qb&a{6*sluA`&zh)Tv^4DaGjgie^@+FA{Lmo6M<>w;a4-U zg*iOYul-DiIo>)Zqk&uQ_(@y($2@0u&C?{e_oIJ89PyKyBWdyiz(WTm9OB`K6rso8 za6}D^&31VM7^l^eNDC0X1k}`xt?b^y zYG15NtP{wY8A!X#ou+b`K9Lye2>JL%>R4JQ62a$tjKD$A4d;R=@}_A^;+(NbLUF~R zprFu#`5M;JrcIMFth7U0ZXPwNR{Xa0`idUfZoaiTYs4L39A(}-$#l>qLDCcIwQ+My zm)R*aw8UcM-zVssdVDl`MT9?SU=XypV$3C3N}_Nd0+vr{kr}(1D+d>^uT{9K;WfZb0!ePfm3Pbxu?up>mi{3YABr$XOeUeltww9h&8*x=mJ zMO_)2N!GlreR8<0%2c!BUJ|bGYIIReHSKT@fAKeS8=8jieLyEt%;kV|&q2H>j+Z!= zs^Etjy^gmTQX^nD=jS7`9Z0sV1)UA*VG|;s#M@&Uo4@9$%K8{5q1;8z}n- zo#<_T6XRq;>)&~p6@Q|76cJL<67$5VNQ2)j%{}Lp$1^6M8tH>?DvfCKq8eB@t27JR znhW{+3$k-@p+*Zop6mI?nB!a=i()e>ig)W_q*b&V_SFEuJ*3*UZ+~D5)9kQGQ6NrH@qJOD ziUz1=dP7U%<9F#_At3eIlRYGx0arcNDK$^XSBI53u=hbfqk_*l&u7*21IUV2xN25{ z^K|D=ZvUyxB!&0w>3Z{w@iCqAYJM_>XM-eu>Ka*|nzka*{cMQ|-z_|{()r@=!>!K; z5NIKJ+`9dv7$K)W{B>(3_l4w#na!^=nAA^!d=kjt-_>!Yc{r zvdSmnS5dgv@4p)k83~;lf*ddq8ce!)ig!C!tH80#=smG>N@;0>&h%fEb@Gq|JU<67 zQ!3tDeHM42DLm7-u@EWpt37?qqT)!WDG?ZqYsTWTO!!3+Dw91x&Q>?IhM|;587+E( zueU~%NK9^2$XLIjeV4*^M!t``uF>!fGpjqQEAJ^ zsWpMC%v;Juh{OsgXp(ANuS$e)38PQws>U4Pzw@>ujWEz&X559P2uGSetoE022_QP> z)0ivztFY3;oHJs=H z{30v6+_;Oe6vt7n8i8K?(keffMs`wZT~D2Dt>t#~*M6brIt{9v?X#uH;Vz~S)Lcs_ zl6*S+9+>QPVQG-|EYqyw>4Ca@yYIS9xtf+(Ir{GrBVA2sXR3^*4Oi%HsgF#};Eu%4 zEMKWq%yS>7=iwuKuRC+i5pvvV;zsE}d9?fGyu)e^M zV_BI<|Ijo(V5*#Rk&vOA*+>SDm@GI%3no+eKM<`46Ubp1%p;3~cv5vd&ASi~p7F-ypU9uWacaDe!;&K@b3+{wssR`tASkCFCIt zNLDHgSSN!FD1$5DZ&zXv0LV}}G=MJBLnu(#ApJlO|N2)6f^rc7LZ8A#52(i1=`gh7 zZ5j*#c}Wa7{XcQR_*2mzw6J$F6o6b*_`v?3F{GA7@+vrV^$fAWDllz4y_Oas6P(#;qdZ7 z2IJ0r0jS4-JK%g^D7DR^;)cFa0mMGGh2MFYi~!1_4%q)Eo(EvCZ1w{%SQ9YwINAr+ zliX|=)b^b=p!f0q!s29w+-1YY=w$_1du$5NgTL7@`d5P-m?HZv!0Is`I8^f}9~Sou zI{=Ka@gYIr80_o-04an#mx=_+;s^+MRKREIUj;g(1@=}9`o|ey4*Z{Hs@?!}$lqL; z(`rwE#s9R0!n7KXgM8qeg}^(X^8@7lWA^}@e2_t4GAw9+z`wHz2TsBjnfWm6uTBua z3*G{bd4$RFpje>*-ABjp;lgCdkm>^19|hrnA$W*zDC@s+GH6ExApdcG{yzmiv@+`7 ziTfYLYz$!evBd)_{2@+w7kE&l&j3+)a{fa<`i}wx^-TioKV}VlGk9nL%}fWVKYICh z`jMgZnE(`cPD8m005CN5!;N?VIzwZN0O*ft56h?AM^`!Di2tF7eb7R^YXE5Qw(yjN zz+`C9)~^6@;s-B}z}X@I0LnfNcnNR+9}&cP765>h{{r;G`-AT)u+{*$99lL7fWeI) z0)ZoK9~4kn0~WM@joBc>Ww6wn-vH=64hNpKQRlEl?6n1$fSYlCrld?l2Vz2E&H=cP z%yKFUXz@1S>pwnVw4zX$z5nCX!9n7%OFZbSYrrHt)Ou2pawi(-sAWm`${*%~`5t;z;ptA-*;GA(p1)(l@LkbtkS&8o5x2&1fpHHDbW17R6q z0t7)a3xUW`nld2Xe|$o1D*wN@N^5{hkFDTD0QM{7iQoExG|;9&VB(_;HYz>{YAY2wG=2og`?%=go5+KE=-p4C z@#7`}vnzUR@n5a@5)j7H{m+Wn5SoLW8Fd!(bM~262gYSi~OB~33JB*w|<`|gs|IZg(fedl_21_@t3t-`YW=;gM zbO!`LNw0yQ9$Ua43C}vHn4wp9z=}s1oZozG69_xjzk^Nl5h5rT89og7yuyrOS>6fr zJcACJd0ZE8stb5P)np)a=p8NyhU$mktOu53xGT6~lo<5S>V#+2LklQO5%@p-LY<$1 zN*_lK$MwTKKkJ6wN*h|x8e9Q|-SWu)P7VZa0xPdYh0-yDV4NBcox|1=8{|a~Y{&e> z3JUrEjZvYp&q4ouGlFk}FbAj*P%muD!s7z{eGCZxRSlL91W-ACP}Af7_OA>J$|(ps z`#(<=c0mvdj4l`?1(!jyL_jdk=7%sIHYcc{1n2~Q34eS#y#)awhSDG`u$?L%?DhNP zlp|!_RMKb#4QG}n0cytjIxQGGO=?cP?s&4cyxO8!xq+SQIP^*&pU#YzRQx6{($k7; zl7*@%ZRj_T+i0ux!QSol-+jgaOM9*%y?Wb?Y5U~!kU!LJdm@_+Sv9t88RHci&9=6c zhV`u>NWDk7URIfDBvWT9VD8kZ@{RD;u(~wekPnU(LCDg%to9$|Is2>lq$T=^ctb3z z0K~cLw`DX6G?uFYFBXK@2)_H=(1&>WdtOF0q;Z`7{GNG{B$-;W77>~{66-zX6E@wr z&A%_)Wl-yx68;pu7e`8}^V#n&bE(!mk#rgq!*deCb?lTrWO=A^oM1UZ*_ywX=#3Wg z`3pxBhjeK>^XGa+Ela8A-WP?w^F`9y49@bB_!>WWN6SC2ci(gGD2yDZ_cC;CVLZQ7 zCm_H&irq{p6phjdZDQPHgR-MCY7mfomfBCzrk31@QXKeH_6f9Bf-W138yguZPT==c zHz}v|HDWD8p0OhYVW(vZXPn_ ztTuwyUuWz-eJ=a^vWkQS5i|~}&TdakTcQ-nZf1%bYO+?x=p^2$%Q5*QLtl7?PV-WO z69_}vO>RkPv%2jH-D5l;vpzX#IR?(&_`RAq~v-_Xh@ww`F?_nRd8)wB06Z zXmyUU+0}|u(bXagLS!D@1?e<~xrWIeobk^-9``03RtGQ(WxEgYck`gOr#O&%nUGX-dLJRWZpC+w@~XPe@pH`GXz*ig1V z^Vy^|*ii4ysa~Jevf-!RHBb=>UoN;Eh;7cf-EYppPIbL*XP;HtT3iL7Kk?6V|Skf5j0oaG0OdMn|{{Qz25$Cnnio~t&Q>YCg%HFv-$TlinJ5MWc=<>hqnvQ2{W@44E7JP z-u|_HS#E>}{hC)+zaP?2rn`51ZW|h8LD4~(xVQaw_C22+me-e$Z1u4wKcN zs>W7l^Sr`e68wy)XZ!*U6T3~Hr!m=uTgT90gTIPD|BqA|L&%7>^Ul|ttoh9GeQ`DrwsHdaC3Wbv7FoC_#XVa7E(tH z{f(BApt`cI<+;kh>e$_fvR+bT;y1mzPY`AkL*Uaxl9`K%WhXAajlqW(GZctosP^8- zf@;nTHR%fl`51y;gboIXuhaRLsSA6kN3LACBGomxg7br95AKP)H%bAY&HGF9*Twsm zPc-M#YXh|+Qd5zQmCXM-A!CKQ*PBd}x!UAhI5Uxty-}vRMX(4JB33{D+)+d$ z@tcENDqXC0OsDsQQcW_FEjOlR=_mEQ_~U^gwM^_hZt+B$4Sdi3uTgGMFZVorg%RjH zv0cwBz*c=3ahc7@vKG=J2hr}n(Rm#&cRl9Fw%0!M2_#kQUrFU(MjV^GVGA_X55ZQM zFSYkbygY^Nebb{HT%6~i+vVl+PyzV}hR$01Bpw>$#S``&&e1=hD`}hyo{v&^7Zn}mT1jhKS8~-yMOtW#Tfk{%eD8}TfG?Tyr z{3t3g`Sd5z&Kkq3d!aTO*CAtPBx_dsRXeHZ-NN?nk143<$rRktt}6=a2E9}JZvqP% z%dZ8H30$PpqB<+sv5!t`{@Srp5>6Z8Ll%pHpfj8M-f;Qu6dlJHbg48I zx)iRg5AfKpiN}F_lCb%$!xis4l&)fhdBCh^uooVznLX{U>g%6SODK(F8<8#f@C+H! zZ=9|?$tXK@J@{v{%o;l&BeRQLVB`+BbOm<}4`-w>eHzH=rDgTo!0*3_eomp{u6#ch zHNpm6$VLkr&hD>xm~FlIdPQcF9*J2pe6PCHpLz9k1Wole7R!?k>5nUsr_5EJyA1f3 z0F}t|C5d3|D(CDSy?XhofnGh2TOA9KvH6#Aq131}=k)a`{v?Mdxm~8>FIXqYv)N*Ps2=l>`3GKpEwu78 zToELsr?$(-y#n|%PcINPaMkKXx;cXT3O;o}-UkNouSsP8b^#2M;FDmySuto))TJrK z|B!e00&G4u3v~yZ7quI=BgCc9Rq`^0R<{TV7M}}C`OCkU%s5CUT5Au!y>D~wA79=& zzbTcDmdv`(?mFU((SNB@DohLfo-pWr{s&Q-g{*-aqw`>7j}g&*K+c0TFrg0|kzdN8 z@s!9Twcm_6nMeXbQ>KTXB_CHxfwRS_A1{24r70>jSgd^>4cYFsoW>v9m+O16)vEbE zBFx^dMK(dQT9qF&V|@ZvOT*8LxzCEjx76s*iWMh5n#|0b7LoAs5Xou|{O+OJI=|Aa z%9MKFEm>Fbwn!-QP(&~kv(D~4*tq`F7wHx&di&Z*m%$3c(iL}~*BD0*^}_SRRx2ts zx~C<2f(;=QWK@gd*hf_O&a4_0pY}|MtPQm7IR#~kZ?K)a$V7Bz}Inws@vAmvcs1k5A%VIhb%slg*WR z^VdsSqaAs9T5+~dyat*6Su6**14|6K4!C@18XrG1dh}I#!JfbK2Jb1Mr0At>3jw4) zbOX*FpG~tgY8Z)8Rx3Vy{Xk-5r;e9%SN-{{KknF+o=f1Q4IM~JR2})M(CC+7Vvvt? zNOsS>b?lzFlYM94G;7tk1~C}?m9Oa}ceF-swosyulzlQgv$XbWm2+&ZVuc&1HSGK%fblj7Z8Eu)?mQlv` zkBM!(PLkO|q$&~8z1Ay6L-R}Lu#V0kn{ApUSGdIQBbBcYzBmFD71D!O2T4n`_X_+X zyP`KLwdHSf#JQz8vc&veBxjB-TAxKk#*U2&xs)zAd2yTCOAKo6ew0KE@%ko(a%irc zJN@?i*HXp6`JEeiY3Z-MZwraNHF!wyE>*eb2yOrVE!p9i7gs z!ve3^*p1|PQ!s0KTJ^@jcw^q?p5{8AD$r)O;{|OBy4=x1aX(VeuArS4FK|-nDZG8| ze}B@fW?RGb~bZ#=VPzjh=i8s%DYctb@^p9fAJ%G>*$`(uu0 zHg$vYcV+#C?Zd8Y6(}~H;hr7qSfDw*X8FZo{;b(utP|v$_uocA;O`3^y@0c$@xZq$ zKA(`jh1)R613o#fCxz)xa*E}M#m^?9IDnwmj9)fgn^|(ivSxpx>;vX1mETUeHB;xz z_wN@5U95DX9wT&r8FM5_I_-B*;tRZ@iq-HzV2wu6Zpyfa$_v-vssxA+pW(M|;)AtK z`G%wfFA>cl zDI6Z<1b?7DYDPgo<3`43Y2dnTtCOiYogzN)Ok#PjR;n~$tr(YR(4*Jlg)X12kr=r3 zrcs@^fF)stsenmV31vjWO=XqhZinfPg%A;6FxrVyGDqDN*bwiPOmOq7B;G5iJw~Vq z_9VN~4|EIR2Ah+sVpBfTeXWX<**!Uu@_lFpOx!1tvQ(~V6}{X%qN`Y0~egSnRia|9QgWk@YjND0?9wWS^c1<#qKY#7R@si`Hd>3j4lmUV^jgd&!c zCiS@P*KyyE3b_s4TP-_PE`EzB7I52jU3G$rzre-u!+!l5#TVlreH@CVMf6jQ`2+3(E_R~m6{xaO~o_b!= zQO_zLXV6^8`?Z$|oUW*hZJk(DpjJ92kXT41qEfdbZ*$P;bkxx&d-ob&isjEK$(?9_ z)}*4Y^bhcnYHo83@% zNpD$Sx8cqug^Xl}nGaNIVyjLC+YBv=7+E{c>#J_S%W51^unSd*8*KnhO9F5dJ^(A%52yX8?r0&-l0zg+7_P2 zsVgXj>1fiV&Pqa%Src>?cCScxcG9A(k4 z(zbwvSmuM1~l`pRJzhF3N z5KCD3Eac=}p@>ChO`zOHX08-_VM-L8UIAJ^7*8~NWj$Y=VnBf?1AON;mr%j_=#vaI zlbGTY0&y0k+2oTIbZH0l5!)`csVecqXQb;X3kALnRbvK9vKzK6TpYRTr|&eaj8wxL zGYV6D-Zs7G80K75;u(g7Esw<75={VrD1 zQ&3iCW&iALQ?gcMIWhl5>KYeZkQ^b%tIMKcZIVo;j0`cKesG9`7S!MyDuQ zXcxg;04@mwNe1_fCDM6&b>v5G%=k!si#e9ni!b1Sitio(Poo?`hYi}YBoyQlo$z4Wrqe*dh;>?5cIZ&>+u?;b0D zqhJ1fe5q#1(iDVg28WyjvV$mqM`n}~OBVzirHRbfjER_;klXczz{ zJc+}J9husL#FvLkM3jZbZS#XJTmdw;`JFpzu5QeoUEa+665n&yj!aNo5_9?JYND1D zHd?F^tihxsH@j!voWYav;E+lEB8W~zybvfaou0S*K4yG4oL4dDoJR%@xT&dg(sLmvj(qOYuug#LEM zNndUyA}}&GK}z&GvPZ3#6A}j1EKMm+gffm$bDPHkaT82gT1lzZ?1o#)2L6Y3M^*XBXb-ZLV} zC^I>Hk_=RMf$$+95Hg_5%^Blt4Mc9xx2ylfo;x=4OWfV1?2P^d7x%caL4DrQ!44YZ zVCQM)IdxDQbD&>VxF~eFF+!VS8YsDc=Wnqw)VTO()G>K}Q+7yqH7A*H)b~<$iU2mffJpL(`NKfDF*Dr_fV=fp-3@e6CjOQkGy1 zw~%2%3k|okr93%_?R9JxpGyXU^{?(zg5sd>U%Ksp*XM=K&;wSRbNO7?{BP39eisC& z40r|?+)}bLe9Fr&*obW>^HRMGYW2XgB__5EB%g_xz6Qh&mT|M|Ryf6M$Gy#8yH0jQ zXG)g*x>w$f`kZcUfFNk%%bJ5XUGDh~{V#3+5l;B1T*VS@pcy+g+t4$6rtW7aq0%jq z`4Z&eTgrORs%1|dG+rwu$M+}#>PR3ZgD7adb-S+YKPa$?vtA{C=2j>kK#<7*r!MbK zqN7n2AgQjPQ%hhU>=O*3>UYUGDbtPBIom0hrQ2c0vs2jdR~anw*m< zcBH`aN*SDRVSh76>bVhJyo`0u&t}R{Inp#-Mylhnciz$%YV&_j80;gsX@wq}AOYg@-(;K~yK%zsT7wv;Xjhcool%*`w4F2KaT3`Jjuz z{x{6rRf@CeB$)PX_^DWNXf8$`Mv5Z?;W@|_34!}4x>+8Wi$CdZ75>E~1U%gA>)pQn zWe}y_Mu#m*7T9Z0AIL>Q)J=r>$1J3SrPyYn;Kq!(yCNv}E2W;4+B~P(U$s_|4}}?i zPOpZjwv6%|P_}4O8C#M%#HY3qhVIDsrd%T_(sFigFa8u+#97*_M?n#DGLxoOpL9d~{4Q7TD+B?>~rn^O!* zce4yKyP8Zs(d))Qj6F<0y?nz(g2YN_$44^fJTkJHFk0D{Dyn458LwS9H`B6Fn%ILP zCN~>7t!ErDmG%}3tI3HJ`^LU)F=Kg;?S@3R2^@0uw~If14YD&UljD)XVL6A@<&#y z&5bG~46BjHkrXCtymQe^FZXiH_jb&0`^t{Au(z)mUB=o@Wi!QMdl`<#V&%orUB#^Xu z_NTevS?>_#+qxTG@jNrK14}AZ_I`ZdXg?{U>t_iuHS(Q9sp7OP8ptfl9#m~hy_?6f z(I)oz7i(Gr{+x2di6{jnt+1a5Da`*$*e7VL`l?uevEVeMo;kBYur3WX1W+=f@QlB! zr(zoqF@EjFTgi<89Gw(azqxCAksiR~G#XvQj%(3JEI${ge`-efi#fqJYN4yE|2KhH zM^bFhNyDd#KhekO&sZm4+FdgG2qJRdAor(yhisjYMEqRK?yps77jO{OV)&%e-o|4@ z)5B0JLn@!MJ=4)QO}^qLAj=a_7-XgGB_rqUGs9}CdHi*Lb8<* z-gk^-s#ZuyLxIRsB}$L5JsD(ZW8OA)EEMEhqvL^duZtOHhUfTWR1zIlDLai+L=(b2 z1FHr0S+xk8uAjpW{{>+@crWjqah03LGs!;O-$>B%@~c4C0R7#gaDF%iebkS= z#nLN+yXe>PlBMIU%0Js!G;hdM23zF>f3>@t$ar@Wq<~vAW4aN>G3TGl;)O#?j>FdV z@>MV5t4gjazH+_-6FInP9*sJt;}~A#JN(iupS!9GLR)De!;mK9ytxGH9E)^_K_z+=t!cV(fzJ5MzE`I%1>V7`uRuzy@^=5y|7JUv)>grng_!juUb4u3b5s14E*7bf}dP?DH%eZ9Mu7XqF;`hHx-1`izQW6R*v1d>j8>RI8cGji7y zl8Gj~!c25!igC8_PU<8a(xCQAE^?sk9HOhdK-}1+(V}N;6dDiv+UWkGzug=Gb1Yxp%?}4wI(#;@=a9S2kckxrru!Oh&1o3@EHQ*r~8cwBYM$W zsV8S?4-EJiRCII4=$W7XY%4!Ed1_c{#z27QIPhc|Q;cThcVY0C1(Z&U?-4I{IqS4o za)su=2Zy}3YM2Kdk$){`Abu%anFkIO=6=r&E6p&6SMlrxj4eyW(=s^)Seqlz=u6%^ zGE8z1&%SB#(&Bd+%N+pHqW<=eJdGaA41A;GJaO zB6cDZE0(H96Z*n5V&m^n5&AqgLMyW^weGeOtar?1?E9mqWG5Mwx|C3pDpHjLyAN9j zY~Dio?)e3v+%ud4 z>}HLHA*i+PRZ#4}$o&1*ig*ua|KxQ_8v)$tILUg^@~p z!Pa||2rXI01_S**S}s^ySld>=YOEOE$^1?ctki4Qx@n6Ztk79p6^J%(bId$~#`1jT z>-oKOZ!=tw$8_vz^hbeBm?mjV@9r!){;N7$jtb@GU`oVt zx7ODgJzsXGXI5z6RhK^g0cDfRt}f@j6&`YHMDay+#zac z-8#5?!XkaNnQc@diV=)y=31ab(Ro4cPg(BiGe2tnc}gs(_>J)Jt_jWw=Ck{yqjAZ- zN>3f#h|)b-yIryM^`T$;=UxO=U1^)(VXAI+#-p&0#TA}ZJ(-=Y-{YO#*K7GNJl%y_ zY~^(_g+?}ypNX?J+^9dt#Hxb0WoSE4%FYVpG{bfi; zv$~*-(QJ~(x0gDWId9r>@3>9U*=VQ9c50tR9HL`h*$h_r6k?6!3 zqNLNod66(jLqlI%R6Qqs>*znYcO3O3urbXuLfPt;zQy_(0#&i7UO; z=qeYg&i9ECAet3gHn%JK!5Zl`{?ml^mq0~-gx4qZukq2Cnw7J8rOhqb`oIi-91zxl z;>R=zirGw3T3(ooW+lFauknqFR%h);e1VD*nbz6J=7-P^wNypy*?X|RjXP;|5VDh> znjbhzxbbXbO`q9&*zD0KcapXQNB!mEzv!#^dH6Zd^JwFb)7t5Mzxcg?m%hv0j@l-NB3QVc{@bX^5)mlW zSb^Z1IofN7e?MZ10Xu34zR`dy(bBCp&8LdENLTBPs7JeaS2w3@-ym-SIO?8UTH`KJ z9mOPV>KxNhzc3c+^TE6gRb7S}G9If|tV>@JN$D5RtGrtO#JjH*d~XGODjys>{vCsy zT*Z8Y`8HYyd@t{36#Nof`YOwy{B%PLWBHuCTtv{fWySgP7Mjp~TLqjuSWtXng9*uFPoV1B^Hbz}i+~_R0*l9uX0{j~d6759|dh;tMkmojtB#mXR zn}wX=4U1^EJVA%s;wMxPbNjHSu}@+_8-*4>F)d>h$%br&%gjiK|6=6fYN;6#0 zwnPmE;LyX-QZ=7>_TH=el;-#qX;Jm3l3fv&)h451P-PdeWLK1J!*+$5gh+o(Cf?!} z-T4dNHQ3XPQeHU+97l7hdrd?eN8EVbxj%s`5G&)GsBoAtX}1}v6q;DHhj7pM2}*Q=D)kFha}lwR6lZLX1)GAh{%D?)(1Hxo zxIPbcWf2VX;3tD)CJm{a9lUj~Yix zVb#)>B+(Band5iqR$gKoA3t`@w!C-YIkZyS#YwdCfRRZesn3#zPKY#2qHp+$R=%%bR%Kn2bt%@mt7C2#$A?2r zM1du>M-u^)6-H*jag<_gA?Qn_vF%fw19`2wxY_*&F{A@a$fj)w`uC6 zNnLDk75=dRwbwmq@EObV{KbBiXIUjOjM?GMn!D)i+U@FZ9Di+Qo=}(KnTW>ExZ`tQ zf&pQ}XsrxC3!e2grdu{Z--IfmRD|=HKudDOQ^(GVy?M2{a$7t?3_b4o}Hkh ziK*^qaBb$6O3JoPpl7d8H3kYg3uD8xw7hs_ue;j%`8fUFMF*&)aDDsYxa)%pi35o0f3j(r2}?QR z)->>KqcPZVUgka^d3BNj;}zaKKIJat2;GI(u}JAZ`5_Mj9&8Bwdqx97Mn--DJEMVq z@B;P1i?raiSkE9J5ah*oScPAxH%R2MpbP%PK@SS(g%3#WKh{aA)Kni`Fj@+ZMb4?7*-sJ^c{5n zSa|kOs1K9x{97Bx0*M@f)wUasf)*ZY7-5x*uO9n*_!*^!IncpBfy3(ZAz7oaURIVs zW{)!jFN4F*0AWC~N2!pL#$e@u0V^QC$2vjyfM5+!pgg;vjz=f(cuU7%C8NTJpu0yW z@Un5#aVjFH?=k4Hkn*vj`rA0nALbv>%SSu7KbVXVnsN;)dh`b`vy3G`#sL3^uIckX zbWPT*yZjMkpSU;h6e-e*wQ!A1BdtgZO?QWcpM?;uRkoV;RPs3p+{(3S<4H#@BVOf1 z7IlSQ1tKZU_>61|ZF*9Ax8GlRBbZQNp}b=xC#ifXlm6*4gzP;Y35g1^LLLXOZLeP@ z-{@}`Dop}b4_Jn{4H+}?VCl7RSU8*MUDlYq4SJ4;cy&0r>Hg5P)mDsrN7i%im6x)a z)!rjW1hP^|b;vOsuca4H5OjLjBo+8NLma8zhwnxo=f?$tO_V$$i82S^(wedJ{h-~QCDFb!xsdDR zt0;9==UXu6MIhpVE%AvJ9}lv{k26{F!LKbtJS@I7B?b!eDtv2PF|2Glze%4BlD?Mc zofv?wSR33Yh(p^=WVij7z+!xM_9IHeF+;2-Pb0pDqiVl4Pm#<(X=L6c#t`RYYG&~f z-tHhcIEA8HRrjiY_`ZqgU$!?;Eh98K^eR1ZQwyKwwRg=KoWf>P?!xBJ({mwausG_fKo{Vis@ zQ`MA2d+XF;#^TEF@pXqIpxDU|g&|GOKXVv&{8^q^@%BmwOe-hznXQzComUMkU&jb#!BJEL+|XV6gmfyik2k>fmJdnF8djD(PxiP>JX z{vyjWuaD~J8tq3FgHv`p>sCHVybU+7KHfF?Gi_3GWE<&6DZSS}sgN%`UQH_Sq*%36 z2gi??yGDKHVowe{K>Ky$Eu8Ky9&f0pgksp*>@k4SwCnS?c>@G)@_4==)@~GK*qQLA z-pa@D{GuWF#K5L;aI((M-)-!0_jmAS^hL(3A4QlZukbsZcn0YLLC86uegjnN*OZn{ zgZ*16aLnbmi`LBNRA0Bx87F*kETp@7zcZXRq6MD4zul0wPr;W=8(*H^{-92~%@NpU zi8fSfUuh$!%Kyy`ton`YWqw48dKv!@C_iuX$V&$u7Lp7k>d!UGc2PngG5fJYK1R9; z#XSSVy29G}HcKBbR$@AS#eBuW^0BV|sBA5_G?~(n-ywxE4y&(c>D1X%?RHr0EZxPsa@*}CW$MJ)Iy{xSipq6m0y|0D(ef+c|2)6Ad~y6{ zKx?wP#&Yl?iJh!Q4rio(ZgSJ+@GJ=$I@Hi&F8^LYseW=&%V=_*Nc34@!b^uE04Lg^ zKZmcRp5Kqwz`|ehF>F*$xaW8?W*KZ`+&!D%y&(F>|`t{`msp(_;2LBaX0+X{ODJig!Qb z{(2if6Vx^46>bnA=o9nbM=6QZr?oDrr{g$hg2Dj9eTMC$3#IrszW?6|NIKMHkRLa`C$C zj@@9K_(sG>+AGi3w&6(;@Ao2rn09YfxC?Rb54mYpJs|J4t~?n@%XGBAu@308oY}Ah zrHKFhAg%iqSZ1#9DuHTvzx$N`+UH}dprhSge&6q92d9$*1jai=2!0Qk*;Veh2-31>-MUe zdKk0FcD&N>JacCP!vzmk4xvEAOH|&t6zsO@w|lZ<+f4KOxy@UY+36^M>@MD2zI#8# z=kykR`3&r{ESJEd{4Qm;eY{au#6MMhHTUT?8)uM|^N}e}t{_?B)4Ic=QTj<r9$ih9p6m3v$0F4FNIgpZ& zhayt3_NZS$8rA&*Nlg5B^>^H$({v4)nSuHjts)NaScwH`A%}{`3-i{zc*+d6bqe!N zmFo!$9g**m8E1=1b=S9l#RR=o$EKcN_D@o3R1;JW_fGZwQJFjWIGhk?rn~W z1V7&yo`qrCi|L~6vzBU;G1aHbKG)4nI{`o0Bb^q04s|vW>zr<$q3P~JlqPAj&i9Tz zf<7!%8J-0mwL0Rizd~LR=1cgaPeea{=$`uO6fENjHfhpjW-*>aR}qy6*ejcjcUsQ8 z&G5p=NEpS?T+{DWdtTGi1Pqp)Kr@W)JlzD2=+G*Lu^gZvho$*YyvpNX&pB-QCjNEl4As(hbreJ<{DV zbT=Z+07{p1E8QR=A>Difb3ga*u zxcCu#E&s)5s^hki*(ubRwX0nLEn5bmc2wfE!dQ9ov{ynar;86((s;u9nP5lw zDpI(Ic|qc_Yal$5JGG$F7rdL7Ma_Gb{UTW8uzzj%Hj zKdHm}oDrKQ)>NA&#bXOr|nmw zjL*_zPjER8PvGaNWBYFc{a^d|$rzBG!eb_Sr`COR<>zsh!{BSePMgV>!c`2iugB1u z8v;`+6WGVST>>qy?)`;=NGYh~@_9Gm<-K)GlHzFi9uFDZC{NLA6;-`F-;aHr-MgP) z1SyHnO)#2e<^y{kGArFYFG{C@dp~Y1s9B8pp?k^$_fxYw?ah&dabZOz=}%Wj6h@-r z{M|LXXIR0z6OOu5c1$fa5%T*9KM04lZawkgT;^R4*2_WYaBDA81;Vqk* zzkR08zZ^NQsD10kk*}$!|NB!)z@fbV3bFX}meDYMCaJ%2WztqA=W6=R+SX?S$K_wH z1!;_V*{@Z!USen~%>Mb( zub@m;a@BhzdYZ{&-K9(>v`jmxqNNXV*H--o!TE>HgDGk(>BIMG?7k;&jQd_!lX2s9N{ODYigq zu4P)cKur*GB-cD>a5@dQTohYC+3W4?eLkidx|-A#HLeJA*jx?IFh-u5x{r;soPz2H zlZvxrt>2YMRcT!Y*pT1*2a6G(UDEjB6NJMemeo|}bqr<;PE@z*Dou2p<;>)WCqnJh zB$oZ>?KMYoY0Sgu?94)hI_pyQLUS))$Cnv@J}Ctq#$h9`b$NZ#`YZFB^oU$6V$`1$ z@UR*~sy3?v#OHsIeuGnc+4nyDV%Jv*cdWzNMK(VFw`0V#1%mod0ryOhdD-4?{tpU4 zHODM$7A9t@av$UtgQYwsD^Dti`B(zWeh$bJ2zDsaKH!Wr&IgEY(S0drc=6*-=%J&h zZC%6|^m5%b^tgXNzuq?Ufr*#)%Glo7(Lpfp(sc8;e7o!PO)(L zP`SsexOY9A&PYy05^H#Cf^*mor-&-sxGXbG6ol!E)b6sH7?z=v7X%z;!Ah3L@TOU4 zEEz7OmeDX=E`ON*HxHKmSW2k z81y!cTW-ufXK|9&EpL-SVBB1e=@mqNkVSq9=rAU<N!Efye4k`I^FNZ`KSR!*wJxFlOhZKo`EpDo`Yoh-z*n$`KDwnci$WxrGSy9X}y=1hJ; zX}1iw!${cp*Wj#YP^R}%51W{sYD<1H4}JMkd6E;&FkiFe~C*N;Nkf}DdtyY zX9bUFt&GG9?LZ!pM_-Hv>Ic!FZXK360_=PPjByr;Ho?5MXLrby*AZg|MB@hj_Y?g64NhA8_(Wy-I~S7q^~G2VL5pU;WdEfQq_LoGsEQDkvO}6P z9_5#eH4AqJS_0$}Yk$ru-c3aQ1Da8u!pXWtw#?hbDTx9dX|7OpWPo4;C{~01s=r7_ zbbYsR3kakQu_l+vc{z>In_1@i`D^am>1Ldhc(pyxW|5Y2h( z3OA+??__jde}tqN$fWY(!6B-OP8S*1`TqGtxoBEOx%sPL(&KG3C=u`6s97>U{Own1 zcA|h&gJ?=q>2TSHW27tDUrX*M=eq%ydi%r2@Ktmzlv z1o~!5oQ454!@R_%L~HoY=n9h?!R55Y^j4w5T&(=8Z!grhq3?I20E~}c`}oSpTJ8l< zADkUXbh%7OVV6pkeRE79@k1}pZR)oz%AYmn=BSxOQZ{;E;}>hM#v#e3ug0+Zb_7D`7acN-HdIeJQMSBd zqSBKy%^DTQ(1Lf~eQqKC6nrj?Tyd)xV4KaD;EU6tIq>?fjDkx*&bKhVja_jZd1jpl zJ2h8|TT@;@Qbt#{W)T#;PIcF6Jok*LB*i!5e%lv%Yw*q^qQX5^-)_m62O}NAJH##D z;t_gpBu`pY%grx2muzfyTPoEYXJtw_8Hu88Q($~ezs1xy>lly8V6OGM`<-#9?JMdS z(h167*@eMglB1fV=Y$Q^e6xH~WfK$1b5?j|Ntw=QO|K{gEK)$OQLS%75G)nlP@~6M zJU>UOW;Ab76*p<3Ihngf-<64FasQB#{2CWtSVY()#ePasG)%gd+9fzIm5#P`)czx6 zI(k<7-A+#KJKLgnb~sCU=6`T@e&T$c(Z$1eKj80x&$SS=kBvCYDOlGN76tTObs}ke z#a1n0ax~x{!nJw{DtD+P&N1C7A51aXKsoLSv9b&E>fhd6-CQw7aR0&PG~EF1Bn|%4 z$@%!J1h zS(ONd{xmII5Gj%N#U{Pq(TQgQ>Y9#A(?pG18oZx{HpPRn;f8*SaGSyib6QiyYDG(gLdrGC?ywo4A1Nw$|IkH%bSp+V>&|*$Gv)t~x-KdtM``K)&^B-4Lug~fflq{|XUv@^3!UN>4HN(KMEM7jKyE+W~4L3E`b zzd~Q}{5`13+J`_)PqkvyUnJdn5iM(f0(xSR+bi6MCY>X-hbIy$y6^P8giL~>koP`^ zOah~#cLe#0+%SQl&rvv9gT2C)XnoYgEE9o<@aiRJVrRv9eo=4Jyl;7_W9#senbmE8 zTjdLqQtyyES$M&Tt5Ba>e;oWH_Et2nOD`p&-PCYenE*y-%}H*OFu1$+Zuo(r+T54cFhT*DZp+;(@|D2%(8RD zMRA7yK7B~}7SgqlSzsHHz!J#2%HogtnQ+^S=TaN_V+EYj*)!HxV$I z&6;09_J|vy)eA0mH136b+vE?YC$`~qn0=%{49|RZWFaon9;@^^m zOBZE7tsM}J`$n8cxIoU)nh`IyR2y7fDu^CCT3n>r@Zl<(#*w#)y@2l%#GJJ{msob zD82FBa*c@Waxc?$^qu52^TF6;w4i5ioRUa;ln2hm?~rJmYc{(nB_f^)=jikuk3NCz z-&Otw?ZKe1Z>KhW+1v1aa;JoS45tDgSzez;)m<|t@$bYY95}Qo)+@44fj;s7Tt$po ziCs|i9FjrIs0@+ap}OHaBrx47dy4tQuu1vXQ*0YA{p-I$W9)2ZEl-vzjnRs=Xg+U z-h!3lS0TA+MOkMRsa~Yv+|=Kun%ckgT-36hTb-m!x(hU|Mr;Z)bKeSjM{V_5Q|15V zXTnBsr=5E!m<^|rjAC(7!AVwI@4V~J=HRW+!0+u$>ae%{Wh=0LILYtjdrRybu09zoK9n!p*f$_MQ0GWwT- zb=ufXd0Ku;*BJ0#gtAOnYPtlS-=oY#oBFQ8yZ~2Jps3Xc7S@nfbhbn@RG%A=zEjd_ zf7f?cL({6u4!RDDNtO0k0rf0U(2M7RIZM6EtHrtgYIE(&>lW==SUqdR zK=cXT>Wp5fRQBAiLlae23dVv5!Ka5?hgXzrnqb3M3`y>V#7r`ZYFwCGfx@1VgV=wF zz;{D9xsNY-_Y;HbC$WBxTvxf3^;s3uf5|*E+E;sL`P;iR#sP-f}$|_Xc>>o zs(uW1Qm_!w{?C85q29u8v*6;8$&U4!Ls_1AGWm;P#b7pZs%#+ckjrfxikp_=PA3bB z(NpK@bl=T|8&i9kq*C8}uW@j*(cq;97xk|sP>K4nN?_ti{|GYKrg%<=Bno0yCA-_o zpl>X*4K@}~)B6`?$qu0JIK*i87MefLNb6N+PLkJ$57KzSR}rlH)gN3MJW!ro8qT`6GX$R&>kQoY(Ky4_^dT%r~j*f6Bl3GdyEBDZeGCuK7mbKqhD^ zDzFq!PQqq_a_IeD;XEXD2nnplp{CIt-v2V_b3@)c7l_#acqLLYB~mr5K{8n#d|9xC z_hEvQ-KIY@XpV`bvdbS3`hDHw55cCgTpBROXS)eRvr*09OM<2)s2_6t!tT!lh;? zPFq`5`uQK$zf8R6%B`kasdSyLncHjPEw9~kkW;|X>#$1bvI>0tm%GOA;28mg8 zCGeFM2u~+glxWj6SuG4}3WW(;b~J0MKkk->VrO6lZIa?1N&R>~lz_A_v+9J5R7&OC z(X45*-xue|PZirK%{llB!dK#BIaT600$T*EPK9KC0$GmwRKxnqK({5b7Quqd7)4J$ z)Ak=kwdtk9*haF>A$1g%3Jmz_Vl$;UNkI%kpmP;k^AW@_Bho3HBCs)=BtPr%-BG^$ z$7&CRIgDCU??5NHLW^2&Fh>A|^KHq3+G2B**Q z@@!QPJhGD!ru2+zxj4WRgV+6SGAIX>(je2gG@bUu&G3hb1+o2!;X>sQ z5F@G`;qaKobNaa*{Io(HRDLm69qJD<+#`kZd-RGFrXvdl6}_sxMwO~GhWypUoN%Mj zhNsNDJ^3zd%dfOt(DpZq%$Ol$B4ifCft-V#A!S#40()sGP1jL@L;*=@bYMrWdWju(Sm8wi%M%mgqR*NspG3J?t*{kE<^WL+Iul8 zV`3VGX&g1iD0^tz8j1lU4#*XNrQ=55hgs{EzJ9s6ZI4r=WNrhulP z7yE1ttrV>ll~WCUWar(Ij371Q0QYx9-H9djs>PDu2N*xbuKq-*9~nz8AD#TPFc^0v&-c*8L!fGFiG8^mKJ0i3>a< zZx#ps=A9KC<$JDTP@#r$hydB#C=!DaHdzJMXL;f*}2W)U;MX-vSX?uR}`oZ^t#`g7&bQ4d|-_@e=3n096~lwg7{V`LGh}N zElpysQOV;~JBG-gKB_q!&P6GGViGi@8@c9FMbSNIL(!k_pcHKQ`+6QFb>(x1Zc}E# zN-VzzQ#dV{8@JFIv=1=ulY*Od*nwTotwxS&>_wv6!GzIz)!smvUVq&;{MF62F3WNvR1qnt=Mryj~khsL0TA224TaEof4);Ee z^lm+W1!+@>?I5Wb;S6(4>_1Gvo*3e;DDc&|%^Cw_s%EeF2H5)3YJ&K(6few)Wa=dN zlBGuJ28R9~Brd-`Ji$M-DU-LBtG`xKBQV8OtHkL}vU?2N*VVn5I5wwWBv74Dv4PAGg2i`8wC5)9WX;T|Q>)=9rtC0y zA}So&_{Io!7pZz`-01T46Rt6e{Kk0kC*4X!tM<;(lUYYT=b>~{t0+(2RS>ZKy5>`62==zZQtakNh?{!Z)Yiz{O4MC4Lq9#x<(UslqDIB54r zJ95h4LRS5~A?-;t9wF5`z7_YtH^-(k`#a|${x5&n)3fz9Og?8O;AFuPeEx|uoK>Kb z!nDRJ?YNzr90!+!GZ#k(6c&aKtVMI&(sIONvL>o}X=KierB7-oY?PoTG>NW7`azB` zRz;Di$n|>DP!1gQy|P(%bOX+^k+!r~iOKBvuFn}xceahm|3o)_v*hQH z*AbVMj{t-%NOJI{a#s%in%=iVyQ~8s85wkw9s?t}Y00Ul9HxSCK`JUb8rLuR=&3?) z&&Ixk-rl?9O-@0JS1U*v8eB%gs2GvKNflefC}_Yb8# zre4x~)!1^<=3A28jI~}E=**f7-!pU^wO*UX$KN!X#gcWaGL$Oe$)_~UaXUd zpqmYrMXg78(IwD>Dg&C{vP3OR1-##~-dz>*Q&RJLHE|uLnr0Kih`sWhexBM+WugpU z1TEc24tv9pp>rY3+%g2&r6xmdLeN_!-zOaL`HQ6XjvmG&jt)IR64`HF94;ED9dL`Ys_(3sCrI2G(vdaP62!JCGOcqT^28fkm*r>*}?|&(;A6aU0?d^3r;epE%-I z;VZRTp{0FYBU5Y9MQlNzLzH>DaqhX;iB(nRXQ-51qsFa)@&{>>TO@w(%0GwLA2|q0 z+m6Igaz+t?mHFbr<@WM&4YlMgQXp9cKNPDQ8mL@LkwJYi5|iRt{>9}h=|?Eoe@+TN zx)cdVwA0sRcw14AbN~M|@_HBw=7J^eBec{5pZznXhB0GcSSga&!1K7RDA`Mm;Nf=%15(|@UjOqY+|kJ^>-I> zn=_Kv8^w@S7DoGJ@(aZ*i;%YQ)Gt59`S~U81aIL$cBTGpT_W^Ib#Or;Agi;J<&6uf z4|dbFHu+K~mS~hZL4#{#IqT^z$2?f;=VIQ?-oq5yIY&SfI?KB1PxwFQOFvlL_LrR7 zilvP1ig*>fl#C{#vSr0V;Y7{Bz^*KQ_5CDqu34@IjWO34tj=r6UeY(;2RN&My58x3 zX=i;v!TVX7Z4+ZX-*o|FRdZ_jlHhcmuJJ1Y*_OewTN{MtaArPh`*ERSsd=P3nYU*u zeb?!M$t=CbQ+Bo{j!iE9DGdVz4buxh8@?wMzBB7d=gBDwd7hl!5uVS!KT4=&)!>E! zR}9j!k*qdK5lBt75#e_Z(l*i(>=B$-#V;8F-ONNL)= z1VPzG!k@S+S#0hB{rB2|JZT%GwBF}`0PDu z5>;c-mC=t3UX=NP3;#v#mkl)uwr{?_DjK`U6OyuTS`tV)o3rC-W|%tgL8$ws`3Lm; zU5gK7eAP0`>vaPUgts`V-RxV(>g}J`YM2B;gF9i*G~){Fu)dn@e~8>Fh)(tFJCk5d zG7Na0zDMl!+1_zPRiXO%kBRTfGhGEdaa)f04um#`h3Dexr8TIXWpm<-KZG%uCNIs( z3{ywiYMYjdbPUXrzfLeMnMrG=7nZw%c6=pvf8u&1zj51(5PWaYN=RU3Sjk;%RrMjs zOZ^&2(ET+D!Hahs6Og59!a14x+V}MwmHhG(uFX@=%UnL_^RvI?8X9B0!8anwRJe}F z5XcaaTHWJT@%%$3$o5ihm%*)zkw%4<@5M4ZmFw`x@8d<5_t{2;rDFpT5-Zjuejrj_ z(A1jVmol_2C7q0l;0~k9E7tE~o(Lc_@`qvalHw&*AkEPh99EIpjCh-c&2wOzzd>zV zg7O~Ek*;Wck4luPu6@U04oZW>v5N+`V)K5Xo`>h}L>`RnU(9 zHdO~zPo4%%86V+0k!>Sf(R$KYAV{-=;5&hMBj!k#jacX2itz%x=E_+Wru}DKM;;-t zeF5#9!s%EO{!*`QHHLi`=8@a!s{WrYAG&p7cRtkBX?LE3&bujo(TDT(0CzOupo>-K zQy&0nyJpF6b-_^k71q`WwDlX_PQJgdA+JeS^)Bfk1f=Wye;t|ql^lMrB!Ki#vbPZa z;vZ0WC(p}hC>Eq11~zo5eYAC3`Qt!7M~=w(aFK&LYu|FMn}4riGD{#RPb+Jtk2?zz zWjbK>D3We3B^JO@Lo8iQl|~-Tl-9w4mgSO?*-Mx_O4jNN1~Bp3E@BS+QX2YdoAP0P zY=$*T|1-F~P$#UgX0#eF$OAN-f20|hY~+3+B|DO=e2IoAzGU=cyzV zqm@(B=K5S1P4zE)!(XfkGY%^WdigWP!CCbbnE`)N@#{uXDPSMK<%u>bIN#gNl5Pm(@}BY)N~0^Y-3 zhW%ZemL1=|4|VZyb<`KbQVq^yOzM{5;fWCkJ#i_bWSh3y)6?IW6%$VTpOR>5(u z{_&~_eHxB!73JBpBZ~ifq5c0{H947@IsNByJ4pq0xt+l4xK6|Qa!y4U`DJ23Nf8-& zftq?b5)~Ch3^IV_ix(?7*XvIf`anDTjvH0MufoQENE_vM!eXM1pQ_ zuV1xOF3<`do&9+AmSXMgcUQQ4#R5m4j|DffC?N4!E+6XUsc$r~MXhE5NN|T*8Av=> z<74}%(S_M{E%g2G_l)T91qgP25Ql0tvqYgJvS2jr4>M0LSz(s_=CaiaJbT!gp<_Qs z8yKcX%jNp^JqjaNSgs{XQk)zfJ$(L+N_6QWlODvKM57CwU(Q%48(3#(42^+ReNw86$mGekbU-xjVeWB@Lu z8;U>3`9&G@Y)1X6ae^t42ot8MvMlKLdp>L<2ZdP#QWU9p(V1CUcm~3n^rzYzA^aNS z4hA&u_{kCt-o|lezVEJ%{2M!sOg3}-ur&*EAMu7_G7Vr6uH}&*vl&1+F401`AS-cqcSec z`bEl>PVX(R33EMnYu8mIVexMrhP!+&DWKkKR zH839&B@(haz6-s)p89`2B6#{*1^UGa{C{rAC_QEh?CLs6_2v;O{NC={cttrJ;xUHz zO7};>U3a?XAko*D@jY?!xM3oTuD`S=>Sdr8s5miiBj0w7-YfSyf9pZvigWE361vDb z6j;8pyIBAI>+qSzdviK1{dwEtSVZy%Q*NId>C2+ct4{A>!swD~kgKlukj7on5f^Ll z@Bq2UT2a?xa?_sM+~&X;KsKE^_*DISFkY9m!A^~!#Si@ujey?QF^%{*l`K@cQ#l+Z z^Yz4?uh^eU{OS4nw(yUkKvm#;-6BqxUibL%_hd?>IXkbfvnzhA7e3>4Oe8?@g9nJE zAxFKf&$nZ9NN?4t9jJ;eIJ$8X{dqv@Algf=*bYk!U@uo>{7v$DlfMxo`0$3kEK0&C zNKGKfia4QBK_x$U`T|9_sdYeSJ0YO()^PXN4ZRx?$;(2JqyF8L^a$g@ZscFHtfX(|8P5f;nyI z{p$Q&csv*ZKzepw;mGaLr6NiOaZGDpeMa#Unnt zTP+GLTdv-6-|Nhb|1^C)qEvlbrA~FgdpB#MtEz%5%Em;w2&_lw!t~U1a5mM^|MKN6 zhDoC&trC5DJ{^ek4hu7z%{i>!WB`xbk~n-sqtqAYljoUDH}$8pNR>NCY`LE!LthJ1QQN9HG3@qygl>h+bao+#fB z#**J!f?ZCDDb;lJy=}@xS;kFnuLUHl-GalA3s6U@J%i6g6P8ViDEVwsiWKFhKcID;oA|eLJcKZx#tMj)O|h>;f5pkaihW8Uc`xva%b6hV_ghs(Q<<_9vb`x}e@DNp=; z5!hdyBTx4*@ZJ0JSNKZ7log0k)z}}#O>g}%o#CB9PTUzKG@urS60}~5_5ASw^NIH= z{3iJ(QVY_(be_IS!HjVBm@Zh0GqcXR>H^NDb^8=(zwl4QjfarZ{M+{dXfx7QDt?sK zk?}v~6T);r>?G0A4wY0y#w#^-0-kkVXsaY;DJbRAu|~{NO1yDYQo1CTZoN}4Dc8da zO6#gXZukxsto3EP{f;7ky`0DekUyp-xmz$Mnc3JYX;Mkb=p>CVC^mlJxOgkJW&N=% zK#IcXt%T!x3ck|&=$xH%LEDU_zvBr-0~N=atCqY?az_i}P`HzLGLel*?{d81 zkr?P_35cJx z%b7?ntJEqQJgSHormRB{87dYtNxn!W=#1w_Z-9c*=8_gf1Q|7~;>^UIu($bI|keBU)6`R1jwvsLDH5nAMHWXRW2+BQrGSN74$ zGmz&SM;25d)zCxtC#n)OFgnI&@m~99bxcb08qH7P9ty)fo|5?J&GggrQ+hM3Z<;*F z7IS{SHsfY0^Pkd41?6{|oz{dKCRM*QhL)n}sH+)2{}FJ*w$bZ# z^z2S4=mbgip5>*714f7J)aM$F(XrRG!SJAiI$ew_J4kKG?H6=>o+jPwTDd$V>RLOx zTh0;a0|_4qRYxL*<-9k`#)3C=XYU0Ct-D#aw?;kp&R1C1-W1c7WiYWJ0q(0tvP>RJ8CS=J%CP0CX-n^ z9IME0F!ga5QQ;}8bI|ddqjU_0NP|1MrDCFFW0o5*jy@%pP9`R?pM6Ry zZR=Auq2lGl_#GyY$Qn>>NtcTc66$iFoIe&o3leE3ePebnO+P8r!VbzyyaK6`5YSRq z&uou5@OGXO5ReR>Uu9dU&3&=GlNfr|xbf|7f*G)6ux+2&|sN5aQmSypL^E`rF z3Q+H)XCzK>4F2UhiD`7HM zR!L}nzu)dq; z+T_kiXUZU*EV0F}??2#J!=uH=^4UhdiD&b3vd?mR}qM#~c!f^)*e(UUlfoy;0 z8pg)KOTrf4Z6>$kYVb{>tXBFb14iX}`jmMGPf3nTYe0_6yM(RK0rJv>tMHo_`Uzd* zu^zs3LlwNVLKPs1r7KNSaXM*P?e2m^DIH#dXjE*YW~xDqY4dK2ohr3Byx$RVjH48@ z1=1QiROKn0VF`?$Zlo7nO$A9WbV}Mixe8oxvms`b#Y(eOjSRIm^_wj^PQR{lN=;Uv5CQtSKTaq- z?24I-Jb&+Kp7P`Zog=^C|DHk;IdT zC>WMe=RS4w`$U)O2E0S~vY202M~0TkF1^4;nZ*j||7KHU{?#sq2#jgx0sfwjdMdOQ zf8kyrDS1SXZ)`cs3`#y7`c<$$Amm-2M|gpGHrLFMa9iu7qy1 zX4OG&Q6xk8L{EuVHaqCdmbQ4u)F(orsQ8o0lJ{X$)ERM z7x}94t8PDGx3U-NyUii%qncl6d5zj`M~FDO@w8ODV7x}%JcxCP78IsX>tN#t!UDQrZ_;;v3XRwm^qfJ%|@MaAOQ+G22NsC2uPe z6$(8Izc@h;isdU4sW&|fZ5?)#YB~!wWgmbZfi`Ulv(h)Jg2@FQjI|zL3Z|JoSbo_k z_b)TA9Z15T8!?|x-Yib->Z_2DnWMen4L#fL(Kyu>PPpg@Slq4?45BT8Oe|cmH%GI4 z555yB1noy5v*go|sctiMte8+cZT{TzEAMu+jJXUokm2*;EvUFyW!20u8o!J4F%`^4~ zIW3SnrJ!vD`4}(;NA)W?wB$b?Ld0wP?;L-v?m^J6C?j4&mQr~;mNDHS3Ja3B^t98l z?p`bWk@P9lml#s)kz2Y*J1FJj9@N^5q??EIf%~EL;}1Zce2StOML)!SuE@SV&ojc4xHfTuojx`|dHj%_A+aHl{?R`MRql$1B$*&arP=RTUK?;yxH7UgN zW@E`No4itsBJD(*_x-fjQkf52COagR(FFbC?@apQ)MkRAw6UkApEJpGb>oh{JAtnZ z8eM%qnp&2>()YKZr*H$IZ6-{%0pYs{Uzi0#(xDsuL-yGHf; zScIdRd_gM&!j-Gf4__)Xrj^RvPUMK>tSo6xj2VoNfgLWW^qIU+&q_Cos}^+bw6z z?aJH^u3&S{xj!U#e2WH+@^0N!8W%<#FIy!ZdfB@#r(hLaY2nqY#EZw(VBvc8r7k&I zT+ZVZ#0gpzltpgVt0W+d4oXigpH+g4jes^r1m(UeIK{If+50(}^oT-zs&hr(7!)A# zld~(>R!7$GsA>@UYVC?27SKY_&tw~96FAGe*9Tah(-h10@WyDb(8R3s%+N2J)_O)D z9x>U{tm&gK&{Df=$^H%GJ;JB&Vo-_x)lI*f)J1FORF+8c_ea%G+&iP;%O)xz7n0SgP6XEI(K+#9WsIO=t_j z05@6MO-i@Jh9e1jZ~W@6mVguD&>iEiYJ&tLGB(v>I^*mkzTqow{SXGVQ|qjkuQ z;m+5d46$-u7k*S>jRBNfyNGkwHxE-^{%ndQfn>dFsm`#f3MP=Qe1=Vx@ZuYIL}ZhDr{lJRxjbxxeu~Lum7gY%$O?zb7L~?#1HQ)J_D$X@q__Q2dj8e=0y|N=0T=0SQILCW;C8@= z=&>)pOgX637ZcICrxIa$HGTK4{n{^W;|93|a*oZ0awkW9^qEV@DZGyK@$NWh5lX&|9R}D33j!3e!NlBS4^ZF<3R5C-k`gcF6uoN&?B%Kzjd#K>2S6FqDo2 zVKhdv_y^JsA8Hm<136^P5^3nEj0^;74i-#iRrw=usW1{e*kc9? z()iZ{DdEW`Fx#VtLo?8-GX23wRsU12=E*3AStwj(eJGMMtW*sQIdT?SD90=gN$;QX zHIH{k3{Q}N;LB8`9#~`^(MO+1d~;C2$mvKPuu3_P=)#Y5$hRD%BUpr=Q1YH=Yxzi7 zF#XV*%$yKNA=2}If_jVdG%z@j!%`$D?&0G-8L>xv%%^S(RY*24q}nG$La5m|5Z_j$ z9GIwo#es{f;0xx@o!%U!cQa<80|mpa3DDg zNdMrSKG`z1L`n*YUPDrbl@I!lr(n@#XtZdykkn!QpVZd;LGptgLZiiJ9?Q5jyU?dX2E3gS6;QlGBPe6nJ3IQ{$lfH(vXe9#N{-3dZQgclV zz<~*esu6;`Ck5cc4*!(|fwrTB(Vo1$CI^heiV{IV388sP4nPLeZjxevBUPbhlh6Rn zVWD}7{^w0nLdXy!K;^0C5*Vt36Z~frickEJ6QBai_Q!~V4J83DA=i8W78r8tlVk@~ z016ng1s!!~Nx%tA2^N96Er% zz$fpYJkT`*NIdC*kf${OEgpabUfzXP-KvfTu)$n;>IG%c!`7BZ9Pp4mD4ME! z7C;te?31oXOlinr37{1=-JolO@{=bp80857E`;w3;2&6LHYngNc&rUNATRczNVp_b zfMHm#|2C$q1^oU$H1P61DG@}!0iXzDz;KwMlEDfGQ1Hu@F6hwQH3AS}+@~DOdDJWT z4ZsYe!GLWa^^zV!F|IpX0RO-kgDQwgz>kf=F&zLzaLOSmB?P`5@DDsO6xx~#0%bfr zg&P8$08CHvAkYYo!V>1c+I|cJ7GT<-?YJNdV}N|vD7}Y*ZbG0sQefPFV*qiO2Kd36 zK;?0Pp@;Ag=3f91jP@u6>~jLewSAZYz~F1aPqdv!8p$HS@P8JsCx07O0skOi{;LDr zcnXd4!#ZFY=ECYGfE8xOqi^8YGidgkZUcV*kIK8VM=0H0fITb={&f&+bq=i-_Td1q z2ovzMn>~eY>KwXHB>V-y3haZSlKH{%7tr!xmnVRqPf{RI+6$PuP$14X$+rONryO}2 z$@fA)Y_R<$DK6OQ7O?e1hmIEqIQkB-_ur$+d%(rh(a`}43HYfGltc)*cmVu^FZ!6) z;4vg1I{5t+bhaBjK#P{yy#W4$Q~O*Q`@h|gz)Ux!1mG2TGALX)G!O|&R)SRfOyjb-@qpH6O6hcKeXKx zqAm$+g5?Tyft7`s@EF;o&tyc90(qbsjI;{9IR=ZBk|q!n?Dm`t52CFCY=n4ln`6{Xg<%UqIy%eE|Nm(EV$Gk^!&@c2@8&N!b#(`p@DG z^#N)oA;i-GNC?}ppYUowza*o8Z1@1}VOb9KixYh42SkTJqfZP||1?yeK|sO=e;_4H z+7rn5W1c~@!+_nekU;aY3fAFi;dsizG6XV42zxZ}2FCbro$^ z=w9*$T$K*ZQN&~*>@4qTbAKd3P{BY~So|KJZ9<^5d|0q!DbfB3-B2D71Du`4+sZy@o5i#vhFjG3AhH~*CnMJ^47~?U;A%>+u zy(cT4wkPNj7UZr1Xblq#i&HK%8M7*Zim;S^asX-tbYfHj(ZJCBM}+Xz0{x%D2x)Bw z{^#<>q4G|R-6}#ADM~%Iqu~+O}Ft*sSu~AXL7Q3j? zwTm@2tg$zY8cU+0Mq`Qk&F<_S_xk&PK2P!-JG0YgcD5V@x5Xb>rqK(z!L#s3Tu6Wq z&3G8>k7K+S02DKQ_BePnT9Owwpv)?1@DAk0e-mWh`Ct`E(>WD%@ypCr|mumH^qPeH^h?Y&%tW?+*eTqL!CDR*(Rrfw9qQfaI1hn zYtYUiii;S%@FT(b@F|YWHac}Q)T$s}s`2?Bj7~FEhAPBgf9E;G05s33D2@bp&4aPx zv~O`mVI;s~8^-h`&J4MIF+y<%`GU0K&(w2+N5?1%6NSYYN{dG;o}#8~m7xHL^ypWE zys$V`bbTeoF*G0V_n7%oqZI)p+v@Zuovh9vdZ4;Ow6t7MZwxin)K<(w4m?8KKzggL zB1LGzGE$8DlespKd|M;MNKZwHCGmD(^}^;<(-AEd|DZ5jS2l}5m2_)c#h+M~xEDAe z%MLT{Bq$zy!=HZqL1EDG5Dgirn2WaaC`WP~PS}QwQ8dA6ylQ5TQACgqp$;}rzs z-=hzk5kUJ*R`mWxAqMcLAu|*K7-k;PXv>9)PRJJ;3Rt_2Dbyrha{B-BGF5@pxj(ea z6+=A!h$YP>;B#c9q79C50X$P_`*n)#UIMrQeq^o_q-e24ag9^Po|iFv>V(QPc&FkB z4^?geHi-_{rPzqNbANI|Fcw4(xFBnK?NKa3C{HI24K3^prZx8~u46sH7W|k(Pum_; zlt5&zPe!%^mo}1*vgC+jL;f@h^opTn+|2dFUeu|f4}Vjn3%tMJkH2S~QjEpzEp*A> za}9WD$S$E&)BRT!oiKj5R&0!9--n>xR189*&s{~Rm+|=wfQm+#7g+ZUe|qkr;x4kq zj1#2%>GMAo7Nmk{$N0uQQy9>FTt7}FE8M9i>3NDBTt>bwB)?X;$f8%GM(~D&qb7V- z_|uHniichX@Bjz^0?F4B;LFSJ6fN_8$uKBP&BUIEuPwmANs=^5KiWX4{Fi%xLb&8& zeB=wqIFe;0!FvOY%FhVp{U#8wk&h)I6ZYDbcv3>khACa&pwKx*l%mOIiwgl+9He?F z$dhTslqyt%xrG8i{K0wNwQ}-NrN9~UB9-SbLfnS8(=}z3bupQFcEDT%=~4!SEFY^( zMQ6wWA=FV>xeozuhw~Xh)Kyh!M33-raz;4|lc}|oJGnE;R?vqLE=t@`rIM_Qa)#0L z+RB-n5-n9%`5o7Qj%uKc=0xa*rpjNj{=isU5?am~LGQOz)nTfTl1h zQ8^8@=c^urQqX7$9w*?k1VNHo5&WY3QP~~k88TJb1kC`)JM`I9{_HWqWt_VO-v_WmwR=CQzaFNErN8sd%kC;-cLAl?sT8YCgYKKIU_-3jg7*^5XHOh*dU#h_xLo%vB zuQ`9Matdk$wkglBNYqKCitMWbtLx|u%F+mQl~;LyO8R!Q(#7*7pHA=n%G-zvk?4`c zOy_4SJ8*C1qgFeqR3j=Rik zz2-CJ79K1Vc53BC#;yQ4QVRd(eN=wrHej-I@nBd%4}MXO^F#^~+?l9Kzy%P3C!YsZD^!ya6?_vzGaqIIP+BogOF9Q{k%S zNCdh(?s6;nt3E{Pz>=yzP^NfaZ48MX;;fN63_N+NluC5s&?wg<|1|)@^~$Kk_7Th; zn|fFsklF|K%AsXd1-R8Im|+mcAnf&GNk%hh`*MqDwB+wt)dLuP?1aP z&Lj=l^QTHl7tT=iLQ>pKP$}|x)3`aRG{Kfd3Ajl^sx^boJz}29A8Rpm?r?tI*IAtG zZ3fHC{KcwtRF7Ll+uT`^Ec#i6>p;Ft%bP=uS-3={58*O3uH_KallwT!kf?ZohHX^E z2`55clK6(us&Cw+T8HX^S89;J7EU8wzfE-yMRAuCt^jy3fL_?G$}oCzn`F1sSjnMt zsyk>1R`9aBz`pRoc~wmmnv*@$9~2pKN%bp^@nmv`qlyl@q6$K(-4#k?)ndQ+zuasjs~z}iPmFpivit@GWOmV$71W}AVSFj5-x!^q&*+d-9Y~XF zs+XWipg;3g>qRFh48!WFr{ZxlqZgqN)TV2;szt}mf7I+y!!N&L zcUhV3uz0bYr`LC>U!Vyglwx-sRv*R)frb_Zp`VVbTZ;~Y)t5Or>JUW9dkI+tsaT4> zKd%tk-_eVFr%dIfYAB7rbrP&wG(Z1S3L-S%MxJs1AUa+7q1k@elQ2#C2zllML<&7CrDtT@bespjP!=uhny~@dQ)ElI-u*{xs*K zdOW(6SE!U;&LU)K0nJFY?12?Hv7xtYhSfBsW$Qi7roStWh9K?Cxz)mu%M^wm^DTrN8lHU-HY4(B)3259;tK(^e3k#Qs7ghm>y zQKIb755|zVkV`TCh|W2s zDUMJ+4JZYYxp^ARuCtm2sO2~0-kjIqCY4>#@TM-5O;FPvmo#TZt(yf^DEqu7hJq$P zAob*6X!2mJC`lX-eqQ-Va~1QIHO^=&F4Vn0fK={Zno@ikg-oo41l$d+mT~)&>&|I{ zK9SremtSedYS}o2mhUM>nA_^52v{MIE(_3NoDz4Z8b^N!);2&z=Y6v0yC{}QYV8=L z;zlmQ+*6$T>b37MPCgm5^%%$bGn^!>noC258ns2aXiJRRX8Z%MtqJ3_2GZPHgo5s1h^vmmT*PJwuT6{DTSeZZF=E@S=jR|M8n@|n-(?5I`Y&~+cgHc*ogG-UbHClh7m<3x0(<5y|G*!L;VBj#i{k?WI z+6x*v&@U}SLE`7bK6F`uklwh7atmdku|Xkln*nh|m_Xc3RBZ@(hkYyWX~A;EXrq!L z+l6Rsx5GWA{vkw5md$~NyhW*yr{6&7;4&c%g&1u2>-c;=@jJ7LNLU#Qz0J_${ocdpb&nkNQxI0chrW`L%)ZF zqY7}F6-FTTEhMY7&W{$o7Ltnrg8420{$$KzIJNoyuaGlX1$lMjA%1T$Y)6K@3h6AC z2A26IKb?4ilOKOuQX&4n`s)%++)OOLXiBK=mgpSWxe8CW(F9U@Pq^+NGD1~B3<~nq zLg&f+p(GHZA+$j`-M^?lIIl7tTV8k7D@jso=!7rbQEaEf<8A3!*z6*ap~rThN(}%0pDYRguk+dD$uU&bqSa=%(@f^v_MZov*qp3 zkwbJ-&!ZT-B5}mujlECaCh)CH|;x}ku*-xdCoLi(0wVozt9#uN}~(u zC34fDhc{#k-1ctar;j0tyLB2`da>@Y$X-8vOWJpZPCWa7XyGVkdLTnc3`axIpy~8M zG;gKuXHU+@kVBcULfqJ>JBq&Mo4LGE#&G&7OBczlMWIs_{gQ_;DYFXtx~)5P;zo%9 zC$PC=x9NUsrBM{0*hN3t<{U;H8@NUBM`<8+FYlv;>ADlS5pYRV9;ZX z@$gI;qBqf+X8jRFEC+^1VL`59F;y`+(TjxZ+EFYBw*NY3v9e~)UaUvA?p`4$v{2o7@k=>3p9 zUj-8T>+#^8u+Sl@-QZ5&fqL=mhP!jzV7+(-#f`zL4Y}^n!|POV5tdrs{cy(yj?sTX zS$Mq#jUs8+@%q!q5^_I=bWPS5=0ZUb#t>=0-bl|T>&K%)+;ABy^=gte9sc)rFF1C? zkNOHolUojEs7U$&rym)-51OQysqT1S`I|abUzo&agLCigbNZ8G)Ac(zFCM^y55i$r zv;DAOpZiJQ3Q2N+ha~MgOJ4?M<*RnSB#{T8$$mdqzZ>Obu?M`u>H8G@?|g`!U!eaH z&n#dU1g-E&eF-w}AX`w=^?Q&R9P&j{061|aV-5x)_SA#W0cqCjt6}@g;t+-%1H=?NUFK8BrO=ut3k{2BdqPL9{i=&7b6bii4C zv^eNwQJAp>whnRWCKZ`m&=x>n+|mE$5wRr=&UL3GT&uyla}16!e)+>K09LU_-c^L~ zJl}d8`j}3S^#ueecF4uDj4pkm-^|xBtR2IQL3H9P{TehgkK$49^=4EB!mdi{7w9!^ ze$-oW2-a=Lz9B#;`m_Eq&f>xTG7yU57GIZ&b}wWoif-T$vp>Lq$qcy|Ljmm~b{RZp zArr!hG6RZ>(Pr=!2K1DkC#OJ(J`O`~RKlGnK?EeWlk3M|y(kxENJM6wp7VsM41MJ? zJix_;pY27Tfu%Y$+#p&jxEBHcL>UT^4~SZNKUH98muqRiaQUppFJ;g9as z+(^Jz22!Pq;gKg|XRTM;j~{dmIC(Td8`6Aq1_;Z*SvL43+o< z?!sJ!QA0{)8UpEo6vHQM)FI3d4YfNZc(rohY&0Py@HU{D6fM0)T>?x&=MKh5d#)s7O{hV-*V1{7@J! zV8(4{6y1KwU_b)g2rw2w;_kru5pl;L&S~AqX4>hVK{T&!83X8p`-Wq<$>xy-Hg%Af zJ&Y>yrJT{9{`;??9-58Ujl8=MMsq$GM)7#(MTwhz-F2ut@t+L|+b_g&hKVo+l4ZU|)y}R)vAg2^lN%p)oxVnK_li=wC(!18|DPGN0*tRYX9|l)IU%oL z762Ez5E#*Sp>a?sjZLv}fa9@9;>?A$Z+UU!NH4FmF|zpqEYFD%#_HVo6pTL`LlMZ= zqCoEnnw&2>U;sU-*}`b1+0BgDeNw3Y1CS`ps6sciG6v!r!lH;m6p24-ogbfrdK;g3 zB@QdvB=D%yNCrHHbB*17ja!f&tMa_dqZ8yljuOTgR_hgLVmV(Z|w<*d~iEBclI`8q?6C!Qe1EuTqNlWv{U!$ z=;2x1CF4olk>pz|@debB*!xB;?qPUQT=u6?iyHIgh_Av=o*2a?C|@I2T!gam=U+w> ziouH-;1#9Ko*NB9GC8J6&IM-TIU<1l}K-#O7i>_ zECAhJ8O2E_$Q47HHRI+=@=7GiY$R@MQmcu;BSFrjf`NyE(R& z**`!_y12Wk3Ied@fc*oR2moT~Wm>@(8g6sNyTH+95AM$rSBXzO%S1CQJ>$(6M7^&Jbo;>D^Z*>^kv6h6@b<9qAn4a?<+m zOp8$^$fx{iI$G?5h!6^b?)*l^oi)rwa0s>lr6`C87tgF)VKswspqq8(byy2LiZKOr zG+i=J!>J{TnDImlwzqI3UDB*3dy1IRm~f&GCl29eJq;^r#!e1$voe{u*KD9A!_E1< zCtNO+G#|sRjV~gckhYu|t2=P#g!~B%1(8lw&G1E>SaTqqS_GPxDw>p_`bJugr!cQ9Nn_$KV^d4gqrQQ`t>-=C&K-HmG zqdn=pUglM(FkA6x)yeGV5rwOtU!3@FMqW3AbRQ0V;7rW6FuVC~3h0mVI1F3S;7x2%AA+hJ@O4 zMhV4TMhY`pBXeHt5{p3$Z`4}EBiMXBJ{E(83-tJmDsTyaKsfQYTJURIJg7L$CN@g} zE{yG9q(uPk!m^e}C?|Il1N@R5V?p0=hIt1pVN_YsBA$42-Lo<+I&yEm z1@4_Ap_IOewT#2%jr$9TxJW{2C_~u|Eys|Q8&HDYY+{+i#pWd|y9u~qYrIA5f81y| zpX8N-sO!?oBAN>s6yDHWvINqrtu1yh7F^ou?JOs-GD9L)CW+--&R@EA5nq?cS85{| z26bokch?V=Lk8Ybuslos!Qv#%%eef>z9`VpIofg&gAA6JdSq)90HUK^O4>NdatYb< z7b!Bs!1q0(VeOdwqouKzpEwF5KpoR8W4uK7n2jti3u?dl$+8Kd`6l05!KFgzr&*Sb z2!+iAbc@U(KvYRT&b2K6W*P-gEfcR$*b8Xx{lPJSd@j?n8?)bAXBEkd0r4Jhw)~9E zI4=#zBHj{663PSg%5KYZ%n)W!3UV>5Rr^@?Le5nJZYRi0 zR9Fl1u;EsM!Vy5mR)IF|j>1|BrOgLCV}Pz&E3SLH4Av&trsoTGrYaPV6_M5($YyM5 zYYR>szAXVgPj9P^d?{_kOuidpi>6J>SjCMSLe zj1gUR>m^PN-eSS#y^`clEH$ie`4ILnz<|cpv?|f>yu(cBVD+cT4XyZin;vdvRiIkX zc}J#JxBAhqEv*s;I6q%@#7=&SZRl6L#b(NUh=V?8cn51c zt}Z>_(OS^c8CHe|4_2`TaUCLRgI-C!trdlvS^o=4Sm>vr)^|vmDVBP}swGh)g_VG0 zG|75nb&?e$t@pV|V7S^Orw())w?|p4BR?pM98FKVCRxL=FJkQ)C$^_9Fupj;D#{}F zR>ml6XlgvvxTv{S@y*{MC&S8-thz2GQLnNE;=y?Gd@C;hboD|v9;-G$S0WkpS)Qj_ z@g#;H3ow2(eTh|U4InM6l7z=rJ(<@4N{MT^^#cZmn+ENjZjH*%7B24Jja>?o_#Gta zqfF~qEL-d_hbFJHmPK6Ni)59vl_cpStpQZK(VBpCAvKw7W^$k*EP1ClS;cc$D81Z1 z3R1EW>_WcVW)-JX+!)!ZaM7OC2sr+@!}_=|w=(Ypn3YI!L7P9_eAOy0+u$rLa@P&3 zXt0lMh_f9I99S09z_wQ|LXVP288m zLp*LQx=CY0Cs2qj@jWUY4ap;|K_s~agxW%bZ7(NG+Ey4vW@B(TY5PuCDh-0CQ)}>J;|jK4&{E8-)LF$Q z?s(t}W$;{)IqBX)EBum*>b4Y|>xr)kacccl_Yj_Nx#fb zwBdI5^5Er_FR=x5S<2Zq@yy6GfdbR(UKY~{KvC1aCuZqv^SjBwS73x618rx88 za5!?_<1ia(o&fFKxec~7;Xrt%$gA`6%{C<(EWhmD-eKbl0foA1LE+e*KJ|i4P5gG* zaD8E?lzb#_6qK~;-NCCFUF_DQL1+zV;?`)M=Yo@VGwFxi&k}RUfhi(=}&prID@-s-w;e zw%aH@^A`{fr426GPGTZ4UL@h2O+zm9bVbrpxi)ck>>)xI{$YEH6$7NGOtO1{1uwm{ zDN$Gt9uvh%V_w@*klEbfj>=p-s{Y4T6p=mlNPPysQZ(tEZ7?THfpDB~2&?+cCcs8V zll<)B854xNw!h=5(FiOxN%R=7db57e_hcLG_4v2R2^0}Q ziFu4REn#2%jk9R!()RMjd4#i5E>3uPEBi4t0$ zrMq2&S;gP=#c#G3$$t{gyO6Abu#<})2pM>4pnVF;;0BbT>xbIKs}5eu88S^CZl8)m zFog*xkTuBVpgkwqZy-Wwf{)p8(Msq0Y4l4er}kl}KIg#!puUYZ-DelCu~|pPqb_AQsF8BOUS0%|aR08uc9D5* z#ezuZ5n%YTN9}dd@a#?P;_7}D|oOFxEp)86$mKidhi9)sI9e!w9 ze!1klAL=6GCc>gSx}pQV8N{v?fMqN4bs`+2{1xk1f(GG7av*30B25VmAXAbcNM6G) zN9RxqUkJfN4BEJk;|i(}bUp z=}7!!h?s_L9Up{W;A?R-v6JIR{1MJvph@ZGD2&svJqy970*brT&8P)Vrff1^)$iHe))iFJqyp+gc+_xSTl#ecgcZShNrnaWOL>N|AgNiU3kf+hwnJjv3N88(_N1p z0jLE~;XVu@gBH4iXz8bpY>Z6!W?)a0lSLd0C%W;i15c+2kZVQfesEO5n&K9lh7|}s zh`!~S22op*miG%a;ZELz1}S_3rQsP3b}`XQ#Y4sS;CM1ak~-=1QlT}Q@%&`F3F1t3 zlx6GrJ4QO5m?4Dpj@AXMq*6! z2mpYY-d!FVgJW*tY46pcB9M7c!;IZJgTXP)3SE;aU5Q zt7((LVdASUT*+U4flwYlEbIU(>243`h|z8|`7%r9tOF_Ljt^^t3k55ayz_J>g^9Oe z{QY!Ba+r9J&)09*)EG$K?yz8TVm+w5ZEBeK+7{mnu`LgsKO;aA#Xwubt|5Rm1{99EcHxg6IpOg*7~t{Yu>QgT?DQ@9SXe3US~w@D zOWzy|i$=e>w~z)hZwqt`!%l>K;s6SG({Vl|K{+CAfg9n(b729r{+X}`2;fx(2y4lv zlVMsq>Ri}4{K5=K<~?KKA7RzFjo8C13aV5?iMbWwM9oa1wt*=|<%PvzXt3iq`s`iU z0vER+y!b1g?|cFlbkTntQZkNlDsT{KB3qeh54oh1Tv7hE0>91-I|n9ni{miIslk^Z6F5YaTeN(yx#(Ju68)Q96Fo#EE>aAqiFRAqV3> zsDYHnrG|JAOBj4Dr6nhs(@+XeYC7y@wDv&E(WDEBePfOvsW|A;f>SQnl7{6{=D996 z)+|46%BY*#siA*b*L#bbhSv6Vh2Oe#i#Bh*?=RPPHM*S`d+6tiolb=Xt-CiM?bI=Q z^0qpQ^MZN@m1)qz;x{p`dZlM!F6ZOIueK}uCs}abUBZkESz!aVfo3UPkblMF0iCe+}=Fbwk(mBFy8N=EU8xd_bn7o&p1W>W)82_!4((2|b{_3|yT0?yDie3V-mxoY z6CLENcXwD;^!BWx*P;W49y}U2@1MoJM!aA5-EYBf+LqtesPB*8`>*fQGkngq`#Y1% zl>8^+rvd(*2M-@MHg@m+34JPlTso;|!!aE<{$A(jn1&lh4-J1EL2^zUFW2hk*iqV= znR{;OrL6;M_MQ6sl9dm#&RtEaQYI>8$=!x$j{jD2k~KB3>5?neDn1{$+7Ok#xb=={ zwbnLm_vh^Un|zxtURpTz;Mzu)%39u?+uln*sn`7L2X`##SSGLYi9J`F+!ir!X@YBt4;QuYZ=<(^X`+sPAb>#XvFy1XB#yfGiu!E7bW{nzu&I> zxmTa>+-*IqS*wjVb~n0L@m|LkQP!k@khixEpPziS|K}IJp;bms_)>QNk&^ct4JbIL z$t~Yi%imAD zJf;8dYld&T_}_~UKd9Ytt_@VSXp zsUZ7Msi{dQx7 zw-MZ1wUyfOku7bd{``ZZofsL_PRM+roz#|tO177J@DKCbOP%?LSM3FIlMVv3qk|Yx zca*wuoS_}1c>dvdN2x3S5Z+0Mv9Obv^|6y6*rBu3oa1cjESUOtk-GDd{_scT`G<2| zq}KdH@vc%U{$YGqVV3J%1;LsLV%F*eLAg*jVX^Mrq)vR+VfP1TcQKOGUEthtf2jI{ z7+K=}@UQzr>mCBM!~G$orx+RBQyR`${Ml1POWR)306ub}mk_mZZ>a?z8QELv#XsEZ zEkITKh!3QXQ2ApY5q<;v3Rhn5E9_IQpK#Nxeq!X2`$NtCVq}T?!yEU9rUL}%mjO6E zb)X>A-u+>_`-5VT0QGQxIN<(Z8Z1DA2TR|heuITJQHfGVoI|I!>zE+%uJKK<%go(#a+3JAvUBC;r1EBc~EJ45%zm#?i_Xg)9v}AK!Bl zWa+B#@hK&f<@Gc~-8)P&Z~_+@0mEz@H4GP{k;7qh=sX-9G+fe4%wnt0TD$E8xmLp8 zS&WpCy-hNPi&A!nO?{S%Q=KD(M2+^X&JGOl@hPeQc53=EL_N367haH$kkC5wMgSF` zRS1e&1)v)M;?}wRs7&@`Q0^EgCzm4h7sQVoDe1X#wMGJ!4;yiGihFb^8~uF;j@}t5 z8M$(6-}c{I77Tt8{=$Tu>|GKvN+{QC6in@N0H=}zzBbaOuaI~z0}VcmpseG*M$&4R zux7+)08KjX0olp&t}axjPB*_h_ra7I@RwNQWF~!Zinzu|;pFCMDVz&O}Cs>6-bbAG`i?LdQ8;XXcPW%TaeK>n(mzAADqNm6p6zyE#Yj{@M*O<>R% z8UA8&e~eU{6Fu9v)`G@h8YOU-6aDuuB-(eZWaadJdSu@JFVGqSe}Xt#MlX|Oj+JV1 zdUFoO{kH?w#p^~Np9(UhcmE>2;^U-Hj+)f#aNX~~HZMWODl$}M0pgk}!F5J~f-d5W z6(R%gfe^WeLA1&;QrUhUd>eT=TIBk~@jz;kf`d{Q zL8=#MBbj+v-C~eVYMhj;E~q6dCW_!ZGqCsVG9YVbut7N)lLawuO2H&MrXbv&^cHhQ zM>Og<9RwQ{?Bf$9o8zpAJWfxL3UkgcCjjR{)o@f@yI=^ZG*JXu^N9d@R?7o2bD{XG z%?vbYFoL!w!mJW1CuWE;2|%;pyp)*|Kw&&I_^crev}K3~6hihUNlp&B!$4byd4Y;2 zOBN2Ql?)*DNDoL$8U;A9c&=ce{i7JjL8J)<;qM|Oi^@`5+pbp#m~9sPG2#fB2^Nr$ zNutSVG708?9PgR$AU7k0+p`#G#Uu|Xh@71SCicL!KLWhjRD^GxQBX;erUL)YKZ@iX zz~BR?d%;&s2lyU<^Dqqjc^e50^6_~9#a&)dXU_0U(vgquwEivPhlblgpUvP_c^DK} z=)n&r(F+TzxsaSmo55|KOjh~$+$rYc1L^DaPnU|5z-R%aMruL-{7J6G1*|tWUGH+1K*G)h?Muq!`3njf^St&nBurW}ciDu%^SynBf$XVg z3rghaVYB=WqQf0w+PCb`JIyC7=DPoh>vwmg1B1NeDRy6Sk5ghEC# zr%Nq4(J?ib^&S9R7r8CQb{i6_?bXumP!s%kVC3M@F!}(VjSry zL#m{gBSF~Dl5{+xmOg2ang-=~f%L5hhLLWwq#E3!YdV#z^BSUQIE0-%mj)C@$|;2l zSxHn2VUHJkjy3TCdt3##%2%s{#XR`IiFKpgmeP}SA2?y1ZuG6uXEtQ$rAc zY4eNe|L`I(z>PA#|Nl;{oAt;QL8MJ>>PD? zm(rRlV2VI6g&cK#8gVUxx*M1Vp_#r=NO8LSA{znxYv6BrH&i*DV$@w*h^} z_UzH7Z_iOpdk*i`qucOqBxJf|;4G!8x!rw%L>5G#oJ8zOPD1ky2{Q4LZ|!lXJ86@Fy#8#ksxaHj%K`SRW52}2DfFU~`9 zZP?<%2P0Vos6@T@fc!}wBLye@4OTxd*KG)Rz9?|ks(uVl2mk0@imI9Scp*Z^8QZd?xCD+iUnVTqe)odd1PIY$Ae)@P%qWIhmCrrI z=GH6A2J9q04c6WNmPuAF!6wc0Z9hS@y)k{O*r*B~d^@KyC=IBzj>FNlX;LXpV0Fbr z)ecxns~Em*&|cIcnaiP2%!}~TlCb5%$fcJ9l@E;(v^2&~LuM3(BF>DgZd2spRtR4; zC?qccrCKoj23(B47=hX?*+6+xZH3Uo|BbFOyHhOpvj;_j(B(AOh8BOQYplKp7jgT=sIgxZ*2^_y>K#gK# z9hpGjX|iV&{-6qsaL`x=S~!`3B1m>7Eaps^0gl(MUSN#`utuDW_7Bq-Y6WuM5}+cT zkBacVK6a9W?Z{1#&vMJh&S3ax`Hd@?81vXbNfJNSPeDGf6wzs21)$6Y2ufHbg>vDN z*=STM8;u}$CivMoM-IwkAmtJdNKM)v6-I2o8bIl32ufcqnYc>jPrXR238q>S_-!%n zl+I8~kk6|{Qp}l@HgF?o{RaM%SEjcsNw>A|efSP=%ErY=Tm!tmtYV|F{Opa7-ejY% z*WhTGwZf>oUiEI34GxQfgq63n(mICPh|k@!7UnkGzy>Ok2IZYt;a*Ov^Jfk?qp$kg z8lAI+p_U|_e-YA`J=Uar5nyu}{*yN$?|x;979lIr{EQ?mUHGndT2Adhz;|_EQJ0th z3A+&Av`!?+*`=fOY#S2^c9bt@`wn1Qr~KllBu|F8&^oi%fi8K6JRmC`H?olYn}H&Z zc|b}MymI5AW;zNyc z=>Gu!7Vwa7AO;lm!tcCQ6vA=v!8fP47ihfNUyFMyX8ZdLULn#0kLFx{yL~_DAgo@Q z5RY|bW{-~YpoWsISwd@L$wu><7J;Uxi;quj8Gb@#559vqw~1uV9n$v56<8TJ!(pR* zeGcvH!4Dwg0H3=xWgAG@bSMKACA#T?YoqU0j(Z4py9jHO+;+uBAnN%}{svNQyNH0s z+kwiU(H@WzdpTx?%?#u>4nZ%ri}D@314g_3h@*{nNM>%FxKoo&B_SNV@& z+8t7HF86sxV9;C~)&44$xP@PKe(no$AArgyZ)c}0@SrNl#9xKAGAwOl+k(HVfk)(9 z?PiM+e{-^b0QoZBKM=cgmVfgT=cTZ7z6vl&9*BjOcqXYx+)iPf<~u>uzta$u0zYsC zwy@D7>7LO5a#DhGMrJa_E&$n9B1km{1mGv}4AhT-daUt)R3ze(s7jYx%|22F#QN|* z`O$hE!;c^;D!RbH-ECX$0@NX(fjmfZH!@Ug;*u!vdbJhjrW5~Qa-Is6?lMr+P6XXa z^EZ<09irHV|BYi}jz>4D=ZUAuCU{Vb>Xv2x8wSLZ&i&@GW)=N0Z#6 z%h~9JyPnYi@@$_-?yn3~>OKRNB~AAW@2^tLOB?|l7XnB5?M0!747Co=#T~1r_}f6S zX1Z_va{Et)Rg4535Uc$4__=>q0n`D2DsKqBe}Sl9uJ|h!SB7&@7Q>ScfK<=kBIGC# z;iLE1=%V*HS|itAk-s2TJqVzFpU99yaGjKN5H2wonI&xW{%0J$b5N`*&$qwMSPaUg zLm82WM>QXE_?T3R)Ho!H+~ZX@w!H!yJcVtn{K7N3AfhfmBznOz_Vu-Oq4)8rF2gV9@ZiH&cnb!I!yh(j_?aom z*8E&wYgch&qO zU=v+gFSvX+;Crp-t2UtNQ80|W(fu0ZnFPP)1a=Rc-hiV(?@%QKB{~E2`RUZ_-=pcL z!T&43|5au3{ZbY2oyWvxA?_GZxmkmaMw5BPL12C^$E;!~Y&jFaQUrvOTcvL;hF_bf zNYM#vqyd1{`+qu^OLZ7lBx!bBl!`{Bm;BTjP#=Qn z@I3*>kQdMn4G}fq1e}>M)Phl;_AiB6wG3)`37N9Xn=#a)q}K^i3dX@4-#PJY6n+D3 zC2&!np&}nIGSIe`9*{rjPzB&&zX@xX{te&}-!r&_^!iPlFHB&gKer{XeuL4MA?mqlbbu?hrLaqZ)1bh##R$n82U!(&8ZHsRF{C2{6(Amvl4P6~ zv$pbCOFSTWD?JrL@7-y!QYg>B)QuT(I2gCPnBSMd&1>aw_-fG^VU_+hsswj{D7yxw zO1|FhS^w=M)mbrV<_4*JPtdwBOq5Sb-1_aLfr|nH_&F8VH2ExuQ*QgWNKqs};nfN= zq%ym`keoT9>{rVH(4kxeZ8;ub;rAe%bQ%MFdFTNJk-ItKeBu)W)qd&$1(2%eBojYY zIeD#O<}p}|3TnPpr9CNo0k%PN&WTf!ZRdc<*wS zne#^n*cp{T(&mC>i5THLumAYB$6AyU6cH&0b(kdU&3kT^UVIg zk6Z-NdZYNaVO6_%{yaO*EAzvEZo`IoUl+4_WB(m|S*9yulRi}gBRy4WE(2ZYCP2Y8 z0?i)Ka|WW_Imk&OuZRj%>k15S{y_{UT@jhOj15oiBZf6C0t3m}x`8JC-h%6KZ%pt+ zE9gEB7A|=`zA!w0mYUqUCQ4bit3a{tSOH7F3OBP%nX_#8(bW9mKyu}p2!^K8rKQ`U z$C+aIwnR^#%_oGDoIZg@d<4RXrCbAID;INE>3r7D*zlgEd{`pk*TpJX?K%vvTP}vj zTo?P&n3cu4r$SKpLr}<@%F!F(mm*0ux&9RsdxWV^4ScQ{+odL-jYH&`N=ohgk1-v$HGQY zUVDb061a2?h{@>i3FVH4ET0MOP#VOud~dNSM@)FRK2S$K-xm{V9GW?-8dR%uP!TH0 z46)>QG2wO=IFwD`S~R{5l2-jgz$P99V(D%zwlJ9Pp@8Mx7LA_v4iw|JPe|S!*nL$# z9;nXOJCT9*y++X2bAcfo#2W{_Wx$C_pvrJiMc%R<{lM_C2dr4Et$rjpK>8nrJNu9V zkD#&3xD6s|az+1qpmgI?tYMh|&X?<35uPas{6ggJT_O9IyCC4@AOywT6U$52doVg! zi=&zF12@efHX3fm(SC10PsWym!g2w0Bosjj@B<&62pLXWMlPcVH$rGA#xL@LFiE2a0NPT^3-mEG2ra-_{kf!b zN*=J<3asSKU7t;g_-lTWT7 zRX+ZXDixAiLpb$>K#_0$4#5M1M^arbWyuLGS5$(Px8(l>-RO9vy1!JAmb| z^O=w(>KTCUZ$*%8c2ER=Ey-u4Gf(sk#J1rh16|$e0jWvcUxHxszW`L~ z2!f{n1$XF7g7s{4*I68Wxelz6=bn&N&;=J{=ShLLQgO|<{X&bsVcuH=lFFNc;E&$I zV@|Xp10A@`=MmrM;`G1Fa~QpM6-SHg@Sc#yKwoYk=-zXw1Sb>x0!A}#<7m?tqU9dK zM&IAX(Q5mGtR&}!;Cq{ay4**Q^inFt$uxTj6^fAg|G++N-r=AC^7Bjh>IhT)G=nrw zeTx7)zD5N=gJ**m?>Yua-XCIFenRo%W2DXr1VUGM%}4{hFLNM?D>n(_)!Pv(GP9)nJN1&aOr4}xxA2(smao-xp{cL++n z668nrU-E``d=2nYA3X5T{HdoI=+Q?H$d8n{=FPRr8-Qp2hwuq+q~cumb@T4*WiOWA zLypUBlMSoYCkW#UhoB}rb>l}bp1S`O*s?v?QofuI_4C5tbw4PC9q(}-*J7HSKMr$7 z!<KDT3oSr&T|(Yw3P~FE7lOg1qp<--08hY?#uGUO@ScbF|n6{ zYO1|JpCS4=m!sKL=dqihwg2NaADtI|-*-|mPHO%;AXUcb1^WG+RGO12)^s%855jH& zBi58DxCGW-j}h;@3Bsu4^oINk^p4oQ;QsHu;ddGQH;KWE66w82iZbtE6n;Y)A07N& zl=o{z(>`tm#?`@Ub!7rHaWT|lWZvf>7is!QRKLBZr=Rx$Uh)6IA6S^-mm={W#I?$p z55RC~5znZe{QEV?ME?391p3I}%Hkflj&%Jhdgp#00W>p$f$Ec7KPNso$+cDanSim* zfw3CNWF_$I@DqIDi6wIty~3fm;55QuJ_7GXJa8gcHbR1oVB8K6FDaFFW|&iJ9|E* z|Fjl}W`Mz)%C!D_1fq^C;6#HjFW|KMs>ZR=nxtRbU}gT{@y=1(hKvE-*cWN)$fz8j z!0=me1t#jZ_r3tuJq`|*^V*t>yl(g+uhfFT>f?_%*t}~n{HdJ+!h?wn^xrH59V_U> zONLxFT6_^3g|`x)#JQjH#F6T)fuSB4$}7-}Oom#FoSgs`X8FX8zpW5ZX|M`GFDC_u z6b2D~E!ZB;?zJk5Pv_^8?ACK#{%D z&cW|afE<;;9L;6&G&qju?HxvCEA&PT6sC06S zmwO+4z)qz>!P+ug6(WbVPDkp0un7WeTfX}^_q3b=1tY=d?f$1i4u>9#u}D%mwTqXD zQdmO;%me=PK+v%xW*2DO92U$lIFOgO_ywPn!zyPzE_i?V?yoO>!{*va-g(HvW@=%| z&T0Uup!eacVlE_6mkYrG`S&cXmo@%-8@R1DNZDAXo$IaFB)!IoxBk^MK(8@0L>_vx zHBQ{~tm@U{@?$X4Ffh41$d`Wh!vE_A1XXE`kgt>$=za1b^R3PjB=<%z{Ixo*2h-J`c4tB!UK?8zKQ!G3{gMnL@L|# z!0?$9N25Q(OHcM48y-A0@BDbEARNjh!J&+Nd0kT6gRdryiirmcUl^%EE@oOrsP3Qpsip zS{LU9`e1b8Q5Rg>!go{adV%ts3N;yH5(?TYg@*)!iJn5R$XmGJMxII5eEghGqc%d|f_I*k}dP$?C)>S!?QEtH+*2XF@TTFND8B?_qT|#6rd9&h3K~Z`D;ONKUA*YuK># zpV-C0Jcv}eow^QU_+`jkn=sK5|CQh01Yt8_$0ZlmK9ZqAINF{52CPGHb~lv2pAX$VmDx&iC{O_o#(-r zh@LH2XD8-Q&p^)$JI=VX>+Je}Hh8}*{&^lAbU(8*v$M0az3+}`BK2brO9yUgZlnIE zR`>8j%~*XG7fqH3`rBRxGfMZ8hFaE?u^#!={%L~h;_M0S)$?bgI8=_C-=(s6W^pOp&t`8uq-2$56Ba5BI zQdi+(fM)QccwboWd8Va3ee#iQskATfyLmb;$`U)%@pLeBn+e_2iWqiMCpD!bzS1H$ z7#Azt4$!6e8D#XS8MLJXyMxu(xoGu&!^HqeUy9}$qz|+`X_c`a=6jA`^|+ST6-FOK z&kS;`8ucjY%~-f*HLzDlwdJoeQgIe`pa$J6ai>5tRH1{KRBLkY9w~>=dT*lMLm|re z^XwKQ4Wx4Aq(%*T)=4e_F}>iKYL936HzUP5&i^b+(S$MvaR_B;8G~o>A=I)0z6+Up z+|rQ_=fUFa3Ly8kmst&NO@UVZEk zy~V{#7ReLK1F|xqi~h)vcLzZ}r@VB$(+nC_N{3wOQw3>(MkfC|wHp?2fI(I7nqaQe zo6`Av2#2i0<7HW10sOK{7s8)DKqF>MIbOOV@DJ7kkFO|=(~qS#aT2LpE6Q#zpQT1S zi`1kqmZs!cNzP5nuD!o82R`A1E?ymdw00>(pZyA72VYi6%HC87{6>1HxH)~RBt59i zx5MSeAOO!UU(~1FH9eCRHD&jh*nIM@iGF{aM(qrOV@E5M^CnoQH>Gr2t5Ve1%F2nZ z`bkY5`azQ_RaGKOiu9Lw3xD9wwF}`({SD%5`(5w4^EyH3d(?{BvviGM^fBDbzLmkC zOnsIRNPR2I*ptRmqZyLs(uBlZ<+SfLgZFA!}M+(6MIMOpF}{ce(@R)dj2@opgFrN(eHPrq6*gcKmd6 z=z^8VG!}KOW;)bG%_MFSqy<6#nFJNG;KLo!&t6>=z7$n;48LkO>EBCa3JR8El$OC@ z74!qWtZJx9Yl018ar6RGi+kf^3WhMsYit!t9YW*)cR~p0`}Ak@br@8WpE~EY;S&mI z=^!1lp-G{JDqNv9&GW`rg8#h2Kp{o(p;p5S(R)`hROj?D9rI$HVT??4)om1d*KvjD z$91%_;itTLrnNJlT-}HaT@l>X*0OoLp2?cRs!BhJuL^xHPG?Y4s@2QNHlWBVbmPUL zWiMYvy+ok*>7=MJZZ^x-hnKrcHSoDLmnAge+_^T%wSJ5rfV?Z=2rZPnc0V!F8st~q zP?3|qpvj}ylM?XVA;tI^;@E*rO0E z^nPNH#XbR5tz{5yiZ_$+D#HJ0Wo?7YceZ6Ffob@Uhx{x4oZvaU@Ied7oi9DwVjHkF z76E(;PH6_#hRkj}weok|^F5=-*~9v8@T<0feLRO4C_ZH@s{P6sSE1ljR@T(Cj+_=G z)?p<&UkKV?#~}6;uQO=zMIExDzGLM?yLlL((zkU8fgwyXJj9@y_XK2JSH`&o!~f2B z3Nvhj-!@dNa0?HANVS}((-|3zD^51qHXO7)zZ-GadoJkbo&|Y0f((6M*prcZEI-=C68#is2dfH)JZXnwlbZqXl+AZaUINp8N02W>AX|a za*n~-#41iOdmJq_F466ECsUL!AHP2+`QzDq=m9#OV&y8h|>e~d+sWu$S?1N9Ndxu(k)BGk<{S^$xhB#M0qM@x7C0DaH z%WNuP+or4>orRj&arlyFe5%2VcN5Q3HwLr&zzLYU#w)rW>q1e@4b3UCg={z*Y74*)ejC-ul+pSBD{&9Xf8MsQK4G#wv*yX58CF&z_@)-+*auZ-vSQcYT^0 zhH(w{Hiwuc!*vbK$UIhR;2p~}$YmTW)7}RN9qYQwVe_SQ3;z))eJJ>=!>OlR3uzoV z$J&y@O37;&{>j(BK7n>xMSi&Bj}$ zAyloIatDOKvMmh3ob=VzYNI|NcsGwOy8ab@kZ}#7>=p(WQTP_D@aGjbl&=S|J~t*F zaH|VKmHF{<)OvuWHvU6MHE(GU7kmR+B6aL_o(i8dqYHbiO)0#!oCBrpvG1@S`ujqE zH9q%z#qx-4k1{XIHSk8oeu7!mK6UhIW*!r+|OojZg>Zf*QtLzwkP-Yv0xJf)c zWkS0iP}L4T*i`YZM0dK_33Zp$4qqnU`o`Lgg7BmexA)>UkX+lV5dN@@EXp?qwel58 zWp}YHLkB;hrKGl%tv0zOVhHT# zNwL)=)328RN7rO<;CBZk;@bmUDL{JSXyeBYO>z%QyPj2wT^S37rPK{g>u|;^nhc$! zt($cKlk1UO0*&m9(c_j5vJEZH$$tGO+=1OPQdg@xH)QmUDbE0n+51i0c#$2!YE!ge zHM65!AbjNG-Ypz0yb6xssDt2L%>-$BCj&nH428Jvz3LrWm4JFPK|^e*)LUvBonA~S z!fV>bWI(|@kgE#Dwr8ZBxLQFdlu4J4A}>lPHpqhqTK&%)t}zCqB2}%Km~X+{^hYh5 za=Jxd_qAA&4vm^x71heNhagYsA}2a)mpt_DkKV7Md(p|VbzeHt#n6Tq^UGq>haaGK zEIMK}_&piQ7&fM{T@B*uaBf!=U^E^vgnLu4!Pbnvb(O}d*7-|)wpquxG*XQ9X#s^M z7+P~#!}~^cD;|iG&xu9j+U*P`t5SjInHr<}vjhlja*V??cZH1y#U@Ip?2-tm%_RZx zMwF8%XR(zYtgF%+`h13E)lTQopM~gce}UiV1KQS~etmr&9&!%j5p{)a)>Wf=LzAS( z&PamzefJ6wcjMOW0egxL0Ha)7E>btycK#!zhjlZEfnG=a!=&L$k($*_4(kuI)E{4q zRD^a9^672}KnpH>N4TKUpXnO4nAxMzlMJl3=5X^0JDFK)5YG1T|qZ(0?4|jl;hPC z(9V(qy1mibU>pYc6>ECDErhY{Y_%(%ZYt={{|#0a{bYcBZ@TpELwIj*1lUN$qq-euqlS>9>ouoDFcgi`Gl($83R>y6=P@Bl*%PCBE?!^{cb z+8RdcqL9{Tt8=xcqXT5@-}8t0lXswh1}#+EM0F5abK9_aJLlhvde6HERoLyywhI5O z&VqhNcHo91#X^qfI4!vq8e4`wqXH{*qdnS+2kj?nRv)U~y31xiBO z;|6tI)Bg{4lx`p@AWAW4+E`YqaJn%-Hkd<)_O;bO`Vpk53TgZCg;bl{+~CB=TAY8y zF}G8~V6_ftySajF8win>h0EWjkz{d7<-H%k_qIE|#=yxE!N$ z0fRQskr8lIj}el$-w5zNBU3w%H+W|rK%h)bla+bdd)~ZiC~G9js#>JoLB>f~q{Ht= zyzG4OU?9e$Dplz`iW(`YTzbs3h{2+Wy<5={-I7~$WTYI?`HX@>iH}&eIGQm^Mx<<( z>Vq>MBe4dnr+SS>>bmEmNOND<+Gd`$^`v>DW!bVAJoU9;WidvwGK@iL=eK$$8yYc2 zjy`4sDlr6)1B8yn(XoVQwzhQpZ}dZ7#~Q@#lHVt8KXm}6SPNf@Q7W}24#gvvI88Wv zEcgsSs7Fd=dO8w&bGQDswdQ zBITX4Z;=-DK>YvVo{$3M!Car!F+GnBLWRA?$S%5&rSKSVd&y$p zEX0$mlMQ}6!-v6pJBMHx?b5X9m^`(s%n&`r;KON)|5S0#91I%*J&X1^#Ru6@xBB*F zY5f$rGJ0+b^!sm=QKl_P;}hnzmhYn zK=BJOo&TqR*=NSA{WhZ^N>bx#Qc#y^z^b1%Vs|!%&9X$~!A&PA)3o9e&kI z9{;;%)Tb2ncqI$o#c%t7`!Mb`hoS?3KkrK`!b5RCV}_wTm$1>lS*=;{q-o!IQsw7` zO*`(4of#$0ltmjj6GiiWDb?LIQ(Ec_l1+}7)3&*WvSc|+E^h|SLTYO-mKw&*z?=M1 zmfEa5+5cpy$ST8%9?z0To$O}=dK)0v)U>d7B;Q%G_izb69Vk6doBr)qO#6OWk#(Jxv^Rz2+PXz3ieg}aYI zL9GOo5@c`STJf^@%>|Uxi9wa=O$~b|qr#J~yxJZB;Tpm~l%jB6S4Q80N69AJ>Mve^ zVGDaNSXHxgk`TD8uDv@i78lrm9t5`ODIi{g1M}qQGq**917E>vD~5FH$h`g!f__qc zdk@N5EUPkXK3Hw&C!mCLcIGr`zQLC-gEtx3X3zv0G!M(*l_wUGH7>#4jK0j546GJ_ zf#YPoBE^2}A@d(LIUzJ{TTwKmotr9H+4Qh?F_!2l;}V_6xcITaXif9y%Q0QWG7Va0 zpf@^&)}l6;xQmQcIhvnq?`^E}y5?&fa$(5lUPT*!{NF+}nt&~3AF?+!X8QY8)|k<7 zlyj&nbtK&Fv0gMQ8op4DX=g8lzEMwwzWL1@Ea}F=g2m)}E83?({u%^Cwb9Oa&gkp& zD&LScYHBG6`{}!mvD15@Wp+geGeS)Ero+G6n;XN;#h)Ph>PMl~)JZU)^M8I*R>$M{*q3B^Mj2QHRWe#o0_S z*oZfm7o%}+&XkS&B1`QNB_yu*cCe;XljH(lC;>{0VHx~sB+1FmVwT#!rLLkg&CHOV zRD2Pj`)z64QbSc*THM~Ax-XLZiQ^bl1(yODuhG>U>2*r_8R`dSIJolFdG6aos8QJm@a-`Oy(+o+QNjMXxsxq@ z*$tCT&z9n|vLU|bafXM{!ECg^FWCiwZY7}HlQQcnhccA1QbK7A8k;Ym!z<Ej0fpaV5t&&mvl!;#mJGQzA(IHYPYKtq3K8%8XmZXQy7!oW& z`US;QNrSF4(l~C63iHh!+0h0!FjS4P?q(qc>{rW<@Wx)~=5R+q@qfUBQ&txgb~T`W zM#z$WU2PC|YW(YlolAl)>>CT}pm+TPos}bfISRY7cY(PX;@5!R;DzkRIY7G%3tZuGqV1^=eWatx68yyt-GH*##TGI?l+EWmv)Rhm{3>JI4^ny~-{Zsg;67DsNmVxw3;kxi~U| zT>-5E%^NF?>qKZF`un+3z$eD4L^Yvhb2~?Cir*|9B5^Yy(;5P5;oulbSAwM5oiR=Q zfjz!C^Sc%|u9lFJy2HhuE;yl6jw@K}?z#p1p5wa$tgNYf9Be4U)e(0K3h;b}zX=z( z-ByEmUL<@gQfJ}~0hTLmt8}B9w_ik^f&xA$rfTtOO$8}8OUZ4r$$M@Cm%-?-85a(X zE`Sy=XnU+E=GN`l4!*rj9wqzCpz|#S6!8#{4-eWOr5W-+hTXl1kZ=v-QFV2Geruhd zSi^lk(*7lTTDKGwRvYS-c0wyy#FZ-lBI_mU7lBf-5pH&$+nyy_$=)!O5@{Ov`*V)2zpeME&aR4;KnXzp%yalD!WS>#pS-1^#BAJR;<^AJC=sI%GyG4#E{u_RG=aOosQ_ zq~lIxyj)l&H zt!~$%SQd)Pwolb{wOKfP0^^9HR6Z!Zy5&Krx_XBWai-$H|6T^=?P5?kWv_5FCHF(J zHi8ZTYIID8+-Y%V*{-%S=!cU!WJYav$*0#&AD&Rc3q$FzenscJJI)Gv+YON7u~yn@ z!&%q-Dk#TrxRko?9C4k|`_qlX@(SDNUUqLP!b^u@V_l6VH*N`1Uc>G4WHU<6gO(}x z3PC6G5O4Krs`@x^GV7Jt-l5tympsrj8LQotB96#Ty~`1BzVeSwWJAHn5vg^~)xx8l zkAmw86ioHSDvy}Y$8zV3Itnh`o~Z6F&cfy~=L+4( zOC2D1z0ljA4GlQ!XqAb38(h-zV_-MGI2AvS=$^e9Ye11JTyVR%ga^)+tt(x*Zbf}Ca#2$RfiLj z)xZ;Ag?CapzU72L^b4I9uIN3@9B0A5+o*4eHxpQSFw`(7&y#ZA)Gs-vXFvGTb1Yvr zRN7t`UO!;;<6b(JF~0sDWj8qHEmYn5UHEgwpo!7*-q7@aQcJJ@LCc94G!-_b4S(>F zn|cPr|G^Un3`Zn1qmVC-_#oYGSrsoBZi)Fd!wdQ?YvTa4^C{q?F`!}ivQu(EbC#v% z;)Ma3%bD`8ONODR0qwx3fI*lumCp2VayA}Q%4V=7h6fVH*!#4czFYnZ=vb71U==UA zeB2-&?Hp_y;lR%FuP$gP%MiyoB87O3d2Mk#1_{L67WOi1LPkhb-dLLH^8c_hvWzf=iynBdF zUQhkZQf*p`)E|PKT#ciGQRe{FZ!dTqIwz01{&QfXOKWRf$hrGR_;~c_W!W zt+q=hTJ;{dq#CYj^=7h~b0^3<4+(Yp>)LvdZ@w&JP(GjmLl{(*((+|htYoR4qgZM= z>e|*xqm&CWK>o1Z>{&ESTMgBrN_>hn21bttU`zEnIa%{eT!FL;;I{(LhBEx{1$i(f zGkv7v3Djl+*r}o8-ULBA-B5D*$)dA?=Mi)CPE z+;V2=kPoF_Dp+{iSgPDIn5YLj2Xz&-=UhRLwqZs$FUiRlKQ7k?BOIo1#P9Mns(Icd zzedotBFY!7=z2_&ZW-xhPaA$WhzE<#{0@c2V%)>H0CJ;02f>1D0Lsh$u+!5;tH5h2 z`gnD7w-O#|(lcfMA(!MfM>L3A!OGrSlCymL+t^iP9d(A^N+7qvJEMu`(Zv-)ak8SW2fi zqq}#|v9LhKi~j1z$1^I!^t<8SYA{*qDKb6p>5NV4Dwxyj`fa%hB|^LfKI#LSisdb- zx(BPH_XYvMGw>4H7boKohF9V0vJ4KU%gDr)*nf59DR%C(0$RSRdGT_LG$JDbue|Du zA)RhRMzabWp>-zC-nuj0Se@bU|1)}t6$&t-dUs@-YaTb=X^U*ros^2HQ(2IbnX@;Y zzatBF;|_!l3Sv-I3jA9}(X*Momsa&aOGKDaHD4RdNUPBJ2F|t=a#P0Q(|?0Y^Jap} z@(Rw?^}$`&yFkn^KWF@N?@IsuoEBS;y>rnB!$j5HR<>iLVh*xqv6t5`Y;PS-7Ne9U zq_?hxr!MNyJ&5Wum?6Ow(bCzK^5Rgv?6@bdj!)B8+OUPjvj#k# zZ@rJhPTgq2L(EmjU%`>@Z};W$sow)gT8rCu%#q^M^Y z1;2K93!6#dW5a?a0xP=xuk?|J*ApAJgCY9D5RnSgS9mI$<>af|(GTTijUi3j)WAah z|12h7v{lS9#K51~opEP-R}jbn+}XXb@&_6Sg=l~TzQm_)Oo8izJ8C}Nea zg&9pxbM~aC+oZdCJ_f#Wjlc_5sjO-AW0`**!(BHD{K8{-nBWsj{k%!%%0{8)Y(dY8 ze_NYN?)wBZpK>MVJr1~Oi}gfa3Au+%o>;(M>}+HW#a{a^={(Jd*PMQ*qxMtqtN4e& zQ4`n(+%A>OVbG>G0($z?5W*EOJVWZalGN=BDuCDO$Y*lEdwJ@fPtT!17CfP`qCi+_ zMqia)bVi@}?wPc`^>eUVVI^2`uj+HFP_#;A@O)bVW8~ySC!WhSvpWoWfx`|=35<%p zsNM^M#;q{a!MZa$t78d8)D@m~4uYQ7$Bq|rP<-YESPgR&(DPv~&UE&|EE0dQN;E0{dE=KIqllO$N}XU}U29;1j?y1ue#4kN8k!#+w+p8XLFCeIga(I`Bre_Kt& zt1k@7T`V9txhEf(a^{y8lrdnpS@3Y+YYO!ya1sge7L6JyBi4q$` zCLU>mzDOZubLRaK=31=S)!Id;7>zeEdU5BY)TDYB17W)d;Hs`5y~X||Q>9=T$Juy0 zqNHiVLlco{>~{vAe?nx!tlfuRtaLG_*X`CH+FJ>8laR2O&Kj(jyC%s8}(!Rmr3y6Bfx#q_&G@b%^){bZlCt*2blO z8GQiTd=N1_{_EEln^6aMQ3nka)~~V4$yoE1lem+p1O1~;SAQg(sbKYvnO9uO$-9A&(5s>4U#rOZ?*e#cQ9tksOO5$Z9RjF z>LQn$g7E}rBMoCL&baJe-Zo|oc)mq-sNwj6yGoDm@tA1=yoa1^27dWCv{qPA2Wn`p zi9`OOE!JnYh3j<0^h`BLeS^ppVy=nqqLVpTeJpEa1@(jJEVie)q-zXrThR!UkA-xk zxEZD`)3KOwxopv6m3?dI^o}&wLRzo$$i`7Epwwg3le*N zRGjX7GeMuW&((=a1!!XRB6n!=nsli94$IkUy!{gg^ceqdma?%|wu01CI38rJRim+1 znmB$tlcoB#W2s>jSku*<9$9JPwxp#spx<#UPeNs=(_p#KJ+Jk_T0g*2>)>b6ikg;* zj9x@wm!tFlIEsR#V-lrO-R~~ACfR7>8P2gbkmZSEbWG)AHZo`|eQ|&LLd028 zQvH$UEC%%(DWLoK5AQFt{>m;Hg%Gn7MT%4kK6SJpMSbE;LoX?IkUh9mA1|Oi_Oe4h z$5I!{)N5U^OdjGOGekH5+B8XI0Ax!U4$?;37<3EA|Co)q8(29?H#k)1-PaUYx<1^X zzM>Y*6!f=#aP<%$`DS~nueL59I1Mf1Fk+iJV;jCe&>u>6En~Evr4vLC%OZPctp+7H zNnMj!>J^!q=cI|ddzUX{#Ii-Dj;OdeMHl~7j6oOPY*U>zPg>@o)u!Xlnz$s|r}n3^ zli^gQ5feHo8SU4Ry^AIue;Mlnq2AauWUA!5XyR#2U#kc9X~_EkMu=DPj=)Z!i&m4) z&T_>R%~KxHuaI>$I1UOX!?U_6^h1831+H2Ouk9DVF8fvz!-oNw->Z}H_?@}}mb7I- zVNV<91_jUWWm0R=31`ibYGxM3i@O6)!up*ofH_{X~pA)Ke1|agH?pZ+S9` z$DXZLmoDC&WkzUB|9DD|{mi)Bz^C~(3jEfF#JB=P+@p_ z8O^40(I<=oS6Q513$N<*ohQan%X0AdB?f8!Eq_hfd>1~*UQt%PE5*_%fZQ}qyc86! zLH1d^!%`rde$?a$J>-0`!S4Q^f7L70ZT}8K%f-_xh6#A}s ztwr(|i$D9yKxK5;R8cA}K&N-2`Q>Ca=9GhGKjRe^-5!wd%TKm) z7Yi;Ayg{`>_+mRZ2ePXmovG%!f#LtZ{UhrX%Ji|kw7+i!@LOKD5Hz%cCZ0d|d@wBc z47!E;m`1512OKss>i4*UCSJH7Slhq(SycUW6t{s=)F`~m!pf6SQ4?=ft(mvF9(()x zXOODlG%Z??=2z6j4gKipTaU9h(AlkNbujR26GkfTr}s!Y={*2-fAPD%;(l|%@K7a9 zJg&>b{tc^KYwu7AOt-7^cd$=O~?C941N&1eg0`1n=4%+{8DtxLw8_-1y&L;F5~wtj@^a zO%U{3f;B5@pWx<71A=6gj0pn2)>8z2K1dTsAfB<*lT-Cvrex^hhP7to=MAJwFz{cN z>bMy1cf99xXAcy(fMP@|zIlI@PHIXaA)u7nXwBwR*llL!x0%540CW-JS}ETxr7+E1p})3ioQdx%S`bodQvqx&FHhB#S->OY8Mz-_559UN{iX6 z^dWfu=|nl$E?ezD)6x+ ze$!RpwVmAUsE?a0`e+7KFDoEEvzqq}rq{DRV))bY0FggO9)w2y)$VCk`=gRYi4%`~C zuB%ny3H$}a!!60ap{CLJy3(@q>Y|J`twid%x|+D~zvb||S>vJfBMcVQ_IRPaAWizw zr!4gjmz6L!99&K&320xqCeF6M>*?C}B3j27C<{j}^mDnJAa$yzi4%1}^}wKb50M%f z;qFbAj&iZpdSm4rGqju2u(x`EEizfqr#E%?A?ux*_%_Gyan0AWZ{hZa1FLOqJMKzq zWhi~4=0L3@e~z z=o4S~I8e<Gjar(dJwZ2mDztvXAj`qh`Ua&S*p+C>z2D+;XI zb3>*eMhy4}7FHTkbP$V^GF!0o_{Y9znUS zWMRGLdB?PYlz(BjNTpsD?i2JV5iZF^%Q*3oem49K`l)C~Efo5;2L=7NXgPmA9(&`% zE!c1+!V21dp`RU?G%_ud{&9Dtd5z_WWOHL^X@f(i4B}Evn@AITH32m9gn*vJU_P1J zM0P@noodx%gJ1UALsTId7{B6>DP#4bC6*U2H<4zwT<7e`&QNtjA=J6zn)77eRO`sY zN^(;O?0G@g#f%O%)x?WvcNlc@cLDh~lk0>Xn<2H_UwQ@`+S*KNm^v%{B^D8r6l3}z!ZP#dQOGBcDhzEm{Tdf>3F;N%2Os_u2Ll-f}M^9>A2g zzIL}{b>&X;I!m|ld9Zz^A6#I(p=dZ7Ctb$i{RqCvBRb8?J<=A=z(Al4*_`G;% zmS0`ReR-jH?^b>H!;ow!vHQF3HMEwXPj&JrO9^q(zT>93eB1!}o={pHJV%8Idem1f zJTR#^>!&A%R3O-6+$DGLa%Ku zGQr#qG`^vSIn8S=1H$hoEXvG62KGFWI<0#XS4a|X>c!j00ln4+ntX08aosbMLOjf9 zRvT%~C5&h|R&==|=tN)INXa9PJ~f#G$){jbbtmQt9?2|d4T)`KzcIco*ge2yYsQWj zq$K9;+in`xau_q=TGPADOgbz72R) zL-n$HSJ6#>rzIcL$!gbVJQOz+hS(p{$r8 zGT9|)9%R#56Ysju95JE)0~}4-h^=XLQKiyPQiyaBH)9yf>I@~OY%Zh(_e0jqd5f^` z0(|EdfujW$$~u-o+1me7$tzK9+3d~c}GO5v0YMv>@ss1o_1W| z2o^!~INPJpVes%o;73k!9MxCGcs3`8A$v}VoR1P^?`)BT)a3Ib_1qR8S4v3I#Fu$Y zVxPENfdkfq`>4Ac30P=l^}jsV!=3Vzr0H%lR?GhqkXJX^8`Z%-OjcbMsUy3|nJnLb zI*3KrZgNBF5hHWH$`btP#Rzn2;oW6pn7H}CG(3J&Y&=Y^TCe;qK_Ajvv!#RGWsq;Z zZc9UY$p}5BM2Fwm z1+8HyrMePX0$*W1GM}vg~#H z0R9_aT4F+SfAe&t@DiS8w7dY%VR(5zfkQP1N-yPUN|t?PeviJuJ5?zpIkm6!l-Ue& zs==TDN^tNjLmM~CX7zUEUzaLlxgguWXvdaRTd<1lC-+}w9y~aA5Xw3ep-COIeu!bD zow)^{_k*na@gi?vprSS3_P~T={1f zd42;H*_z~OZv2keG0XcoZ_u|6z&VlnN^D*>Rb)byJJH+$(nS~ve477+$iL!G@dY#} zMXU1NE^hH;XxvSJ6rU^&arMU9j8Raw30zH`EJtN>KJ+3LB1L(xXHQ=1oUCGfVK|Q#OETevD6JYA~kh~rzKTS(Zr+GwVWT0*D!9piqpud zwMuPc^y1WNN~Q8uXTYB9m7gf3xU^evMTsoQH&vP^@7s}GgJ8~UUPWWXyaUuSRTI~- zR;EI&TY!-pJ`N2-atxX^U2h74|1nGmOp;mVWsq-`iy_h% z6NUhCy(XaCA#wt9k)__f$x!78hTEgnijUJ<-wkne^jfa>wiCFvvUpT%_`l?Rgej zb|`qvC%V1=EA}V?d&5AT8Bc#LGF`}r>*WoXX2>57e!bob2tmh#LeOuBUUUSY!|w%D zZxniy8%{Xwqu;~c!0>M$8D4`vkI=*hmNpWp_r8f-0oM`bEOEk-xA8xZ{#f=r^V;wH zab0mbl8!c>KT?L)_^S0+4S=hR#&T;N#d!D31t}B~Cm0e(LEs*1o{FgIPKTi?FTi<* zG`AD<&ZA|s;=A=5y;*7q%^59emyHIkxerTiNMAk_ZG zS@!ft^?k&Up@UNP)qz64Y%gz9vK=Q!A3@{5s!dhKsxED|^)jV7;|fZ}pv~BtVWqOG z1}XZ>Gg=xTi%u+8ElRB6g$Zn$%$3EUF`Zcf8`JHYUY2B?E+MaUKqHd`L*L2XxD%c( zcLJ@NbvrQ-4m=Xo)>BctZZAeZnESw%(KYRw!8+{WpCpy4w{B6fK$KbQT1IQQ@?F?M z4H-@Ync8B3C~^Wcx{ECxR>Y(!sEycIFDp7fQ3jrAhJ$xm73M%-q9PCn^ijfO?kX)O zf=eCj;7Bf}>|M*pQVz~#YSU9<(+%#_9Qp9$!EULk1tOig?%a36zC{7)3ac9z`P@t=j% zb!Q4GyL?tb1_ovR#xjJnMrcN!vt>&jV;ktx9gb&>JsI_!%DX>>Nxx=dziY*8X{@~2 zV08yuL8=3V&e6nY2b#?R-uEWMqbYrnR*JUIky_+4$o00!8ZuX=#?D3RJnR)QhMVWg zvrHFR>h1?3m22R8%&Sm{{dziM(SI=T4m3n{*na$p$h3E!CSKOLI1j8spNUl7aF+h+ zRhq))$c9rgePS{871&4L#Xh|K$-U6+td|}03Er^tE*$P~4U$hoau=nFr@R$4f*@0h zZvW<`8OKBe7C?tb@0bqJl)pgQ@&!xXUYxouL;P{QiMBB}Q+i(NkAT`(GN>W#`o|03 zGeSe>p7)tS+i=x@K_Sawm4OST`|Yx<>--0t{x5Vqy%gQ<;8p;m=gsiLLfO{be*&wf z?gHBM#tRFv8B)qJ1_gKvXzm9uJF+f-yfOeCC?_DF;%L`;t}F&!s3f3o8Pb(KGm%;^ zQluiLI~dEkoI#zM2?+HpP6N02P_wu%LZQpLqGR>Ic#2wr@IRL1BIN=CgMC?oLF#l& z7J|ljTafi4Sq`s7z*pnAHY@$OMY7<_ky`v9-C2fRUYJB9-^G$XXff!$bD5NInvVbQ zN?#Fg*K|J&I|w@$>iXf|TLf#fC9*+HYqVwi3it;5#9|Mn4m@`#faW#schV}mqM)z`-Ci?WoVJvOJxH($e>aG z?eIjTqOQ!SY_^QSQTT_6?Mnf1qpZ)C6NheZ`#9vI*o{#W>eyobJ3(J*r5wn`t^}9+ z??vk7l?4HarS8NNPE1ck4c<{YQp_r;Y^PPg!-~<7^%&0O)kR}JiWJ^JZ!_otzF*Ct z`2IdNY}iqX>ijI7X+%PaW$Z@WSQuSB-?YqBX8MTnDH=*CI`p$N!!_hKxm#Mt-6?Ff zYy$DC0lmcsB2;-Bk<7QrPDr$XO2_~u&# z#U4r?cO2Ycf+BnR_|vSeKK6QVd3p`>ct1_Rc5Af=b9Oq@y-}wfba0 zXV*cUz-0m&HqytD`V5u>bcd;zEZH}y*rYK^DP3STr^omjL2A8RWS;!oX(-zWEQc&= zuYMVaNY={(!f&omA3GilFJntY-QpRtS}2jYL00D24N$H91}eT$D@!l-_>`uTNa3aI ze&KTuQ^+|BPQi+5&2kxiICrtAjfh{l)GbFVN6%*YxER+LZiV!zoq>Ei@f)h-58PQu z-V5v$b9p<`N|k171(P7JnL@AaVp?l#Q?@b<+$8;e+$N~Lc#qD?f$}#=Rt@{rJZ_7^ z+hc3Js=~_W3>DAOqPW1lwjW;a29+b$GSoHExtADMG?^p}k(T60@7|IFfm^QY0t3i1 zSL))Q3uw`Ok?ZymA8|{C8;I}fMq?X@mEhKKA4|GWkn0x9m2HIZ`2nmE@mwu719E+& z=W?aho243i8PpwP3(3%lQqM!+fm1%_{3}pAzuy)RJ;6{zC7QTJ#?Yk<_kN|~)_jeM z=eO935K`OJmtJ~cG1eEdg_MPV!+Redmrr=l*!35gz=HL$C=#gH`Ix0IU5vwSVd6R; zQ~GtQ)cqD?YrwVp(0100v|uw zu8Aj`gZJGvsf{trALWZ)s2CG%%wR{y5`9Hc7C-IdnE~nV(c7sDUaswAh97>>y!k7~ zJFk7KxCF($j^AoZu}gO@tm(O5G&i2XvEkk>F^GjfV2-XXDVuiTHmypvcWCaMw#Fz& z4@=a|Sj=hFAaJO=ROCxNUsvOIjrZ<=83OxrEQp@elM@54ok(sqz?TL!@vUN9HRlg& z_D%L>4{K_)Z>MI@+tj+rE2q3cBd!d|Xnci!4kgp$otlr$&Qd!|W%e^E_9oRA=>Gw| CPob3n delta 116593 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 NT%B4keX$Er>wgkmai#zO diff --git a/lib/cassandra-driver-internal-only-3.0.0a2.post0-2429ba3.zip b/lib/cassandra-driver-internal-only-3.0.0a2.post0-96883eb.zip similarity index 78% rename from lib/cassandra-driver-internal-only-3.0.0a2.post0-2429ba3.zip rename to lib/cassandra-driver-internal-only-3.0.0a2.post0-96883eb.zip index 435c5e16dc4a259dc101aa210571ba2fb30d542d..e55b4c327ecbd9af157820449cadad773088fbb8 100644 GIT binary patch delta 27685 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 delta 26038 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 diff --git a/src/java/org/apache/cassandra/concurrent/Stage.java b/src/java/org/apache/cassandra/concurrent/Stage.java index a57587c026..ccb156501e 100644 --- a/src/java/org/apache/cassandra/concurrent/Stage.java +++ b/src/java/org/apache/cassandra/concurrent/Stage.java @@ -27,7 +27,7 @@ public enum Stage READ, MUTATION, COUNTER_MUTATION, - MATERIALIZED_VIEW_MUTATION, + VIEW_MUTATION, GOSSIP, REQUEST_RESPONSE, ANTI_ENTROPY, @@ -61,7 +61,7 @@ public enum Stage return "internal"; case MUTATION: case COUNTER_MUTATION: - case MATERIALIZED_VIEW_MUTATION: + case VIEW_MUTATION: case READ: case REQUEST_RESPONSE: case READ_REPAIR: diff --git a/src/java/org/apache/cassandra/concurrent/StageManager.java b/src/java/org/apache/cassandra/concurrent/StageManager.java index ee1fbe5ccc..a2ad89240b 100644 --- a/src/java/org/apache/cassandra/concurrent/StageManager.java +++ b/src/java/org/apache/cassandra/concurrent/StageManager.java @@ -47,7 +47,7 @@ public class StageManager { stages.put(Stage.MUTATION, multiThreadedLowSignalStage(Stage.MUTATION, getConcurrentWriters())); stages.put(Stage.COUNTER_MUTATION, multiThreadedLowSignalStage(Stage.COUNTER_MUTATION, getConcurrentCounterWriters())); - stages.put(Stage.MATERIALIZED_VIEW_MUTATION, multiThreadedLowSignalStage(Stage.MATERIALIZED_VIEW_MUTATION, getConcurrentMaterializedViewWriters())); + stages.put(Stage.VIEW_MUTATION, multiThreadedLowSignalStage(Stage.VIEW_MUTATION, getConcurrentViewWriters())); stages.put(Stage.READ, multiThreadedLowSignalStage(Stage.READ, getConcurrentReaders())); stages.put(Stage.REQUEST_RESPONSE, multiThreadedLowSignalStage(Stage.REQUEST_RESPONSE, FBUtilities.getAvailableProcessors())); stages.put(Stage.INTERNAL_RESPONSE, multiThreadedStage(Stage.INTERNAL_RESPONSE, FBUtilities.getAvailableProcessors())); diff --git a/src/java/org/apache/cassandra/config/CFMetaData.java b/src/java/org/apache/cassandra/config/CFMetaData.java index a26b19d686..cf1dbbf6f5 100644 --- a/src/java/org/apache/cassandra/config/CFMetaData.java +++ b/src/java/org/apache/cassandra/config/CFMetaData.java @@ -62,7 +62,7 @@ public final class CFMetaData { public enum Flag { - SUPER, COUNTER, DENSE, COMPOUND, VIEW + SUPER, COUNTER, DENSE, COMPOUND } private static final Logger logger = LoggerFactory.getLogger(CFMetaData.class); @@ -79,7 +79,7 @@ public final class CFMetaData private final boolean isCompound; private final boolean isSuper; private final boolean isCounter; - private final boolean isMaterializedView; + private final boolean isView; private final boolean isIndex; @@ -94,7 +94,6 @@ public final class CFMetaData private volatile AbstractType keyValidator = BytesType.instance; private volatile Map droppedColumns = new HashMap<>(); private volatile Triggers triggers = Triggers.none(); - private volatile MaterializedViews materializedViews = MaterializedViews.none(); private volatile Indexes indexes = Indexes.none(); /* @@ -219,12 +218,6 @@ public final class CFMetaData return this; } - public CFMetaData materializedViews(MaterializedViews prop) - { - materializedViews = prop; - return this; - } - public CFMetaData indexes(Indexes indexes) { this.indexes = indexes; @@ -238,7 +231,7 @@ public final class CFMetaData boolean isCounter, boolean isDense, boolean isCompound, - boolean isMaterializedView, + boolean isView, List partitionKeyColumns, List clusteringColumns, PartitionColumns partitionColumns, @@ -252,7 +245,7 @@ public final class CFMetaData this.isCompound = isCompound; this.isSuper = isSuper; this.isCounter = isCounter; - this.isMaterializedView = isMaterializedView; + this.isView = isView; EnumSet flags = EnumSet.noneOf(Flag.class); if (isSuper) @@ -263,8 +256,6 @@ public final class CFMetaData flags.add(Flag.DENSE); if (isCompound) flags.add(Flag.COMPOUND); - if (isMaterializedView) - flags.add(Flag.VIEW); this.flags = Sets.immutableEnumSet(flags); isIndex = cfName.contains("."); @@ -307,16 +298,6 @@ public final class CFMetaData this.compactValueColumn = CompactTables.getCompactValueColumn(partitionColumns, isSuper()); } - public MaterializedViews getMaterializedViews() - { - return materializedViews; - } - - public boolean hasMaterializedViews() - { - return !materializedViews.isEmpty(); - } - public Indexes getIndexes() { return indexes; @@ -329,7 +310,7 @@ public final class CFMetaData boolean isCompound, boolean isSuper, boolean isCounter, - boolean isMaterializedView, + boolean isView, List columns, IPartitioner partitioner) { @@ -363,7 +344,7 @@ public final class CFMetaData isCounter, isDense, isCompound, - isMaterializedView, + isView, partitions, clusterings, builder.build(), @@ -464,7 +445,7 @@ public final class CFMetaData isCounter(), isDense(), isCompound(), - isMaterializedView(), + isView(), copy(partitionKeyColumns), copy(clusteringColumns), copy(partitionColumns), @@ -481,7 +462,7 @@ public final class CFMetaData isCounter, isDense, isCompound, - isMaterializedView, + isView, copy(partitionKeyColumns), copy(clusteringColumns), copy(partitionColumns), @@ -511,7 +492,6 @@ public final class CFMetaData return newCFMD.params(oldCFMD.params) .droppedColumns(new HashMap<>(oldCFMD.droppedColumns)) .triggers(oldCFMD.triggers) - .materializedViews(oldCFMD.materializedViews) .indexes(oldCFMD.indexes); } @@ -702,7 +682,6 @@ public final class CFMetaData && Objects.equal(columnMetadata, other.columnMetadata) && Objects.equal(droppedColumns, other.droppedColumns) && Objects.equal(triggers, other.triggers) - && Objects.equal(materializedViews, other.materializedViews) && Objects.equal(indexes, other.indexes); } @@ -720,7 +699,6 @@ public final class CFMetaData .append(columnMetadata) .append(droppedColumns) .append(triggers) - .append(materializedViews) .append(indexes) .toHashCode(); } @@ -731,7 +709,8 @@ public final class CFMetaData */ public boolean reload() { - return apply(SchemaKeyspace.createTableFromName(ksName, cfName)); + return apply(isView ? SchemaKeyspace.createViewFromName(ksName, cfName).metadata + : SchemaKeyspace.createTableFromName(ksName, cfName)); } /** @@ -766,7 +745,6 @@ public final class CFMetaData droppedColumns = cfm.droppedColumns; triggers = cfm.triggers; - materializedViews = cfm.materializedViews; indexes = cfm.indexes; logger.debug("application result is {}", this); @@ -1070,9 +1048,9 @@ public final class CFMetaData return isCompound; } - public boolean isMaterializedView() + public boolean isView() { - return isMaterializedView; + return isView; } public Serializers serializers() @@ -1121,7 +1099,6 @@ public final class CFMetaData .append("columnMetadata", columnMetadata.values()) .append("droppedColumns", droppedColumns) .append("triggers", triggers) - .append("materializedViews", materializedViews) .append("indexes", indexes) .toString(); } @@ -1134,7 +1111,7 @@ public final class CFMetaData private final boolean isCompound; private final boolean isSuper; private final boolean isCounter; - private final boolean isMaterializedView; + private final boolean isView; private IPartitioner partitioner; private UUID tableId; @@ -1144,7 +1121,7 @@ public final class CFMetaData private final List> staticColumns = new ArrayList<>(); private final List> regularColumns = new ArrayList<>(); - private Builder(String keyspace, String table, boolean isDense, boolean isCompound, boolean isSuper, boolean isCounter, boolean isMaterializedView) + private Builder(String keyspace, String table, boolean isDense, boolean isCompound, boolean isSuper, boolean isCounter, boolean isView) { this.keyspace = keyspace; this.table = table; @@ -1152,7 +1129,7 @@ public final class CFMetaData this.isCompound = isCompound; this.isSuper = isSuper; this.isCounter = isCounter; - this.isMaterializedView = isMaterializedView; + this.isView = isView; this.partitioner = DatabaseDescriptor.getPartitioner(); } @@ -1296,7 +1273,7 @@ public final class CFMetaData isCounter, isDense, isCompound, - isMaterializedView, + isView, partitions, clusterings, builder.build(), diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java index ed220b8677..7553c920d3 100644 --- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java +++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java @@ -1143,7 +1143,7 @@ public class DatabaseDescriptor return conf.concurrent_counter_writes; } - public static int getConcurrentMaterializedViewWriters() + public static int getConcurrentViewWriters() { return conf.concurrent_materialized_view_writes; } diff --git a/src/java/org/apache/cassandra/config/MaterializedViewDefinition.java b/src/java/org/apache/cassandra/config/MaterializedViewDefinition.java deleted file mode 100644 index 90fa35c1bf..0000000000 --- a/src/java/org/apache/cassandra/config/MaterializedViewDefinition.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.cassandra.config; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.apache.cassandra.cql3.ColumnIdentifier; - -public class MaterializedViewDefinition -{ - public final String baseCfName; - public final String viewName; - // The order of partititon columns and clustering columns is important, so we cannot switch these two to sets - public final List partitionColumns; - public final List clusteringColumns; - public final Set included; - public final boolean includeAll; - - public MaterializedViewDefinition(MaterializedViewDefinition def) - { - this(def.baseCfName, def.viewName, new ArrayList<>(def.partitionColumns), new ArrayList<>(def.clusteringColumns), new HashSet<>(def.included)); - } - - /** - * @param baseCfName Name of the column family from which this view is based - * @param viewName Name of the view - * @param partitionColumns List of all of the partition columns, in the order they are defined - * @param clusteringColumns List of all of the clustering columns, in the order they are defined - * @param included - */ - public MaterializedViewDefinition(String baseCfName, String viewName, List partitionColumns, List clusteringColumns, Set included) - { - assert partitionColumns != null && !partitionColumns.isEmpty(); - assert included != null; - this.baseCfName = baseCfName; - this.viewName = viewName; - this.partitionColumns = partitionColumns; - this.clusteringColumns = clusteringColumns; - this.includeAll = included.isEmpty(); - this.included = included; - } - - /** - * @return true if the view specified by this definition will include the column, false otherwise - */ - public boolean includes(ColumnIdentifier column) - { - return includeAll - || partitionColumns.contains(column) - || clusteringColumns.contains(column) - || included.contains(column); - } - - /** - * Replace the column {@param from} with {@param to} in this materialized view definition's partition, - * clustering, or included columns. - */ - public void renameColumn(ColumnIdentifier from, ColumnIdentifier to) - { - if (!includeAll && included.contains(from)) - { - included.remove(from); - included.add(to); - } - - int partitionIndex = partitionColumns.indexOf(from); - if (partitionIndex >= 0) - partitionColumns.set(partitionIndex, to); - - int clusteringIndex = clusteringColumns.indexOf(from); - if (clusteringIndex >= 0) - clusteringColumns.set(clusteringIndex, to); - } -} diff --git a/src/java/org/apache/cassandra/config/Schema.java b/src/java/org/apache/cassandra/config/Schema.java index 7cc8394d35..bcde978479 100644 --- a/src/java/org/apache/cassandra/config/Schema.java +++ b/src/java/org/apache/cassandra/config/Schema.java @@ -147,6 +147,7 @@ public class Schema public Schema load(KeyspaceMetadata keyspaceDef) { keyspaceDef.tables.forEach(this::load); + keyspaceDef.views.forEach(this::load); setKeyspaceMetadata(keyspaceDef); return this; } @@ -224,8 +225,11 @@ public class Schema public CFMetaData getCFMetaData(String keyspaceName, String cfName) { assert keyspaceName != null; + KeyspaceMetadata ksm = keyspaces.get(keyspaceName); - return (ksm == null) ? null : ksm.tables.getNullable(cfName); + return ksm == null + ? null + : ksm.getTableOrViewNullable(cfName); } /** @@ -246,6 +250,13 @@ public class Schema return getCFMetaData(descriptor.ksname, descriptor.cfname); } + public ViewDefinition getView(String keyspaceName, String viewName) + { + assert keyspaceName != null; + KeyspaceMetadata ksm = keyspaces.get(keyspaceName); + return (ksm == null) ? null : ksm.views.getNullable(viewName); + } + /** * Get metadata about keyspace by its name * @@ -274,12 +285,12 @@ public class Schema * * @return metadata about ColumnFamilies the belong to the given keyspace */ - public Tables getTables(String keyspaceName) + public Iterable getTablesAndViews(String keyspaceName) { assert keyspaceName != null; KeyspaceMetadata ksm = keyspaces.get(keyspaceName); assert ksm != null; - return ksm.tables; + return ksm.tablesAndViews(); } /** @@ -355,6 +366,24 @@ public class Schema cfIdMap.put(key, cfm.cfId); } + /** + * Load individual View Definition to the schema + * (to make View lookup faster) + * + * @param view The View definition to load + */ + public void load(ViewDefinition view) + { + CFMetaData cfm = view.metadata; + Pair key = Pair.create(cfm.ksName, cfm.cfName); + + if (cfIdMap.containsKey(key)) + throw new RuntimeException(String.format("Attempting to load already loaded view %s.%s", cfm.ksName, cfm.cfName)); + + logger.debug("Adding {} to cfIdMap", cfm); + cfIdMap.put(key, cfm.cfId); + } + /** * Used for ColumnFamily data eviction out from the schema * @@ -365,6 +394,16 @@ public class Schema cfIdMap.remove(Pair.create(cfm.ksName, cfm.cfName)); } + /** + * Used for View eviction from the schema + * + * @param view The view definition to evict + */ + private void unload(ViewDefinition view) + { + cfIdMap.remove(Pair.create(view.ksName, view.viewName)); + } + /* Function helpers */ /** @@ -442,6 +481,7 @@ public class Schema { KeyspaceMetadata ksm = getKSMetaData(keyspaceName); ksm.tables.forEach(this::unload); + ksm.views.forEach(this::unload); clearKeyspaceMetadata(ksm); } @@ -468,13 +508,13 @@ public class Schema KeyspaceMetadata ksm = Schema.instance.getKSMetaData(ksName); String snapshotName = Keyspace.getTimestampedSnapshotName(ksName); - CompactionManager.instance.interruptCompactionFor(ksm.tables, true); + CompactionManager.instance.interruptCompactionFor(ksm.tablesAndViews(), true); Keyspace keyspace = Keyspace.open(ksm.name); // remove all cfs from the keyspace instance. List droppedCfs = new ArrayList<>(); - for (CFMetaData cfm : ksm.tables) + for (CFMetaData cfm : ksm.tablesAndViews()) { ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(cfm.cfName); @@ -556,6 +596,67 @@ public class Schema CommitLog.instance.forceRecycleAllSegments(Collections.singleton(cfm.cfId)); } + public void addView(ViewDefinition view) + { + assert getCFMetaData(view.ksName, view.viewName) == null; + + update(view.ksName, ks -> + { + load(view); + + // make sure it's init-ed w/ the old definitions first, + // since we're going to call initCf on the new one manually + Keyspace.open(view.ksName); + + return ks.withSwapped(ks.views.with(view)); + }); + + Keyspace.open(view.ksName).initCf(view.metadata.cfId, view.viewName, true); + Keyspace.open(view.ksName).viewManager.reload(); + MigrationManager.instance.notifyCreateView(view); + } + + public void updateView(String ksName, String viewName) + { + Optional optView = getKSMetaData(ksName).views.get(viewName); + assert optView.isPresent(); + ViewDefinition view = optView.get(); + boolean columnsDidChange = view.metadata.reload(); + + Keyspace keyspace = Keyspace.open(view.ksName); + keyspace.getColumnFamilyStore(view.viewName).reload(); + Keyspace.open(view.ksName).viewManager.update(view.viewName); + MigrationManager.instance.notifyUpdateView(view, columnsDidChange); + } + + public void dropView(String ksName, String viewName) + { + KeyspaceMetadata oldKsm = getKSMetaData(ksName); + assert oldKsm != null; + ColumnFamilyStore cfs = Keyspace.open(ksName).getColumnFamilyStore(viewName); + assert cfs != null; + + // make sure all the indexes are dropped, or else. + cfs.indexManager.markAllIndexesRemoved(); + + // reinitialize the keyspace. + ViewDefinition view = oldKsm.views.get(viewName).get(); + KeyspaceMetadata newKsm = oldKsm.withSwapped(oldKsm.views.without(viewName)); + + unload(view); + setKeyspaceMetadata(newKsm); + + CompactionManager.instance.interruptCompactionFor(Collections.singleton(view.metadata), true); + + if (DatabaseDescriptor.isAutoSnapshot()) + cfs.snapshot(Keyspace.getTimestampedSnapshotName(cfs.name)); + Keyspace.open(ksName).dropCf(view.metadata.cfId); + Keyspace.open(ksName).viewManager.reload(); + MigrationManager.instance.notifyDropView(view); + + CommitLog.instance.forceRecycleAllSegments(Collections.singleton(view.metadata.cfId)); + } + public void addType(UserType ut) { update(ut.keyspace, ks -> ks.withSwapped(ks.types.with(ut))); diff --git a/src/java/org/apache/cassandra/config/ViewDefinition.java b/src/java/org/apache/cassandra/config/ViewDefinition.java new file mode 100644 index 0000000000..39695b98ce --- /dev/null +++ b/src/java/org/apache/cassandra/config/ViewDefinition.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.cassandra.config; + +import java.util.Objects; +import java.util.UUID; + +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import org.apache.cassandra.cql3.ColumnIdentifier; + +public class ViewDefinition +{ + public final String ksName; + public final String viewName; + public final UUID baseTableId; + public final boolean includeAllColumns; + // The order of partititon columns and clustering columns is important, so we cannot switch these two to sets + public final CFMetaData metadata; + + public ViewDefinition(ViewDefinition def) + { + this(def.ksName, def.viewName, def.baseTableId, def.includeAllColumns, def.metadata); + } + + /** + * @param viewName Name of the view + * @param baseTableId Internal ID of the table which this view is based off of + * @param includeAllColumns Whether to include all columns or not + */ + public ViewDefinition(String ksName, String viewName, UUID baseTableId, boolean includeAllColumns, CFMetaData metadata) + { + this.ksName = ksName; + this.viewName = viewName; + this.baseTableId = baseTableId; + this.includeAllColumns = includeAllColumns; + this.metadata = metadata; + } + + /** + * @return true if the view specified by this definition will include the column, false otherwise + */ + public boolean includes(ColumnIdentifier column) + { + return metadata.getColumnDefinition(column) != null; + } + + public ViewDefinition copy() + { + return new ViewDefinition(ksName, viewName, baseTableId, includeAllColumns, metadata.copy()); + } + + public CFMetaData baseTableMetadata() + { + return Schema.instance.getCFMetaData(baseTableId); + } + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + + if (!(o instanceof ViewDefinition)) + return false; + + ViewDefinition other = (ViewDefinition) o; + return Objects.equals(ksName, other.ksName) + && Objects.equals(viewName, other.viewName) + && Objects.equals(baseTableId, other.baseTableId) + && Objects.equals(includeAllColumns, other.includeAllColumns) + && Objects.equals(metadata, other.metadata); + } + + @Override + public int hashCode() + { + return new HashCodeBuilder(29, 1597) + .append(ksName) + .append(viewName) + .append(baseTableId) + .append(includeAllColumns) + .append(metadata) + .toHashCode(); + } + + @Override + public String toString() + { + return new ToStringBuilder(this) + .append("ksName", ksName) + .append("viewName", viewName) + .append("baseTableId", baseTableId) + .append("includeAllColumns", includeAllColumns) + .append("metadata", metadata) + .toString(); + } +} diff --git a/src/java/org/apache/cassandra/cql3/Cql.g b/src/java/org/apache/cassandra/cql3/Cql.g index 87bec4bc1f..f6d54f5359 100644 --- a/src/java/org/apache/cassandra/cql3/Cql.g +++ b/src/java/org/apache/cassandra/cql3/Cql.g @@ -740,7 +740,7 @@ indexIdent returns [IndexTarget.Raw id] * PRIMARY KEY () * WITH = AND ...; */ -createMaterializedViewStatement returns [CreateMaterializedViewStatement expr] +createMaterializedViewStatement returns [CreateViewStatement expr] @init { boolean ifNotExists = false; List partitionKeys = new ArrayList<>(); @@ -753,7 +753,7 @@ createMaterializedViewStatement returns [CreateMaterializedViewStatement expr] '(' '(' k1=cident { partitionKeys.add(k1); } ( ',' kn=cident { partitionKeys.add(kn); } )* ')' ( ',' c1=cident { compositeKeys.add(c1); } )* ')' | '(' k1=cident { partitionKeys.add(k1); } ( ',' cn=cident { compositeKeys.add(cn); } )* ')' ) - { $expr = new CreateMaterializedViewStatement(cf, basecf, sclause, wclause, partitionKeys, compositeKeys, ifNotExists); } + { $expr = new CreateViewStatement(cf, basecf, sclause, wclause, partitionKeys, compositeKeys, ifNotExists); } ( K_WITH cfamProperty[expr.properties] ( K_AND cfamProperty[expr.properties] )*)? ; @@ -820,14 +820,14 @@ alterTableStatement returns [AlterTableStatement expr] } ; -alterMaterializedViewStatement returns [AlterMaterializedViewStatement expr] +alterMaterializedViewStatement returns [AlterViewStatement expr] @init { TableAttributes attrs = new TableAttributes(); } : K_ALTER K_MATERIALIZED K_VIEW name=columnFamilyName K_WITH properties[attrs] { - $expr = new AlterMaterializedViewStatement(name, attrs); + $expr = new AlterViewStatement(name, attrs); } ; @@ -886,10 +886,10 @@ dropIndexStatement returns [DropIndexStatement expr] /** * DROP MATERIALIZED VIEW [IF EXISTS] */ -dropMaterializedViewStatement returns [DropMaterializedViewStatement expr] +dropMaterializedViewStatement returns [DropViewStatement expr] @init { boolean ifExists = false; } : K_DROP K_MATERIALIZED K_VIEW (K_IF K_EXISTS { ifExists = true; } )? cf=columnFamilyName - { $expr = new DropMaterializedViewStatement(cf, ifExists); } + { $expr = new DropViewStatement(cf, ifExists); } ; /** diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java index fac0c5318a..af9a75c5d7 100644 --- a/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AlterTableStatement.java @@ -19,6 +19,8 @@ package org.apache.cassandra.cql3.statements; import java.util.*; +import com.google.common.collect.Iterables; + import org.apache.cassandra.auth.Permission; import org.apache.cassandra.config.*; import org.apache.cassandra.cql3.CFName; @@ -27,6 +29,7 @@ import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.CollectionType; import org.apache.cassandra.db.marshal.CounterColumnType; +import org.apache.cassandra.db.view.View; import org.apache.cassandra.exceptions.*; import org.apache.cassandra.schema.IndexMetadata; import org.apache.cassandra.schema.Indexes; @@ -81,7 +84,7 @@ public class AlterTableStatement extends SchemaAlteringStatement public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException { CFMetaData meta = validateColumnFamily(keyspace(), columnFamily()); - if (meta.isMaterializedView()) + if (meta.isView()) throw new InvalidRequestException("Cannot use ALTER TABLE on Materialized View"); CFMetaData cfm = meta.copy(); @@ -95,7 +98,8 @@ public class AlterTableStatement extends SchemaAlteringStatement def = cfm.getColumnDefinition(columnName); } - List materializedViewUpdates = null; + List viewUpdates = null; + Iterable views = View.findAll(keyspace(), columnFamily()); switch (oType) { @@ -156,19 +160,20 @@ public class AlterTableStatement extends SchemaAlteringStatement ? ColumnDefinition.staticDef(cfm, columnName.bytes, type) : ColumnDefinition.regularDef(cfm, columnName.bytes, type)); - // Adding a column to a table which has an include all materialized view requires the column to be added - // to the materialized view as well - for (MaterializedViewDefinition mv : cfm.getMaterializedViews()) + // Adding a column to a table which has an include all view requires the column to be added to the view + // as well + if (!isStatic) { - if (mv.includeAll) + for (ViewDefinition view : views) { - CFMetaData indexCfm = Schema.instance.getCFMetaData(keyspace(), mv.viewName).copy(); - indexCfm.addColumnDefinition(isStatic - ? ColumnDefinition.staticDef(indexCfm, columnName.bytes, type) - : ColumnDefinition.regularDef(indexCfm, columnName.bytes, type)); - if (materializedViewUpdates == null) - materializedViewUpdates = new ArrayList<>(); - materializedViewUpdates.add(indexCfm); + if (view.includeAllColumns) + { + ViewDefinition viewCopy = view.copy(); + viewCopy.metadata.addColumnDefinition(ColumnDefinition.regularDef(viewCopy.metadata, columnName.bytes, type)); + if (viewUpdates == null) + viewUpdates = new ArrayList<>(); + viewUpdates.add(viewCopy); + } } } break; @@ -221,17 +226,16 @@ public class AlterTableStatement extends SchemaAlteringStatement // In any case, we update the column definition cfm.addOrReplaceColumnDefinition(def.withNewType(validatorType)); - // We have to alter the schema of the materialized view table as well; it doesn't affect the definition however - for (MaterializedViewDefinition mv : cfm.getMaterializedViews()) + // We have to alter the schema of the view table as well; it doesn't affect the definition however + for (ViewDefinition view : views) { - if (!mv.includes(columnName)) continue; - // We have to use the pre-adjusted CFM, otherwise we can't resolve the Index - CFMetaData indexCfm = Schema.instance.getCFMetaData(keyspace(), mv.viewName).copy(); - indexCfm.addOrReplaceColumnDefinition(def.withNewType(validatorType)); + if (!view.includes(columnName)) continue; + ViewDefinition viewCopy = view.copy(); + viewCopy.metadata.addOrReplaceColumnDefinition(def.withNewType(validatorType)); - if (materializedViewUpdates == null) - materializedViewUpdates = new ArrayList<>(); - materializedViewUpdates.add(indexCfm); + if (viewUpdates == null) + viewUpdates = new ArrayList<>(); + viewUpdates.add(viewCopy); } break; @@ -276,20 +280,16 @@ public class AlterTableStatement extends SchemaAlteringStatement } cfm.indexes(allIndexes); - // If a column is dropped which is the target of a materialized view, - // then we need to drop the view. - // If a column is dropped which was selected into a materialized view, - // we need to drop that column from the included materialzied view table - // and definition. + // If a column is dropped which is included in a view, we don't allow the drop to take place. boolean rejectAlter = false; StringBuilder builder = new StringBuilder(); - for (MaterializedViewDefinition mv : cfm.getMaterializedViews()) + for (ViewDefinition view : views) { - if (!mv.includes(columnName)) continue; + if (!view.includes(columnName)) continue; if (rejectAlter) builder.append(','); rejectAlter = true; - builder.append(mv.viewName); + builder.append(view.viewName); } if (rejectAlter) throw new InvalidRequestException(String.format("Cannot drop column %s, depended on by materialized views (%s.{%s})", @@ -304,7 +304,7 @@ public class AlterTableStatement extends SchemaAlteringStatement TableParams params = attrs.asAlteredTableParams(cfm.params); - if (cfm.hasMaterializedViews() && params.gcGraceSeconds == 0) + if (!Iterables.isEmpty(views) && params.gcGraceSeconds == 0) { throw new InvalidRequestException("Cannot alter gc_grace_seconds of the base table of a " + "materialized view to 0, since this value is used to TTL " + @@ -326,36 +326,31 @@ public class AlterTableStatement extends SchemaAlteringStatement ColumnIdentifier to = entry.getValue().prepare(cfm); cfm.renameColumn(from, to); - // If the materialized view includes a renamed column, it must be renamed in the index table and the definition. - for (MaterializedViewDefinition mv : cfm.getMaterializedViews()) + // If the view includes a renamed column, it must be renamed in the view table and the definition. + for (ViewDefinition view : views) { - if (!mv.includes(from)) continue; + if (!view.includes(from)) continue; - CFMetaData indexCfm = Schema.instance.getCFMetaData(keyspace(), mv.viewName).copy(); - ColumnIdentifier indexFrom = entry.getKey().prepare(indexCfm); - ColumnIdentifier indexTo = entry.getValue().prepare(indexCfm); - indexCfm.renameColumn(indexFrom, indexTo); + ViewDefinition viewCopy = view.copy(); + ColumnIdentifier viewFrom = entry.getKey().prepare(viewCopy.metadata); + ColumnIdentifier viewTo = entry.getValue().prepare(viewCopy.metadata); + viewCopy.metadata.renameColumn(viewFrom, viewTo); - MaterializedViewDefinition mvCopy = new MaterializedViewDefinition(mv); - mvCopy.renameColumn(from, to); - - cfm.materializedViews(cfm.getMaterializedViews().replace(mvCopy)); - - if (materializedViewUpdates == null) - materializedViewUpdates = new ArrayList<>(); - materializedViewUpdates.add(indexCfm); + if (viewUpdates == null) + viewUpdates = new ArrayList<>(); + viewUpdates.add(viewCopy); } } break; } - if (materializedViewUpdates != null) - { - for (CFMetaData mvUpdates : materializedViewUpdates) - MigrationManager.announceColumnFamilyUpdate(mvUpdates, false, isLocalOnly); - } - MigrationManager.announceColumnFamilyUpdate(cfm, false, isLocalOnly); + + if (viewUpdates != null) + { + for (ViewDefinition viewUpdate : viewUpdates) + MigrationManager.announceViewUpdate(viewUpdate, isLocalOnly); + } return true; } diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java index 4298c224eb..e176cec7ee 100644 --- a/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AlterTypeStatement.java @@ -115,6 +115,16 @@ public abstract class AlterTypeStatement extends SchemaAlteringStatement MigrationManager.announceColumnFamilyUpdate(copy, false, isLocalOnly); } + for (ViewDefinition view : ksm.views) + { + ViewDefinition copy = view.copy(); + boolean modified = false; + for (ColumnDefinition def : copy.metadata.allColumns()) + modified |= updateDefinition(copy.metadata, def, toUpdate.keyspace, toUpdate.name, updated); + if (modified) + MigrationManager.announceViewUpdate(copy, isLocalOnly); + } + // Other user types potentially using the updated type for (UserType ut : ksm.types) { diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterMaterializedViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterViewStatement.java similarity index 79% rename from src/java/org/apache/cassandra/cql3/statements/AlterMaterializedViewStatement.java rename to src/java/org/apache/cassandra/cql3/statements/AlterViewStatement.java index bc4ba11174..e578c4fee1 100644 --- a/src/java/org/apache/cassandra/cql3/statements/AlterMaterializedViewStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AlterViewStatement.java @@ -19,8 +19,10 @@ package org.apache.cassandra.cql3.statements; import org.apache.cassandra.auth.Permission; import org.apache.cassandra.config.CFMetaData; +import org.apache.cassandra.config.Schema; +import org.apache.cassandra.config.ViewDefinition; import org.apache.cassandra.cql3.CFName; -import org.apache.cassandra.db.view.MaterializedView; +import org.apache.cassandra.db.view.View; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.RequestValidationException; import org.apache.cassandra.exceptions.UnauthorizedException; @@ -31,11 +33,11 @@ import org.apache.cassandra.transport.Event; import static org.apache.cassandra.thrift.ThriftValidation.validateColumnFamily; -public class AlterMaterializedViewStatement extends SchemaAlteringStatement +public class AlterViewStatement extends SchemaAlteringStatement { private final TableAttributes attrs; - public AlterMaterializedViewStatement(CFName name, TableAttributes attrs) + public AlterViewStatement(CFName name, TableAttributes attrs) { super(name); this.attrs = attrs; @@ -43,7 +45,7 @@ public class AlterMaterializedViewStatement extends SchemaAlteringStatement public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException { - CFMetaData baseTable = MaterializedView.findBaseTable(keyspace(), columnFamily()); + CFMetaData baseTable = View.findBaseTable(keyspace(), columnFamily()); if (baseTable != null) state.hasColumnFamilyAccess(keyspace(), baseTable.cfName, Permission.ALTER); } @@ -56,32 +58,33 @@ public class AlterMaterializedViewStatement extends SchemaAlteringStatement public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException { CFMetaData meta = validateColumnFamily(keyspace(), columnFamily()); - if (!meta.isMaterializedView()) + if (!meta.isView()) throw new InvalidRequestException("Cannot use ALTER MATERIALIZED VIEW on Table"); - CFMetaData cfm = meta.copy(); + ViewDefinition view = Schema.instance.getView(keyspace(), columnFamily()); + ViewDefinition viewCopy = view.copy(); if (attrs == null) throw new InvalidRequestException("ALTER MATERIALIZED VIEW WITH invoked, but no parameters found"); attrs.validate(); - TableParams params = attrs.asAlteredTableParams(cfm.params); + TableParams params = attrs.asAlteredTableParams(view.metadata.params); if (params.gcGraceSeconds == 0) { throw new InvalidRequestException("Cannot alter gc_grace_seconds of a materialized view to 0, since this " + "value is used to TTL undelivered updates. Setting gc_grace_seconds too " + "low might cause undelivered updates to expire before being replayed."); } - cfm.params(params); + view.metadata.params(params); - MigrationManager.announceColumnFamilyUpdate(cfm, false, isLocalOnly); + MigrationManager.announceViewUpdate(viewCopy, isLocalOnly); return true; } public String toString() { - return String.format("AlterMaterializedViewStatement(name=%s)", cfName); + return String.format("AlterViewStatement(name=%s)", cfName); } public Event.SchemaChange changeEvent() diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateTriggerStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateTriggerStatement.java index 706cfeac1f..2589622090 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateTriggerStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateTriggerStatement.java @@ -59,7 +59,7 @@ public class CreateTriggerStatement extends SchemaAlteringStatement public void validate(ClientState state) throws RequestValidationException { CFMetaData cfm = ThriftValidation.validateColumnFamily(keyspace(), columnFamily()); - if (cfm.isMaterializedView()) + if (cfm.isView()) throw new InvalidRequestException("Cannot CREATE TRIGGER against a materialized view"); try diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateMaterializedViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java similarity index 78% rename from src/java/org/apache/cassandra/cql3/statements/CreateMaterializedViewStatement.java rename to src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java index dee05774a2..1a020ce16d 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateMaterializedViewStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java @@ -28,23 +28,27 @@ import com.google.common.collect.Iterables; import org.apache.cassandra.auth.Permission; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.ColumnDefinition; -import org.apache.cassandra.config.MaterializedViewDefinition; +import org.apache.cassandra.config.Schema; +import org.apache.cassandra.config.ViewDefinition; import org.apache.cassandra.cql3.CFName; import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.selection.RawSelector; import org.apache.cassandra.cql3.selection.Selectable; -import org.apache.cassandra.db.view.MaterializedView; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.ReversedType; +import org.apache.cassandra.db.view.View; import org.apache.cassandra.exceptions.AlreadyExistsException; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.RequestValidationException; import org.apache.cassandra.exceptions.UnauthorizedException; +import org.apache.cassandra.schema.TableParams; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.ClientWarn; import org.apache.cassandra.service.MigrationManager; import org.apache.cassandra.thrift.ThriftValidation; import org.apache.cassandra.transport.Event; -public class CreateMaterializedViewStatement extends SchemaAlteringStatement +public class CreateViewStatement extends SchemaAlteringStatement { private final CFName baseName; private final List selectClause; @@ -54,13 +58,13 @@ public class CreateMaterializedViewStatement extends SchemaAlteringStatement public final CFProperties properties = new CFProperties(); private final boolean ifNotExists; - public CreateMaterializedViewStatement(CFName viewName, - CFName baseName, - List selectClause, - List notNullWhereClause, - List partitionKeys, - List clusteringKeys, - boolean ifNotExists) + public CreateViewStatement(CFName viewName, + CFName baseName, + List selectClause, + List notNullWhereClause, + List partitionKeys, + List clusteringKeys, + boolean ifNotExists) { super(viewName); this.baseName = baseName; @@ -84,6 +88,31 @@ public class CreateMaterializedViewStatement extends SchemaAlteringStatement // We do validation in announceMigration to reduce doubling up of work } + private interface AddColumn { + void add(ColumnIdentifier identifier, AbstractType type); + } + + private void add(CFMetaData baseCfm, Iterable columns, AddColumn adder) + { + for (ColumnIdentifier column : columns) + { + AbstractType type = baseCfm.getColumnDefinition(column).type; + if (properties.definedOrdering.containsKey(column)) + { + boolean desc = properties.definedOrdering.get(column); + if (!desc && type.isReversed()) + { + type = ((ReversedType)type).baseType; + } + else if (desc && !type.isReversed()) + { + type = ReversedType.getInstance(type); + } + } + adder.add(column, type); + } + } + public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException { // We need to make sure that: @@ -109,8 +138,7 @@ public class CreateMaterializedViewStatement extends SchemaAlteringStatement if (cfm.isCounter()) throw new InvalidRequestException("Materialized views are not supported on counter tables"); - - if (cfm.isMaterializedView()) + if (cfm.isView()) throw new InvalidRequestException("Materialized views cannot be created against other materialized views"); if (cfm.params.gcGraceSeconds == 0) @@ -194,18 +222,25 @@ public class CreateMaterializedViewStatement extends SchemaAlteringStatement } // We need to include all of the primary key colums from the base table in order to make sure that we do not - // overwrite values in the materialized view. We cannot support "collapsing" the base table into a smaller - // number of rows in the view because if we need to generate a tombstone, we have no way of knowing which value - // is currently being used in the view and whether or not to generate a tombstone. - // In order to not surprise our users, we require that they include all of the columns. We provide them with - // a list of all of the columns left to include. + // overwrite values in the view. We cannot support "collapsing" the base table into a smaller number of rows in + // the view because if we need to generate a tombstone, we have no way of knowing which value is currently being + // used in the view and whether or not to generate a tombstone. In order to not surprise our users, we require + // that they include all of the columns. We provide them with a list of all of the columns left to include. boolean missingClusteringColumns = false; StringBuilder columnNames = new StringBuilder(); + List includedColumns = new ArrayList<>(); for (ColumnDefinition def : cfm.allColumns()) { + ColumnIdentifier identifier = def.name; + + if ((included.isEmpty() || included.contains(identifier)) + && !targetClusteringColumns.contains(identifier) && !targetPartitionKeys.contains(identifier) + && !def.isStatic()) + { + includedColumns.add(identifier); + } if (!def.isPrimaryKeyColumn()) continue; - ColumnIdentifier identifier = def.name; if (!targetClusteringColumns.contains(identifier) && !targetPartitionKeys.contains(identifier)) { if (missingClusteringColumns) @@ -225,16 +260,21 @@ public class CreateMaterializedViewStatement extends SchemaAlteringStatement if (targetClusteringColumns.isEmpty()) throw new InvalidRequestException("No columns are defined for Materialized View other than primary key"); - MaterializedViewDefinition definition = new MaterializedViewDefinition(baseName.getColumnFamily(), - columnFamily(), - targetPartitionKeys, - targetClusteringColumns, - included); + CFMetaData.Builder cfmBuilder = CFMetaData.Builder.createView(keyspace(), columnFamily()); + add(cfm, targetPartitionKeys, cfmBuilder::addPartitionKey); + add(cfm, targetClusteringColumns, cfmBuilder::addClusteringColumn); + add(cfm, includedColumns, cfmBuilder::addRegularColumn); + TableParams params = properties.properties.asNewTableParams(); + CFMetaData viewCfm = cfmBuilder.build().params(params); + ViewDefinition definition = new ViewDefinition(keyspace(), + columnFamily(), + Schema.instance.getId(keyspace(), baseName.getColumnFamily()), + included.isEmpty(), + viewCfm); - CFMetaData indexCf = MaterializedView.getCFMetaData(definition, cfm, properties); try { - MigrationManager.announceNewColumnFamily(indexCf, isLocalOnly); + MigrationManager.announceNewView(definition, isLocalOnly); } catch (AlreadyExistsException e) { @@ -243,11 +283,6 @@ public class CreateMaterializedViewStatement extends SchemaAlteringStatement throw e; } - CFMetaData newCfm = cfm.copy(); - newCfm.materializedViews(newCfm.getMaterializedViews().with(definition)); - - MigrationManager.announceColumnFamilyUpdate(newCfm, false, isLocalOnly); - return true; } diff --git a/src/java/org/apache/cassandra/cql3/statements/DropTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropTableStatement.java index 35dc94728d..14d89d92bd 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DropTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropTableStatement.java @@ -19,12 +19,13 @@ package org.apache.cassandra.cql3.statements; import org.apache.cassandra.auth.Permission; import org.apache.cassandra.config.CFMetaData; -import org.apache.cassandra.config.MaterializedViewDefinition; +import org.apache.cassandra.config.ViewDefinition; import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.CFName; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.UnauthorizedException; +import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.MigrationManager; import org.apache.cassandra.transport.Event; @@ -61,20 +62,24 @@ public class DropTableStatement extends SchemaAlteringStatement { try { - CFMetaData cfm = Schema.instance.getCFMetaData(keyspace(), columnFamily()); + KeyspaceMetadata ksm = Schema.instance.getKSMetaData(keyspace()); + CFMetaData cfm = ksm.tables.getNullable(columnFamily()); if (cfm != null) { - if (cfm.isMaterializedView()) + if (cfm.isView()) throw new InvalidRequestException("Cannot use DROP TABLE on Materialized View"); boolean rejectDrop = false; StringBuilder messageBuilder = new StringBuilder(); - for (MaterializedViewDefinition def : cfm.getMaterializedViews()) + for (ViewDefinition def : ksm.views) { - if (rejectDrop) - messageBuilder.append(','); - rejectDrop = true; - messageBuilder.append(def.viewName); + if (def.baseTableId.equals(cfm.cfId)) + { + if (rejectDrop) + messageBuilder.append(','); + rejectDrop = true; + messageBuilder.append(def.viewName); + } } if (rejectDrop) { diff --git a/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java index 75f6200e59..74c8c36176 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java @@ -87,7 +87,7 @@ public class DropTypeStatement extends SchemaAlteringStatement if (!ut.name.equals(name.getUserTypeName()) && isUsedBy(ut)) throw new InvalidRequestException(String.format("Cannot drop user type %s as it is still used by user type %s", name, ut.asCQL3Type())); - for (CFMetaData cfm : ksm.tables) + for (CFMetaData cfm : ksm.tablesAndViews()) for (ColumnDefinition def : cfm.allColumns()) if (isUsedBy(def.type)) throw new InvalidRequestException(String.format("Cannot drop user type %s as it is still used by table %s.%s", name, cfm.ksName, cfm.cfName)); diff --git a/src/java/org/apache/cassandra/cql3/statements/DropMaterializedViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropViewStatement.java similarity index 58% rename from src/java/org/apache/cassandra/cql3/statements/DropMaterializedViewStatement.java rename to src/java/org/apache/cassandra/cql3/statements/DropViewStatement.java index 8adba45019..f2be370c7f 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DropMaterializedViewStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropViewStatement.java @@ -21,8 +21,9 @@ package org.apache.cassandra.cql3.statements; import org.apache.cassandra.auth.Permission; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.Schema; +import org.apache.cassandra.config.ViewDefinition; import org.apache.cassandra.cql3.CFName; -import org.apache.cassandra.db.view.MaterializedView; +import org.apache.cassandra.db.view.View; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.UnauthorizedException; @@ -30,11 +31,11 @@ import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.MigrationManager; import org.apache.cassandra.transport.Event; -public class DropMaterializedViewStatement extends SchemaAlteringStatement +public class DropViewStatement extends SchemaAlteringStatement { public final boolean ifExists; - public DropMaterializedViewStatement(CFName cf, boolean ifExists) + public DropViewStatement(CFName cf, boolean ifExists) { super(cf); this.ifExists = ifExists; @@ -42,7 +43,7 @@ public class DropMaterializedViewStatement extends SchemaAlteringStatement public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException { - CFMetaData baseTable = MaterializedView.findBaseTable(keyspace(), columnFamily()); + CFMetaData baseTable = View.findBaseTable(keyspace(), columnFamily()); if (baseTable != null) state.hasColumnFamilyAccess(keyspace(), baseTable.cfName, Permission.ALTER); } @@ -61,25 +62,25 @@ public class DropMaterializedViewStatement extends SchemaAlteringStatement { try { - CFMetaData viewCfm = Schema.instance.getCFMetaData(keyspace(), columnFamily()); - if (viewCfm == null) - throw new ConfigurationException(String.format("Cannot drop non existing materialized view '%s' in keyspace '%s'.", columnFamily(), keyspace())); - if (!viewCfm.isMaterializedView()) - throw new ConfigurationException(String.format("Cannot drop non materialized view '%s' in keyspace '%s'", columnFamily(), keyspace())); +// ViewDefinition view = Schema.instance.getViewDefinition(keyspace(), columnFamily()); +// if (view == null) +// { +// if (Schema.instance.getCFMetaData(keyspace(), columnFamily()) != null) +// throw new ConfigurationException(String.format("Cannot drop table '%s' in keyspace '%s'.", columnFamily(), keyspace())); +// +// throw new ConfigurationException(String.format("Cannot drop non existing materialized view '%s' in keyspace '%s'.", columnFamily(), keyspace())); +// } +// +// CFMetaData baseCfm = Schema.instance.getCFMetaData(view.baseTableId); +// if (baseCfm == null) +// { +// if (ifExists) +// throw new ConfigurationException(String.format("Cannot drop materialized view '%s' in keyspace '%s' without base CF.", columnFamily(), keyspace())); +// else +// throw new InvalidRequestException(String.format("View '%s' could not be found in any of the tables of keyspace '%s'", cfName, keyspace())); +// } - CFMetaData baseCfm = MaterializedView.findBaseTable(keyspace(), columnFamily()); - if (baseCfm == null) - { - if (ifExists) - throw new ConfigurationException(String.format("Cannot drop materialized view '%s' in keyspace '%s' without base CF.", columnFamily(), keyspace())); - else - throw new InvalidRequestException(String.format("View '%s' could not be found in any of the tables of keyspace '%s'", cfName, keyspace())); - } - - CFMetaData updatedCfm = baseCfm.copy(); - updatedCfm.materializedViews(updatedCfm.getMaterializedViews().without(columnFamily())); - MigrationManager.announceColumnFamilyUpdate(updatedCfm, false, isLocalOnly); - MigrationManager.announceColumnFamilyDrop(keyspace(), columnFamily(), isLocalOnly); + MigrationManager.announceViewDrop(keyspace(), columnFamily(), isLocalOnly); return true; } catch (ConfigurationException e) diff --git a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java index 3855b6a506..a04af4c671 100644 --- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java @@ -21,13 +21,13 @@ import java.nio.ByteBuffer; import java.util.*; import com.google.common.collect.Iterables; - -import static org.apache.cassandra.cql3.statements.RequestValidations.checkNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.apache.cassandra.auth.Permission; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.ColumnDefinition; -import org.apache.cassandra.config.MaterializedViewDefinition; +import org.apache.cassandra.config.ViewDefinition; import org.apache.cassandra.cql3.*; import org.apache.cassandra.cql3.ColumnIdentifier.Raw; import org.apache.cassandra.cql3.functions.Function; @@ -38,10 +38,8 @@ import org.apache.cassandra.db.filter.*; import org.apache.cassandra.db.marshal.BooleanType; import org.apache.cassandra.db.partitions.*; import org.apache.cassandra.db.rows.RowIterator; -import org.apache.cassandra.exceptions.InvalidRequestException; -import org.apache.cassandra.exceptions.RequestExecutionException; -import org.apache.cassandra.exceptions.RequestValidationException; -import org.apache.cassandra.exceptions.UnauthorizedException; +import org.apache.cassandra.db.view.View; +import org.apache.cassandra.exceptions.*; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.service.StorageProxy; @@ -52,9 +50,9 @@ import org.apache.cassandra.triggers.TriggerExecutor; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.UUIDGen; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse; +import static org.apache.cassandra.cql3.statements.RequestValidations.checkNull; import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull; /* @@ -170,14 +168,9 @@ public abstract class ModificationStatement implements CQLStatement return cfm.isCounter(); } - public boolean isMaterializedView() + public boolean isView() { - return cfm.isMaterializedView(); - } - - public boolean hasMaterializedViews() - { - return !cfm.getMaterializedViews().isEmpty(); + return cfm.isView(); } public long getTimestamp(long now, QueryOptions options) throws InvalidRequestException @@ -203,13 +196,16 @@ public abstract class ModificationStatement implements CQLStatement if (hasConditions()) state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.SELECT); - // MV updates need to get the current state from the table, and might update the materialized views + // MV updates need to get the current state from the table, and might update the views // Require Permission.SELECT on the base table, and Permission.MODIFY on the views - if (hasMaterializedViews()) + Iterator views = View.findAll(keyspace(), columnFamily()).iterator(); + if (views.hasNext()) { state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.SELECT); - for (MaterializedViewDefinition view : cfm.getMaterializedViews()) - state.hasColumnFamilyAccess(keyspace(), view.viewName, Permission.MODIFY); + do + { + state.hasColumnFamilyAccess(keyspace(), views.next().viewName, Permission.MODIFY); + } while (views.hasNext()); } for (Function function : getFunctions()) @@ -221,7 +217,7 @@ public abstract class ModificationStatement implements CQLStatement checkFalse(hasConditions() && attrs.isTimestampSet(), "Cannot provide custom timestamp for conditional updates"); checkFalse(isCounter() && attrs.isTimestampSet(), "Cannot provide custom timestamp for counter updates"); checkFalse(isCounter() && attrs.isTimeToLiveSet(), "Cannot provide custom TTL for counter updates"); - checkFalse(isMaterializedView(), "Cannot directly modify a materialized view"); + checkFalse(isView(), "Cannot directly modify a materialized view"); } public PartitionColumns updatedColumns() diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java index 7ad6c09af7..18e402b2be 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java @@ -44,7 +44,7 @@ import org.apache.cassandra.db.partitions.PartitionIterator; import org.apache.cassandra.db.rows.ComplexColumnData; import org.apache.cassandra.db.rows.Row; import org.apache.cassandra.db.rows.RowIterator; -import org.apache.cassandra.db.view.MaterializedView; +import org.apache.cassandra.db.view.View; import org.apache.cassandra.dht.AbstractBounds; import org.apache.cassandra.exceptions.*; import org.apache.cassandra.index.Index; @@ -175,9 +175,9 @@ public class SelectStatement implements CQLStatement public void checkAccess(ClientState state) throws InvalidRequestException, UnauthorizedException { - if (cfm.isMaterializedView()) + if (cfm.isView()) { - CFMetaData baseTable = MaterializedView.findBaseTable(keyspace(), columnFamily()); + CFMetaData baseTable = View.findBaseTable(keyspace(), columnFamily()); if (baseTable != null) state.hasColumnFamilyAccess(keyspace(), baseTable.cfName, Permission.SELECT); } diff --git a/src/java/org/apache/cassandra/cql3/statements/TruncateStatement.java b/src/java/org/apache/cassandra/cql3/statements/TruncateStatement.java index 5dd306a543..66b3da0793 100644 --- a/src/java/org/apache/cassandra/cql3/statements/TruncateStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/TruncateStatement.java @@ -65,7 +65,7 @@ public class TruncateStatement extends CFStatement implements CQLStatement try { CFMetaData metaData = Schema.instance.getCFMetaData(keyspace(), columnFamily()); - if (metaData.isMaterializedView()) + if (metaData.isView()) throw new InvalidRequestException("Cannot TRUNCATE materialized view directly; must truncate base table instead"); StorageProxy.truncateBlocking(keyspace(), columnFamily()); diff --git a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java index c7d8926c2e..0d6d801313 100644 --- a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java +++ b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java @@ -48,11 +48,11 @@ import org.apache.cassandra.db.commitlog.ReplayPosition; import org.apache.cassandra.db.compaction.*; import org.apache.cassandra.db.filter.ClusteringIndexFilter; import org.apache.cassandra.db.filter.DataLimits; +import org.apache.cassandra.db.view.ViewManager; import org.apache.cassandra.db.lifecycle.*; import org.apache.cassandra.db.partitions.CachedPartition; import org.apache.cassandra.db.partitions.PartitionUpdate; import org.apache.cassandra.db.rows.CellPath; -import org.apache.cassandra.db.view.MaterializedViewManager; import org.apache.cassandra.dht.*; import org.apache.cassandra.dht.Range; import org.apache.cassandra.exceptions.ConfigurationException; @@ -193,7 +193,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean private final AtomicInteger fileIndexGenerator = new AtomicInteger(0); public final SecondaryIndexManager indexManager; - public final MaterializedViewManager materializedViewManager; + public final ViewManager.ForStore viewManager; /* These are locally held copies to be changed from the config during runtime */ private volatile DefaultInteger minCompactionThreshold; @@ -231,7 +231,6 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean indexManager.reload(); - materializedViewManager.reload(); // If the CF comparator has changed, we need to change the memtable, // because the old one still aliases the previous comparator. if (data.getView().getCurrentMemtable().initialComparator != metadata.comparator) @@ -377,7 +376,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean this.maxCompactionThreshold = new DefaultInteger(metadata.params.compaction.maxCompactionThreshold()); this.directories = directories; this.indexManager = new SecondaryIndexManager(this); - this.materializedViewManager = new MaterializedViewManager(this); + this.viewManager = keyspace.viewManager.forTable(metadata.cfId); this.metric = new TableMetrics(this); fileIndexGenerator.set(generation); sampleLatencyNanos = DatabaseDescriptor.getReadRpcTimeout() / 2; @@ -513,7 +512,6 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean data.dropSSTables(); LifecycleTransaction.waitForDeletions(); indexManager.invalidateAllIndexesBlocking(); - materializedViewManager.invalidate(); invalidateCaches(); } @@ -633,8 +631,6 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean // must be called after all sstables are loaded since row cache merges all row versions public void init() { - materializedViewManager.init(); - if (!isRowCacheEnabled()) return; @@ -1912,7 +1908,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean // flush the CF being truncated before forcing the new segment forceBlockingFlush(); - materializedViewManager.forceBlockingFlush(); + viewManager.forceBlockingFlush(); // sleep a little to make sure that our truncatedAt comes after any sstable // that was part of the flushed we forced; otherwise on a tie, it won't get deleted. @@ -1921,7 +1917,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean else { dumpMemtable(); - materializedViewManager.dumpMemtables(); + viewManager.dumpMemtables(); } Runnable truncateRunnable = new Runnable() @@ -1940,7 +1936,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean indexManager.truncateAllIndexesBlocking(truncatedAt); - materializedViewManager.truncateBlocking(truncatedAt); + viewManager.truncateBlocking(truncatedAt); SystemKeyspace.saveTruncationRecord(ColumnFamilyStore.this, truncatedAt, replayAfter); logger.debug("cleaning out row cache"); @@ -1974,7 +1970,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean logger.debug("Cancelling in-progress compactions for {}", metadata.cfName); Iterable selfWithAuxiliaryCfs = interruptViews - ? Iterables.concat(concatWithIndexes(), materializedViewManager.allViewsCfs()) + ? Iterables.concat(concatWithIndexes(), viewManager.allViewsCfs()) : concatWithIndexes(); for (ColumnFamilyStore cfs : selfWithAuxiliaryCfs) diff --git a/src/java/org/apache/cassandra/db/Keyspace.java b/src/java/org/apache/cassandra/db/Keyspace.java index 4661baee38..1169b45016 100644 --- a/src/java/org/apache/cassandra/db/Keyspace.java +++ b/src/java/org/apache/cassandra/db/Keyspace.java @@ -40,7 +40,7 @@ import org.apache.cassandra.db.compaction.CompactionManager; import org.apache.cassandra.db.lifecycle.SSTableSet; import org.apache.cassandra.db.partitions.PartitionUpdate; import org.apache.cassandra.db.rows.UnfilteredRowIterator; -import org.apache.cassandra.db.view.MaterializedViewManager; +import org.apache.cassandra.db.view.ViewManager; import org.apache.cassandra.exceptions.WriteTimeoutException; import org.apache.cassandra.index.Index; import org.apache.cassandra.index.SecondaryIndexManager; @@ -81,12 +81,13 @@ public class Keyspace private volatile KeyspaceMetadata metadata; //OpOrder is defined globally since we need to order writes across - //Keyspaces in the case of MaterializedViews (batchlog of MV mutations) + //Keyspaces in the case of Views (batchlog of view mutations) public static final OpOrder writeOrder = new OpOrder(); /* ColumnFamilyStore per column family */ private final ConcurrentMap columnFamilyStores = new ConcurrentHashMap<>(); private volatile AbstractReplicationStrategy replicationStrategy; + public final ViewManager viewManager; public static final Function keyspaceTransformer = new Function() { @@ -305,11 +306,13 @@ public class Keyspace createReplicationStrategy(metadata); this.metric = new KeyspaceMetrics(this); - for (CFMetaData cfm : metadata.tables) + this.viewManager = new ViewManager(this); + for (CFMetaData cfm : metadata.tablesAndViews()) { logger.debug("Initializing {}.{}", getName(), cfm.cfName); initCf(cfm.cfId, cfm.cfName, loadSSTables); } + this.viewManager.reload(); } private Keyspace(KeyspaceMetadata metadata) @@ -317,6 +320,7 @@ public class Keyspace this.metadata = metadata; createReplicationStrategy(metadata); this.metric = new KeyspaceMetrics(this); + this.viewManager = new ViewManager(this); } public static Keyspace mockKS(KeyspaceMetadata metadata) @@ -418,11 +422,11 @@ public class Keyspace throw new RuntimeException("Testing write failures"); Lock lock = null; - boolean requiresViewUpdate = updateIndexes && MaterializedViewManager.updatesAffectView(Collections.singleton(mutation), false); + boolean requiresViewUpdate = updateIndexes && viewManager.updatesAffectView(Collections.singleton(mutation), false); if (requiresViewUpdate) { - lock = MaterializedViewManager.acquireLockFor(mutation.key().getKey()); + lock = ViewManager.acquireLockFor(mutation.key().getKey()); if (lock == null) { @@ -430,11 +434,11 @@ public class Keyspace { logger.debug("Could not acquire lock for {}", ByteBufferUtil.bytesToHex(mutation.key().getKey())); Tracing.trace("Could not acquire MV lock"); - throw new WriteTimeoutException(WriteType.MATERIALIZED_VIEW, ConsistencyLevel.LOCAL_ONE, 0, 1); + throw new WriteTimeoutException(WriteType.VIEW, ConsistencyLevel.LOCAL_ONE, 0, 1); } else { - //This MV update can't happen right now. so rather than keep this thread busy + //This view update can't happen right now. so rather than keep this thread busy // we will re-apply ourself to the queue and try again later StageManager.getStage(Stage.MUTATION).execute(() -> { if (writeCommitLog) @@ -472,7 +476,7 @@ public class Keyspace try { Tracing.trace("Creating materialized view mutations from base table replica"); - cfs.materializedViewManager.pushViewReplicaUpdates(upd, !isClReplay); + viewManager.pushViewReplicaUpdates(upd, !isClReplay); } catch (Throwable t) { diff --git a/src/java/org/apache/cassandra/db/SystemKeyspace.java b/src/java/org/apache/cassandra/db/SystemKeyspace.java index d54ee8bd83..6b2585e14a 100644 --- a/src/java/org/apache/cassandra/db/SystemKeyspace.java +++ b/src/java/org/apache/cassandra/db/SystemKeyspace.java @@ -95,8 +95,8 @@ public final class SystemKeyspace public static final String SSTABLE_ACTIVITY = "sstable_activity"; public static final String SIZE_ESTIMATES = "size_estimates"; public static final String AVAILABLE_RANGES = "available_ranges"; - public static final String MATERIALIZED_VIEWS_BUILDS_IN_PROGRESS = "materialized_views_builds_in_progress"; - public static final String BUILT_MATERIALIZED_VIEWS = "built_materialized_views"; + public static final String VIEWS_BUILDS_IN_PROGRESS = "views_builds_in_progress"; + public static final String BUILT_VIEWS = "built_views"; @Deprecated public static final String LEGACY_HINTS = "hints"; @Deprecated public static final String LEGACY_BATCHLOG = "batchlog"; @@ -246,9 +246,9 @@ public final class SystemKeyspace + "ranges set," + "PRIMARY KEY ((keyspace_name)))"); - private static final CFMetaData MaterializedViewsBuildsInProgress = - compile(MATERIALIZED_VIEWS_BUILDS_IN_PROGRESS, - "materialized views builds current progress", + private static final CFMetaData ViewsBuildsInProgress = + compile(VIEWS_BUILDS_IN_PROGRESS, + "views builds current progress", "CREATE TABLE %s (" + "keyspace_name text," + "view_name text," @@ -256,9 +256,9 @@ public final class SystemKeyspace + "generation_number int," + "PRIMARY KEY ((keyspace_name), view_name))"); - private static final CFMetaData BuiltMaterializedViews = - compile(BUILT_MATERIALIZED_VIEWS, - "built materialized views", + private static final CFMetaData BuiltViews = + compile(BUILT_VIEWS, + "built views", "CREATE TABLE %s (" + "keyspace_name text," + "view_name text," @@ -414,7 +414,7 @@ public final class SystemKeyspace public static KeyspaceMetadata metadata() { - return KeyspaceMetadata.create(NAME, KeyspaceParams.local(), tables(), Types.none(), functions()); + return KeyspaceMetadata.create(NAME, KeyspaceParams.local(), tables(), Views.none(), Types.none(), functions()); } private static Tables tables() @@ -430,8 +430,8 @@ public final class SystemKeyspace SSTableActivity, SizeEstimates, AvailableRanges, - MaterializedViewsBuildsInProgress, - BuiltMaterializedViews, + ViewsBuildsInProgress, + BuiltViews, LegacyHints, LegacyBatchlog, LegacyKeyspaces, @@ -531,61 +531,61 @@ public final class SystemKeyspace public static boolean isViewBuilt(String keyspaceName, String viewName) { String req = "SELECT view_name FROM %s.\"%s\" WHERE keyspace_name=? AND view_name=?"; - UntypedResultSet result = executeInternal(String.format(req, NAME, BUILT_MATERIALIZED_VIEWS), keyspaceName, viewName); + UntypedResultSet result = executeInternal(String.format(req, NAME, BUILT_VIEWS), keyspaceName, viewName); return !result.isEmpty(); } - public static void setMaterializedViewBuilt(String keyspaceName, String viewName) + public static void setViewBuilt(String keyspaceName, String viewName) { String req = "INSERT INTO %s.\"%s\" (keyspace_name, view_name) VALUES (?, ?)"; - executeInternal(String.format(req, NAME, BUILT_MATERIALIZED_VIEWS), keyspaceName, viewName); - forceBlockingFlush(BUILT_MATERIALIZED_VIEWS); + executeInternal(String.format(req, NAME, BUILT_VIEWS), keyspaceName, viewName); + forceBlockingFlush(BUILT_VIEWS); } - public static void setMaterializedViewRemoved(String keyspaceName, String viewName) + public static void setViewRemoved(String keyspaceName, String viewName) { String buildReq = "DELETE FROM %S.%s WHERE keyspace_name = ? AND view_name = ?"; - executeInternal(String.format(buildReq, NAME, MATERIALIZED_VIEWS_BUILDS_IN_PROGRESS), keyspaceName, viewName); - forceBlockingFlush(MATERIALIZED_VIEWS_BUILDS_IN_PROGRESS); + executeInternal(String.format(buildReq, NAME, VIEWS_BUILDS_IN_PROGRESS), keyspaceName, viewName); + forceBlockingFlush(VIEWS_BUILDS_IN_PROGRESS); String builtReq = "DELETE FROM %s.\"%s\" WHERE keyspace_name = ? AND view_name = ?"; - executeInternal(String.format(builtReq, NAME, BUILT_MATERIALIZED_VIEWS), keyspaceName, viewName); - forceBlockingFlush(BUILT_MATERIALIZED_VIEWS); + executeInternal(String.format(builtReq, NAME, BUILT_VIEWS), keyspaceName, viewName); + forceBlockingFlush(BUILT_VIEWS); } - public static void beginMaterializedViewBuild(String ksname, String viewName, int generationNumber) + public static void beginViewBuild(String ksname, String viewName, int generationNumber) { - executeInternal(String.format("INSERT INTO system.%s (keyspace_name, view_name, generation_number) VALUES (?, ?, ?)", MATERIALIZED_VIEWS_BUILDS_IN_PROGRESS), + executeInternal(String.format("INSERT INTO system.%s (keyspace_name, view_name, generation_number) VALUES (?, ?, ?)", VIEWS_BUILDS_IN_PROGRESS), ksname, viewName, generationNumber); } - public static void finishMaterializedViewBuildStatus(String ksname, String viewName) + public static void finishViewBuildStatus(String ksname, String viewName) { // We flush the view built first, because if we fail now, we'll restart at the last place we checkpointed - // materialized view build. + // view build. // If we flush the delete first, we'll have to restart from the beginning. - // Also, if the build succeeded, but the materialized view build failed, we will be able to skip the - // materialized view build check next boot. - setMaterializedViewBuilt(ksname, viewName); - forceBlockingFlush(BUILT_MATERIALIZED_VIEWS); - executeInternal(String.format("DELETE FROM system.%s WHERE keyspace_name = ? AND view_name = ?", MATERIALIZED_VIEWS_BUILDS_IN_PROGRESS), ksname, viewName); - forceBlockingFlush(MATERIALIZED_VIEWS_BUILDS_IN_PROGRESS); + // Also, if the build succeeded, but the view build failed, we will be able to skip the view build check + // next boot. + setViewBuilt(ksname, viewName); + forceBlockingFlush(BUILT_VIEWS); + executeInternal(String.format("DELETE FROM system.%s WHERE keyspace_name = ? AND view_name = ?", VIEWS_BUILDS_IN_PROGRESS), ksname, viewName); + forceBlockingFlush(VIEWS_BUILDS_IN_PROGRESS); } - public static void updateMaterializedViewBuildStatus(String ksname, String viewName, Token token) + public static void updateViewBuildStatus(String ksname, String viewName, Token token) { String req = "INSERT INTO system.%s (keyspace_name, view_name, last_token) VALUES (?, ?, ?)"; - Token.TokenFactory factory = MaterializedViewsBuildsInProgress.partitioner.getTokenFactory(); - executeInternal(String.format(req, MATERIALIZED_VIEWS_BUILDS_IN_PROGRESS), ksname, viewName, factory.toString(token)); + Token.TokenFactory factory = ViewsBuildsInProgress.partitioner.getTokenFactory(); + executeInternal(String.format(req, VIEWS_BUILDS_IN_PROGRESS), ksname, viewName, factory.toString(token)); } - public static Pair getMaterializedViewBuildStatus(String ksname, String viewName) + public static Pair getViewBuildStatus(String ksname, String viewName) { String req = "SELECT generation_number, last_token FROM system.%s WHERE keyspace_name = ? AND view_name = ?"; - UntypedResultSet queryResultSet = executeInternal(String.format(req, MATERIALIZED_VIEWS_BUILDS_IN_PROGRESS), ksname, viewName); + UntypedResultSet queryResultSet = executeInternal(String.format(req, VIEWS_BUILDS_IN_PROGRESS), ksname, viewName); if (queryResultSet == null || queryResultSet.isEmpty()) return null; @@ -597,7 +597,7 @@ public final class SystemKeyspace generation = row.getInt("generation_number"); if (row.has("last_key")) { - Token.TokenFactory factory = MaterializedViewsBuildsInProgress.partitioner.getTokenFactory(); + Token.TokenFactory factory = ViewsBuildsInProgress.partitioner.getTokenFactory(); lastKey = factory.fromString(row.getString("last_key")); } diff --git a/src/java/org/apache/cassandra/db/WriteType.java b/src/java/org/apache/cassandra/db/WriteType.java index 20fb6a9f23..fdbe97d8df 100644 --- a/src/java/org/apache/cassandra/db/WriteType.java +++ b/src/java/org/apache/cassandra/db/WriteType.java @@ -25,5 +25,5 @@ public enum WriteType COUNTER, BATCH_LOG, CAS, - MATERIALIZED_VIEW; + VIEW; } diff --git a/src/java/org/apache/cassandra/db/compaction/CompactionManager.java b/src/java/org/apache/cassandra/db/compaction/CompactionManager.java index 5207f49811..75d50e76f3 100644 --- a/src/java/org/apache/cassandra/db/compaction/CompactionManager.java +++ b/src/java/org/apache/cassandra/db/compaction/CompactionManager.java @@ -45,7 +45,7 @@ import org.apache.cassandra.db.lifecycle.LifecycleTransaction; import org.apache.cassandra.db.lifecycle.SSTableSet; import org.apache.cassandra.db.lifecycle.View; import org.apache.cassandra.db.rows.UnfilteredRowIterator; -import org.apache.cassandra.db.view.MaterializedViewBuilder; +import org.apache.cassandra.db.view.ViewBuilder; import org.apache.cassandra.dht.Bounds; import org.apache.cassandra.dht.Range; import org.apache.cassandra.dht.Token; @@ -1334,7 +1334,7 @@ public class CompactionManager implements CompactionManagerMBean } } - public Future submitMaterializedViewBuilder(final MaterializedViewBuilder builder) + public Future submitViewBuilder(final ViewBuilder builder) { Runnable runnable = new Runnable() { diff --git a/src/java/org/apache/cassandra/db/compaction/OperationType.java b/src/java/org/apache/cassandra/db/compaction/OperationType.java index f8f016cb5a..a69622b714 100644 --- a/src/java/org/apache/cassandra/db/compaction/OperationType.java +++ b/src/java/org/apache/cassandra/db/compaction/OperationType.java @@ -36,7 +36,7 @@ public enum OperationType FLUSH("Flush"), STREAM("Stream"), WRITE("Write"), - VIEW_BUILD("Materialized view build"); + VIEW_BUILD("View build"); public final String type; public final String fileName; diff --git a/src/java/org/apache/cassandra/db/view/MaterializedViewManager.java b/src/java/org/apache/cassandra/db/view/MaterializedViewManager.java deleted file mode 100644 index 41f4ed0850..0000000000 --- a/src/java/org/apache/cassandra/db/view/MaterializedViewManager.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.cassandra.db.view; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentNavigableMap; -import java.util.concurrent.ConcurrentSkipListMap; -import java.util.concurrent.locks.Lock; - -import com.google.common.collect.Lists; -import com.google.common.util.concurrent.Striped; - -import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.config.MaterializedViewDefinition; -import org.apache.cassandra.db.ColumnFamilyStore; -import org.apache.cassandra.db.IMutation; -import org.apache.cassandra.db.Keyspace; -import org.apache.cassandra.db.Mutation; -import org.apache.cassandra.db.SystemKeyspace; -import org.apache.cassandra.db.commitlog.ReplayPosition; -import org.apache.cassandra.db.partitions.PartitionUpdate; -import org.apache.cassandra.dht.Token; -import org.apache.cassandra.exceptions.OverloadedException; -import org.apache.cassandra.exceptions.UnavailableException; -import org.apache.cassandra.exceptions.WriteTimeoutException; -import org.apache.cassandra.service.StorageProxy; -import org.apache.cassandra.service.StorageService; - -/** - * Manages {@link MaterializedView}'s for a single {@link ColumnFamilyStore}. All of the materialized views for that - * table are created when this manager is initialized. - * - * The main purposes of the manager are to provide a single location for updates to be vetted to see whether they update - * any views {@link MaterializedViewManager#updateAffectsView(PartitionUpdate)}, provide locks to prevent multiple - * updates from creating incoherent updates in the view {@link MaterializedViewManager#acquireLockFor(ByteBuffer)}, and - * to affect change on the view. - */ -public class MaterializedViewManager -{ - private static final Striped LOCKS = Striped.lazyWeakLock(DatabaseDescriptor.getConcurrentWriters() * 1024); - private static final boolean enableCoordinatorBatchlog = Boolean.getBoolean("cassandra.mv_enable_coordinator_batchlog"); - - private final ConcurrentNavigableMap viewsByName; - - private final ColumnFamilyStore baseCfs; - - public MaterializedViewManager(ColumnFamilyStore baseCfs) - { - this.viewsByName = new ConcurrentSkipListMap<>(); - - this.baseCfs = baseCfs; - } - - public Iterable allViews() - { - return viewsByName.values(); - } - - public Iterable allViewsCfs() - { - List viewColumnFamilies = new ArrayList<>(); - for (MaterializedView view : allViews()) - viewColumnFamilies.add(view.getViewCfs()); - return viewColumnFamilies; - } - - public void init() - { - reload(); - } - - public void invalidate() - { - for (MaterializedView view : allViews()) - removeMaterializedView(view.name); - } - - public void reload() - { - Map newViewsByName = new HashMap<>(); - for (MaterializedViewDefinition definition : baseCfs.metadata.getMaterializedViews()) - { - newViewsByName.put(definition.viewName, definition); - } - - for (String viewName : viewsByName.keySet()) - { - if (!newViewsByName.containsKey(viewName)) - removeMaterializedView(viewName); - } - - for (Map.Entry entry : newViewsByName.entrySet()) - { - if (!viewsByName.containsKey(entry.getKey())) - addMaterializedView(entry.getValue()); - } - - for (MaterializedView view : allViews()) - { - view.build(); - // We provide the new definition from the base metadata - view.updateDefinition(newViewsByName.get(view.name)); - } - } - - public void buildAllViews() - { - for (MaterializedView view : allViews()) - view.build(); - } - - public void removeMaterializedView(String name) - { - MaterializedView view = viewsByName.remove(name); - - if (view == null) - return; - - SystemKeyspace.setMaterializedViewRemoved(baseCfs.metadata.ksName, view.name); - } - - public void addMaterializedView(MaterializedViewDefinition definition) - { - MaterializedView view = new MaterializedView(definition, baseCfs); - - viewsByName.put(definition.viewName, view); - } - - /** - * Calculates and pushes updates to the views replicas. The replicas are determined by - * {@link MaterializedViewUtils#getViewNaturalEndpoint(String, Token, Token)}. - */ - public void pushViewReplicaUpdates(PartitionUpdate update, boolean writeCommitLog) - { - List mutations = null; - TemporalRow.Set temporalRows = null; - for (Map.Entry view : viewsByName.entrySet()) - { - - temporalRows = view.getValue().getTemporalRowSet(update, temporalRows, false); - - Collection viewMutations = view.getValue().createMutations(update, temporalRows, false); - if (viewMutations != null && !viewMutations.isEmpty()) - { - if (mutations == null) - mutations = Lists.newLinkedList(); - mutations.addAll(viewMutations); - } - } - if (mutations != null) - { - StorageProxy.mutateMV(update.partitionKey().getKey(), mutations, writeCommitLog); - } - } - - public boolean updateAffectsView(PartitionUpdate upd) - { - for (MaterializedView view : allViews()) - { - if (view.updateAffectsView(upd)) - return true; - } - return false; - } - - public static Lock acquireLockFor(ByteBuffer key) - { - Lock lock = LOCKS.get(key); - - if (lock.tryLock()) - return lock; - - return null; - } - - public static boolean updatesAffectView(Collection mutations, boolean coordinatorBatchlog) - { - if (coordinatorBatchlog && !enableCoordinatorBatchlog) - return false; - - for (IMutation mutation : mutations) - { - for (PartitionUpdate cf : mutation.getPartitionUpdates()) - { - Keyspace keyspace = Keyspace.open(cf.metadata().ksName); - - if (coordinatorBatchlog && keyspace.getReplicationStrategy().getReplicationFactor() == 1) - continue; - - MaterializedViewManager viewManager = keyspace.getColumnFamilyStore(cf.metadata().cfId).materializedViewManager; - if (viewManager.updateAffectsView(cf)) - return true; - } - } - - return false; - } - - - public void forceBlockingFlush() - { - for (ColumnFamilyStore viewCfs : allViewsCfs()) - viewCfs.forceBlockingFlush(); - } - - public void dumpMemtables() - { - for (ColumnFamilyStore viewCfs : allViewsCfs()) - viewCfs.dumpMemtable(); - } - - public void truncateBlocking(long truncatedAt) - { - for (ColumnFamilyStore viewCfs : allViewsCfs()) - { - ReplayPosition replayAfter = viewCfs.discardSSTables(truncatedAt); - SystemKeyspace.saveTruncationRecord(viewCfs, truncatedAt, replayAfter); - } - } -} diff --git a/src/java/org/apache/cassandra/db/view/MaterializedView.java b/src/java/org/apache/cassandra/db/view/View.java similarity index 81% rename from src/java/org/apache/cassandra/db/view/MaterializedView.java rename to src/java/org/apache/cassandra/db/view/View.java index 52034cc4ca..7bcb59217f 100644 --- a/src/java/org/apache/cassandra/db/view/MaterializedView.java +++ b/src/java/org/apache/cassandra/db/view/View.java @@ -25,6 +25,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; +import java.util.UUID; import javax.annotation.Nullable; @@ -32,10 +33,9 @@ import com.google.common.collect.Iterables; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.ColumnDefinition; -import org.apache.cassandra.config.MaterializedViewDefinition; +import org.apache.cassandra.config.ViewDefinition; import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.ColumnIdentifier; -import org.apache.cassandra.cql3.statements.CFProperties; import org.apache.cassandra.db.AbstractReadCommandBuilder.SinglePartitionSliceBuilder; import org.apache.cassandra.db.CBuilder; import org.apache.cassandra.db.Clustering; @@ -43,7 +43,6 @@ import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.db.DeletionInfo; import org.apache.cassandra.db.DeletionTime; -import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.LivenessInfo; import org.apache.cassandra.db.Mutation; import org.apache.cassandra.db.RangeTombstone; @@ -65,23 +64,23 @@ import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.service.pager.QueryPager; /** - * A Materialized View copies data from a base table into a view table which can be queried independently from the - * base. Every update which targets the base table must be fed through the {@link MaterializedViewManager} to ensure + * A View copies data from a base table into a view table which can be queried independently from the + * base. Every update which targets the base table must be fed through the {@link ViewManager} to ensure * that if a view needs to be updated, the updates are properly created and fed into the view. * * This class does the job of translating the base row to the view row. * * It handles reading existing state and figuring out what tombstones need to be generated. * - * createMutations below is the "main method" + * {@link View#createMutations(AbstractBTreePartition, TemporalRow.Set, boolean)} is the "main method" * */ -public class MaterializedView +public class View { /** * The columns should all be updated together, so we use this object as group. */ - private static class MVColumns + private static class Columns { //These are the base column definitions in terms of the *views* partitioning. //Meaning we can see (for example) the partition key of the view contains a clustering key @@ -90,7 +89,7 @@ public class MaterializedView public final List primaryKeyDefs; public final List baseComplexColumns; - private MVColumns(List partitionDefs, List primaryKeyDefs, List baseComplexColumns) + private Columns(List partitionDefs, List primaryKeyDefs, List baseComplexColumns) { this.partitionDefs = partitionDefs; this.primaryKeyDefs = primaryKeyDefs; @@ -99,42 +98,32 @@ public class MaterializedView } public final String name; + private volatile ViewDefinition definition; private final ColumnFamilyStore baseCfs; - private ColumnFamilyStore _viewCfs = null; - private MVColumns columns; + private Columns columns; private final boolean viewHasAllPrimaryKeys; - private final boolean includeAll; - private MaterializedViewBuilder builder; + private final boolean includeAllColumns; + private ViewBuilder builder; - public MaterializedView(MaterializedViewDefinition definition, - ColumnFamilyStore baseCfs) + public View(ViewDefinition definition, + ColumnFamilyStore baseCfs) { this.baseCfs = baseCfs; name = definition.viewName; - includeAll = definition.includeAll; + includeAllColumns = definition.includeAllColumns; viewHasAllPrimaryKeys = updateDefinition(definition); } - /** - * Lazily fetch the CFS instance for the view. - * We do this lazily to avoid initilization issues. - * - * @return The views CFS instance - */ - public ColumnFamilyStore getViewCfs() + public ViewDefinition getDefinition() { - if (_viewCfs == null) - _viewCfs = Keyspace.openAndGetStore(Schema.instance.getCFMetaData(baseCfs.keyspace.getName(), name)); - - return _viewCfs; + return definition; } - /** * Lookup column definitions in the base table that correspond to the view columns (should be 1:1) * @@ -170,17 +159,20 @@ public class MaterializedView * @return true if the view contains only columns which are part of the base's primary key; false if there is at * least one column which is not. */ - public boolean updateDefinition(MaterializedViewDefinition definition) + public boolean updateDefinition(ViewDefinition definition) { - List partitionDefs = new ArrayList<>(definition.partitionColumns.size()); - List primaryKeyDefs = new ArrayList<>(definition.partitionColumns.size() - + definition.clusteringColumns.size()); + this.definition = definition; + + CFMetaData viewCfm = definition.metadata; + List partitionDefs = new ArrayList<>(viewCfm.partitionKeyColumns().size()); + List primaryKeyDefs = new ArrayList<>(viewCfm.partitionKeyColumns().size() + + viewCfm.clusteringColumns().size()); List baseComplexColumns = new ArrayList<>(); // We only add the partition columns to the partitions list, but both partition columns and clustering // columns are added to the primary keys list - boolean partitionAllPrimaryKeyColumns = resolveAndAddColumns(definition.partitionColumns, primaryKeyDefs, partitionDefs); - boolean clusteringAllPrimaryKeyColumns = resolveAndAddColumns(definition.clusteringColumns, primaryKeyDefs); + boolean partitionAllPrimaryKeyColumns = resolveAndAddColumns(Iterables.transform(viewCfm.partitionKeyColumns(), cd -> cd.name), primaryKeyDefs, partitionDefs); + boolean clusteringAllPrimaryKeyColumns = resolveAndAddColumns(Iterables.transform(viewCfm.clusteringColumns(), cd -> cd.name), primaryKeyDefs); for (ColumnDefinition cdef : baseCfs.metadata.allColumns()) { @@ -190,7 +182,7 @@ public class MaterializedView } } - this.columns = new MVColumns(partitionDefs, primaryKeyDefs, baseComplexColumns); + this.columns = new Columns(partitionDefs, primaryKeyDefs, baseComplexColumns); return partitionAllPrimaryKeyColumns && clusteringAllPrimaryKeyColumns; } @@ -211,10 +203,10 @@ public class MaterializedView public boolean updateAffectsView(AbstractBTreePartition partition) { // If we are including all of the columns, then any update will be included - if (includeAll) + if (includeAllColumns) return true; - // If there are range tombstones, tombstones will also need to be generated for the materialized view + // If there are range tombstones, tombstones will also need to be generated for the view // This requires a query of the base rows and generating tombstones for all of those values if (!partition.deletionInfo().isLive()) return true; @@ -229,7 +221,7 @@ public class MaterializedView for (ColumnData data : row) { - if (getViewCfs().metadata.getColumnDefinition(data.column().name) != null) + if (definition.metadata.getColumnDefinition(data.column().name) != null) return true; } } @@ -246,9 +238,9 @@ public class MaterializedView */ private Clustering viewClustering(TemporalRow temporalRow, TemporalRow.Resolver resolver) { - CFMetaData viewCfm = getViewCfs().metadata; + CFMetaData viewCfm = definition.metadata; int numViewClustering = viewCfm.clusteringColumns().size(); - CBuilder clustering = CBuilder.create(getViewCfs().getComparator()); + CBuilder clustering = CBuilder.create(viewCfm.comparator); for (int i = 0; i < numViewClustering; i++) { ColumnDefinition definition = viewCfm.clusteringColumns().get(i); @@ -267,7 +259,7 @@ public class MaterializedView TemporalRow.Resolver resolver, int nowInSec) { - CFMetaData viewCfm = getViewCfs().metadata; + CFMetaData viewCfm = definition.metadata; Row.Builder builder = BTreeRow.unsortedBuilder(nowInSec); builder.newRow(viewClustering(temporalRow, resolver)); builder.addRowDeletion(deletion); @@ -284,8 +276,7 @@ public class MaterializedView TemporalRow.Resolver resolver, int nowInSec) { - - CFMetaData viewCfm = getViewCfs().metadata; + CFMetaData viewCfm = definition.metadata; Row.Builder builder = BTreeRow.unsortedBuilder(nowInSec); builder.newRow(viewClustering(temporalRow, resolver)); builder.addComplexDeletion(deletedColumn, deletionTime); @@ -311,7 +302,7 @@ public class MaterializedView partitionKey[i] = value; } - CFMetaData metadata = getViewCfs().metadata; + CFMetaData metadata = definition.metadata; return metadata.decorateKey(CFMetaData.serializePartitionKey(metadata .getKeyValidatorAsClusteringComparator() .make(partitionKey))); @@ -348,14 +339,14 @@ public class MaterializedView } /** - * @return Mutation which is the transformed base table mutation for the materialized view. + * @return Mutation which is the transformed base table mutation for the view. */ private PartitionUpdate createUpdatesForInserts(TemporalRow temporalRow) { TemporalRow.Resolver resolver = TemporalRow.latest; DecoratedKey partitionKey = viewPartitionKey(temporalRow, resolver); - ColumnFamilyStore viewCfs = getViewCfs(); + CFMetaData viewCfm = definition.metadata; if (partitionKey == null) { @@ -365,18 +356,18 @@ public class MaterializedView Row.Builder regularBuilder = BTreeRow.unsortedBuilder(temporalRow.nowInSec); - CBuilder clustering = CBuilder.create(viewCfs.getComparator()); - for (int i = 0; i < viewCfs.metadata.clusteringColumns().size(); i++) + CBuilder clustering = CBuilder.create(viewCfm.comparator); + for (int i = 0; i < viewCfm.clusteringColumns().size(); i++) { - clustering.add(temporalRow.clusteringValue(viewCfs.metadata.clusteringColumns().get(i), resolver)); + clustering.add(temporalRow.clusteringValue(viewCfm.clusteringColumns().get(i), resolver)); } regularBuilder.newRow(clustering.build()); - regularBuilder.addPrimaryKeyLivenessInfo(LivenessInfo.create(viewCfs.metadata, + regularBuilder.addPrimaryKeyLivenessInfo(LivenessInfo.create(viewCfm, temporalRow.viewClusteringTimestamp(), temporalRow.viewClusteringTtl(), temporalRow.viewClusteringLocalDeletionTime())); - for (ColumnDefinition columnDefinition : viewCfs.metadata.allColumns()) + for (ColumnDefinition columnDefinition : viewCfm.allColumns()) { if (columnDefinition.isPrimaryKeyColumn()) continue; @@ -387,7 +378,7 @@ public class MaterializedView } } - return PartitionUpdate.singleRowUpdate(viewCfs.metadata, partitionKey, regularBuilder.build()); + return PartitionUpdate.singleRowUpdate(viewCfm, partitionKey, regularBuilder.build()); } /** @@ -597,7 +588,7 @@ public class MaterializedView for (ColumnDefinition def : this.columns.primaryKeyDefs) columns.add(def.name); - TemporalRow.Set rowSet = null; + TemporalRow.Set rowSet; if (existing == null) { rowSet = separateRows(partition, columns); @@ -671,79 +662,21 @@ public class MaterializedView this.builder = null; } - this.builder = new MaterializedViewBuilder(baseCfs, this); - CompactionManager.instance.submitMaterializedViewBuilder(builder); + this.builder = new ViewBuilder(baseCfs, this); + CompactionManager.instance.submitViewBuilder(builder); } @Nullable - public static CFMetaData findBaseTable(String keyspace, String view) + public static CFMetaData findBaseTable(String keyspace, String viewName) + { + ViewDefinition view = Schema.instance.getView(keyspace, viewName); + return (view == null) ? null : Schema.instance.getCFMetaData(view.baseTableId); + } + + public static Iterable findAll(String keyspace, String baseTable) { KeyspaceMetadata ksm = Schema.instance.getKSMetaData(keyspace); - if (ksm == null) - return null; - - for (CFMetaData cfm : ksm.tables) - if (cfm.getMaterializedViews().get(view).isPresent()) - return cfm; - - return null; - } - - /** - * @return CFMetaData which represents the definition given - */ - public static CFMetaData getCFMetaData(MaterializedViewDefinition definition, - CFMetaData baseCf, - CFProperties properties) - { - CFMetaData.Builder viewBuilder = CFMetaData.Builder - .createView(baseCf.ksName, definition.viewName); - - ColumnDefinition nonPkTarget = null; - - for (ColumnIdentifier targetIdentifier : definition.partitionColumns) - { - ColumnDefinition target = baseCf.getColumnDefinition(targetIdentifier); - if (!target.isPartitionKey()) - nonPkTarget = target; - - viewBuilder.addPartitionKey(target.name, properties.getReversableType(targetIdentifier, target.type)); - } - - Collection included = new ArrayList<>(); - for(ColumnIdentifier identifier : definition.included) - { - ColumnDefinition cfDef = baseCf.getColumnDefinition(identifier); - assert cfDef != null; - included.add(cfDef); - } - - boolean includeAll = included.isEmpty(); - - for (ColumnIdentifier ident : definition.clusteringColumns) - { - ColumnDefinition column = baseCf.getColumnDefinition(ident); - viewBuilder.addClusteringColumn(ident, properties.getReversableType(ident, column.type)); - } - - for (ColumnDefinition column : baseCf.partitionColumns().regulars) - { - if (column != nonPkTarget && (includeAll || included.contains(column))) - { - viewBuilder.addRegularColumn(column.name, column.type); - } - } - - //Add any extra clustering columns - for (ColumnDefinition column : Iterables.concat(baseCf.partitionKeyColumns(), baseCf.clusteringColumns())) - { - if ( (!definition.partitionColumns.contains(column.name) && !definition.clusteringColumns.contains(column.name)) && - (includeAll || included.contains(column)) ) - { - viewBuilder.addRegularColumn(column.name, column.type); - } - } - - return viewBuilder.build().params(properties.properties.asNewTableParams()); + final UUID baseId = Schema.instance.getId(keyspace, baseTable); + return Iterables.filter(ksm.views, view -> view.baseTableId.equals(baseId)); } } diff --git a/src/java/org/apache/cassandra/db/view/MaterializedViewBuilder.java b/src/java/org/apache/cassandra/db/view/ViewBuilder.java similarity index 87% rename from src/java/org/apache/cassandra/db/view/MaterializedViewBuilder.java rename to src/java/org/apache/cassandra/db/view/ViewBuilder.java index e23fd84299..62aa3324e1 100644 --- a/src/java/org/apache/cassandra/db/view/MaterializedViewBuilder.java +++ b/src/java/org/apache/cassandra/db/view/ViewBuilder.java @@ -39,7 +39,6 @@ import org.apache.cassandra.db.compaction.CompactionInfo; import org.apache.cassandra.db.compaction.CompactionManager; import org.apache.cassandra.db.compaction.OperationType; import org.apache.cassandra.db.lifecycle.SSTableSet; -import org.apache.cassandra.db.lifecycle.View; import org.apache.cassandra.db.partitions.FilteredPartition; import org.apache.cassandra.db.partitions.PartitionIterator; import org.apache.cassandra.db.rows.RowIterator; @@ -57,18 +56,18 @@ import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.UUIDGen; import org.apache.cassandra.utils.concurrent.Refs; -public class MaterializedViewBuilder extends CompactionInfo.Holder +public class ViewBuilder extends CompactionInfo.Holder { private final ColumnFamilyStore baseCfs; - private final MaterializedView view; + private final View view; private final UUID compactionId; private volatile Token prevToken = null; - private static final Logger logger = LoggerFactory.getLogger(MaterializedViewBuilder.class); + private static final Logger logger = LoggerFactory.getLogger(ViewBuilder.class); private volatile boolean isStopped = false; - public MaterializedViewBuilder(ColumnFamilyStore baseCfs, MaterializedView view) + public ViewBuilder(ColumnFamilyStore baseCfs, View view) { this.baseCfs = baseCfs; this.view = view; @@ -109,13 +108,13 @@ public class MaterializedViewBuilder extends CompactionInfo.Holder return; Iterable> ranges = StorageService.instance.getLocalRanges(baseCfs.metadata.ksName); - final Pair buildStatus = SystemKeyspace.getMaterializedViewBuildStatus(ksname, viewName); + final Pair buildStatus = SystemKeyspace.getViewBuildStatus(ksname, viewName); Token lastToken; - Function> function; + Function> function; if (buildStatus == null) { baseCfs.forceBlockingFlush(); - function = View.select(SSTableSet.CANONICAL); + function = org.apache.cassandra.db.lifecycle.View.select(SSTableSet.CANONICAL); int generation = Integer.MIN_VALUE; try (Refs temp = baseCfs.selectAndReference(function).refs) @@ -126,17 +125,17 @@ public class MaterializedViewBuilder extends CompactionInfo.Holder } } - SystemKeyspace.beginMaterializedViewBuild(ksname, viewName, generation); + SystemKeyspace.beginViewBuild(ksname, viewName, generation); lastToken = null; } else { - function = new Function>() + function = new Function>() { @Nullable - public Iterable apply(View view) + public Iterable apply(org.apache.cassandra.db.lifecycle.View view) { - Iterable readers = View.select(SSTableSet.CANONICAL).apply(view); + Iterable readers = org.apache.cassandra.db.lifecycle.View.select(SSTableSet.CANONICAL).apply(view); if (readers != null) return Iterables.filter(readers, ssTableReader -> ssTableReader.descriptor.generation <= buildStatus.left); return null; @@ -163,7 +162,7 @@ public class MaterializedViewBuilder extends CompactionInfo.Holder if (prevToken == null || prevToken.compareTo(token) != 0) { - SystemKeyspace.updateMaterializedViewBuildStatus(ksname, viewName, key.getToken()); + SystemKeyspace.updateViewBuildStatus(ksname, viewName, key.getToken()); prevToken = token; } } @@ -173,13 +172,13 @@ public class MaterializedViewBuilder extends CompactionInfo.Holder } if (!isStopped) - SystemKeyspace.finishMaterializedViewBuildStatus(ksname, viewName); + SystemKeyspace.finishViewBuildStatus(ksname, viewName); } catch (Exception e) { - final MaterializedViewBuilder builder = new MaterializedViewBuilder(baseCfs, view); - ScheduledExecutors.nonPeriodicTasks.schedule(() -> CompactionManager.instance.submitMaterializedViewBuilder(builder), + final ViewBuilder builder = new ViewBuilder(baseCfs, view); + ScheduledExecutors.nonPeriodicTasks.schedule(() -> CompactionManager.instance.submitViewBuilder(builder), 5, TimeUnit.MINUTES); logger.warn("Materialized View failed to complete, sleeping 5 minutes before restarting", e); diff --git a/src/java/org/apache/cassandra/db/view/ViewManager.java b/src/java/org/apache/cassandra/db/view/ViewManager.java new file mode 100644 index 0000000000..2364ed1284 --- /dev/null +++ b/src/java/org/apache/cassandra/db/view/ViewManager.java @@ -0,0 +1,271 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.cassandra.db.view; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentNavigableMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.locks.Lock; + +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Striped; + +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.config.ViewDefinition; +import org.apache.cassandra.db.ColumnFamilyStore; +import org.apache.cassandra.db.IMutation; +import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.db.Mutation; +import org.apache.cassandra.db.SystemKeyspace; +import org.apache.cassandra.db.commitlog.ReplayPosition; +import org.apache.cassandra.db.partitions.PartitionUpdate; +import org.apache.cassandra.dht.Token; +import org.apache.cassandra.service.StorageProxy; + +/** + * Manages {@link View}'s for a single {@link ColumnFamilyStore}. All of the views for that table are created when this + * manager is initialized. + * + * The main purposes of the manager are to provide a single location for updates to be vetted to see whether they update + * any views {@link ViewManager#updatesAffectView(Collection, boolean)}, provide locks to prevent multiple + * updates from creating incoherent updates in the view {@link ViewManager#acquireLockFor(ByteBuffer)}, and + * to affect change on the view. + */ +public class ViewManager +{ + public class ForStore + { + private final ConcurrentNavigableMap viewsByName; + + public ForStore() + { + this.viewsByName = new ConcurrentSkipListMap<>(); + } + + public Iterable allViews() + { + return viewsByName.values(); + } + + public Iterable allViewsCfs() + { + List viewColumnFamilies = new ArrayList<>(); + for (View view : allViews()) + viewColumnFamilies.add(keyspace.getColumnFamilyStore(view.getDefinition().viewName)); + return viewColumnFamilies; + } + + public void forceBlockingFlush() + { + for (ColumnFamilyStore viewCfs : allViewsCfs()) + viewCfs.forceBlockingFlush(); + } + + public void dumpMemtables() + { + for (ColumnFamilyStore viewCfs : allViewsCfs()) + viewCfs.dumpMemtable(); + } + + public void truncateBlocking(long truncatedAt) + { + for (ColumnFamilyStore viewCfs : allViewsCfs()) + { + ReplayPosition replayAfter = viewCfs.discardSSTables(truncatedAt); + SystemKeyspace.saveTruncationRecord(viewCfs, truncatedAt, replayAfter); + } + } + + public void addView(View view) + { + viewsByName.put(view.name, view); + } + + public void removeView(String name) + { + viewsByName.remove(name); + } + } + + private static final Striped LOCKS = Striped.lazyWeakLock(DatabaseDescriptor.getConcurrentViewWriters() * 1024); + + private static final boolean enableCoordinatorBatchlog = Boolean.getBoolean("cassandra.mv_enable_coordinator_batchlog"); + + private final ConcurrentNavigableMap viewManagersByStore; + private final ConcurrentNavigableMap viewsByName; + private final Keyspace keyspace; + + public ViewManager(Keyspace keyspace) + { + this.viewManagersByStore = new ConcurrentSkipListMap<>(); + this.viewsByName = new ConcurrentSkipListMap<>(); + this.keyspace = keyspace; + } + + /** + * Calculates and pushes updates to the views replicas. The replicas are determined by + * {@link ViewUtils#getViewNaturalEndpoint(String, Token, Token)}. + */ + public void pushViewReplicaUpdates(PartitionUpdate update, boolean writeCommitLog) + { + List mutations = null; + TemporalRow.Set temporalRows = null; + for (Map.Entry view : viewsByName.entrySet()) + { + temporalRows = view.getValue().getTemporalRowSet(update, temporalRows, false); + + Collection viewMutations = view.getValue().createMutations(update, temporalRows, false); + if (viewMutations != null && !viewMutations.isEmpty()) + { + if (mutations == null) + mutations = Lists.newLinkedList(); + mutations.addAll(viewMutations); + } + } + + if (mutations != null) + StorageProxy.mutateMV(update.partitionKey().getKey(), mutations, writeCommitLog); + } + + public boolean updatesAffectView(Collection mutations, boolean coordinatorBatchlog) + { + if (coordinatorBatchlog && !enableCoordinatorBatchlog) + return false; + + for (IMutation mutation : mutations) + { + for (PartitionUpdate cf : mutation.getPartitionUpdates()) + { + assert keyspace.getName().equals(cf.metadata().ksName); + + if (coordinatorBatchlog && keyspace.getReplicationStrategy().getReplicationFactor() == 1) + continue; + + for (View view : allViews()) + { + if (!cf.metadata().cfId.equals(view.getDefinition().baseTableId)) + continue; + + if (view.updateAffectsView(cf)) + return true; + } + } + } + + return false; + } + + public Iterable allViews() + { + return viewsByName.values(); + } + + public void update(String viewName) + { + View view = viewsByName.get(viewName); + assert view != null : "When updating a view, it should already be in the ViewManager"; + view.build(); + + // We provide the new definition from the base metadata + Optional viewDefinition = keyspace.getMetadata().views.get(viewName); + assert viewDefinition.isPresent() : "When updating a view, it should still be in the Keyspaces views"; + view.updateDefinition(viewDefinition.get()); + } + + public void reload() + { + Map newViewsByName = new HashMap<>(); + for (ViewDefinition definition : keyspace.getMetadata().views) + { + newViewsByName.put(definition.viewName, definition); + } + + for (String viewName : viewsByName.keySet()) + { + if (!newViewsByName.containsKey(viewName)) + removeView(viewName); + } + + for (Map.Entry entry : newViewsByName.entrySet()) + { + if (!viewsByName.containsKey(entry.getKey())) + addView(entry.getValue()); + } + + for (View view : allViews()) + { + view.build(); + // We provide the new definition from the base metadata + view.updateDefinition(newViewsByName.get(view.name)); + } + } + + public void addView(ViewDefinition definition) + { + View view = new View(definition, keyspace.getColumnFamilyStore(definition.baseTableId)); + forTable(view.getDefinition().baseTableId).addView(view); + viewsByName.put(definition.viewName, view); + } + + public void removeView(String name) + { + View view = viewsByName.remove(name); + + if (view == null) + return; + + forTable(view.getDefinition().baseTableId).removeView(name); + SystemKeyspace.setViewRemoved(keyspace.getName(), view.name); + } + + public void buildAllViews() + { + for (View view : allViews()) + view.build(); + } + + public ForStore forTable(UUID baseId) + { + ForStore forStore = viewManagersByStore.get(baseId); + if (forStore == null) + { + forStore = new ForStore(); + ForStore previous = viewManagersByStore.put(baseId, forStore); + if (previous != null) + forStore = previous; + } + return forStore; + } + + public static Lock acquireLockFor(ByteBuffer key) + { + Lock lock = LOCKS.get(key); + + if (lock.tryLock()) + return lock; + + return null; + } +} diff --git a/src/java/org/apache/cassandra/db/view/MaterializedViewUtils.java b/src/java/org/apache/cassandra/db/view/ViewUtils.java similarity index 98% rename from src/java/org/apache/cassandra/db/view/MaterializedViewUtils.java rename to src/java/org/apache/cassandra/db/view/ViewUtils.java index ea817506e1..628142de98 100644 --- a/src/java/org/apache/cassandra/db/view/MaterializedViewUtils.java +++ b/src/java/org/apache/cassandra/db/view/ViewUtils.java @@ -29,9 +29,9 @@ import org.apache.cassandra.locator.AbstractReplicationStrategy; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.FBUtilities; -public final class MaterializedViewUtils +public final class ViewUtils { - private MaterializedViewUtils() + private ViewUtils() { } diff --git a/src/java/org/apache/cassandra/dht/ByteOrderedPartitioner.java b/src/java/org/apache/cassandra/dht/ByteOrderedPartitioner.java index 46872c1429..bbf6fd6a88 100644 --- a/src/java/org/apache/cassandra/dht/ByteOrderedPartitioner.java +++ b/src/java/org/apache/cassandra/dht/ByteOrderedPartitioner.java @@ -276,7 +276,7 @@ public class ByteOrderedPartitioner implements IPartitioner for (String ks : Schema.instance.getKeyspaces()) { - for (CFMetaData cfmd : Schema.instance.getTables(ks)) + for (CFMetaData cfmd : Schema.instance.getTablesAndViews(ks)) { for (Range r : sortedRanges) { diff --git a/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java b/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java index 464ac3d5a2..96b4ca03df 100644 --- a/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java +++ b/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java @@ -216,7 +216,7 @@ public class OrderPreservingPartitioner implements IPartitioner for (String ks : Schema.instance.getKeyspaces()) { - for (CFMetaData cfmd : Schema.instance.getTables(ks)) + for (CFMetaData cfmd : Schema.instance.getTablesAndViews(ks)) { for (Range r : sortedRanges) { diff --git a/src/java/org/apache/cassandra/metrics/MVWriteMetrics.java b/src/java/org/apache/cassandra/metrics/ViewWriteMetrics.java similarity index 93% rename from src/java/org/apache/cassandra/metrics/MVWriteMetrics.java rename to src/java/org/apache/cassandra/metrics/ViewWriteMetrics.java index 39a55749a0..c99cc5c221 100644 --- a/src/java/org/apache/cassandra/metrics/MVWriteMetrics.java +++ b/src/java/org/apache/cassandra/metrics/ViewWriteMetrics.java @@ -22,12 +22,12 @@ import com.codahale.metrics.Counter; import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics; -public class MVWriteMetrics extends ClientRequestMetrics +public class ViewWriteMetrics extends ClientRequestMetrics { public final Counter viewReplicasAttempted; public final Counter viewReplicasSuccess; - public MVWriteMetrics(String scope) { + public ViewWriteMetrics(String scope) { super(scope); viewReplicasAttempted = Metrics.counter(factory.createMetricName("ViewReplicasAttempted")); viewReplicasSuccess = Metrics.counter(factory.createMetricName("ViewReplicasSuccess")); diff --git a/src/java/org/apache/cassandra/schema/KeyspaceMetadata.java b/src/java/org/apache/cassandra/schema/KeyspaceMetadata.java index 372ff6ede4..0a0d9d8d07 100644 --- a/src/java/org/apache/cassandra/schema/KeyspaceMetadata.java +++ b/src/java/org/apache/cassandra/schema/KeyspaceMetadata.java @@ -22,9 +22,12 @@ import java.util.Optional; import java.util.Set; import com.google.common.base.Objects; +import com.google.common.collect.Iterables; +import org.apache.avro.reflect.Nullable; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.Schema; +import org.apache.cassandra.config.ViewDefinition; import org.apache.cassandra.exceptions.ConfigurationException; /** @@ -35,51 +38,72 @@ public final class KeyspaceMetadata public final String name; public final KeyspaceParams params; public final Tables tables; + public final Views views; public final Types types; public final Functions functions; - private KeyspaceMetadata(String name, KeyspaceParams params, Tables tables, Types types, Functions functions) + private KeyspaceMetadata(String name, KeyspaceParams params, Tables tables, Views views, Types types, Functions functions) { this.name = name; this.params = params; this.tables = tables; + this.views = views; this.types = types; this.functions = functions; } public static KeyspaceMetadata create(String name, KeyspaceParams params) { - return new KeyspaceMetadata(name, params, Tables.none(), Types.none(), Functions.none()); + return new KeyspaceMetadata(name, params, Tables.none(), Views.none(), Types.none(), Functions.none()); } public static KeyspaceMetadata create(String name, KeyspaceParams params, Tables tables) { - return new KeyspaceMetadata(name, params, tables, Types.none(), Functions.none()); + return new KeyspaceMetadata(name, params, tables, Views.none(), Types.none(), Functions.none()); } - public static KeyspaceMetadata create(String name, KeyspaceParams params, Tables tables, Types types, Functions functions) + public static KeyspaceMetadata create(String name, KeyspaceParams params, Tables tables, Views views, Types types, Functions functions) { - return new KeyspaceMetadata(name, params, tables, types, functions); + return new KeyspaceMetadata(name, params, tables, views, types, functions); } public KeyspaceMetadata withSwapped(KeyspaceParams params) { - return new KeyspaceMetadata(name, params, tables, types, functions); + return new KeyspaceMetadata(name, params, tables, views, types, functions); } - public KeyspaceMetadata withSwapped(Tables tables) + public KeyspaceMetadata withSwapped(Tables regular) { - return new KeyspaceMetadata(name, params, tables, types, functions); + return new KeyspaceMetadata(name, params, regular, views, types, functions); + } + + public KeyspaceMetadata withSwapped(Views views) + { + return new KeyspaceMetadata(name, params, tables, views, types, functions); } public KeyspaceMetadata withSwapped(Types types) { - return new KeyspaceMetadata(name, params, tables, types, functions); + return new KeyspaceMetadata(name, params, tables, views, types, functions); } public KeyspaceMetadata withSwapped(Functions functions) { - return new KeyspaceMetadata(name, params, tables, types, functions); + return new KeyspaceMetadata(name, params, tables, views, types, functions); + } + + public Iterable tablesAndViews() + { + return Iterables.concat(tables, views.metadatas()); + } + + @Nullable + public CFMetaData getTableOrViewNullable(String tableOrViewName) + { + ViewDefinition view = views.getNullable(tableOrViewName); + return view == null + ? tables.getNullable(tableOrViewName) + : view.metadata; } public Set existingIndexNames(String cfToExclude) @@ -94,7 +118,7 @@ public final class KeyspaceMetadata public Optional findIndexedTable(String indexName) { - for (CFMetaData cfm : tables) + for (CFMetaData cfm : tablesAndViews()) if (cfm.getIndexes().has(indexName)) return Optional.of(cfm); @@ -104,7 +128,7 @@ public final class KeyspaceMetadata @Override public int hashCode() { - return Objects.hashCode(name, params, tables, functions, types); + return Objects.hashCode(name, params, tables, views, functions, types); } @Override @@ -121,6 +145,7 @@ public final class KeyspaceMetadata return name.equals(other.name) && params.equals(other.params) && tables.equals(other.tables) + && views.equals(other.views) && functions.equals(other.functions) && types.equals(other.types); } @@ -132,6 +157,7 @@ public final class KeyspaceMetadata .add("name", name) .add("params", params) .add("tables", tables) + .add("views", views) .add("functions", functions) .add("types", types) .toString(); @@ -144,6 +170,6 @@ public final class KeyspaceMetadata + "or contain non-alphanumeric-underscore characters (got \"%s\")", Schema.NAME_LENGTH, name)); - tables.forEach(CFMetaData::validate); + tablesAndViews().forEach(CFMetaData::validate); } } diff --git a/src/java/org/apache/cassandra/schema/SchemaKeyspace.java b/src/java/org/apache/cassandra/schema/SchemaKeyspace.java index 55b841bd95..c92261245a 100644 --- a/src/java/org/apache/cassandra/schema/SchemaKeyspace.java +++ b/src/java/org/apache/cassandra/schema/SchemaKeyspace.java @@ -68,14 +68,14 @@ public final class SchemaKeyspace public static final String COLUMNS = "columns"; public static final String DROPPED_COLUMNS = "dropped_columns"; public static final String TRIGGERS = "triggers"; - public static final String MATERIALIZED_VIEWS = "materialized_views"; + public static final String VIEWS = "views"; public static final String TYPES = "types"; public static final String FUNCTIONS = "functions"; public static final String AGGREGATES = "aggregates"; public static final String INDEXES = "indexes"; public static final List ALL = - ImmutableList.of(KEYSPACES, TABLES, COLUMNS, TRIGGERS, MATERIALIZED_VIEWS, TYPES, FUNCTIONS, AGGREGATES, INDEXES); + ImmutableList.of(KEYSPACES, TABLES, COLUMNS, TRIGGERS, VIEWS, TYPES, FUNCTIONS, AGGREGATES, INDEXES); private static final CFMetaData Keyspaces = compile(KEYSPACES, @@ -144,17 +144,31 @@ public final class SchemaKeyspace + "options frozen>," + "PRIMARY KEY ((keyspace_name), table_name, trigger_name))"); - private static final CFMetaData MaterializedViews = - compile(MATERIALIZED_VIEWS, - "materialized views definitions", + private static final CFMetaData Views = + compile(VIEWS, + "view definitions", "CREATE TABLE %s (" + "keyspace_name text," - + "table_name text," + "view_name text," - + "target_columns frozen>," - + "clustering_columns frozen>," - + "included_columns frozen>," - + "PRIMARY KEY ((keyspace_name), table_name, view_name))"); + + "base_table_id uuid," + + "base_table_name text," + + "bloom_filter_fp_chance double," + + "caching frozen>," + + "comment text," + + "compaction frozen>," + + "compression frozen>," + + "dclocal_read_repair_chance double," + + "default_time_to_live int," + + "extensions frozen>," + + "gc_grace_seconds int," + + "id uuid," + + "include_all_columns boolean," + + "max_index_interval int," + + "memtable_flush_period_in_ms int," + + "min_index_interval int," + + "read_repair_chance double," + + "speculative_retry text," + + "PRIMARY KEY ((keyspace_name), view_name))"); private static final CFMetaData Indexes = compile(INDEXES, @@ -210,7 +224,7 @@ public final class SchemaKeyspace + "PRIMARY KEY ((keyspace_name), aggregate_name, signature))"); public static final List ALL_TABLE_METADATA = - ImmutableList.of(Keyspaces, Tables, Columns, Triggers, DroppedColumns, MaterializedViews, Types, Functions, Aggregates, Indexes); + ImmutableList.of(Keyspaces, Tables, Columns, Triggers, DroppedColumns, Views, Types, Functions, Aggregates, Indexes); private static CFMetaData compile(String name, String description, String schema) { @@ -265,9 +279,10 @@ public final class SchemaKeyspace readSchemaPartitionForKeyspaceAndApply(TYPES, key, types -> readSchemaPartitionForKeyspaceAndApply(TABLES, key, - tables -> readSchemaPartitionForKeyspaceAndApply(FUNCTIONS, key, + tables -> readSchemaPartitionForKeyspaceAndApply(VIEWS, key, + views -> readSchemaPartitionForKeyspaceAndApply(FUNCTIONS, key, functions -> readSchemaPartitionForKeyspaceAndApply(AGGREGATES, key, - aggregates -> keyspaces.add(createKeyspaceFromSchemaPartitions(partition, tables, types, functions, aggregates))))) + aggregates -> keyspaces.add(createKeyspaceFromSchemaPartitions(partition, tables, views, types, functions, aggregates)))))) ); } } @@ -473,6 +488,7 @@ public final class SchemaKeyspace // current state of the schema Map oldKeyspaces = readSchemaForKeyspaces(KEYSPACES, keyspaces); Map oldColumnFamilies = readSchemaForKeyspaces(TABLES, keyspaces); + Map oldViews = readSchemaForKeyspaces(VIEWS, keyspaces); Map oldTypes = readSchemaForKeyspaces(TYPES, keyspaces); Map oldFunctions = readSchemaForKeyspaces(FUNCTIONS, keyspaces); Map oldAggregates = readSchemaForKeyspaces(AGGREGATES, keyspaces); @@ -485,12 +501,14 @@ public final class SchemaKeyspace // with new data applied Map newKeyspaces = readSchemaForKeyspaces(KEYSPACES, keyspaces); Map newColumnFamilies = readSchemaForKeyspaces(TABLES, keyspaces); + Map newViews = readSchemaForKeyspaces(VIEWS, keyspaces); Map newTypes = readSchemaForKeyspaces(TYPES, keyspaces); Map newFunctions = readSchemaForKeyspaces(FUNCTIONS, keyspaces); Map newAggregates = readSchemaForKeyspaces(AGGREGATES, keyspaces); Set keyspacesToDrop = mergeKeyspaces(oldKeyspaces, newKeyspaces); mergeTables(oldColumnFamilies, newColumnFamilies); + mergeViews(oldViews, newViews); mergeTypes(oldTypes, newTypes); mergeFunctions(oldFunctions, newFunctions); mergeAggregates(oldAggregates, newAggregates); @@ -546,6 +564,27 @@ public final class SchemaKeyspace }); } + private static void mergeViews(Map before, Map after) + { + diffSchema(before, after, new Differ() + { + public void onDropped(UntypedResultSet.Row oldRow) + { + Schema.instance.dropView(oldRow.getString("keyspace_name"), oldRow.getString("view_name")); + } + + public void onAdded(UntypedResultSet.Row newRow) + { + Schema.instance.addView(createViewFromViewRow(newRow)); + } + + public void onUpdated(UntypedResultSet.Row oldRow, UntypedResultSet.Row newRow) + { + Schema.instance.updateView(newRow.getString("keyspace_name"), newRow.getString("view_name")); + } + }); + } + private static void mergeTypes(Map before, Map after) { diffSchema(before, after, new Differ() @@ -697,6 +736,7 @@ public final class SchemaKeyspace Mutation mutation = makeCreateKeyspaceMutation(keyspace.name, keyspace.params, timestamp); keyspace.tables.forEach(table -> addTableToSchemaMutation(table, timestamp, true, mutation)); + keyspace.views.forEach(view -> addViewToSchemaMutation(view, timestamp, true, mutation)); keyspace.types.forEach(type -> addTypeToSchemaMutation(type, timestamp, mutation)); keyspace.functions.udfs().forEach(udf -> addFunctionToSchemaMutation(udf, timestamp, mutation)); keyspace.functions.udas().forEach(uda -> addAggregateToSchemaMutation(uda, timestamp, mutation)); @@ -717,6 +757,7 @@ public final class SchemaKeyspace private static KeyspaceMetadata createKeyspaceFromSchemaPartitions(RowIterator serializedParams, RowIterator serializedTables, + RowIterator serializedViews, RowIterator serializedTypes, RowIterator serializedFunctions, RowIterator serializedAggregates) @@ -725,13 +766,14 @@ public final class SchemaKeyspace KeyspaceParams params = createKeyspaceParamsFromSchemaPartition(serializedParams); Tables tables = createTablesFromTablesPartition(serializedTables); + Views views = createViewsFromViewsPartition(serializedViews); Types types = createTypesFromPartition(serializedTypes); Collection udfs = createFunctionsFromFunctionsPartition(serializedFunctions); Collection udas = createAggregatesFromAggregatesPartition(serializedAggregates); Functions functions = org.apache.cassandra.schema.Functions.builder().add(udfs).add(udas).build(); - return KeyspaceMetadata.create(name, params, tables, types, functions); + return KeyspaceMetadata.create(name, params, tables, views, types, functions); } /** @@ -849,9 +891,6 @@ public final class SchemaKeyspace for (TriggerMetadata trigger : table.getTriggers()) addTriggerToSchemaMutation(table, trigger, timestamp, mutation); - for (MaterializedViewDefinition materializedView: table.getMaterializedViews()) - addMaterializedViewToSchemaMutation(table, materializedView, timestamp, mutation); - for (IndexMetadata index : table.getIndexes()) addIndexToSchemaMutation(table, index, timestamp, mutation); } @@ -931,22 +970,6 @@ public final class SchemaKeyspace for (TriggerMetadata trigger : triggerDiff.entriesOnlyOnRight().values()) addTriggerToSchemaMutation(newTable, trigger, timestamp, mutation); - MapDifference materializedViewDiff = materializedViewsDiff(oldTable.getMaterializedViews(), newTable.getMaterializedViews()); - - // dropped materialized views - for (MaterializedViewDefinition materializedView : materializedViewDiff.entriesOnlyOnLeft().values()) - dropMaterializedViewFromSchemaMutation(oldTable, materializedView, timestamp, mutation); - - // newly created materialized views - for (MaterializedViewDefinition materializedView : materializedViewDiff.entriesOnlyOnRight().values()) - addMaterializedViewToSchemaMutation(newTable, materializedView, timestamp, mutation); - - // updated materialized views need to be updated - for (MapDifference.ValueDifference diff : materializedViewDiff.entriesDiffering().values()) - { - addUpdatedMaterializedViewDefinitionToSchemaMutation(newTable, diff.rightValue(), timestamp, mutation); - } - MapDifference indexesDiff = indexesDiff(oldTable.getIndexes(), newTable.getIndexes()); @@ -989,17 +1012,6 @@ public final class SchemaKeyspace return Maps.difference(beforeMap, afterMap); } - private static MapDifference materializedViewsDiff(MaterializedViews before, MaterializedViews after) - { - Map beforeMap = new HashMap<>(); - before.forEach(v -> beforeMap.put(v.viewName, v)); - - Map afterMap = new HashMap<>(); - after.forEach(v -> afterMap.put(v.viewName, v)); - - return Maps.difference(beforeMap, afterMap); - } - public static Mutation makeDropTableMutation(KeyspaceMetadata keyspace, CFMetaData table, long timestamp) { // Include the serialized keyspace in case the target node missed a CREATE KEYSPACE migration (see CASSANDRA-5631). @@ -1013,9 +1025,6 @@ public final class SchemaKeyspace for (TriggerMetadata trigger : table.getTriggers()) dropTriggerFromSchemaMutation(table, trigger, timestamp, mutation); - for (MaterializedViewDefinition materializedView : table.getMaterializedViews()) - dropMaterializedViewFromSchemaMutation(table, materializedView, timestamp, mutation); - for (IndexMetadata index : table.getIndexes()) dropIndexFromSchemaMutation(table, index, timestamp, mutation); @@ -1083,12 +1092,8 @@ public final class SchemaKeyspace Triggers triggers = readSchemaPartitionForTableAndApply(TRIGGERS, keyspace, table, SchemaKeyspace::createTriggersFromTriggersPartition); - MaterializedViews views = - readSchemaPartitionForTableAndApply(MATERIALIZED_VIEWS, keyspace, table, SchemaKeyspace::createMaterializedViewsFromMaterializedViewsPartition); - CFMetaData cfm = createTableFromTableRowAndColumns(row, columns).droppedColumns(droppedColumns) - .triggers(triggers) - .materializedViews(views); + .triggers(triggers); // the CFMetaData itself is required to build the collection of indexes as // the column definitions are needed because we store only the name each @@ -1114,7 +1119,6 @@ public final class SchemaKeyspace boolean isCounter = flags.contains(CFMetaData.Flag.COUNTER); boolean isDense = flags.contains(CFMetaData.Flag.DENSE); boolean isCompound = flags.contains(CFMetaData.Flag.COMPOUND); - boolean isMaterializedView = flags.contains(CFMetaData.Flag.VIEW); return CFMetaData.create(keyspace, table, @@ -1123,7 +1127,7 @@ public final class SchemaKeyspace isCompound, isSuper, isCounter, - isMaterializedView, + false, columns, DatabaseDescriptor.getPartitioner()) .params(createTableParamsFromRow(row)); @@ -1274,82 +1278,164 @@ public final class SchemaKeyspace } /* - * Materialized View metadata serialization/deserialization. + * View metadata serialization/deserialization. */ - private static void addMaterializedViewToSchemaMutation(CFMetaData table, MaterializedViewDefinition materializedView, long timestamp, Mutation mutation) + public static Mutation makeCreateViewMutation(KeyspaceMetadata keyspace, ViewDefinition view, long timestamp) { - RowUpdateBuilder builder = new RowUpdateBuilder(MaterializedViews, timestamp, mutation) - .clustering(table.cfName, materializedView.viewName); + // Include the serialized keyspace in case the target node missed a CREATE KEYSPACE migration (see CASSANDRA-5631). + Mutation mutation = makeCreateKeyspaceMutation(keyspace.name, keyspace.params, timestamp); + addViewToSchemaMutation(view, timestamp, true, mutation); + return mutation; + } - builder.frozenList("target_columns", materializedView.partitionColumns.stream().map(ColumnIdentifier::toString).collect(Collectors.toList())); - builder.frozenList("clustering_columns", materializedView.clusteringColumns.stream().map(ColumnIdentifier::toString).collect(Collectors.toList())); - builder.frozenList("included_columns", materializedView.included.stream().map(ColumnIdentifier::toString).collect(Collectors.toList())); + private static void addViewToSchemaMutation(ViewDefinition view, long timestamp, boolean includeColumns, Mutation mutation) + { + RowUpdateBuilder builder = new RowUpdateBuilder(Views, timestamp, mutation) + .clustering(view.viewName); + + CFMetaData table = view.metadata; + + builder.add("include_all_columns", view.includeAllColumns) + .add("base_table_id", view.baseTableId) + .add("base_table_name", view.baseTableMetadata().cfName) + .add("id", table.cfId); + + addTableParamsToSchemaMutation(table.params, builder); + + if (includeColumns) + { + for (ColumnDefinition column : table.allColumns()) + addColumnToSchemaMutation(table, column, timestamp, mutation); + + for (CFMetaData.DroppedColumn column : table.getDroppedColumns().values()) + addDroppedColumnToSchemaMutation(table, column, timestamp, mutation); + } builder.build(); } - private static void dropMaterializedViewFromSchemaMutation(CFMetaData table, MaterializedViewDefinition materializedView, long timestamp, Mutation mutation) + public static Mutation makeDropViewMutation(KeyspaceMetadata keyspace, ViewDefinition view, long timestamp) { - RowUpdateBuilder.deleteRow(MaterializedViews, timestamp, mutation, table.cfName, materializedView.viewName); + // Include the serialized keyspace in case the target node missed a CREATE KEYSPACE migration (see CASSANDRA-5631). + Mutation mutation = makeCreateKeyspaceMutation(keyspace.name, keyspace.params, timestamp); + + RowUpdateBuilder.deleteRow(Views, timestamp, mutation, view.viewName); + + CFMetaData table = view.metadata; + for (ColumnDefinition column : table.allColumns()) + dropColumnFromSchemaMutation(table, column, timestamp, mutation); + + for (IndexMetadata index : table.getIndexes()) + dropIndexFromSchemaMutation(table, index, timestamp, mutation); + + return mutation; } - private static void addUpdatedMaterializedViewDefinitionToSchemaMutation(CFMetaData table, MaterializedViewDefinition materializedView, long timestamp, Mutation mutation) + public static Mutation makeUpdateViewMutation(KeyspaceMetadata keyspace, + ViewDefinition oldView, + ViewDefinition newView, + long timestamp) { - addMaterializedViewToSchemaMutation(table, materializedView, timestamp, mutation); + Mutation mutation = makeCreateKeyspaceMutation(keyspace.name, keyspace.params, timestamp); + + addViewToSchemaMutation(newView, timestamp, false, mutation); + + MapDifference columnDiff = Maps.difference(oldView.metadata.getColumnMetadata(), + newView.metadata.getColumnMetadata()); + + // columns that are no longer needed + for (ColumnDefinition column : columnDiff.entriesOnlyOnLeft().values()) + { + dropColumnFromSchemaMutation(oldView.metadata, column, timestamp, mutation); + } + + // newly added columns + for (ColumnDefinition column : columnDiff.entriesOnlyOnRight().values()) + addColumnToSchemaMutation(newView.metadata, column, timestamp, mutation); + + // old columns with updated attributes + for (ByteBuffer name : columnDiff.entriesDiffering().keySet()) + addColumnToSchemaMutation(newView.metadata, newView.metadata.getColumnDefinition(name), timestamp, mutation); + + // dropped columns + MapDifference droppedColumnDiff = + Maps.difference(oldView.metadata.getDroppedColumns(), oldView.metadata.getDroppedColumns()); + + // newly dropped columns + for (CFMetaData.DroppedColumn column : droppedColumnDiff.entriesOnlyOnRight().values()) + addDroppedColumnToSchemaMutation(oldView.metadata, column, timestamp, mutation); + + // columns added then dropped again + for (ByteBuffer name : droppedColumnDiff.entriesDiffering().keySet()) + addDroppedColumnToSchemaMutation(newView.metadata, newView.metadata.getDroppedColumns().get(name), timestamp, mutation); + + return mutation; + } + + public static ViewDefinition createViewFromName(String keyspace, String view) + { + return readSchemaPartitionForTableAndApply(VIEWS, keyspace, view, partition -> + { + if (partition.isEmpty()) + throw new RuntimeException(String.format("%s:%s not found in the schema definitions keyspace.", keyspace, view)); + + return createViewFromViewPartition(partition); + }); + } + + private static ViewDefinition createViewFromViewPartition(RowIterator partition) + { + String query = String.format("SELECT * FROM %s.%s", NAME, VIEWS); + return createViewFromViewRow(QueryProcessor.resultify(query, partition).one()); } /** - * Deserialize materialized views from storage-level representation. + * Deserialize views from storage-level representation. * - * @param partition storage-level partition containing the materialized view definitions - * @return the list of processed MaterializedViewDefinitions + * @param partition storage-level partition containing the view definitions + * @return the list of processed ViewDefinitions */ - private static MaterializedViews createMaterializedViewsFromMaterializedViewsPartition(RowIterator partition) + private static Views createViewsFromViewsPartition(RowIterator partition) { - MaterializedViews.Builder views = org.apache.cassandra.schema.MaterializedViews.builder(); - String query = String.format("SELECT * FROM %s.%s", NAME, MATERIALIZED_VIEWS); + Views.Builder views = org.apache.cassandra.schema.Views.builder(); + String query = String.format("SELECT * FROM %s.%s", NAME, VIEWS); for (UntypedResultSet.Row row : QueryProcessor.resultify(query, partition)) { - MaterializedViewDefinition mv = createMaterializedViewFromMaterializedViewRow(row); - views.add(mv); + ViewDefinition view = createViewFromViewRow(row); + views.add(view); } return views.build(); } - private static MaterializedViewDefinition createMaterializedViewFromMaterializedViewRow(UntypedResultSet.Row row) + private static ViewDefinition createViewFromViewRow(UntypedResultSet.Row row) { - String name = row.getString("view_name"); - List partitionColumnNames = row.getFrozenList("target_columns", UTF8Type.instance); + String keyspace = row.getString("keyspace_name"); + String view = row.getString("view_name"); + UUID id = row.getUUID("id"); + UUID baseTableId = row.getUUID("base_table_id"); + boolean includeAll = row.getBoolean("include_all_columns"); - String cfName = row.getString("table_name"); - List clusteringColumnNames = row.getFrozenList("clustering_columns", UTF8Type.instance); + List columns = + readSchemaPartitionForTableAndApply(COLUMNS, keyspace, view, SchemaKeyspace::createColumnsFromColumnsPartition); - List partitionColumns = new ArrayList<>(); - for (String columnName : partitionColumnNames) - { - partitionColumns.add(ColumnIdentifier.getInterned(columnName, true)); - } + Map droppedColumns = + readSchemaPartitionForTableAndApply(DROPPED_COLUMNS, keyspace, view, SchemaKeyspace::createDroppedColumnsFromDroppedColumnsPartition); - List clusteringColumns = new ArrayList<>(); - for (String columnName : clusteringColumnNames) - { - clusteringColumns.add(ColumnIdentifier.getInterned(columnName, true)); - } + CFMetaData cfm = CFMetaData.create(keyspace, + view, + id, + false, + true, + false, + false, + true, + columns, + DatabaseDescriptor.getPartitioner()) + .params(createTableParamsFromRow(row)) + .droppedColumns(droppedColumns); - List includedColumnNames = row.getFrozenList("included_columns", UTF8Type.instance); - Set includedColumns = new HashSet<>(); - if (includedColumnNames != null) - { - for (String columnName : includedColumnNames) - includedColumns.add(ColumnIdentifier.getInterned(columnName, true)); - } - - return new MaterializedViewDefinition(cfName, - name, - partitionColumns, - clusteringColumns, - includedColumns); + return new ViewDefinition(keyspace, view, baseTableId, includeAll, cfm); } /* diff --git a/src/java/org/apache/cassandra/schema/MaterializedViews.java b/src/java/org/apache/cassandra/schema/Views.java similarity index 51% rename from src/java/org/apache/cassandra/schema/MaterializedViews.java rename to src/java/org/apache/cassandra/schema/Views.java index 1c55736050..5888b9dc93 100644 --- a/src/java/org/apache/cassandra/schema/MaterializedViews.java +++ b/src/java/org/apache/cassandra/schema/Views.java @@ -22,19 +22,23 @@ package org.apache.cassandra.schema; import java.util.Iterator; import java.util.Optional; -import com.google.common.collect.ImmutableMap; +import javax.annotation.Nullable; -import org.apache.cassandra.config.MaterializedViewDefinition; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +import org.apache.cassandra.config.CFMetaData; +import org.apache.cassandra.config.ViewDefinition; import static com.google.common.collect.Iterables.filter; -public final class MaterializedViews implements Iterable +public final class Views implements Iterable { - private final ImmutableMap materializedViews; + private final ImmutableMap views; - private MaterializedViews(Builder builder) + private Views(Builder builder) { - materializedViews = builder.materializedViews.build(); + views = builder.views.build(); } public static Builder builder() @@ -42,55 +46,72 @@ public final class MaterializedViews implements Iterable iterator() + public Iterator iterator() { - return materializedViews.values().iterator(); + return views.values().iterator(); + } + + public Iterable metadatas() + { + return Iterables.transform(views.values(), view -> view.metadata); } public int size() { - return materializedViews.size(); + return views.size(); } public boolean isEmpty() { - return materializedViews.isEmpty(); + return views.isEmpty(); } /** * Get the materialized view with the specified name * * @param name a non-qualified materialized view name - * @return an empty {@link Optional} if the materialized view name is not found; a non-empty optional of {@link MaterializedViewDefinition} otherwise + * @return an empty {@link Optional} if the materialized view name is not found; a non-empty optional of {@link ViewDefinition} otherwise */ - public Optional get(String name) + public Optional get(String name) { - return Optional.ofNullable(materializedViews.get(name)); + return Optional.ofNullable(views.get(name)); + } + + /** + * Get the view with the specified name + * + * @param name a non-qualified view name + * @return null if the view name is not found; the found {@link ViewDefinition} otherwise + */ + @Nullable + public ViewDefinition getNullable(String name) + { + return views.get(name); } /** * Create a MaterializedViews instance with the provided materialized view added */ - public MaterializedViews with(MaterializedViewDefinition materializedView) + public Views with(ViewDefinition view) { - if (get(materializedView.viewName).isPresent()) - throw new IllegalStateException(String.format("Materialized View %s already exists", materializedView.viewName)); + if (get(view.viewName).isPresent()) + throw new IllegalStateException(String.format("Materialized View %s already exists", view.viewName)); - return builder().add(this).add(materializedView).build(); + return builder().add(this).add(view).build(); } /** * Creates a MaterializedViews instance with the materializedView with the provided name removed */ - public MaterializedViews without(String name) + public Views without(String name) { - MaterializedViewDefinition materializedView = - get(name).orElseThrow(() -> new IllegalStateException(String.format("Materialized View %s doesn't exists", name))); + ViewDefinition materializedView = + get(name).orElseThrow(() -> new IllegalStateException(String.format("Materialized View %s doesn't exists", name))); return builder().add(filter(this, v -> v != materializedView)).build(); } @@ -98,51 +119,52 @@ public final class MaterializedViews implements Iterable materializedViews = new ImmutableMap.Builder<>(); + final ImmutableMap.Builder views = new ImmutableMap.Builder<>(); private Builder() { } - public MaterializedViews build() + public Views build() { - return new MaterializedViews(this); + return new Views(this); } - public Builder add(MaterializedViewDefinition materializedView) + + public Builder add(ViewDefinition view) { - materializedViews.put(materializedView.viewName, materializedView); + views.put(view.viewName, view); return this; } - public Builder add(Iterable materializedViews) + public Builder add(Iterable views) { - materializedViews.forEach(this::add); + views.forEach(this::add); return this; } } diff --git a/src/java/org/apache/cassandra/service/CassandraDaemon.java b/src/java/org/apache/cassandra/service/CassandraDaemon.java index 230b46a693..1408a70ce1 100644 --- a/src/java/org/apache/cassandra/service/CassandraDaemon.java +++ b/src/java/org/apache/cassandra/service/CassandraDaemon.java @@ -230,7 +230,7 @@ public class CassandraDaemon if (keyspaceName.equals(SystemKeyspace.NAME)) continue; - for (CFMetaData cfm : Schema.instance.getTables(keyspaceName)) + for (CFMetaData cfm : Schema.instance.getTablesAndViews(keyspaceName)) ColumnFamilyStore.scrubDataDirectories(cfm); } @@ -295,22 +295,19 @@ public class CassandraDaemon } } - Runnable indexRebuild = new Runnable() + Runnable viewRebuild = new Runnable() { @Override public void run() { for (Keyspace keyspace : Keyspace.all()) { - for (ColumnFamilyStore cf: keyspace.getColumnFamilyStores()) - { - cf.materializedViewManager.buildAllViews(); - } + keyspace.viewManager.buildAllViews(); } } }; - ScheduledExecutors.optionalTasks.schedule(indexRebuild, StorageService.RING_DELAY, TimeUnit.MILLISECONDS); + ScheduledExecutors.optionalTasks.schedule(viewRebuild, StorageService.RING_DELAY, TimeUnit.MILLISECONDS); SystemKeyspace.finishStartup(); diff --git a/src/java/org/apache/cassandra/service/MigrationListener.java b/src/java/org/apache/cassandra/service/MigrationListener.java index 358b2367c7..f4b3e7cdad 100644 --- a/src/java/org/apache/cassandra/service/MigrationListener.java +++ b/src/java/org/apache/cassandra/service/MigrationListener.java @@ -31,6 +31,11 @@ public abstract class MigrationListener { } + public void onCreateView(String ksName, String viewName) + { + onCreateColumnFamily(ksName, viewName); + } + public void onCreateUserType(String ksName, String typeName) { } @@ -51,6 +56,11 @@ public abstract class MigrationListener { } + public void onUpdateView(String ksName, String viewName, boolean columnsDidChange) + { + onUpdateColumnFamily(ksName, viewName, columnsDidChange); + } + public void onUpdateUserType(String ksName, String typeName) { } @@ -71,6 +81,10 @@ public abstract class MigrationListener { } + public void onDropView(String ksName, String viewName) + { + } + public void onDropUserType(String ksName, String typeName) { } diff --git a/src/java/org/apache/cassandra/service/MigrationManager.java b/src/java/org/apache/cassandra/service/MigrationManager.java index dad6aa7cf6..c820f18a39 100644 --- a/src/java/org/apache/cassandra/service/MigrationManager.java +++ b/src/java/org/apache/cassandra/service/MigrationManager.java @@ -31,6 +31,7 @@ import org.apache.cassandra.concurrent.Stage; import org.apache.cassandra.concurrent.StageManager; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.Schema; +import org.apache.cassandra.config.ViewDefinition; import org.apache.cassandra.cql3.functions.UDAggregate; import org.apache.cassandra.cql3.functions.UDFunction; import org.apache.cassandra.db.*; @@ -162,6 +163,12 @@ public class MigrationManager listener.onCreateColumnFamily(cfm.ksName, cfm.cfName); } + public void notifyCreateView(ViewDefinition view) + { + for (MigrationListener listener : listeners) + listener.onCreateView(view.ksName, view.viewName); + } + public void notifyCreateUserType(UserType ut) { for (MigrationListener listener : listeners) @@ -192,6 +199,12 @@ public class MigrationManager listener.onUpdateColumnFamily(cfm.ksName, cfm.cfName, columnsDidChange); } + public void notifyUpdateView(ViewDefinition view, boolean columnsDidChange) + { + for (MigrationListener listener : listeners) + listener.onUpdateView(view.ksName, view.viewName, columnsDidChange); + } + public void notifyUpdateUserType(UserType ut) { for (MigrationListener listener : listeners) @@ -225,6 +238,12 @@ public class MigrationManager listener.onDropColumnFamily(cfm.ksName, cfm.cfName); } + public void notifyDropView(ViewDefinition view) + { + for (MigrationListener listener : listeners) + listener.onDropView(view.ksName, view.viewName); + } + public void notifyDropUserType(UserType ut) { for (MigrationListener listener : listeners) @@ -276,13 +295,28 @@ public class MigrationManager KeyspaceMetadata ksm = Schema.instance.getKSMetaData(cfm.ksName); if (ksm == null) throw new ConfigurationException(String.format("Cannot add table '%s' to non existing keyspace '%s'.", cfm.cfName, cfm.ksName)); - else if (ksm.tables.get(cfm.cfName).isPresent()) + // If we have a table or a view which has the same name, we can't add a new one + else if (ksm.getTableOrViewNullable(cfm.cfName) != null) throw new AlreadyExistsException(cfm.ksName, cfm.cfName); logger.info(String.format("Create new table: %s", cfm)); announce(SchemaKeyspace.makeCreateTableMutation(ksm, cfm, FBUtilities.timestampMicros()), announceLocally); } + public static void announceNewView(ViewDefinition view, boolean announceLocally) throws ConfigurationException + { + view.metadata.validate(); + + KeyspaceMetadata ksm = Schema.instance.getKSMetaData(view.ksName); + if (ksm == null) + throw new ConfigurationException(String.format("Cannot add table '%s' to non existing keyspace '%s'.", view.viewName, view.ksName)); + else if (ksm.getTableOrViewNullable(view.viewName) != null) + throw new AlreadyExistsException(view.ksName, view.viewName); + + logger.info(String.format("Create new view: %s", view)); + announce(SchemaKeyspace.makeCreateViewMutation(ksm, view, FBUtilities.timestampMicros()), announceLocally); + } + public static void announceNewType(UserType newType, boolean announceLocally) { KeyspaceMetadata ksm = Schema.instance.getKSMetaData(newType.keyspace); @@ -340,6 +374,21 @@ public class MigrationManager announce(SchemaKeyspace.makeUpdateTableMutation(ksm, oldCfm, cfm, FBUtilities.timestampMicros(), fromThrift), announceLocally); } + public static void announceViewUpdate(ViewDefinition view, boolean announceLocally) throws ConfigurationException + { + view.metadata.validate(); + + ViewDefinition oldView = Schema.instance.getView(view.ksName, view.viewName); + if (oldView == null) + throw new ConfigurationException(String.format("Cannot update non existing materialized view '%s' in keyspace '%s'.", view.viewName, view.ksName)); + KeyspaceMetadata ksm = Schema.instance.getKSMetaData(view.ksName); + + oldView.metadata.validateCompatility(view.metadata); + + logger.info(String.format("Update view '%s/%s' From %s To %s", view.ksName, view.viewName, oldView, view)); + announce(SchemaKeyspace.makeUpdateViewMutation(ksm, oldView, view, FBUtilities.timestampMicros()), announceLocally); + } + public static void announceTypeUpdate(UserType updatedType, boolean announceLocally) { announceNewType(updatedType, announceLocally); @@ -376,6 +425,17 @@ public class MigrationManager announce(SchemaKeyspace.makeDropTableMutation(ksm, oldCfm, FBUtilities.timestampMicros()), announceLocally); } + public static void announceViewDrop(String ksName, String viewName, boolean announceLocally) throws ConfigurationException + { + ViewDefinition view = Schema.instance.getView(ksName, viewName); + if (view == null) + throw new ConfigurationException(String.format("Cannot drop non existing materialized view '%s' in keyspace '%s'.", viewName, ksName)); + KeyspaceMetadata ksm = Schema.instance.getKSMetaData(ksName); + + logger.info(String.format("Drop table '%s/%s'", view.ksName, view.viewName)); + announce(SchemaKeyspace.makeDropViewMutation(ksm, view, FBUtilities.timestampMicros()), announceLocally); + } + public static void announceTypeDrop(UserType droppedType) { announceTypeDrop(droppedType, false); diff --git a/src/java/org/apache/cassandra/service/StartupChecks.java b/src/java/org/apache/cassandra/service/StartupChecks.java index eca0c7eecc..16ff488078 100644 --- a/src/java/org/apache/cassandra/service/StartupChecks.java +++ b/src/java/org/apache/cassandra/service/StartupChecks.java @@ -270,7 +270,7 @@ public class StartupChecks // we do a one-off scrub of the system keyspace first; we can't load the list of the rest of the keyspaces, // until system keyspace is opened. - for (CFMetaData cfm : Schema.instance.getTables(SystemKeyspace.NAME)) + for (CFMetaData cfm : Schema.instance.getTablesAndViews(SystemKeyspace.NAME)) ColumnFamilyStore.scrubDataDirectories(cfm); try diff --git a/src/java/org/apache/cassandra/service/StorageProxy.java b/src/java/org/apache/cassandra/service/StorageProxy.java index e3b884e797..d209af6b7d 100644 --- a/src/java/org/apache/cassandra/service/StorageProxy.java +++ b/src/java/org/apache/cassandra/service/StorageProxy.java @@ -48,12 +48,9 @@ import org.apache.cassandra.db.filter.DataLimits; import org.apache.cassandra.db.filter.TombstoneOverwhelmingException; import org.apache.cassandra.db.partitions.*; import org.apache.cassandra.db.rows.RowIterator; -import org.apache.cassandra.db.view.MaterializedViewManager; -import org.apache.cassandra.db.view.MaterializedViewUtils; -import org.apache.cassandra.dht.AbstractBounds; -import org.apache.cassandra.dht.Bounds; -import org.apache.cassandra.dht.RingPosition; -import org.apache.cassandra.dht.Token; +import org.apache.cassandra.db.view.ViewManager; +import org.apache.cassandra.db.view.ViewUtils; +import org.apache.cassandra.dht.*; import org.apache.cassandra.exceptions.*; import org.apache.cassandra.gms.FailureDetector; import org.apache.cassandra.gms.Gossiper; @@ -98,7 +95,7 @@ public class StorageProxy implements StorageProxyMBean private static final ClientRequestMetrics writeMetrics = new ClientRequestMetrics("Write"); private static final CASClientRequestMetrics casWriteMetrics = new CASClientRequestMetrics("CASWrite"); private static final CASClientRequestMetrics casReadMetrics = new CASClientRequestMetrics("CASRead"); - private static final MVWriteMetrics mvWriteMetrics = new MVWriteMetrics("MVWrite"); + private static final ViewWriteMetrics viewWriteMetrics = new ViewWriteMetrics("ViewWrite"); private static final double CONCURRENT_SUBREQUESTS_MARGIN = 0.10; @@ -655,6 +652,7 @@ public class StorageProxy implements StorageProxyMBean * @param mutations the mutations to be applied across the replicas */ public static void mutateMV(ByteBuffer dataKey, Collection mutations, boolean writeCommitLog) + throws UnavailableException, OverloadedException, WriteTimeoutException { Tracing.trace("Determining replicas for mutation"); final String localDataCenter = DatabaseDescriptor.getEndpointSnitch().getDatacenter(FBUtilities.getBroadcastAddress()); @@ -679,15 +677,15 @@ public class StorageProxy implements StorageProxyMBean { String keyspaceName = mutation.getKeyspaceName(); Token tk = mutation.key().getToken(); - InetAddress pairedEndpoint = MaterializedViewUtils.getViewNaturalEndpoint(keyspaceName, baseToken, tk); + InetAddress pairedEndpoint = ViewUtils.getViewNaturalEndpoint(keyspaceName, baseToken, tk); List naturalEndpoints = Lists.newArrayList(pairedEndpoint); - WriteResponseHandlerWrapper wrapper = wrapMVBatchResponseHandler(mutation, - consistencyLevel, - consistencyLevel, - naturalEndpoints, - WriteType.BATCH, - cleanup); + WriteResponseHandlerWrapper wrapper = wrapViewBatchResponseHandler(mutation, + consistencyLevel, + consistencyLevel, + naturalEndpoints, + WriteType.BATCH, + cleanup); // When local node is the endpoint and there are no pending nodes we can // Just apply the mutation locally. @@ -704,12 +702,12 @@ public class StorageProxy implements StorageProxyMBean writeCommitLog); // now actually perform the writes and wait for them to complete - asyncWriteBatchedMutations(wrappers, localDataCenter, Stage.MATERIALIZED_VIEW_MUTATION); + asyncWriteBatchedMutations(wrappers, localDataCenter, Stage.VIEW_MUTATION); } } finally { - mvWriteMetrics.addNano(System.nanoTime() - startTime); + viewWriteMetrics.addNano(System.nanoTime() - startTime); } } @@ -721,7 +719,9 @@ public class StorageProxy implements StorageProxyMBean { Collection augmented = TriggerExecutor.instance.execute(mutations); - boolean updatesView = MaterializedViewManager.updatesAffectView(mutations, true); + boolean updatesView = Keyspace.open(mutations.iterator().next().getKeyspaceName()) + .viewManager + .updatesAffectView(mutations, true); if (augmented != null) mutateAtomically(augmented, consistencyLevel, updatesView); @@ -974,14 +974,14 @@ public class StorageProxy implements StorageProxyMBean /** * Same as performWrites except does not initiate writes (but does perform availability checks). - * Keeps track of MVWriteMetrics + * Keeps track of ViewWriteMetrics */ - private static WriteResponseHandlerWrapper wrapMVBatchResponseHandler(Mutation mutation, - ConsistencyLevel consistency_level, - ConsistencyLevel batchConsistencyLevel, - List naturalEndpoints, - WriteType writeType, - BatchlogResponseHandler.BatchlogCleanup cleanup) + private static WriteResponseHandlerWrapper wrapViewBatchResponseHandler(Mutation mutation, + ConsistencyLevel consistency_level, + ConsistencyLevel batchConsistencyLevel, + List naturalEndpoints, + WriteType writeType, + BatchlogResponseHandler.BatchlogCleanup cleanup) { Keyspace keyspace = Keyspace.open(mutation.getKeyspaceName()); AbstractReplicationStrategy rs = keyspace.getReplicationStrategy(); @@ -989,7 +989,7 @@ public class StorageProxy implements StorageProxyMBean Token tk = mutation.key().getToken(); Collection pendingEndpoints = StorageService.instance.getTokenMetadata().pendingEndpointsFor(tk, keyspaceName); AbstractWriteResponseHandler writeHandler = rs.getWriteResponseHandler(naturalEndpoints, pendingEndpoints, consistency_level, null, writeType); - BatchlogResponseHandler batchHandler = new MVWriteMetricsWrapped(writeHandler, batchConsistencyLevel.blockFor(keyspace), cleanup); + BatchlogResponseHandler batchHandler = new ViewWriteMetricsWrapped(writeHandler, batchConsistencyLevel.blockFor(keyspace), cleanup); return new WriteResponseHandlerWrapper(batchHandler, mutation); } @@ -2282,20 +2282,20 @@ public class StorageProxy implements StorageProxyMBean } /** - * This class captures metrics for materialized views writes. + * This class captures metrics for views writes. */ - private static class MVWriteMetricsWrapped extends BatchlogResponseHandler + private static class ViewWriteMetricsWrapped extends BatchlogResponseHandler { - public MVWriteMetricsWrapped(AbstractWriteResponseHandler writeHandler, int i, BatchlogCleanup cleanup) + public ViewWriteMetricsWrapped(AbstractWriteResponseHandler writeHandler, int i, BatchlogCleanup cleanup) { super(writeHandler, i, cleanup); - mvWriteMetrics.viewReplicasAttempted.inc(totalEndpoints()); + viewWriteMetrics.viewReplicasAttempted.inc(totalEndpoints()); } public void response(MessageIn msg) { super.response(msg); - mvWriteMetrics.viewReplicasSuccess.inc(); + viewWriteMetrics.viewReplicasSuccess.inc(); } } diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java index f09563015e..f0ad46ffa5 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -630,12 +630,12 @@ public class StorageService extends NotificationBroadcasterSupport implements IE public void runMayThrow() throws InterruptedException { inShutdownHook = true; - ExecutorService materializedViewMutationStage = StageManager.getStage(Stage.MATERIALIZED_VIEW_MUTATION); + ExecutorService viewMutationStage = StageManager.getStage(Stage.VIEW_MUTATION); ExecutorService counterMutationStage = StageManager.getStage(Stage.COUNTER_MUTATION); ExecutorService mutationStage = StageManager.getStage(Stage.MUTATION); if (mutationStage.isShutdown() && counterMutationStage.isShutdown() - && materializedViewMutationStage.isShutdown()) + && viewMutationStage.isShutdown()) return; // drained already if (daemon != null) @@ -646,11 +646,11 @@ public class StorageService extends NotificationBroadcasterSupport implements IE // In-progress writes originating here could generate hints to be written, so shut down MessagingService // before mutation stage, so we can get all the hints saved before shutting down MessagingService.instance().shutdown(); - materializedViewMutationStage.shutdown(); + viewMutationStage.shutdown(); HintsService.instance.pauseDispatch(); counterMutationStage.shutdown(); mutationStage.shutdown(); - materializedViewMutationStage.awaitTermination(3600, TimeUnit.SECONDS); + viewMutationStage.awaitTermination(3600, TimeUnit.SECONDS); counterMutationStage.awaitTermination(3600, TimeUnit.SECONDS); mutationStage.awaitTermination(3600, TimeUnit.SECONDS); StorageProxy.instance.verifyNoHintsInProgress(); @@ -3179,7 +3179,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE if (ksMetaData == null) throw new IllegalArgumentException("Unknown keyspace '" + keyspaceName + "'"); - CFMetaData cfMetaData = ksMetaData.tables.getNullable(cf); + CFMetaData cfMetaData = ksMetaData.getTableOrViewNullable(cf); if (cfMetaData == null) throw new IllegalArgumentException("Unknown table '" + cf + "' in keyspace '" + keyspaceName + "'"); @@ -3876,11 +3876,11 @@ public class StorageService extends NotificationBroadcasterSupport implements IE HintsService.instance.pauseDispatch(); ExecutorService counterMutationStage = StageManager.getStage(Stage.COUNTER_MUTATION); - ExecutorService materializedViewMutationStage = StageManager.getStage(Stage.MATERIALIZED_VIEW_MUTATION); + ExecutorService viewMutationStage = StageManager.getStage(Stage.VIEW_MUTATION); ExecutorService mutationStage = StageManager.getStage(Stage.MUTATION); if (mutationStage.isTerminated() && counterMutationStage.isTerminated() - && materializedViewMutationStage.isTerminated()) + && viewMutationStage.isTerminated()) { logger.warn("Cannot drain node (did it already happen?)"); return; @@ -3894,10 +3894,10 @@ public class StorageService extends NotificationBroadcasterSupport implements IE MessagingService.instance().shutdown(); setMode(Mode.DRAINING, "clearing mutation stage", false); - materializedViewMutationStage.shutdown(); + viewMutationStage.shutdown(); counterMutationStage.shutdown(); mutationStage.shutdown(); - materializedViewMutationStage.awaitTermination(3600, TimeUnit.SECONDS); + viewMutationStage.awaitTermination(3600, TimeUnit.SECONDS); counterMutationStage.awaitTermination(3600, TimeUnit.SECONDS); mutationStage.awaitTermination(3600, TimeUnit.SECONDS); diff --git a/src/java/org/apache/cassandra/streaming/StreamReceiveTask.java b/src/java/org/apache/cassandra/streaming/StreamReceiveTask.java index cb99654b79..f261954705 100644 --- a/src/java/org/apache/cassandra/streaming/StreamReceiveTask.java +++ b/src/java/org/apache/cassandra/streaming/StreamReceiveTask.java @@ -21,6 +21,7 @@ import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import com.google.common.collect.Iterables; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,10 +34,10 @@ import org.apache.cassandra.db.compaction.OperationType; import org.apache.cassandra.db.lifecycle.LifecycleTransaction; import org.apache.cassandra.db.partitions.PartitionUpdate; import org.apache.cassandra.db.rows.UnfilteredRowIterator; +import org.apache.cassandra.db.view.View; import org.apache.cassandra.io.sstable.ISSTableScanner; import org.apache.cassandra.io.sstable.SSTableMultiWriter; import org.apache.cassandra.io.sstable.format.SSTableReader; -import org.apache.cassandra.io.sstable.format.SSTableWriter; import org.apache.cassandra.utils.JVMStabilityInspector; import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.concurrent.Refs; @@ -127,7 +128,7 @@ public class StreamReceiveTask extends StreamTask return; } ColumnFamilyStore cfs = Keyspace.open(kscf.left).getColumnFamilyStore(kscf.right); - boolean hasMaterializedViews = cfs.materializedViewManager.allViews().iterator().hasNext(); + boolean hasViews = !Iterables.isEmpty(View.findAll(kscf.left, kscf.right)); try { @@ -143,11 +144,11 @@ public class StreamReceiveTask extends StreamTask try (Refs refs = Refs.ref(readers)) { - //We have a special path for Materialized view. - //Since the MV requires cleaning up any pre-existing state, we must put + //We have a special path for views. + //Since the view requires cleaning up any pre-existing state, we must put //all partitions through the same write path as normal mutations. - //This also ensures any 2i's are also updated - if (hasMaterializedViews) + //This also ensures any 2is are also updated + if (hasViews) { for (SSTableReader reader : readers) { @@ -183,7 +184,7 @@ public class StreamReceiveTask extends StreamTask { //We don't keep the streamed sstables since we've applied them manually //So we abort the txn and delete the streamed sstables - if (hasMaterializedViews) + if (hasViews) { cfs.forceBlockingFlush(); task.txn.abort(); diff --git a/src/java/org/apache/cassandra/thrift/CassandraServer.java b/src/java/org/apache/cassandra/thrift/CassandraServer.java index 207bb6f4d5..7017bc1a16 100644 --- a/src/java/org/apache/cassandra/thrift/CassandraServer.java +++ b/src/java/org/apache/cassandra/thrift/CassandraServer.java @@ -44,6 +44,7 @@ import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.TimeUUIDType; import org.apache.cassandra.db.partitions.*; import org.apache.cassandra.db.rows.*; +import org.apache.cassandra.db.view.View; import org.apache.cassandra.dht.*; import org.apache.cassandra.dht.Range; import org.apache.cassandra.exceptions.*; @@ -815,7 +816,7 @@ public class CassandraServer implements Cassandra.Iface cState.hasColumnFamilyAccess(keyspace, column_parent.column_family, Permission.MODIFY); CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace, column_parent.column_family, false); - if (metadata.isMaterializedView()) + if (metadata.isView()) throw new org.apache.cassandra.exceptions.InvalidRequestException("Cannot modify Materialized Views directly"); ThriftValidation.validateKey(metadata, key); @@ -912,7 +913,7 @@ public class CassandraServer implements Cassandra.Iface cState.hasColumnFamilyAccess(keyspace, column_family, Permission.SELECT); CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace, column_family, false); - if (metadata.isMaterializedView()) + if (metadata.isView()) throw new org.apache.cassandra.exceptions.InvalidRequestException("Cannot modify Materialized Views directly"); ThriftValidation.validateKey(metadata, key); @@ -1108,7 +1109,7 @@ public class CassandraServer implements Cassandra.Iface cState.hasColumnFamilyAccess(keyspace, cfName, Permission.MODIFY); CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace, cfName); - if (metadata.isMaterializedView()) + if (metadata.isView()) throw new org.apache.cassandra.exceptions.InvalidRequestException("Cannot modify Materialized Views directly"); ThriftValidation.validateKey(metadata, key); @@ -1325,7 +1326,7 @@ public class CassandraServer implements Cassandra.Iface cState.hasColumnFamilyAccess(keyspace, column_path.column_family, Permission.MODIFY); CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace, column_path.column_family, isCommutativeOp); - if (metadata.isMaterializedView()) + if (metadata.isView()) throw new org.apache.cassandra.exceptions.InvalidRequestException("Cannot modify Materialized Views directly"); ThriftValidation.validateKey(metadata, key); @@ -1903,7 +1904,7 @@ public class CassandraServer implements Cassandra.Iface cState.hasColumnFamilyAccess(keyspace, column_family, Permission.DROP); CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace, column_family); - if (metadata.isMaterializedView()) + if (metadata.isView()) throw new org.apache.cassandra.exceptions.InvalidRequestException("Cannot drop Materialized Views from Thrift"); MigrationManager.announceColumnFamilyDrop(keyspace, column_family); @@ -2015,9 +2016,9 @@ public class CassandraServer implements Cassandra.Iface if (oldCfm == null) throw new InvalidRequestException("Could not find table definition to modify."); - if (oldCfm.isMaterializedView()) + if (oldCfm.isView()) throw new InvalidRequestException("Cannot modify Materialized View table " + oldCfm.cfName + " as it may break the schema. You should use cqlsh to modify Materialized View tables instead."); - if (!oldCfm.getMaterializedViews().isEmpty()) + if (!Iterables.isEmpty(View.findAll(cf_def.keyspace, cf_def.name))) throw new InvalidRequestException("Cannot modify table with Materialized View " + oldCfm.cfName + " as it may break the schema. You should use cqlsh to modify tables with Materialized Views instead."); if (!oldCfm.isThriftCompatible()) @@ -2047,7 +2048,7 @@ public class CassandraServer implements Cassandra.Iface String keyspace = cState.getKeyspace(); cState.hasColumnFamilyAccess(keyspace, cfname, Permission.MODIFY); CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace, cfname, false); - if (metadata.isMaterializedView()) + if (metadata.isView()) throw new org.apache.cassandra.exceptions.InvalidRequestException("Cannot truncate Materialized Views"); if (startSessionIfRequested()) @@ -2134,7 +2135,7 @@ public class CassandraServer implements Cassandra.Iface cState.hasColumnFamilyAccess(keyspace, column_parent.column_family, Permission.MODIFY); CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace, column_parent.column_family, true); - if (metadata.isMaterializedView()) + if (metadata.isView()) throw new org.apache.cassandra.exceptions.InvalidRequestException("Cannot modify Materialized Views directly"); ThriftValidation.validateKey(metadata, key); diff --git a/src/java/org/apache/cassandra/thrift/ThriftConversion.java b/src/java/org/apache/cassandra/thrift/ThriftConversion.java index 86cfe42b27..b721226f87 100644 --- a/src/java/org/apache/cassandra/thrift/ThriftConversion.java +++ b/src/java/org/apache/cassandra/thrift/ThriftConversion.java @@ -162,7 +162,7 @@ public class ThriftConversion public static KsDef toThrift(KeyspaceMetadata ksm) { List cfDefs = new ArrayList<>(); - for (CFMetaData cfm : ksm.tables) + for (CFMetaData cfm : ksm.tables) // do not include views if (cfm.isThriftCompatible()) // Don't expose CF that cannot be correctly handle by thrift; see CASSANDRA-4377 for further details cfDefs.add(toThrift(cfm)); @@ -272,8 +272,8 @@ public class ThriftConversion defaultValidator); } - // We do not allow Thrift materialized views, so we always set it to false - boolean isMaterializedView = false; + // We do not allow Thrift views, so we always set it to false + boolean isView = false; CFMetaData newCFMD = CFMetaData.create(cf_def.keyspace, cf_def.name, @@ -282,7 +282,7 @@ public class ThriftConversion isCompound, isSuper, isCounter, - isMaterializedView, + isView, defs, DatabaseDescriptor.getPartitioner()); diff --git a/src/java/org/apache/cassandra/utils/NativeSSTableLoaderClient.java b/src/java/org/apache/cassandra/utils/NativeSSTableLoaderClient.java index d6020767ec..8dff5320a1 100644 --- a/src/java/org/apache/cassandra/utils/NativeSSTableLoaderClient.java +++ b/src/java/org/apache/cassandra/utils/NativeSSTableLoaderClient.java @@ -80,6 +80,8 @@ public class NativeSSTableLoaderClient extends SSTableLoader.Client } tables.putAll(fetchTablesMetadata(keyspace, session, partitioner)); + // We only need the CFMetaData for the views, so we only load that. + tables.putAll(fetchViewMetadata(keyspace, session, partitioner)); } } @@ -111,41 +113,61 @@ public class NativeSSTableLoaderClient extends SSTableLoader.Client for (Row row : session.execute(query, keyspace)) { String name = row.getString("table_name"); - UUID id = row.getUUID("id"); - - Set flags = row.isNull("flags") - ? Collections.emptySet() - : CFMetaData.flagsFromStrings(row.getSet("flags", String.class)); - - boolean isSuper = flags.contains(CFMetaData.Flag.SUPER); - boolean isCounter = flags.contains(CFMetaData.Flag.COUNTER); - boolean isDense = flags.contains(CFMetaData.Flag.DENSE); - boolean isCompound = flags.contains(CFMetaData.Flag.COMPOUND); - boolean isMaterializedView = flags.contains(CFMetaData.Flag.VIEW); - - String columnsQuery = String.format("SELECT * FROM %s.%s WHERE keyspace_name = ? AND table_name = ?", - SchemaKeyspace.NAME, - SchemaKeyspace.COLUMNS); - - List defs = new ArrayList<>(); - for (Row colRow : session.execute(columnsQuery, keyspace, name)) - defs.add(createDefinitionFromRow(colRow, keyspace, name)); - - tables.put(name, CFMetaData.create(keyspace, - name, - id, - isDense, - isCompound, - isSuper, - isCounter, - isMaterializedView, - defs, - partitioner)); + tables.put(name, createTableMetadata(keyspace, session, partitioner, false, row, name)); } return tables; } + /* + * In the case where we are creating View CFMetaDatas, we + */ + private static Map fetchViewMetadata(String keyspace, Session session, IPartitioner partitioner) + { + Map tables = new HashMap<>(); + String query = String.format("SELECT * FROM %s.%s WHERE keyspace_name = ?", SchemaKeyspace.NAME, SchemaKeyspace.VIEWS); + + for (Row row : session.execute(query, keyspace)) + { + String name = row.getString("view_name"); + tables.put(name, createTableMetadata(keyspace, session, partitioner, true, row, name)); + } + + return tables; + } + + private static CFMetaData createTableMetadata(String keyspace, Session session, IPartitioner partitioner, boolean isView, Row row, String name) + { + UUID id = row.getUUID("id"); + Set flags = row.isNull("flags") + ? Collections.emptySet() + : CFMetaData.flagsFromStrings(row.getSet("flags", String.class)); + + boolean isSuper = flags.contains(CFMetaData.Flag.SUPER); + boolean isCounter = flags.contains(CFMetaData.Flag.COUNTER); + boolean isDense = flags.contains(CFMetaData.Flag.DENSE); + boolean isCompound = flags.contains(CFMetaData.Flag.COMPOUND); + + String columnsQuery = String.format("SELECT * FROM %s.%s WHERE keyspace_name = ? AND table_name = ?", + SchemaKeyspace.NAME, + SchemaKeyspace.COLUMNS); + + List defs = new ArrayList<>(); + for (Row colRow : session.execute(columnsQuery, keyspace, name)) + defs.add(createDefinitionFromRow(colRow, keyspace, name)); + + return CFMetaData.create(keyspace, + name, + id, + isDense, + isCompound, + isSuper, + isCounter, + isView, + defs, + partitioner); + } + private static ColumnDefinition createDefinitionFromRow(Row row, String keyspace, String table) { ColumnIdentifier name = ColumnIdentifier.getInterned(row.getBytes("column_name_bytes"), row.getString("column_name")); diff --git a/test/long/org/apache/cassandra/cql3/MaterializedViewLongTest.java b/test/long/org/apache/cassandra/cql3/ViewLongTest.java similarity index 94% rename from test/long/org/apache/cassandra/cql3/MaterializedViewLongTest.java rename to test/long/org/apache/cassandra/cql3/ViewLongTest.java index b833e60ef7..3808b739a4 100644 --- a/test/long/org/apache/cassandra/cql3/MaterializedViewLongTest.java +++ b/test/long/org/apache/cassandra/cql3/ViewLongTest.java @@ -38,10 +38,10 @@ import org.apache.cassandra.concurrent.StageManager; import org.apache.cassandra.batchlog.BatchlogManager; import org.apache.cassandra.utils.WrappedRunnable; -public class MaterializedViewLongTest extends CQLTester +public class ViewLongTest extends CQLTester { int protocolVersion = 4; - private final List materializedViews = new ArrayList<>(); + private final List views = new ArrayList<>(); @BeforeClass public static void startup() @@ -51,13 +51,13 @@ public class MaterializedViewLongTest extends CQLTester @Before public void begin() { - materializedViews.clear(); + views.clear(); } @After public void end() throws Throwable { - for (String viewName : materializedViews) + for (String viewName : views) executeNet(protocolVersion, "DROP MATERIALIZED VIEW " + viewName); } @@ -66,7 +66,7 @@ public class MaterializedViewLongTest extends CQLTester executeNet(protocolVersion, String.format(query, name)); // If exception is thrown, the view will not be added to the list; since it shouldn't have been created, this is // the desired behavior - materializedViews.add(name); + views.add(name); } @Test @@ -134,8 +134,8 @@ public class MaterializedViewLongTest extends CQLTester break; try { - // This will throw exceptions whenever there are exceptions trying to push the materialized view values - // out, caused by the view becoming overwhelmed. + // This will throw exceptions whenever there are exceptions trying to push the view values out, caused + // by the view becoming overwhelmed. BatchlogManager.instance.startBatchlogReplay().get(); } catch (Throwable ignore) diff --git a/test/unit/org/apache/cassandra/SchemaLoader.java b/test/unit/org/apache/cassandra/SchemaLoader.java index 175e492858..72bfd00ece 100644 --- a/test/unit/org/apache/cassandra/SchemaLoader.java +++ b/test/unit/org/apache/cassandra/SchemaLoader.java @@ -309,7 +309,7 @@ public class SchemaLoader private static void useCompression(List schema) { for (KeyspaceMetadata ksm : schema) - for (CFMetaData cfm : ksm.tables) + for (CFMetaData cfm : ksm.tablesAndViews()) cfm.compression(CompressionParams.snappy()); } diff --git a/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java b/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java index 5887c9a638..3a3b6eee06 100644 --- a/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java +++ b/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java @@ -52,7 +52,7 @@ public class DatabaseDescriptorTest // test serialization of all defined test CFs. for (String keyspaceName : Schema.instance.getNonSystemKeyspaces()) { - for (CFMetaData cfm : Schema.instance.getTables(keyspaceName)) + for (CFMetaData cfm : Schema.instance.getTablesAndViews(keyspaceName)) { CFMetaData cfmDupe = ThriftConversion.fromThrift(ThriftConversion.toThrift(cfm)); assertNotNull(cfmDupe); diff --git a/test/unit/org/apache/cassandra/cql3/MaterializedViewTest.java b/test/unit/org/apache/cassandra/cql3/ViewTest.java similarity index 78% rename from test/unit/org/apache/cassandra/cql3/MaterializedViewTest.java rename to test/unit/org/apache/cassandra/cql3/ViewTest.java index f4b4a828c7..9c47707cd7 100644 --- a/test/unit/org/apache/cassandra/cql3/MaterializedViewTest.java +++ b/test/unit/org/apache/cassandra/cql3/ViewTest.java @@ -22,11 +22,7 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.concurrent.TimeUnit; import org.junit.After; @@ -51,10 +47,10 @@ import org.apache.cassandra.serializers.TimeSerializer; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; -public class MaterializedViewTest extends CQLTester +public class ViewTest extends CQLTester { int protocolVersion = 4; - private final List materializedViews = new ArrayList<>(); + private final List views = new ArrayList<>(); @BeforeClass public static void startup() @@ -64,13 +60,13 @@ public class MaterializedViewTest extends CQLTester @Before public void begin() { - materializedViews.clear(); + views.clear(); } @After public void end() throws Throwable { - for (String viewName : materializedViews) + for (String viewName : views) executeNet(protocolVersion, "DROP MATERIALIZED VIEW " + viewName); } @@ -79,19 +75,58 @@ public class MaterializedViewTest extends CQLTester executeNet(protocolVersion, String.format(query, name)); // If exception is thrown, the view will not be added to the list; since it shouldn't have been created, this is // the desired behavior - materializedViews.add(name); + views.add(name); } - private void updateMV(String query, Object... params) throws Throwable + private void updateView(String query, Object... params) throws Throwable { executeNet(protocolVersion, query, params); - while (!(((SEPExecutor) StageManager.getStage(Stage.MATERIALIZED_VIEW_MUTATION)).getPendingTasks() == 0 - && ((SEPExecutor) StageManager.getStage(Stage.MATERIALIZED_VIEW_MUTATION)).getActiveCount() == 0)) + while (!(((SEPExecutor) StageManager.getStage(Stage.VIEW_MUTATION)).getPendingTasks() == 0 + && ((SEPExecutor) StageManager.getStage(Stage.VIEW_MUTATION)).getActiveCount() == 0)) { Thread.sleep(1); } } + @Test + public void testCaseSensitivity() throws Throwable + { + createTable("CREATE TABLE %s (\"theKey\" int, \"theClustering\" int, \"theValue\" int, PRIMARY KEY (\"theKey\", \"theClustering\"))"); + + execute("USE " + keyspace()); + executeNet(protocolVersion, "USE " + keyspace()); + + execute("INSERT INTO %s (\"theKey\", \"theClustering\", \"theValue\") VALUES (?, ?, ?)", 0, 0, 0); + + createView("mv_test", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s " + + "WHERE \"theKey\" IS NOT NULL AND \"theClustering\" IS NOT NULL AND \"theValue\" IS NOT NULL " + + "PRIMARY KEY (\"theKey\", \"theClustering\")"); + + while (!SystemKeyspace.isViewBuilt(keyspace(), "mv_test")) + Thread.sleep(10); + createView("mv_test2", "CREATE MATERIALIZED VIEW %s AS SELECT \"theKey\", \"theClustering\", \"theValue\" FROM %%s " + + "WHERE \"theKey\" IS NOT NULL AND \"theClustering\" IS NOT NULL AND \"theValue\" IS NOT NULL " + + "PRIMARY KEY (\"theKey\", \"theClustering\")"); + while (!SystemKeyspace.isViewBuilt(keyspace(), "mv_test2")) + Thread.sleep(10); + + for (String mvname : Arrays.asList("mv_test", "mv_test2")) + { + assertRows(execute("SELECT \"theKey\", \"theClustering\", \"theValue\" FROM " + mvname), + row(0, 0, 0) + ); + } + + executeNet(protocolVersion, "ALTER TABLE %s RENAME \"theClustering\" TO \"Col\""); + + for (String mvname : Arrays.asList("mv_test", "mv_test2")) + { + assertRows(execute("SELECT \"theKey\", \"Col\", \"theValue\" FROM " + mvname), + row(0, 0, 0) + ); + } + } + @Test public void testPartitionTombstone() throws Throwable { @@ -102,13 +137,13 @@ public class MaterializedViewTest extends CQLTester createView("view1", "CREATE MATERIALIZED VIEW view1 AS SELECT k1 FROM %%s WHERE k1 IS NOT NULL AND c1 IS NOT NULL AND val IS NOT NULL PRIMARY KEY (val, k1, c1)"); - updateMV("INSERT INTO %s (k1, c1, val) VALUES (1, 2, 200)"); - updateMV("INSERT INTO %s (k1, c1, val) VALUES (1, 3, 300)"); + updateView("INSERT INTO %s (k1, c1, val) VALUES (1, 2, 200)"); + updateView("INSERT INTO %s (k1, c1, val) VALUES (1, 3, 300)"); Assert.assertEquals(1, execute("select * from %s").size()); Assert.assertEquals(1, execute("select * from view1").size()); - updateMV("DELETE FROM %s WHERE k1 = 1"); + updateView("DELETE FROM %s WHERE k1 = 1"); Assert.assertEquals(0, execute("select * from %s").size()); Assert.assertEquals(0, execute("select * from view1").size()); @@ -124,13 +159,13 @@ public class MaterializedViewTest extends CQLTester createView("view1", "CREATE MATERIALIZED VIEW view1 AS SELECT k1 FROM %%s WHERE k1 IS NOT NULL AND c1 IS NOT NULL AND val IS NOT NULL PRIMARY KEY (val, k1, c1)"); - updateMV("INSERT INTO %s (k1, c1, val) VALUES (1, 2, 200)"); - updateMV("INSERT INTO %s (k1, c1, val) VALUES (1, 3, 300)"); + updateView("INSERT INTO %s (k1, c1, val) VALUES (1, 2, 200)"); + updateView("INSERT INTO %s (k1, c1, val) VALUES (1, 3, 300)"); Assert.assertEquals(2, execute("select * from %s").size()); Assert.assertEquals(2, execute("select * from view1").size()); - updateMV("DELETE FROM %s WHERE k1 = 1 and c1 = 3"); + updateView("DELETE FROM %s WHERE k1 = 1 and c1 = 3"); Assert.assertEquals(1, execute("select * from %s").size()); Assert.assertEquals(1, execute("select * from view1").size()); @@ -198,11 +233,11 @@ public class MaterializedViewTest extends CQLTester executeNet(protocolVersion, "USE " + keyspace()); createView("mv1_test", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE bigintval IS NOT NULL AND k IS NOT NULL AND asciival IS NOT NULL PRIMARY KEY (bigintval, k, asciival)"); - updateMV("INSERT INTO %s(k,asciival,bigintval)VALUES(?,?,?)", 0, "foo", 1L); + updateView("INSERT INTO %s(k,asciival,bigintval)VALUES(?,?,?)", 0, "foo", 1L); try { - updateMV("INSERT INTO mv1_test(k,asciival,bigintval) VALUES(?,?,?)", 1, "foo", 2L); + updateView("INSERT INTO mv1_test(k,asciival,bigintval) VALUES(?,?,?)", 1, "foo", 2L); Assert.fail("Shouldn't be able to modify a MV directly"); } catch (Exception e) @@ -234,7 +269,7 @@ public class MaterializedViewTest extends CQLTester CFMetaData metadata = Schema.instance.getCFMetaData(keyspace(), "mv1_test"); Assert.assertNotNull(metadata.getColumnDefinition(ByteBufferUtil.bytes("foo"))); - updateMV("INSERT INTO %s(k,asciival,bigintval,foo)VALUES(?,?,?,?)", 0, "foo", 1L, "bar"); + updateView("INSERT INTO %s(k,asciival,bigintval,foo)VALUES(?,?,?,?)", 0, "foo", 1L, "bar"); assertRows(execute("SELECT foo from %s"), row("bar")); //Test alter rename @@ -271,7 +306,7 @@ public class MaterializedViewTest extends CQLTester createView("mv_static", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE val IS NOT NULL AND k IS NOT NULL AND c IS NOT NULL PRIMARY KEY (val,k,c)"); for (int i = 0; i < 100; i++) - updateMV("INSERT into %s (k,c,sval,val)VALUES(?,?,?,?)", 0, i % 2, "bar" + i, "baz"); + updateView("INSERT into %s (k,c,sval,val)VALUES(?,?,?,?)", 0, i % 2, "bar" + i, "baz"); Assert.assertEquals(2, execute("select * from %s").size()); @@ -298,7 +333,7 @@ public class MaterializedViewTest extends CQLTester createView("mv_tstest", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE val IS NOT NULL AND k IS NOT NULL AND c IS NOT NULL PRIMARY KEY (val,k,c)"); for (int i = 0; i < 100; i++) - updateMV("INSERT into %s (k,c,val)VALUES(?,?,?)", 0, i % 2, "baz"); + updateView("INSERT into %s (k,c,val)VALUES(?,?,?)", 0, i % 2, "baz"); Keyspace.open(keyspace()).getColumnFamilyStore(currentTable()).forceBlockingFlush(); @@ -309,13 +344,13 @@ public class MaterializedViewTest extends CQLTester assertRows(execute("SELECT c from mv_tstest where k = 0 and val = ?", "baz"), row(0), row(1)); //Make sure an old TS does nothing - updateMV("UPDATE %s USING TIMESTAMP 100 SET val = ? where k = ? AND c = ?", "bar", 0, 0); + updateView("UPDATE %s USING TIMESTAMP 100 SET val = ? where k = ? AND c = ?", "bar", 0, 0); assertRows(execute("SELECT val from %s where k = 0 and c = 0"), row("baz")); assertRows(execute("SELECT c from mv_tstest where k = 0 and val = ?", "baz"), row(0), row(1)); assertRows(execute("SELECT c from mv_tstest where k = 0 and val = ?", "bar")); //Latest TS - updateMV("UPDATE %s SET val = ? where k = ? AND c = ?", "bar", 0, 0); + updateView("UPDATE %s SET val = ? where k = ? AND c = ?", "bar", 0, 0); assertRows(execute("SELECT val from %s where k = 0 and c = 0"), row("bar")); assertRows(execute("SELECT c from mv_tstest where k = 0 and val = ?", "bar"), row(0)); assertRows(execute("SELECT c from mv_tstest where k = 0 and val = ?", "baz"), row(1)); @@ -503,7 +538,7 @@ public class MaterializedViewTest extends CQLTester createView("mv_test1", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE textval2 IS NOT NULL AND k IS NOT NULL AND asciival IS NOT NULL AND bigintval IS NOT NULL AND textval1 IS NOT NULL PRIMARY KEY ((textval2, k), asciival, bigintval, textval1)"); for (int i = 0; i < 100; i++) - updateMV("INSERT into %s (k,asciival,bigintval,textval1,textval2)VALUES(?,?,?,?,?)", 0, "foo", (long) i % 2, "bar" + i, "baz"); + updateView("INSERT into %s (k,asciival,bigintval,textval1,textval2)VALUES(?,?,?,?,?)", 0, "foo", (long) i % 2, "bar" + i, "baz"); Assert.assertEquals(50, execute("select * from %s where k = 0 and asciival = 'foo' and bigintval = 0").size()); Assert.assertEquals(50, execute("select * from %s where k = 0 and asciival = 'foo' and bigintval = 1").size()); @@ -527,7 +562,7 @@ public class MaterializedViewTest extends CQLTester Assert.assertEquals(100, execute("select asciival from mv_test3 where textval2 = ? and k = ?", "baz", 0).size()); //Write a RT and verify the data is removed from index - updateMV("DELETE FROM %s WHERE k = ? AND asciival = ? and bigintval = ?", 0, "foo", 0L); + updateView("DELETE FROM %s WHERE k = ? AND asciival = ? and bigintval = ?", 0, "foo", 0L); Assert.assertEquals(50, execute("select asciival from mv_test3 where textval2 = ? and k = ?", "baz", 0).size()); } @@ -550,7 +585,7 @@ public class MaterializedViewTest extends CQLTester createView("mv", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE textval1 IS NOT NULL AND k IS NOT NULL AND asciival IS NOT NULL AND bigintval IS NOT NULL PRIMARY KEY ((textval1, k), asciival, bigintval)"); for (int i = 0; i < 100; i++) - updateMV("INSERT into %s (k,asciival,bigintval,textval1)VALUES(?,?,?,?)", 0, "foo", (long) i % 2, "bar" + i); + updateView("INSERT into %s (k,asciival,bigintval,textval1)VALUES(?,?,?,?)", 0, "foo", (long) i % 2, "bar" + i); Assert.assertEquals(1, execute("select * from %s where k = 0 and asciival = 'foo' and bigintval = 0").size()); Assert.assertEquals(1, execute("select * from %s where k = 0 and asciival = 'foo' and bigintval = 1").size()); @@ -560,7 +595,7 @@ public class MaterializedViewTest extends CQLTester Assert.assertEquals(2, execute("select * from mv").size()); //Write a RT and verify the data is removed from index - updateMV("DELETE FROM %s WHERE k = ? AND asciival = ? and bigintval = ?", 0, "foo", 0L); + updateView("DELETE FROM %s WHERE k = ? AND asciival = ? and bigintval = ?", 0, "foo", 0L); Assert.assertEquals(1, execute("select * from %s").size()); Assert.assertEquals(1, execute("select * from mv").size()); @@ -583,7 +618,7 @@ public class MaterializedViewTest extends CQLTester createView("mv", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE textval1 IS NOT NULL AND k IS NOT NULL AND asciival IS NOT NULL AND bigintval IS NOT NULL PRIMARY KEY ((textval1, k), asciival, bigintval)"); for (int i = 0; i < 100; i++) - updateMV("INSERT into %s (k,asciival,bigintval,textval1)VALUES(?,?,?,?)", 0, "foo", (long) i % 2, "bar" + i); + updateView("INSERT into %s (k,asciival,bigintval,textval1)VALUES(?,?,?,?)", 0, "foo", (long) i % 2, "bar" + i); Assert.assertEquals(1, execute("select * from %s where k = 0 and asciival = 'foo' and bigintval = 0").size()); Assert.assertEquals(1, execute("select * from %s where k = 0 and asciival = 'foo' and bigintval = 1").size()); @@ -593,7 +628,7 @@ public class MaterializedViewTest extends CQLTester Assert.assertEquals(2, execute("select * from mv").size()); //Write a RT and verify the data is removed from index - updateMV("DELETE FROM %s WHERE k = ? AND asciival = ? and bigintval >= ?", 0, "foo", 0L); + updateView("DELETE FROM %s WHERE k = ? AND asciival = ? and bigintval >= ?", 0, "foo", 0L); Assert.assertEquals(0, execute("select * from %s").size()); Assert.assertEquals(0, execute("select * from mv").size()); @@ -688,8 +723,8 @@ public class MaterializedViewTest extends CQLTester } } - updateMV("INSERT INTO %s (k, asciival, bigintval) VALUES (?, ?, fromJson(?))", 0, "ascii text", "123123123123"); - updateMV("INSERT INTO %s (k, asciival) VALUES (?, fromJson(?))", 0, "\"ascii text\""); + updateView("INSERT INTO %s (k, asciival, bigintval) VALUES (?, ?, fromJson(?))", 0, "ascii text", "123123123123"); + updateView("INSERT INTO %s (k, asciival) VALUES (?, fromJson(?))", 0, "\"ascii text\""); assertRows(execute("SELECT bigintval FROM %s WHERE k = ? and asciival = ?", 0, "ascii text"), row(123123123123L)); //Check the MV @@ -700,7 +735,7 @@ public class MaterializedViewTest extends CQLTester //UPDATE BASE - updateMV("INSERT INTO %s (k, asciival, bigintval) VALUES (?, ?, fromJson(?))", 0, "ascii text", "1"); + updateView("INSERT INTO %s (k, asciival, bigintval) VALUES (?, ?, fromJson(?))", 0, "ascii text", "1"); assertRows(execute("SELECT bigintval FROM %s WHERE k = ? and asciival = ?", 0, "ascii text"), row(1L)); //Check the MV @@ -712,7 +747,7 @@ public class MaterializedViewTest extends CQLTester //test truncate also truncates all MV - updateMV("TRUNCATE %s"); + updateView("TRUNCATE %s"); assertRows(execute("SELECT bigintval FROM %s WHERE k = ? and asciival = ?", 0, "ascii text")); assertRows(execute("SELECT k, bigintval from mv1_asciival WHERE asciival = ?", "ascii text")); @@ -734,12 +769,12 @@ public class MaterializedViewTest extends CQLTester createView("mv", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE k IS NOT NULL AND intval IS NOT NULL PRIMARY KEY (intval, k)"); - updateMV("INSERT INTO %s (k, intval, listval) VALUES (?, ?, fromJson(?))", 0, 0, "[1, 2, 3]"); + updateView("INSERT INTO %s (k, intval, listval) VALUES (?, ?, fromJson(?))", 0, 0, "[1, 2, 3]"); assertRows(execute("SELECT k, listval FROM %s WHERE k = ?", 0), row(0, list(1, 2, 3))); assertRows(execute("SELECT k, listval from mv WHERE intval = ?", 0), row(0, list(1, 2, 3))); - updateMV("INSERT INTO %s (k, intval) VALUES (?, ?)", 1, 1); - updateMV("INSERT INTO %s (k, listval) VALUES (?, fromJson(?))", 1, "[1, 2, 3]"); + updateView("INSERT INTO %s (k, intval) VALUES (?, ?)", 1, 1); + updateView("INSERT INTO %s (k, listval) VALUES (?, fromJson(?))", 1, "[1, 2, 3]"); assertRows(execute("SELECT k, listval FROM %s WHERE k = ?", 1), row(1, list(1, 2, 3))); assertRows(execute("SELECT k, listval from mv WHERE intval = ?", 1), row(1, list(1, 2, 3))); } @@ -757,11 +792,11 @@ public class MaterializedViewTest extends CQLTester createView("mv", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE k IS NOT NULL AND intval IS NOT NULL PRIMARY KEY (intval, k)"); - updateMV("INSERT INTO %s (k, intval) VALUES (?, ?)", 0, 0); + updateView("INSERT INTO %s (k, intval) VALUES (?, ?)", 0, 0); assertRows(execute("SELECT k, intval FROM %s WHERE k = ?", 0), row(0, 0)); assertRows(execute("SELECT k, intval from mv WHERE intval = ?", 0), row(0, 0)); - updateMV("INSERT INTO %s (k, intval) VALUES (?, ?)", 0, 1); + updateView("INSERT INTO %s (k, intval) VALUES (?, ?)", 0, 1); assertRows(execute("SELECT k, intval FROM %s WHERE k = ?", 0), row(0, 1)); assertRows(execute("SELECT k, intval from mv WHERE intval = ?", 1), row(0, 1)); } @@ -780,18 +815,45 @@ public class MaterializedViewTest extends CQLTester createView("mv", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE k IS NOT NULL AND decimalval IS NOT NULL PRIMARY KEY (decimalval, k)"); - updateMV("INSERT INTO %s (k, asciival) VALUES (?, ?)", 0, "ascii text"); - updateMV("INSERT INTO %s (k, decimalval) VALUES (?, fromJson(?))", 0, "123123"); + updateView("INSERT INTO %s (k, asciival) VALUES (?, ?)", 0, "ascii text"); + updateView("INSERT INTO %s (k, decimalval) VALUES (?, fromJson(?))", 0, "123123"); assertRows(execute("SELECT k, decimalval FROM %s WHERE k = ?", 0), row(0, new BigDecimal("123123"))); assertRows(execute("SELECT k, asciival from mv WHERE decimalval = fromJson(?)", "123123.123123")); assertRows(execute("SELECT k, asciival from mv WHERE decimalval = fromJson(?)", "123123"), row(0, "ascii text")); // accept strings for numbers that cannot be represented as doubles - updateMV("INSERT INTO %s (k, decimalval) VALUES (?, fromJson(?))", 0, "\"123123.123123\""); + updateView("INSERT INTO %s (k, decimalval) VALUES (?, fromJson(?))", 0, "\"123123.123123\""); assertRows(execute("SELECT k, decimalval, asciival FROM %s WHERE k = ?", 0), row(0, new BigDecimal("123123.123123"), "ascii text")); assertRows(execute("SELECT k, asciival from mv WHERE decimalval = fromJson(?)", "\"123123.123123\""), row(0, "ascii text")); } + @Test + public void testReuseName() throws Throwable + { + createTable("CREATE TABLE %s (" + + "k int, " + + "intval int, " + + "PRIMARY KEY (k))"); + + execute("USE " + keyspace()); + executeNet(protocolVersion, "USE " + keyspace()); + + createView("mv", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE k IS NOT NULL AND intval IS NOT NULL PRIMARY KEY (intval, k)"); + + updateView("INSERT INTO %s (k, intval) VALUES (?, ?)", 0, 0); + assertRows(execute("SELECT k, intval FROM %s WHERE k = ?", 0), row(0, 0)); + assertRows(execute("SELECT k, intval from mv WHERE intval = ?", 0), row(0, 0)); + + executeNet(protocolVersion, "DROP MATERIALIZED VIEW mv"); + views.remove("mv"); + + createView("mv", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE k IS NOT NULL AND intval IS NOT NULL PRIMARY KEY (intval, k)"); + + updateView("INSERT INTO %s (k, intval) VALUES (?, ?)", 0, 1); + assertRows(execute("SELECT k, intval FROM %s WHERE k = ?", 0), row(0, 1)); + assertRows(execute("SELECT k, intval from mv WHERE intval = ?", 1), row(0, 1)); + } + @Test public void testAllTypes() throws Throwable { @@ -856,10 +918,10 @@ public class MaterializedViewTest extends CQLTester createFunctionOverload(func1, "int", "CREATE FUNCTION %s (a text) CALLED ON NULL INPUT RETURNS text LANGUAGE java AS $$ return new String(a); $$"); // ================ ascii ================ - updateMV("INSERT INTO %s (k, asciival) VALUES (?, fromJson(?))", 0, "\"ascii text\""); + updateView("INSERT INTO %s (k, asciival) VALUES (?, fromJson(?))", 0, "\"ascii text\""); assertRows(execute("SELECT k, asciival FROM %s WHERE k = ?", 0), row(0, "ascii text")); - updateMV("INSERT INTO %s (k, asciival) VALUES (?, fromJson(?))", 0, "\"ascii \\\" text\""); + updateView("INSERT INTO %s (k, asciival) VALUES (?, fromJson(?))", 0, "\"ascii \\\" text\""); assertRows(execute("SELECT k, asciival FROM %s WHERE k = ?", 0), row(0, "ascii \" text")); // test that we can use fromJson() in other valid places in queries @@ -869,192 +931,192 @@ public class MaterializedViewTest extends CQLTester assertRows(execute("SELECT k, udtval from mv_asciival WHERE asciival = ?", "ascii text")); assertRows(execute("SELECT k, udtval from mv_asciival WHERE asciival = ?", "ascii \" text"), row(0, null)); - updateMV("UPDATE %s SET asciival = fromJson(?) WHERE k = fromJson(?)", "\"ascii \\\" text\"", "0"); + updateView("UPDATE %s SET asciival = fromJson(?) WHERE k = fromJson(?)", "\"ascii \\\" text\"", "0"); assertRows(execute("SELECT k, udtval from mv_asciival WHERE asciival = ?", "ascii \" text"), row(0, null)); - updateMV("DELETE FROM %s WHERE k = fromJson(?)", "0"); + updateView("DELETE FROM %s WHERE k = fromJson(?)", "0"); assertRows(execute("SELECT k, asciival FROM %s WHERE k = ?", 0)); assertRows(execute("SELECT k, udtval from mv_asciival WHERE asciival = ?", "ascii \" text")); - updateMV("INSERT INTO %s (k, asciival) VALUES (?, fromJson(?))", 0, "\"ascii text\""); + updateView("INSERT INTO %s (k, asciival) VALUES (?, fromJson(?))", 0, "\"ascii text\""); assertRows(execute("SELECT k, udtval from mv_asciival WHERE asciival = ?", "ascii text"), row(0, null)); // ================ bigint ================ - updateMV("INSERT INTO %s (k, bigintval) VALUES (?, fromJson(?))", 0, "123123123123"); + updateView("INSERT INTO %s (k, bigintval) VALUES (?, fromJson(?))", 0, "123123123123"); assertRows(execute("SELECT k, bigintval FROM %s WHERE k = ?", 0), row(0, 123123123123L)); assertRows(execute("SELECT k, asciival from mv_bigintval WHERE bigintval = ?", 123123123123L), row(0, "ascii text")); // ================ blob ================ - updateMV("INSERT INTO %s (k, blobval) VALUES (?, fromJson(?))", 0, "\"0x00000001\""); + updateView("INSERT INTO %s (k, blobval) VALUES (?, fromJson(?))", 0, "\"0x00000001\""); assertRows(execute("SELECT k, blobval FROM %s WHERE k = ?", 0), row(0, ByteBufferUtil.bytes(1))); assertRows(execute("SELECT k, asciival from mv_blobval WHERE blobval = ?", ByteBufferUtil.bytes(1)), row(0, "ascii text")); // ================ boolean ================ - updateMV("INSERT INTO %s (k, booleanval) VALUES (?, fromJson(?))", 0, "true"); + updateView("INSERT INTO %s (k, booleanval) VALUES (?, fromJson(?))", 0, "true"); assertRows(execute("SELECT k, booleanval FROM %s WHERE k = ?", 0), row(0, true)); assertRows(execute("SELECT k, asciival from mv_booleanval WHERE booleanval = ?", true), row(0, "ascii text")); - updateMV("INSERT INTO %s (k, booleanval) VALUES (?, fromJson(?))", 0, "false"); + updateView("INSERT INTO %s (k, booleanval) VALUES (?, fromJson(?))", 0, "false"); assertRows(execute("SELECT k, booleanval FROM %s WHERE k = ?", 0), row(0, false)); assertRows(execute("SELECT k, asciival from mv_booleanval WHERE booleanval = ?", true)); assertRows(execute("SELECT k, asciival from mv_booleanval WHERE booleanval = ?", false), row(0, "ascii text")); // ================ date ================ - updateMV("INSERT INTO %s (k, dateval) VALUES (?, fromJson(?))", 0, "\"1987-03-23\""); + updateView("INSERT INTO %s (k, dateval) VALUES (?, fromJson(?))", 0, "\"1987-03-23\""); assertRows(execute("SELECT k, dateval FROM %s WHERE k = ?", 0), row(0, SimpleDateSerializer.dateStringToDays("1987-03-23"))); assertRows(execute("SELECT k, asciival from mv_dateval WHERE dateval = fromJson(?)", "\"1987-03-23\""), row(0, "ascii text")); // ================ decimal ================ - updateMV("INSERT INTO %s (k, decimalval) VALUES (?, fromJson(?))", 0, "123123.123123"); + updateView("INSERT INTO %s (k, decimalval) VALUES (?, fromJson(?))", 0, "123123.123123"); assertRows(execute("SELECT k, decimalval FROM %s WHERE k = ?", 0), row(0, new BigDecimal("123123.123123"))); assertRows(execute("SELECT k, asciival from mv_decimalval WHERE decimalval = fromJson(?)", "123123.123123"), row(0, "ascii text")); - updateMV("INSERT INTO %s (k, decimalval) VALUES (?, fromJson(?))", 0, "123123"); + updateView("INSERT INTO %s (k, decimalval) VALUES (?, fromJson(?))", 0, "123123"); assertRows(execute("SELECT k, decimalval FROM %s WHERE k = ?", 0), row(0, new BigDecimal("123123"))); assertRows(execute("SELECT k, asciival from mv_decimalval WHERE decimalval = fromJson(?)", "123123.123123")); assertRows(execute("SELECT k, asciival from mv_decimalval WHERE decimalval = fromJson(?)", "123123"), row(0, "ascii text")); // accept strings for numbers that cannot be represented as doubles - updateMV("INSERT INTO %s (k, decimalval) VALUES (?, fromJson(?))", 0, "\"123123.123123\""); + updateView("INSERT INTO %s (k, decimalval) VALUES (?, fromJson(?))", 0, "\"123123.123123\""); assertRows(execute("SELECT k, decimalval FROM %s WHERE k = ?", 0), row(0, new BigDecimal("123123.123123"))); - updateMV("INSERT INTO %s (k, decimalval) VALUES (?, fromJson(?))", 0, "\"-1.23E-12\""); + updateView("INSERT INTO %s (k, decimalval) VALUES (?, fromJson(?))", 0, "\"-1.23E-12\""); assertRows(execute("SELECT k, decimalval FROM %s WHERE k = ?", 0), row(0, new BigDecimal("-1.23E-12"))); assertRows(execute("SELECT k, asciival from mv_decimalval WHERE decimalval = fromJson(?)", "\"-1.23E-12\""), row(0, "ascii text")); // ================ double ================ - updateMV("INSERT INTO %s (k, doubleval) VALUES (?, fromJson(?))", 0, "123123.123123"); + updateView("INSERT INTO %s (k, doubleval) VALUES (?, fromJson(?))", 0, "123123.123123"); assertRows(execute("SELECT k, doubleval FROM %s WHERE k = ?", 0), row(0, 123123.123123d)); assertRows(execute("SELECT k, asciival from mv_doubleval WHERE doubleval = fromJson(?)", "123123.123123"), row(0, "ascii text")); - updateMV("INSERT INTO %s (k, doubleval) VALUES (?, fromJson(?))", 0, "123123"); + updateView("INSERT INTO %s (k, doubleval) VALUES (?, fromJson(?))", 0, "123123"); assertRows(execute("SELECT k, doubleval FROM %s WHERE k = ?", 0), row(0, 123123.0d)); assertRows(execute("SELECT k, asciival from mv_doubleval WHERE doubleval = fromJson(?)", "123123"), row(0, "ascii text")); // ================ float ================ - updateMV("INSERT INTO %s (k, floatval) VALUES (?, fromJson(?))", 0, "123123.123123"); + updateView("INSERT INTO %s (k, floatval) VALUES (?, fromJson(?))", 0, "123123.123123"); assertRows(execute("SELECT k, floatval FROM %s WHERE k = ?", 0), row(0, 123123.123123f)); assertRows(execute("SELECT k, asciival from mv_floatval WHERE floatval = fromJson(?)", "123123.123123"), row(0, "ascii text")); - updateMV("INSERT INTO %s (k, floatval) VALUES (?, fromJson(?))", 0, "123123"); + updateView("INSERT INTO %s (k, floatval) VALUES (?, fromJson(?))", 0, "123123"); assertRows(execute("SELECT k, floatval FROM %s WHERE k = ?", 0), row(0, 123123.0f)); assertRows(execute("SELECT k, asciival from mv_floatval WHERE floatval = fromJson(?)", "123123"), row(0, "ascii text")); // ================ inet ================ - updateMV("INSERT INTO %s (k, inetval) VALUES (?, fromJson(?))", 0, "\"127.0.0.1\""); + updateView("INSERT INTO %s (k, inetval) VALUES (?, fromJson(?))", 0, "\"127.0.0.1\""); assertRows(execute("SELECT k, inetval FROM %s WHERE k = ?", 0), row(0, InetAddress.getByName("127.0.0.1"))); assertRows(execute("SELECT k, asciival from mv_inetval WHERE inetval = fromJson(?)", "\"127.0.0.1\""), row(0, "ascii text")); - updateMV("INSERT INTO %s (k, inetval) VALUES (?, fromJson(?))", 0, "\"::1\""); + updateView("INSERT INTO %s (k, inetval) VALUES (?, fromJson(?))", 0, "\"::1\""); assertRows(execute("SELECT k, inetval FROM %s WHERE k = ?", 0), row(0, InetAddress.getByName("::1"))); assertRows(execute("SELECT k, asciival from mv_inetval WHERE inetval = fromJson(?)", "\"127.0.0.1\"")); assertRows(execute("SELECT k, asciival from mv_inetval WHERE inetval = fromJson(?)", "\"::1\""), row(0, "ascii text")); // ================ int ================ - updateMV("INSERT INTO %s (k, intval) VALUES (?, fromJson(?))", 0, "123123"); + updateView("INSERT INTO %s (k, intval) VALUES (?, fromJson(?))", 0, "123123"); assertRows(execute("SELECT k, intval FROM %s WHERE k = ?", 0), row(0, 123123)); assertRows(execute("SELECT k, asciival from mv_intval WHERE intval = fromJson(?)", "123123"), row(0, "ascii text")); // ================ text (varchar) ================ - updateMV("INSERT INTO %s (k, textval) VALUES (?, fromJson(?))", 0, "\"some \\\" text\""); + updateView("INSERT INTO %s (k, textval) VALUES (?, fromJson(?))", 0, "\"some \\\" text\""); assertRows(execute("SELECT k, textval FROM %s WHERE k = ?", 0), row(0, "some \" text")); - updateMV("INSERT INTO %s (k, textval) VALUES (?, fromJson(?))", 0, "\"\\u2013\""); + updateView("INSERT INTO %s (k, textval) VALUES (?, fromJson(?))", 0, "\"\\u2013\""); assertRows(execute("SELECT k, textval FROM %s WHERE k = ?", 0), row(0, "\u2013")); assertRows(execute("SELECT k, asciival from mv_textval WHERE textval = fromJson(?)", "\"\\u2013\""), row(0, "ascii text")); - updateMV("INSERT INTO %s (k, textval) VALUES (?, fromJson(?))", 0, "\"abcd\""); + updateView("INSERT INTO %s (k, textval) VALUES (?, fromJson(?))", 0, "\"abcd\""); assertRows(execute("SELECT k, textval FROM %s WHERE k = ?", 0), row(0, "abcd")); assertRows(execute("SELECT k, asciival from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, "ascii text")); // ================ time ================ - updateMV("INSERT INTO %s (k, timeval) VALUES (?, fromJson(?))", 0, "\"07:35:07.000111222\""); + updateView("INSERT INTO %s (k, timeval) VALUES (?, fromJson(?))", 0, "\"07:35:07.000111222\""); assertRows(execute("SELECT k, timeval FROM %s WHERE k = ?", 0), row(0, TimeSerializer.timeStringToLong("07:35:07.000111222"))); assertRows(execute("SELECT k, asciival from mv_timeval WHERE timeval = fromJson(?)", "\"07:35:07.000111222\""), row(0, "ascii text")); // ================ timestamp ================ - updateMV("INSERT INTO %s (k, timestampval) VALUES (?, fromJson(?))", 0, "123123123123"); + updateView("INSERT INTO %s (k, timestampval) VALUES (?, fromJson(?))", 0, "123123123123"); assertRows(execute("SELECT k, timestampval FROM %s WHERE k = ?", 0), row(0, new Date(123123123123L))); assertRows(execute("SELECT k, asciival from mv_timestampval WHERE timestampval = fromJson(?)", "123123123123"), row(0, "ascii text")); - updateMV("INSERT INTO %s (k, timestampval) VALUES (?, fromJson(?))", 0, "\"2014-01-01\""); + updateView("INSERT INTO %s (k, timestampval) VALUES (?, fromJson(?))", 0, "\"2014-01-01\""); assertRows(execute("SELECT k, timestampval FROM %s WHERE k = ?", 0), row(0, new SimpleDateFormat("y-M-d").parse("2014-01-01"))); assertRows(execute("SELECT k, asciival from mv_timestampval WHERE timestampval = fromJson(?)", "\"2014-01-01\""), row(0, "ascii text")); // ================ timeuuid ================ - updateMV("INSERT INTO %s (k, timeuuidval) VALUES (?, fromJson(?))", 0, "\"6bddc89a-5644-11e4-97fc-56847afe9799\""); + updateView("INSERT INTO %s (k, timeuuidval) VALUES (?, fromJson(?))", 0, "\"6bddc89a-5644-11e4-97fc-56847afe9799\""); assertRows(execute("SELECT k, timeuuidval FROM %s WHERE k = ?", 0), row(0, UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799"))); - updateMV("INSERT INTO %s (k, timeuuidval) VALUES (?, fromJson(?))", 0, "\"6BDDC89A-5644-11E4-97FC-56847AFE9799\""); + updateView("INSERT INTO %s (k, timeuuidval) VALUES (?, fromJson(?))", 0, "\"6BDDC89A-5644-11E4-97FC-56847AFE9799\""); assertRows(execute("SELECT k, timeuuidval FROM %s WHERE k = ?", 0), row(0, UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799"))); assertRows(execute("SELECT k, asciival from mv_timeuuidval WHERE timeuuidval = fromJson(?)", "\"6BDDC89A-5644-11E4-97FC-56847AFE9799\""), row(0, "ascii text")); // ================ uuidval ================ - updateMV("INSERT INTO %s (k, uuidval) VALUES (?, fromJson(?))", 0, "\"6bddc89a-5644-11e4-97fc-56847afe9799\""); + updateView("INSERT INTO %s (k, uuidval) VALUES (?, fromJson(?))", 0, "\"6bddc89a-5644-11e4-97fc-56847afe9799\""); assertRows(execute("SELECT k, uuidval FROM %s WHERE k = ?", 0), row(0, UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799"))); - updateMV("INSERT INTO %s (k, uuidval) VALUES (?, fromJson(?))", 0, "\"6BDDC89A-5644-11E4-97FC-56847AFE9799\""); + updateView("INSERT INTO %s (k, uuidval) VALUES (?, fromJson(?))", 0, "\"6BDDC89A-5644-11E4-97FC-56847AFE9799\""); assertRows(execute("SELECT k, uuidval FROM %s WHERE k = ?", 0), row(0, UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799"))); assertRows(execute("SELECT k, asciival from mv_uuidval WHERE uuidval = fromJson(?)", "\"6BDDC89A-5644-11E4-97FC-56847AFE9799\""), row(0, "ascii text")); // ================ varint ================ - updateMV("INSERT INTO %s (k, varintval) VALUES (?, fromJson(?))", 0, "123123123123"); + updateView("INSERT INTO %s (k, varintval) VALUES (?, fromJson(?))", 0, "123123123123"); assertRows(execute("SELECT k, varintval FROM %s WHERE k = ?", 0), row(0, new BigInteger("123123123123"))); assertRows(execute("SELECT k, asciival from mv_varintval WHERE varintval = fromJson(?)", "123123123123"), row(0, "ascii text")); // accept strings for numbers that cannot be represented as longs - updateMV("INSERT INTO %s (k, varintval) VALUES (?, fromJson(?))", 0, "\"1234567890123456789012345678901234567890\""); + updateView("INSERT INTO %s (k, varintval) VALUES (?, fromJson(?))", 0, "\"1234567890123456789012345678901234567890\""); assertRows(execute("SELECT k, varintval FROM %s WHERE k = ?", 0), row(0, new BigInteger("1234567890123456789012345678901234567890"))); assertRows(execute("SELECT k, asciival from mv_varintval WHERE varintval = fromJson(?)", "\"1234567890123456789012345678901234567890\""), row(0, "ascii text")); // ================ lists ================ - updateMV("INSERT INTO %s (k, listval) VALUES (?, fromJson(?))", 0, "[1, 2, 3]"); + updateView("INSERT INTO %s (k, listval) VALUES (?, fromJson(?))", 0, "[1, 2, 3]"); assertRows(execute("SELECT k, listval FROM %s WHERE k = ?", 0), row(0, list(1, 2, 3))); assertRows(execute("SELECT k, listval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, list(1, 2, 3))); - updateMV("INSERT INTO %s (k, listval) VALUES (?, fromJson(?))", 0, "[1]"); + updateView("INSERT INTO %s (k, listval) VALUES (?, fromJson(?))", 0, "[1]"); assertRows(execute("SELECT k, listval FROM %s WHERE k = ?", 0), row(0, list(1))); assertRows(execute("SELECT k, listval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, list(1))); - updateMV("UPDATE %s SET listval = listval + fromJson(?) WHERE k = ?", "[2]", 0); + updateView("UPDATE %s SET listval = listval + fromJson(?) WHERE k = ?", "[2]", 0); assertRows(execute("SELECT k, listval FROM %s WHERE k = ?", 0), row(0, list(1, 2))); assertRows(execute("SELECT k, listval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, list(1, 2))); - updateMV("UPDATE %s SET listval = fromJson(?) + listval WHERE k = ?", "[0]", 0); + updateView("UPDATE %s SET listval = fromJson(?) + listval WHERE k = ?", "[0]", 0); assertRows(execute("SELECT k, listval FROM %s WHERE k = ?", 0), row(0, list(0, 1, 2))); assertRows(execute("SELECT k, listval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, list(0, 1, 2))); - updateMV("UPDATE %s SET listval[1] = fromJson(?) WHERE k = ?", "10", 0); + updateView("UPDATE %s SET listval[1] = fromJson(?) WHERE k = ?", "10", 0); assertRows(execute("SELECT k, listval FROM %s WHERE k = ?", 0), row(0, list(0, 10, 2))); assertRows(execute("SELECT k, listval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, list(0, 10, 2))); - updateMV("DELETE listval[1] FROM %s WHERE k = ?", 0); + updateView("DELETE listval[1] FROM %s WHERE k = ?", 0); assertRows(execute("SELECT k, listval FROM %s WHERE k = ?", 0), row(0, list(0, 2))); assertRows(execute("SELECT k, listval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, list(0, 2))); - updateMV("INSERT INTO %s (k, listval) VALUES (?, fromJson(?))", 0, "[]"); + updateView("INSERT INTO %s (k, listval) VALUES (?, fromJson(?))", 0, "[]"); assertRows(execute("SELECT k, listval FROM %s WHERE k = ?", 0), row(0, null)); assertRows(execute("SELECT k, listval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, null)); // frozen - updateMV("INSERT INTO %s (k, frozenlistval) VALUES (?, fromJson(?))", 0, "[1, 2, 3]"); + updateView("INSERT INTO %s (k, frozenlistval) VALUES (?, fromJson(?))", 0, "[1, 2, 3]"); assertRows(execute("SELECT k, frozenlistval FROM %s WHERE k = ?", 0), row(0, list(1, 2, 3))); assertRows(execute("SELECT k, frozenlistval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, list(1, 2, 3))); assertRows(execute("SELECT k, textval from mv_frozenlistval where frozenlistval = fromJson(?)", "[1, 2, 3]"), row(0, "abcd")); - updateMV("INSERT INTO %s (k, frozenlistval) VALUES (?, fromJson(?))", 0, "[3, 2, 1]"); + updateView("INSERT INTO %s (k, frozenlistval) VALUES (?, fromJson(?))", 0, "[3, 2, 1]"); assertRows(execute("SELECT k, frozenlistval FROM %s WHERE k = ?", 0), row(0, list(3, 2, 1))); assertRows(execute("SELECT k, textval from mv_frozenlistval where frozenlistval = fromJson(?)", "[1, 2, 3]")); assertRows(execute("SELECT k, textval from mv_frozenlistval where frozenlistval = fromJson(?)", "[3, 2, 1]"), row(0, "abcd")); assertRows(execute("SELECT k, frozenlistval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, list(3, 2, 1))); - updateMV("INSERT INTO %s (k, frozenlistval) VALUES (?, fromJson(?))", 0, "[]"); + updateView("INSERT INTO %s (k, frozenlistval) VALUES (?, fromJson(?))", 0, "[]"); assertRows(execute("SELECT k, frozenlistval FROM %s WHERE k = ?", 0), row(0, list())); assertRows(execute("SELECT k, frozenlistval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, list())); // ================ sets ================ - updateMV("INSERT INTO %s (k, setval) VALUES (?, fromJson(?))", - 0, "[\"6bddc89a-5644-11e4-97fc-56847afe9798\", \"6bddc89a-5644-11e4-97fc-56847afe9799\"]"); + updateView("INSERT INTO %s (k, setval) VALUES (?, fromJson(?))", + 0, "[\"6bddc89a-5644-11e4-97fc-56847afe9798\", \"6bddc89a-5644-11e4-97fc-56847afe9799\"]"); assertRows(execute("SELECT k, setval FROM %s WHERE k = ?", 0), row(0, set(UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9798"), (UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799")))) ); @@ -1062,45 +1124,45 @@ public class MaterializedViewTest extends CQLTester row(0, set(UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9798"), (UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799"))))); // duplicates are okay, just like in CQL - updateMV("INSERT INTO %s (k, setval) VALUES (?, fromJson(?))", - 0, "[\"6bddc89a-5644-11e4-97fc-56847afe9798\", \"6bddc89a-5644-11e4-97fc-56847afe9798\", \"6bddc89a-5644-11e4-97fc-56847afe9799\"]"); + updateView("INSERT INTO %s (k, setval) VALUES (?, fromJson(?))", + 0, "[\"6bddc89a-5644-11e4-97fc-56847afe9798\", \"6bddc89a-5644-11e4-97fc-56847afe9798\", \"6bddc89a-5644-11e4-97fc-56847afe9799\"]"); assertRows(execute("SELECT k, setval FROM %s WHERE k = ?", 0), row(0, set(UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9798"), (UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799")))) ); assertRows(execute("SELECT k, setval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, set(UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9798"), (UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799"))))); - updateMV("UPDATE %s SET setval = setval + fromJson(?) WHERE k = ?", "[\"6bddc89a-5644-0000-97fc-56847afe9799\"]", 0); + updateView("UPDATE %s SET setval = setval + fromJson(?) WHERE k = ?", "[\"6bddc89a-5644-0000-97fc-56847afe9799\"]", 0); assertRows(execute("SELECT k, setval FROM %s WHERE k = ?", 0), row(0, set(UUID.fromString("6bddc89a-5644-0000-97fc-56847afe9799"), UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9798"), (UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799")))) ); assertRows(execute("SELECT k, setval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, set(UUID.fromString("6bddc89a-5644-0000-97fc-56847afe9799"), UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9798"), (UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799"))))); - updateMV("UPDATE %s SET setval = setval - fromJson(?) WHERE k = ?", "[\"6bddc89a-5644-0000-97fc-56847afe9799\"]", 0); + updateView("UPDATE %s SET setval = setval - fromJson(?) WHERE k = ?", "[\"6bddc89a-5644-0000-97fc-56847afe9799\"]", 0); assertRows(execute("SELECT k, setval FROM %s WHERE k = ?", 0), row(0, set(UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9798"), (UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799")))) ); assertRows(execute("SELECT k, setval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, set(UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9798"), (UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799"))))); - updateMV("INSERT INTO %s (k, setval) VALUES (?, fromJson(?))", 0, "[]"); + updateView("INSERT INTO %s (k, setval) VALUES (?, fromJson(?))", 0, "[]"); assertRows(execute("SELECT k, setval FROM %s WHERE k = ?", 0), row(0, null)); assertRows(execute("SELECT k, setval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, null)); // frozen - updateMV("INSERT INTO %s (k, frozensetval) VALUES (?, fromJson(?))", - 0, "[\"6bddc89a-5644-11e4-97fc-56847afe9798\", \"6bddc89a-5644-11e4-97fc-56847afe9799\"]"); + updateView("INSERT INTO %s (k, frozensetval) VALUES (?, fromJson(?))", + 0, "[\"6bddc89a-5644-11e4-97fc-56847afe9798\", \"6bddc89a-5644-11e4-97fc-56847afe9799\"]"); assertRows(execute("SELECT k, frozensetval FROM %s WHERE k = ?", 0), row(0, set(UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9798"), (UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799")))) ); assertRows(execute("SELECT k, frozensetval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, set(UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9798"), (UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799"))))); - updateMV("INSERT INTO %s (k, frozensetval) VALUES (?, fromJson(?))", - 0, "[\"6bddc89a-0000-11e4-97fc-56847afe9799\", \"6bddc89a-5644-11e4-97fc-56847afe9798\"]"); + updateView("INSERT INTO %s (k, frozensetval) VALUES (?, fromJson(?))", + 0, "[\"6bddc89a-0000-11e4-97fc-56847afe9799\", \"6bddc89a-5644-11e4-97fc-56847afe9798\"]"); assertRows(execute("SELECT k, frozensetval FROM %s WHERE k = ?", 0), row(0, set(UUID.fromString("6bddc89a-0000-11e4-97fc-56847afe9799"), (UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9798")))) ); @@ -1108,54 +1170,54 @@ public class MaterializedViewTest extends CQLTester row(0, set(UUID.fromString("6bddc89a-0000-11e4-97fc-56847afe9799"), (UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9798"))))); // ================ maps ================ - updateMV("INSERT INTO %s (k, mapval) VALUES (?, fromJson(?))", 0, "{\"a\": 1, \"b\": 2}"); + updateView("INSERT INTO %s (k, mapval) VALUES (?, fromJson(?))", 0, "{\"a\": 1, \"b\": 2}"); assertRows(execute("SELECT k, mapval FROM %s WHERE k = ?", 0), row(0, map("a", 1, "b", 2))); assertRows(execute("SELECT k, mapval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, map("a", 1, "b", 2))); - updateMV("UPDATE %s SET mapval[?] = ? WHERE k = ?", "c", 3, 0); + updateView("UPDATE %s SET mapval[?] = ? WHERE k = ?", "c", 3, 0); assertRows(execute("SELECT k, mapval FROM %s WHERE k = ?", 0), row(0, map("a", 1, "b", 2, "c", 3)) ); assertRows(execute("SELECT k, mapval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, map("a", 1, "b", 2, "c", 3))); - updateMV("UPDATE %s SET mapval[?] = ? WHERE k = ?", "b", 10, 0); + updateView("UPDATE %s SET mapval[?] = ? WHERE k = ?", "b", 10, 0); assertRows(execute("SELECT k, mapval FROM %s WHERE k = ?", 0), row(0, map("a", 1, "b", 10, "c", 3)) ); assertRows(execute("SELECT k, mapval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, map("a", 1, "b", 10, "c", 3))); - updateMV("DELETE mapval[?] FROM %s WHERE k = ?", "b", 0); + updateView("DELETE mapval[?] FROM %s WHERE k = ?", "b", 0); assertRows(execute("SELECT k, mapval FROM %s WHERE k = ?", 0), row(0, map("a", 1, "c", 3)) ); assertRows(execute("SELECT k, mapval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, map("a", 1, "c", 3))); - updateMV("INSERT INTO %s (k, mapval) VALUES (?, fromJson(?))", 0, "{}"); + updateView("INSERT INTO %s (k, mapval) VALUES (?, fromJson(?))", 0, "{}"); assertRows(execute("SELECT k, mapval FROM %s WHERE k = ?", 0), row(0, null)); assertRows(execute("SELECT k, mapval from mv_textval WHERE textval = fromJson(?)", "\"abcd\""), row(0, null)); // frozen - updateMV("INSERT INTO %s (k, frozenmapval) VALUES (?, fromJson(?))", 0, "{\"a\": 1, \"b\": 2}"); + updateView("INSERT INTO %s (k, frozenmapval) VALUES (?, fromJson(?))", 0, "{\"a\": 1, \"b\": 2}"); assertRows(execute("SELECT k, frozenmapval FROM %s WHERE k = ?", 0), row(0, map("a", 1, "b", 2))); assertRows(execute("SELECT k, textval FROM mv_frozenmapval WHERE frozenmapval = fromJson(?)", "{\"a\": 1, \"b\": 2}"), row(0, "abcd")); - updateMV("INSERT INTO %s (k, frozenmapval) VALUES (?, fromJson(?))", 0, "{\"b\": 2, \"a\": 3}"); + updateView("INSERT INTO %s (k, frozenmapval) VALUES (?, fromJson(?))", 0, "{\"b\": 2, \"a\": 3}"); assertRows(execute("SELECT k, frozenmapval FROM %s WHERE k = ?", 0), row(0, map("a", 3, "b", 2))); assertRows(execute("SELECT k, frozenmapval FROM %s WHERE k = ?", 0), row(0, map("a", 3, "b", 2))); // ================ tuples ================ - updateMV("INSERT INTO %s (k, tupleval) VALUES (?, fromJson(?))", 0, "[1, \"foobar\", \"6bddc89a-5644-11e4-97fc-56847afe9799\"]"); + updateView("INSERT INTO %s (k, tupleval) VALUES (?, fromJson(?))", 0, "[1, \"foobar\", \"6bddc89a-5644-11e4-97fc-56847afe9799\"]"); assertRows(execute("SELECT k, tupleval FROM %s WHERE k = ?", 0), row(0, tuple(1, "foobar", UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799"))) ); assertRows(execute("SELECT k, textval FROM mv_tupleval WHERE tupleval = ?", tuple(1, "foobar", UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799"))), row(0, "abcd")); - updateMV("INSERT INTO %s (k, tupleval) VALUES (?, fromJson(?))", 0, "[1, null, \"6bddc89a-5644-11e4-97fc-56847afe9799\"]"); + updateView("INSERT INTO %s (k, tupleval) VALUES (?, fromJson(?))", 0, "[1, null, \"6bddc89a-5644-11e4-97fc-56847afe9799\"]"); assertRows(execute("SELECT k, tupleval FROM %s WHERE k = ?", 0), row(0, tuple(1, null, UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799"))) ); @@ -1164,7 +1226,7 @@ public class MaterializedViewTest extends CQLTester row(0, "abcd")); // ================ UDTs ================ - updateMV("INSERT INTO %s (k, udtval) VALUES (?, fromJson(?))", 0, "{\"a\": 1, \"b\": \"6bddc89a-5644-11e4-97fc-56847afe9799\", \"c\": [\"foo\", \"bar\"]}"); + updateView("INSERT INTO %s (k, udtval) VALUES (?, fromJson(?))", 0, "{\"a\": 1, \"b\": \"6bddc89a-5644-11e4-97fc-56847afe9799\", \"c\": [\"foo\", \"bar\"]}"); assertRows(execute("SELECT k, udtval.a, udtval.b, udtval.c FROM %s WHERE k = ?", 0), row(0, 1, UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799"), set("bar", "foo")) ); @@ -1172,7 +1234,7 @@ public class MaterializedViewTest extends CQLTester row(0, "abcd")); // order of fields shouldn't matter - updateMV("INSERT INTO %s (k, udtval) VALUES (?, fromJson(?))", 0, "{\"b\": \"6bddc89a-5644-11e4-97fc-56847afe9799\", \"a\": 1, \"c\": [\"foo\", \"bar\"]}"); + updateView("INSERT INTO %s (k, udtval) VALUES (?, fromJson(?))", 0, "{\"b\": \"6bddc89a-5644-11e4-97fc-56847afe9799\", \"a\": 1, \"c\": [\"foo\", \"bar\"]}"); assertRows(execute("SELECT k, udtval.a, udtval.b, udtval.c FROM %s WHERE k = ?", 0), row(0, 1, UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799"), set("bar", "foo")) ); @@ -1180,7 +1242,7 @@ public class MaterializedViewTest extends CQLTester row(0, "abcd")); // test nulls - updateMV("INSERT INTO %s (k, udtval) VALUES (?, fromJson(?))", 0, "{\"a\": null, \"b\": \"6bddc89a-5644-11e4-97fc-56847afe9799\", \"c\": [\"foo\", \"bar\"]}"); + updateView("INSERT INTO %s (k, udtval) VALUES (?, fromJson(?))", 0, "{\"a\": null, \"b\": \"6bddc89a-5644-11e4-97fc-56847afe9799\", \"c\": [\"foo\", \"bar\"]}"); assertRows(execute("SELECT k, udtval.a, udtval.b, udtval.c FROM %s WHERE k = ?", 0), row(0, null, UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799"), set("bar", "foo")) ); @@ -1189,7 +1251,7 @@ public class MaterializedViewTest extends CQLTester row(0, "abcd")); // test missing fields - updateMV("INSERT INTO %s (k, udtval) VALUES (?, fromJson(?))", 0, "{\"a\": 1, \"b\": \"6bddc89a-5644-11e4-97fc-56847afe9799\"}"); + updateView("INSERT INTO %s (k, udtval) VALUES (?, fromJson(?))", 0, "{\"a\": 1, \"b\": \"6bddc89a-5644-11e4-97fc-56847afe9799\"}"); assertRows(execute("SELECT k, udtval.a, udtval.b, udtval.c FROM %s WHERE k = ?", 0), row(0, 1, UUID.fromString("6bddc89a-5644-11e4-97fc-56847afe9799"), null) ); @@ -1213,10 +1275,10 @@ public class MaterializedViewTest extends CQLTester createView("mv", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE c IS NOT NULL AND a IS NOT NULL AND b IS NOT NULL PRIMARY KEY (c, a, b)"); - updateMV("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?) USING TTL 5", 1, 1, 1, 1); + updateView("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?) USING TTL 5", 1, 1, 1, 1); Thread.sleep(TimeUnit.SECONDS.toMillis(3)); - updateMV("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 1, 1, 2); + updateView("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 1, 1, 2); Thread.sleep(TimeUnit.SECONDS.toMillis(3)); List results = executeNet(protocolVersion, "SELECT d FROM mv WHERE c = 2 AND a = 1 AND b = 1").all(); @@ -1239,7 +1301,7 @@ public class MaterializedViewTest extends CQLTester for (int i = 0; i < 50; i++) { - updateMV("INSERT INTO %s (a, b, c) VALUES (?, ?, ?) USING TIMESTAMP 1", 1, 1, i); + updateView("INSERT INTO %s (a, b, c) VALUES (?, ?, ?) USING TIMESTAMP 1", 1, 1, i); } ResultSet mvRows = executeNet(protocolVersion, "SELECT c FROM mv"); @@ -1248,4 +1310,46 @@ public class MaterializedViewTest extends CQLTester int expected = rows.get(0).getInt("c"); assertRowsNet(protocolVersion, mvRows, row(expected)); } + + @Test + public void testClusteringOrder() throws Throwable + { + createTable("CREATE TABLE %s (" + + "a int," + + "b int," + + "c int," + + "d int," + + "PRIMARY KEY (a, b, c))" + + "WITH CLUSTERING ORDER BY (b ASC, c DESC)"); + + executeNet(protocolVersion, "USE " + keyspace()); + + createView("mv1", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE b IS NOT NULL AND c IS NOT NULL PRIMARY KEY (a, b, c) WITH CLUSTERING ORDER BY (b DESC)"); + createView("mv2", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE b IS NOT NULL AND c IS NOT NULL PRIMARY KEY (a, c, b) WITH CLUSTERING ORDER BY (c ASC)"); + createView("mv3", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE b IS NOT NULL AND c IS NOT NULL PRIMARY KEY (a, b, c)"); + createView("mv4", "CREATE MATERIALIZED VIEW %s AS SELECT * FROM %%s WHERE b IS NOT NULL AND c IS NOT NULL PRIMARY KEY (a, c, b) WITH CLUSTERING ORDER BY (c DESC)"); + + updateView("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?) USING TIMESTAMP 1", 1, 1, 1, 1); + updateView("INSERT INTO %s (a, b, c, d) VALUES (?, ?, ?, ?) USING TIMESTAMP 1", 1, 2, 2, 2); + + ResultSet mvRows = executeNet(protocolVersion, "SELECT b FROM mv1"); + assertRowsNet(protocolVersion, mvRows, + row(2), + row(1)); + + mvRows = executeNet(protocolVersion, "SELECT c FROM mv2"); + assertRowsNet(protocolVersion, mvRows, + row(1), + row(2)); + + mvRows = executeNet(protocolVersion, "SELECT b FROM mv3"); + assertRowsNet(protocolVersion, mvRows, + row(1), + row(2)); + + mvRows = executeNet(protocolVersion, "SELECT c FROM mv4"); + assertRowsNet(protocolVersion, mvRows, + row(2), + row(1)); + } } diff --git a/test/unit/org/apache/cassandra/db/view/MaterializedViewUtilsTest.java b/test/unit/org/apache/cassandra/db/view/ViewUtilsTest.java similarity index 91% rename from test/unit/org/apache/cassandra/db/view/MaterializedViewUtilsTest.java rename to test/unit/org/apache/cassandra/db/view/ViewUtilsTest.java index 25447146cc..8fd0cfb4f9 100644 --- a/test/unit/org/apache/cassandra/db/view/MaterializedViewUtilsTest.java +++ b/test/unit/org/apache/cassandra/db/view/ViewUtilsTest.java @@ -40,7 +40,7 @@ import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.schema.ReplicationParams; import org.apache.cassandra.service.StorageService; -public class MaterializedViewUtilsTest +public class ViewUtilsTest { @BeforeClass public static void setUp() throws ConfigurationException @@ -74,9 +74,9 @@ public class MaterializedViewUtilsTest KeyspaceMetadata meta = KeyspaceMetadata.create("Keyspace1", KeyspaceParams.create(false, replicationMap)); Schema.instance.setKeyspaceMetadata(meta); - InetAddress naturalEndpoint = MaterializedViewUtils.getViewNaturalEndpoint("Keyspace1", - new StringToken("CA"), - new StringToken("BB")); + InetAddress naturalEndpoint = ViewUtils.getViewNaturalEndpoint("Keyspace1", + new StringToken("CA"), + new StringToken("BB")); Assert.assertEquals(InetAddress.getByName("127.0.0.2"), naturalEndpoint); } @@ -106,9 +106,9 @@ public class MaterializedViewUtilsTest KeyspaceMetadata meta = KeyspaceMetadata.create("Keyspace1", KeyspaceParams.create(false, replicationMap)); Schema.instance.setKeyspaceMetadata(meta); - InetAddress naturalEndpoint = MaterializedViewUtils.getViewNaturalEndpoint("Keyspace1", - new StringToken("CA"), - new StringToken("BB")); + InetAddress naturalEndpoint = ViewUtils.getViewNaturalEndpoint("Keyspace1", + new StringToken("CA"), + new StringToken("BB")); Assert.assertEquals(InetAddress.getByName("127.0.0.1"), naturalEndpoint); } diff --git a/test/unit/org/apache/cassandra/dht/LengthPartitioner.java b/test/unit/org/apache/cassandra/dht/LengthPartitioner.java index 40a6774a67..9cefbf259b 100644 --- a/test/unit/org/apache/cassandra/dht/LengthPartitioner.java +++ b/test/unit/org/apache/cassandra/dht/LengthPartitioner.java @@ -126,7 +126,7 @@ public class LengthPartitioner implements IPartitioner for (String ks : Schema.instance.getKeyspaces()) { - for (CFMetaData cfmd : Schema.instance.getTables(ks)) + for (CFMetaData cfmd : Schema.instance.getTablesAndViews(ks)) { for (Range r : sortedRanges) { diff --git a/test/unit/org/apache/cassandra/hints/HintTest.java b/test/unit/org/apache/cassandra/hints/HintTest.java index c198149493..4c7ec70fc7 100644 --- a/test/unit/org/apache/cassandra/hints/HintTest.java +++ b/test/unit/org/apache/cassandra/hints/HintTest.java @@ -67,7 +67,7 @@ public class HintTest @Before public void resetGcGraceSeconds() { - for (CFMetaData table : Schema.instance.getTables(KEYSPACE)) + for (CFMetaData table : Schema.instance.getTablesAndViews(KEYSPACE)) table.gcGraceSeconds(TableParams.DEFAULT_GC_GRACE_SECONDS); } diff --git a/test/unit/org/apache/cassandra/schema/LegacySchemaMigratorTest.java b/test/unit/org/apache/cassandra/schema/LegacySchemaMigratorTest.java index 73eed06999..d841e919ef 100644 --- a/test/unit/org/apache/cassandra/schema/LegacySchemaMigratorTest.java +++ b/test/unit/org/apache/cassandra/schema/LegacySchemaMigratorTest.java @@ -316,6 +316,7 @@ public class LegacySchemaMigratorTest return KeyspaceMetadata.create(keyspace, KeyspaceParams.simple(1), Tables.none(), + Views.none(), Types.of(udt1, udt2, udt3), Functions.none()); } @@ -353,6 +354,7 @@ public class LegacySchemaMigratorTest return KeyspaceMetadata.create(keyspace, KeyspaceParams.simple(1), Tables.none(), + Views.none(), Types.none(), Functions.of(udf1, udf2, udf3)); } @@ -365,6 +367,7 @@ public class LegacySchemaMigratorTest return KeyspaceMetadata.create(keyspace, KeyspaceParams.simple(1), Tables.none(), + Views.none(), Types.none(), Functions.of()); }