hqlopt.cpp 152 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023
  1. /*##############################################################################
  2. HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ############################################################################## */
  13. #include "hqlopt.ipp"
  14. #include "hqlpmap.hpp"
  15. #include "jexcept.hpp"
  16. #include "jlog.hpp"
  17. #include "hqlutil.hpp"
  18. #include "hqlfold.hpp"
  19. #include "hqlthql.hpp"
  20. #include "hqlerror.hpp"
  21. #include "hqlerrors.hpp"
  22. #include "hqlexpr.ipp" // Not needed, but without it I don't see the symbols in the debugger.
  23. #include "hqlattr.hpp"
  24. #include "hqlmeta.hpp"
  25. #define MIGRATE_JOIN_CONDITIONS // This works, but I doubt it is generally worth the effort. - maybe on a flag.
  26. //#define TRACE_USAGE
  27. /*
  28. Notes:
  29. * Need to carefully keep track of usage counts after the expression tree has been transformed, otherwise activities end up being duplicated.
  30. o The usage count of the current expression doesn't matter since it won't be referenced any more...
  31. o Any replacement nodes need to inherit the link count of the item they are replacing.
  32. o Link counts for new children need to be incremented (they may already exist so don't set to 1).
  33. o Link counts for children that are no longer used should be decremented. However since items are not
  34. combined if the children are shared they will no longer be referenced, so it won't be a disaster if
  35. that doesn't happen (note aggregate child stripping is an exception).
  36. o If removal of a node causes other child expressions to no longer be linked, the whole branch needs removing.
  37. (I don't think we currently have any examples).
  38. o I try and track new datasets created when projects are expanded.
  39. o Moving a filter over a project doesn't change the normalized inputs, so the selectorSequence doesn't need changing.
  40. Known issues:
  41. o The usage counts are done at a global level, whilst the transformations are dependent on the context. That means it might be possible
  42. to decrement a link count too many times, causing activities to appear unshared when in reality they are.
  43. o Sometimes the order the graph is traversed in produces a non optimal result. For instance filter2(filter1(project1(x)) and filter1(project2(x))
  44. would best be converted to project1(filter2([filter1(x)])) and project2[filter1(x)] where filter1(x) is shared. However it is just as likely to produce:
  45. project1(filter2,1(x)) and project2(filter1(x)) because the filters are also combined.
  46. o Similarly nodes can become unshared if
  47. i) an unshared node is optimized
  48. ii) a different (shared) node is then optimized to generate the same expression as the original.
  49. Because the second version is marked as shared it won't get transformed, but the first instance will have been.
  50. This has been worked around to a certain extent by moving some of the code into the null transformer.
  51. o Sharing between subqueries is too aggressive. This is worked around by reoptimizing the subqueries.
  52. o Constant folding can create new datasets with no associated usage. The code is now structured to allow the constant fold to
  53. be included, but I suspect it makes it too inefficient, and I don't know of any examples causing problems.
  54. */
  55. //---------------------------------------------------------------------------
  56. IHqlExpression * createFilterCondition(const HqlExprArray & conds)
  57. {
  58. if (conds.ordinality() == 0)
  59. return createConstant(true);
  60. OwnedITypeInfo boolType = makeBoolType();
  61. return createBalanced(no_and, boolType, conds);
  62. }
  63. IHqlExpression * createFilterCondition(const HqlExprArray & conds, IHqlExpression * oldDataset, IHqlExpression * newDataset)
  64. {
  65. OwnedHqlExpr mapped = createFilterCondition(conds);
  66. return replaceSelector(mapped, oldDataset->queryNormalizedSelector(), newDataset->queryNormalizedSelector());
  67. }
  68. bool optimizeFilterConditions(IErrorReceiver & errorProcessor, HqlExprArray & conds)
  69. {
  70. ForEachItemInRev(i, conds)
  71. {
  72. IHqlExpression & cur = conds.item(i);
  73. if (cur.isConstant())
  74. {
  75. OwnedHqlExpr folded = foldHqlExpression(errorProcessor, &cur);
  76. IValue * value = folded->queryValue();
  77. if (value)
  78. {
  79. if (!value->getBoolValue())
  80. {
  81. conds.kill();
  82. conds.append(*folded.getClear());
  83. return true;
  84. }
  85. conds.remove(i);
  86. }
  87. }
  88. }
  89. return conds.ordinality() == 0;
  90. }
  91. //---------------------------------------------------------------------------
  92. ExpandMonitor::~ExpandMonitor()
  93. {
  94. if (!complex)
  95. {
  96. unsigned max = datasetsChanged.ordinality();
  97. for (unsigned i=0; i < max; i+= 2)
  98. {
  99. IHqlExpression & newValue = datasetsChanged.item(i);
  100. IHqlExpression & oldValue = datasetsChanged.item(i+1);
  101. if (newValue.queryBody() != oldValue.queryBody())// && oldValue->queryTransformExtra())
  102. optimizer.inheritUsage(&newValue, &oldValue);
  103. }
  104. }
  105. }
  106. IHqlExpression * ExpandMonitor::onExpandSelector()
  107. {
  108. //SELF.someField := LEFT
  109. complex = true;
  110. return NULL;
  111. }
  112. void ExpandMonitor::onDatasetChanged(IHqlExpression * newValue, IHqlExpression * oldValue)
  113. {
  114. //NB: Cannot call inheritUsage here because a different transform is in operation
  115. datasetsChanged.append(*LINK(newValue));
  116. datasetsChanged.append(*LINK(oldValue));
  117. }
  118. //MORE: This needs improving... especially caching. Probably stored in the expressions and used for filter scoring
  119. //(cardinality, cost, ...) - investigate some schemes + review hole implementation
  120. static bool isComplexExpansion(IHqlExpression * expr)
  121. {
  122. switch (expr->getOperator())
  123. {
  124. case no_select:
  125. {
  126. bool isNew;
  127. IHqlExpression * ds = querySelectorDataset(expr, isNew);
  128. //A select from a create row is likely to be optimized
  129. return isNew && (ds->getOperator() != no_createrow);
  130. }
  131. case NO_AGGREGATE:
  132. case no_call:
  133. case no_externalcall:
  134. case no_rowdiff:
  135. return true;
  136. case no_constant:
  137. return false;
  138. }
  139. ForEachChild(i, expr)
  140. if (isComplexExpansion(expr->queryChild(i)))
  141. return true;
  142. return false;
  143. }
  144. void ExpandComplexityMonitor::analyseTransform(IHqlExpression * transform)
  145. {
  146. ForEachChild(i, transform)
  147. {
  148. IHqlExpression * cur = transform->queryChild(i);
  149. switch (cur->getOperator())
  150. {
  151. case no_assignall:
  152. analyseTransform(cur);
  153. break;
  154. case no_assign:
  155. onExpand(cur->queryChild(0), cur->queryChild(1));
  156. break;
  157. case no_skip:
  158. if (isComplexExpansion(cur->queryChild(0)))
  159. complex = true;
  160. break;
  161. }
  162. if (complex)
  163. break;
  164. }
  165. }
  166. void ExpandComplexityMonitor::onExpand(IHqlExpression * select, IHqlExpression * newValue)
  167. {
  168. if (complex)
  169. return;
  170. if (select->isDataset())
  171. {
  172. switch (newValue->getOperator())
  173. {
  174. case no_null:
  175. case no_select:
  176. case no_getresult:
  177. case no_getgraphresult:
  178. case no_id2blob:
  179. //MORE: Should be a common list somewhere...
  180. break;
  181. default:
  182. complex = true;
  183. return;
  184. }
  185. }
  186. if (!newValue->isPure())
  187. complex = true;
  188. else if (isComplexExpansion(newValue))
  189. complex = true;
  190. }
  191. //---------------------------------------------------------------------------
  192. static HqlTransformerInfo cTreeOptimizerInfo("CTreeOptimizer");
  193. CTreeOptimizer::CTreeOptimizer(IErrorReceiver & _errorProcessor, unsigned _options) : PARENT(cTreeOptimizerInfo), errorProcessor(_errorProcessor)
  194. {
  195. options = _options;
  196. optimizeFlags |= TCOtransformNonActive;
  197. }
  198. IHqlExpression * CTreeOptimizer::extractFilterDs(HqlExprArray & conds, IHqlExpression * expr)
  199. {
  200. if (expr->getOperator() != no_filter || isShared(expr))
  201. return expr;
  202. IHqlExpression * ds = extractFilterDs(conds, expr->queryChild(0));
  203. unsigned max = expr->numChildren();
  204. for (unsigned i = 1; i < max; i++)
  205. {
  206. IHqlExpression * cur = queryRealChild(expr, i);
  207. if (cur)
  208. cur->unwindList(conds, no_and);
  209. }
  210. return ds;
  211. }
  212. inline IHqlExpression * makeChildList(IHqlExpression * expr)
  213. {
  214. IHqlExpression * exprList = NULL;
  215. unsigned num = expr->numChildren();
  216. for (unsigned i=1; i<num; i++)
  217. exprList = createComma(exprList, LINK(expr->queryChild(i)));
  218. return exprList;
  219. }
  220. IHqlExpression * CTreeOptimizer::removeChildNode(IHqlExpression * expr)
  221. {
  222. IHqlExpression * child = expr->queryChild(0);
  223. DBGLOG("Optimizer: Node %s remove child: %s", queryNode0Text(expr), queryNode1Text(child));
  224. noteUnused(child);
  225. return replaceChild(expr, child->queryChild(0));
  226. }
  227. IHqlExpression * CTreeOptimizer::removeParentNode(IHqlExpression * expr)
  228. {
  229. IHqlExpression * child = expr->queryChild(0);
  230. DBGLOG("Optimizer: Node %s remove self (now %s)", queryNode0Text(expr), queryNode1Text(child));
  231. // Need to dec link count of child because it is just about to inherited the link count from the parent
  232. decUsage(child);
  233. return LINK(child);
  234. }
  235. IHqlExpression * CTreeOptimizer::swapNodeWithChild(IHqlExpression * parent)
  236. {
  237. IHqlExpression * child = parent->queryChild(0);
  238. DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(parent), queryNode1Text(child));
  239. OwnedHqlExpr newParent = swapDatasets(parent);
  240. //if this is the only reference to the child (almost certainly true) then no longer refd, so don't inc usage for child.
  241. noteUnused(child);
  242. if (!alreadyHasUsage(newParent))
  243. incUsage(newParent->queryChild(0));
  244. return newParent.getClear();
  245. }
  246. IHqlExpression * CTreeOptimizer::forceSwapNodeWithChild(IHqlExpression * parent)
  247. {
  248. OwnedHqlExpr swapped = swapNodeWithChild(parent);
  249. queryBodyExtra(swapped)->setStopHoist();
  250. return swapped.getClear();
  251. }
  252. IHqlExpression * CTreeOptimizer::swapNodeWithChild(IHqlExpression * parent, unsigned childIndex)
  253. {
  254. IHqlExpression * child = parent->queryChild(0);
  255. DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(parent), queryNode1Text(child));
  256. OwnedHqlExpr newChild = replaceChildDataset(parent, child->queryChild(childIndex), 0);
  257. OwnedHqlExpr swapped = insertChildDataset(child, newChild, childIndex);
  258. if (!alreadyHasUsage(swapped))
  259. incUsage(newChild);
  260. noteUnused(child);
  261. return swapped.getClear();
  262. }
  263. IHqlExpression * CTreeOptimizer::swapIntoIf(IHqlExpression * expr, bool force)
  264. {
  265. IHqlExpression * child = expr->queryChild(0);
  266. //Can't optimize over a condition once a graph has been resourced, otherwise the activities aren't found.
  267. if (child->hasAttribute(_resourced_Atom))
  268. return LINK(expr);
  269. IHqlExpression * body = expr->queryBody();
  270. IHqlExpression * cond = child->queryChild(0);
  271. IHqlExpression * left = child->queryChild(1);
  272. IHqlExpression * right = child->queryChild(2);
  273. OwnedHqlExpr newLeft = replaceChildDataset(body, left, 0);
  274. OwnedHqlExpr newRight = replaceChildDataset(body, right, 0);
  275. HqlExprArray args;
  276. args.append(*LINK(cond));
  277. args.append(*LINK(newLeft));
  278. args.append(*LINK(newRight));
  279. OwnedHqlExpr newIf = child->clone(args);
  280. if (!alreadyHasUsage(newIf))
  281. {
  282. incUsage(newLeft);
  283. incUsage(newRight);
  284. }
  285. OwnedHqlExpr transformedIf = transform(newIf);
  286. if (force || (newIf != transformedIf))
  287. {
  288. //Need to call dec on all expressions that are no longer used... left and right still used by newLeft/newRight
  289. if (!alreadyHasUsage(newIf))
  290. {
  291. //This may possibly leave left/right linked once if transformed(newLeft) doesn't use left any more.
  292. //But a recursiveDecUsage could cause too much to be decremented.
  293. if (newLeft != transformedIf->queryChild(1))
  294. decUsage(newLeft);
  295. if (newRight != transformedIf->queryChild(2))
  296. decUsage(newRight);
  297. }
  298. noteUnused(child);
  299. DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(expr), queryNode1Text(child));
  300. return transformedIf.getClear();
  301. }
  302. if (!alreadyHasUsage(newIf))
  303. {
  304. decUsage(newLeft);
  305. decUsage(newRight);
  306. }
  307. return LINK(expr);
  308. }
  309. //NB: Similar logic to swapIntoIf()
  310. IHqlExpression * CTreeOptimizer::swapIntoAddFiles(IHqlExpression * expr, bool force)
  311. {
  312. IHqlExpression * child = expr->queryChild(0);
  313. IHqlExpression * body = expr->queryBody();
  314. bool changed = false;
  315. HqlExprArray replacedArgs;
  316. HqlExprArray transformedArgs;
  317. ForEachChild(idx, child)
  318. {
  319. IHqlExpression * in = child->queryChild(idx);
  320. if (!in->isDataset() && !in->isDatarow())
  321. {
  322. replacedArgs.append(*LINK(in));
  323. transformedArgs.append(*LINK(in));
  324. }
  325. else
  326. {
  327. IHqlExpression * next = replaceChild(body, in);
  328. replacedArgs.append(*next);
  329. //MORE: Will be linked too many times if changed and item already exists
  330. incUsage(next); //Link so values get correctly inherited if they are transformed.
  331. IHqlExpression * transformed = transform(next);
  332. transformedArgs.append(*transformed);
  333. if (transformed != next)
  334. changed = true;
  335. }
  336. }
  337. if (force || changed)
  338. {
  339. ForEachItemIn(i, replacedArgs)
  340. {
  341. if (&replacedArgs.item(i) != &transformedArgs.item(i))
  342. decUsage(&replacedArgs.item(i)); //If they are the same then inheritUsage wont't have been called, so don't decrement.
  343. }
  344. //Need to call dec on all expressions that are no longer used... grand children should not be decremented
  345. noteUnused(child);
  346. //And create the new funnel
  347. DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(expr), queryNode1Text(child));
  348. return child->clone(transformedArgs);
  349. }
  350. //Note, replaced == args so no need to call decUsage on args
  351. ForEachItemIn(i, replacedArgs)
  352. {
  353. IHqlExpression & cur = replacedArgs.item(i);
  354. if (!cur.isAttribute())
  355. decUsage(&cur); //If they are the same then inheritUsage wont't have been called, so don't decrement.
  356. }
  357. return LINK(expr);
  358. }
  359. IHqlExpression * CTreeOptimizer::moveFilterOverSelect(IHqlExpression * expr)
  360. {
  361. IHqlExpression * select = expr->queryChild(0);
  362. if (!isNewSelector(select))
  363. return NULL;
  364. IHqlExpression * ds = select->queryChild(0);
  365. //MORE: If ds is a row then the filter needs to be moved to the root dataset
  366. if (!ds->isDataset())
  367. return NULL;
  368. IHqlExpression * newScope = select->queryNormalizedSelector();
  369. HqlExprArray args, hoisted, notHoisted;
  370. HqlExprCopyArray inScope;
  371. unwindFilterConditions(args, expr);
  372. ForEachItemIn(i, args)
  373. {
  374. IHqlExpression & cur = args.item(i);
  375. inScope.kill();
  376. cur.gatherTablesUsed(NULL, &inScope);
  377. if (inScope.find(*newScope) == NotFound)
  378. hoisted.append(OLINK(cur));
  379. else
  380. notHoisted.append(OLINK(cur));
  381. }
  382. if (hoisted.ordinality() == 0)
  383. return NULL;
  384. DBGLOG("Optimizer: Move filter over select (%d/%d)", hoisted.ordinality(), args.ordinality());
  385. //Create a filtered dataset
  386. IHqlExpression * inDs = LINK(ds);
  387. if (inDs->isDatarow())
  388. inDs = createDatasetFromRow(inDs);
  389. hoisted.add(*inDs, 0);
  390. OwnedHqlExpr newDs = expr->clone(hoisted);
  391. //Now a select on that
  392. args.kill();
  393. unwindChildren(args, select);
  394. args.replace(*LINK(newDs), 0);
  395. OwnedHqlExpr newSelect = select->clone(args);
  396. if (!alreadyHasUsage(newSelect))
  397. incUsage(newDs);
  398. if (notHoisted.ordinality())
  399. {
  400. notHoisted.add(*LINK(select), 0);
  401. OwnedHqlExpr unhoistedFilter = expr->clone(notHoisted);
  402. OwnedHqlExpr ret = replaceChild(unhoistedFilter, newSelect);
  403. if (!alreadyHasUsage(ret))
  404. incUsage(newSelect);
  405. return ret.getClear();
  406. }
  407. return newSelect.getClear();
  408. }
  409. IHqlExpression * CTreeOptimizer::optimizeAggregateUnsharedDataset(IHqlExpression * expr, bool isSimpleCount)
  410. {
  411. if (isShared(expr) || (getNumChildTables(expr) != 1))
  412. return LINK(expr);
  413. //Don't include any operations which rely on the order/distribution:
  414. bool childIsSimpleCount = isSimpleCount;
  415. node_operator op = expr->getOperator();
  416. IHqlExpression * ds = expr->queryChild(0);
  417. switch (op)
  418. {
  419. case no_filter:
  420. case no_aggregate:
  421. childIsSimpleCount = false;
  422. break;
  423. case no_hqlproject:
  424. case no_newusertable:
  425. case no_newaggregate:
  426. case no_sort:
  427. case no_subsort:
  428. case no_distribute:
  429. case no_keyeddistribute:
  430. case no_fetch:
  431. case no_transformebcdic:
  432. case no_transformascii:
  433. if (childIsSimpleCount && !isPureActivity(expr))
  434. childIsSimpleCount = false;
  435. break;
  436. case no_compound_indexread:
  437. case no_compound_diskread:
  438. case no_keyedlimit:
  439. break;
  440. case no_limit:
  441. if (expr->hasAttribute(onFailAtom))
  442. return LINK(expr);
  443. //fall through
  444. case no_choosen:
  445. case no_topn:
  446. if (isSimpleCount)
  447. break;
  448. return LINK(expr);
  449. default:
  450. return LINK(expr);
  451. }
  452. OwnedHqlExpr optimizedDs = optimizeAggregateUnsharedDataset(ds, childIsSimpleCount);
  453. //Remove items that are really inefficient and unnecessary, but don't for the moment remove projects or anything that changes the
  454. //record structure.
  455. switch (op)
  456. {
  457. case no_sort:
  458. case no_subsort:
  459. case no_distribute:
  460. case no_keyeddistribute:
  461. noteUnused(expr);
  462. return optimizedDs.getClear();
  463. case no_topn:
  464. {
  465. assertex(isSimpleCount);
  466. noteUnused(expr);
  467. OwnedHqlExpr ret = createDataset(no_choosen, optimizedDs.getClear(), LINK(expr->queryChild(2)));
  468. incUsage(ret);
  469. return expr->cloneAllAnnotations(ret);
  470. }
  471. case no_hqlproject:
  472. case no_newusertable:
  473. if (isSimpleCount && (options & HOOinsidecompound))
  474. {
  475. if (expr->hasAttribute(_countProject_Atom) || expr->hasAttribute(prefetchAtom))
  476. break;
  477. if (isPureActivity(expr) && !isAggregateDataset(expr))
  478. {
  479. noteUnused(expr);
  480. return optimizedDs.getClear();
  481. }
  482. }
  483. break;
  484. }
  485. if (ds == optimizedDs)
  486. return LINK(expr);
  487. OwnedHqlExpr replaced = replaceChild(expr, optimizedDs);
  488. incUsage(replaced);
  489. noteUnused(expr);
  490. return replaced.getClear();
  491. }
  492. IHqlExpression * CTreeOptimizer::optimizeAggregateDataset(IHqlExpression * transformed)
  493. {
  494. HqlExprArray children;
  495. unwindChildren(children, transformed);
  496. IHqlExpression * root = &children.item(0);
  497. HqlExprAttr ds = root;
  498. IHqlExpression * wrapper = NULL;
  499. node_operator aggOp = transformed->getOperator();
  500. bool insideShared = false;
  501. bool isScalarAggregate = (aggOp != no_newaggregate) && (aggOp != no_aggregate);
  502. bool isSimpleCount = isSimpleCountExistsAggregate(transformed, false, true);
  503. loop
  504. {
  505. node_operator dsOp = ds->getOperator();
  506. IHqlExpression * next = NULL;
  507. switch (dsOp)
  508. {
  509. case no_hqlproject:
  510. case no_newusertable:
  511. if (ds->hasAttribute(prefetchAtom))
  512. break;
  513. //MORE: If the record is empty then either remove the project if no SKIP, or convert the SKIP to a filter
  514. //Don't remove projects for the moment because they can make counts of disk reads much less
  515. //efficient. Delete the following lines once we have a count-diskread activity
  516. if (!isScalarAggregate && !(options & (HOOcompoundproject|HOOinsidecompound)) && !ds->hasAttribute(_countProject_Atom) )
  517. break;
  518. if (isPureActivity(ds) && !isAggregateDataset(ds))
  519. {
  520. OwnedMapper mapper = getMapper(ds);
  521. ExpandSelectorMonitor expandMonitor(*this);
  522. HqlExprArray newChildren;
  523. unsigned num = children.ordinality();
  524. LinkedHqlExpr oldDs = ds;
  525. LinkedHqlExpr newDs = ds->queryChild(0);
  526. if (transformed->getOperator() == no_aggregate)
  527. {
  528. oldDs.setown(createSelector(no_left, ds, querySelSeq(transformed)));
  529. newDs.setown(createSelector(no_left, newDs, querySelSeq(transformed)));
  530. }
  531. for (unsigned idx = 1; idx < num; idx++)
  532. {
  533. OwnedHqlExpr mapped = expandFields(mapper, &children.item(idx), oldDs, newDs, &expandMonitor);
  534. if (containsCounter(mapped))
  535. expandMonitor.setComplex();
  536. newChildren.append(*mapped.getClear());
  537. }
  538. if (!expandMonitor.isComplex())
  539. {
  540. for (unsigned idx = 1; idx < num; idx++)
  541. children.replace(OLINK(newChildren.item(idx-1)), idx);
  542. next = ds->queryChild(0);
  543. }
  544. }
  545. break;
  546. case no_fetch:
  547. if (isSimpleCount && !containsSkip(ds->queryChild(3)))
  548. next = ds->queryChild(1);
  549. break;
  550. case no_group:
  551. if (isScalarAggregate)
  552. next = ds->queryChild(0);
  553. break;
  554. case no_sort:
  555. case no_subsort:
  556. case no_sorted:
  557. //MORE: Allowed if the transform is commutative for no_aggregate
  558. if (aggOp != no_aggregate)
  559. next = ds->queryChild(0);
  560. break;
  561. case no_distribute:
  562. case no_distributed:
  563. case no_keyeddistribute:
  564. case no_preservemeta:
  565. if (isScalarAggregate || !isGrouped(ds->queryChild(0)))
  566. next = ds->queryChild(0);
  567. break;
  568. case no_preload:
  569. wrapper = ds;
  570. next = ds->queryChild(0);
  571. break;
  572. case no_iterate:
  573. if (isSimpleCount && !containsSkip(ds->queryChild(1)))
  574. next = ds->queryChild(0);
  575. break;
  576. }
  577. if (!next)
  578. break;
  579. if (!insideShared)
  580. {
  581. insideShared = isShared(ds);
  582. noteUnused(ds);
  583. }
  584. ds.set(next);
  585. }
  586. //Not completely sure about usageCounting being maintained correctly
  587. if (!insideShared)
  588. {
  589. OwnedHqlExpr newDs = (aggOp != no_aggregate) ? optimizeAggregateUnsharedDataset(ds, isSimpleCount) : LINK(ds);
  590. if (newDs != ds)
  591. {
  592. HqlMapTransformer mapper;
  593. mapper.setMapping(ds, newDs);
  594. mapper.setSelectorMapping(ds, newDs);
  595. ForEachItemIn(i, children)
  596. children.replace(*mapper.transformRoot(&children.item(i)), i);
  597. ds.set(newDs);
  598. }
  599. }
  600. if (ds == root)
  601. return LINK(transformed);
  602. if (wrapper)
  603. {
  604. if (ds == root->queryChild(0))
  605. {
  606. incUsage(root);
  607. return LINK(transformed);
  608. }
  609. }
  610. //A different node is now shared between the graphs
  611. if (insideShared)
  612. incUsage(ds);
  613. if (wrapper)
  614. {
  615. HqlExprArray args;
  616. args.append(*ds.getClear());
  617. unwindChildren(args, wrapper, 1);
  618. ds.setown(wrapper->clone(args));
  619. incUsage(ds);
  620. }
  621. DBGLOG("Optimizer: Aggregate replace %s with %s", queryNode0Text(root), queryNode1Text(ds));
  622. children.replace(*ds.getClear(), 0);
  623. return transformed->clone(children);
  624. }
  625. static IHqlExpression * skipMetaAliases(IHqlExpression * expr)
  626. {
  627. loop
  628. {
  629. switch (expr->getOperator())
  630. {
  631. case no_dataset_alias:
  632. break;
  633. default:
  634. return expr;
  635. }
  636. expr = expr->queryChild(0);
  637. }
  638. }
  639. IHqlExpression * CTreeOptimizer::optimizeDatasetIf(IHqlExpression * transformed)
  640. {
  641. //if(cond, ds(filt1), ds(filt2)) => ds(if(cond,filt1,filt2))
  642. HqlExprArray leftFilter, rightFilter;
  643. IHqlExpression * unfilteredLeft = extractFilterDs(leftFilter, transformed->queryChild(1));
  644. IHqlExpression * unfilteredRight = extractFilterDs(rightFilter, transformed->queryChild(2));
  645. IHqlExpression * left = skipMetaAliases(unfilteredLeft);
  646. IHqlExpression * right = skipMetaAliases(unfilteredRight);
  647. if (left->queryBody() == right->queryBody())
  648. {
  649. //If one (or both) or the datasets are aliases then ensure that one of the of the
  650. //aliases is used in the replacement.
  651. IHqlExpression * baseDataset = unfilteredLeft;
  652. if (right->queryNormalizedSelector() != unfilteredRight->queryNormalizedSelector())
  653. baseDataset = unfilteredRight;
  654. HqlExprArray args;
  655. args.append(*LINK(baseDataset));
  656. OwnedHqlExpr leftCond = createFilterCondition(leftFilter, unfilteredLeft, baseDataset);
  657. OwnedHqlExpr rightCond = createFilterCondition(rightFilter, unfilteredRight, baseDataset);
  658. if (leftCond == rightCond)
  659. {
  660. args.append(*leftCond.getClear());
  661. }
  662. else
  663. {
  664. IHqlExpression * cond = transformed->queryChild(0);
  665. args.append(*createValue(no_if, cond->getType(), LINK(cond), leftCond.getClear(), rightCond.getClear()));
  666. }
  667. OwnedHqlExpr ret = createDataset(no_filter, args);
  668. DBGLOG("Optimizer: Convert %s to a filter", queryNode0Text(transformed));
  669. //NOTE: left and right never walk over any shared nodes, so don't need to decrement usage for
  670. //child(1), child(2) or intermediate nodes to left/right, since not referenced any more.
  671. if (baseDataset == left)
  672. noteUnused(right); // dataset is now used one less time
  673. else
  674. noteUnused(left);
  675. return transformed->cloneAllAnnotations(ret);
  676. }
  677. return LINK(transformed);
  678. }
  679. static bool branchesMatch(unsigned options, IHqlExpression * left, IHqlExpression * right)
  680. {
  681. if (left->queryBody() == right->queryBody())
  682. return true;
  683. node_operator leftOp = left->getOperator();
  684. if (leftOp != right->getOperator())
  685. return false;
  686. switch (leftOp)
  687. {
  688. case no_hqlproject:
  689. case no_newusertable:
  690. break;
  691. default:
  692. return false;
  693. }
  694. if (left->numChildren() != right->numChildren())
  695. return false;
  696. //Check for the situation where the only difference between two projects is the selector sequence
  697. ForEachChild(i, left)
  698. {
  699. IHqlExpression * curLeft = left->queryChild(i);
  700. if (curLeft->isAttribute() && (curLeft->queryName() == _selectorSequence_Atom))
  701. continue;
  702. IHqlExpression * curRight = right->queryChild(i);
  703. if (curLeft->queryBody() != curRight->queryBody())
  704. {
  705. //The following code allows LEFT to be referred to within the transform, but I don't think it is worth enabling
  706. //because of the potential cost of replacing the selseq within the transform.
  707. if (options & HOOexpensive)
  708. {
  709. if ((leftOp != no_hqlproject) || !curLeft->isTransform())
  710. return false;
  711. if (!recordTypesMatch(curLeft,curRight))
  712. return false;
  713. OwnedHqlExpr newTransform = replaceExpression(curLeft, querySelSeq(left), querySelSeq(right));
  714. if (newTransform->queryBody() != curRight->queryBody())
  715. return false;
  716. }
  717. return false;
  718. }
  719. }
  720. return true;
  721. }
  722. IHqlExpression * CTreeOptimizer::optimizeIf(IHqlExpression * expr)
  723. {
  724. IHqlExpression * trueExpr = expr->queryChild(1);
  725. IHqlExpression * falseExpr = expr->queryChild(2);
  726. if (!falseExpr)
  727. return NULL;
  728. if (branchesMatch(options, trueExpr, falseExpr))
  729. {
  730. noteUnused(trueExpr); // inherit usage() will increase the usage again
  731. noteUnused(falseExpr);
  732. return LINK(trueExpr);
  733. }
  734. IHqlExpression * cond = expr->queryChild(0);
  735. IValue * condValue = cond->queryValue();
  736. if (condValue)
  737. {
  738. if (condValue->getBoolValue())
  739. {
  740. recursiveDecUsage(falseExpr);
  741. decUsage(trueExpr); // inherit usage() will increase the usage again
  742. return LINK(trueExpr);
  743. }
  744. else
  745. {
  746. recursiveDecUsage(trueExpr);
  747. decUsage(falseExpr); // inherit usage() will increase the usage again
  748. return LINK(falseExpr);
  749. }
  750. }
  751. //Usage counts aren't handled correctly for datarows, so only optimize datasets, otherwise it can get bigger.
  752. if (!expr->isDataset())
  753. return NULL;
  754. //if(c1, if(c2, x, y), z) y==z => if(c1 && c2, x, z)
  755. //if(c1, if(c2, x, y), z) x==z => if(c1 && !c2, y, z)
  756. //if(c1, z, if(c2, x, y)) x==z => if(c1 || c2, z, y)
  757. //if(c1, z, if(c2, x, y)) y==z => if(c1 || !c2, z, x)
  758. //Only do these changes if c2 has no additional dependencies than c1
  759. HqlExprArray args;
  760. if ((trueExpr->getOperator() == no_if) && !isShared(trueExpr))
  761. {
  762. IHqlExpression * childCond = trueExpr->queryChild(0);
  763. if (introducesNewDependencies(cond, childCond))
  764. return NULL;
  765. IHqlExpression * childTrue = trueExpr->queryChild(1);
  766. IHqlExpression * childFalse = trueExpr->queryChild(2);
  767. if (falseExpr->queryBody() == childFalse->queryBody())
  768. {
  769. args.append(*createBoolExpr(no_and, LINK(cond), LINK(childCond)));
  770. args.append(*LINK(childTrue));
  771. args.append(*LINK(falseExpr));
  772. }
  773. else if (falseExpr->queryBody() == childTrue->queryBody())
  774. {
  775. args.append(*createBoolExpr(no_and, LINK(cond), getInverse(childCond)));
  776. args.append(*LINK(childFalse));
  777. args.append(*LINK(falseExpr));
  778. }
  779. if (args.ordinality())
  780. {
  781. DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(expr), queryNode1Text(trueExpr));
  782. noteUnused(falseExpr);
  783. }
  784. }
  785. if (args.empty() && (falseExpr->getOperator() == no_if) && !isShared(falseExpr))
  786. {
  787. IHqlExpression * childCond = falseExpr->queryChild(0);
  788. if (introducesNewDependencies(cond, childCond))
  789. return NULL;
  790. IHqlExpression * childTrue = falseExpr->queryChild(1);
  791. IHqlExpression * childFalse = falseExpr->queryChild(2);
  792. if (trueExpr->queryBody() == childTrue->queryBody())
  793. {
  794. args.append(*createBoolExpr(no_or, LINK(cond), LINK(childCond)));
  795. args.append(*LINK(trueExpr));
  796. args.append(*LINK(childFalse));
  797. }
  798. else if (trueExpr->queryBody() == childFalse->queryBody())
  799. {
  800. args.append(*createBoolExpr(no_or, LINK(cond), getInverse(childCond)));
  801. args.append(*LINK(trueExpr));
  802. args.append(*LINK(childTrue));
  803. }
  804. if (args.ordinality())
  805. {
  806. DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(expr), queryNode1Text(falseExpr));
  807. noteUnused(trueExpr);
  808. }
  809. }
  810. if (args.ordinality())
  811. return expr->clone(args);
  812. return NULL;
  813. }
  814. bool CTreeOptimizer::expandFilterCondition(HqlExprArray & expanded, HqlExprArray & unexpanded, IHqlExpression * filter, bool moveOver, bool onlyKeyed)
  815. {
  816. HqlExprArray conds;
  817. unwindFilterConditions(conds, filter);
  818. IHqlExpression * child = filter->queryChild(0);
  819. IHqlExpression * grandchild = child->queryChild(0);
  820. OwnedMapper mapper = getMapper(child);
  821. ForEachItemIn(i, conds)
  822. {
  823. IHqlExpression * cur = &conds.item(i);
  824. bool isKeyed = containsAssertKeyed(cur);
  825. if (!onlyKeyed || isKeyed || (options & HOOfiltersharedproject) )
  826. {
  827. ExpandComplexityMonitor expandMonitor(*this);
  828. OwnedHqlExpr expandedFilter;
  829. if (moveOver)
  830. expandedFilter.setown(expandFields(mapper, cur, child, grandchild, &expandMonitor));
  831. else
  832. expandedFilter.setown(mapper->expandFields(cur, child, grandchild, grandchild, &expandMonitor));
  833. if (expandedFilter->isConstant())
  834. {
  835. expandedFilter.setown(foldHqlExpression(errorProcessor, expandedFilter));
  836. IValue * value = expandedFilter->queryValue();
  837. if (value && !value->getBoolValue())
  838. {
  839. if (onlyKeyed)
  840. DBGLOG("Optimizer: Merging filter over shared project always false");
  841. expanded.kill();
  842. expanded.append(*LINK(expandedFilter));
  843. return true;
  844. }
  845. }
  846. if ((!onlyKeyed || isKeyed) && !expandMonitor.isComplex())
  847. expanded.append(*LINK(expandedFilter));
  848. else
  849. unexpanded.append(*LINK(cur));
  850. }
  851. else
  852. unexpanded.append(*LINK(cur));
  853. }
  854. return expanded.ordinality() != 0;
  855. }
  856. IHqlExpression * CTreeOptimizer::hoistMetaOverProject(IHqlExpression * expr)
  857. {
  858. IHqlExpression * child = expr->queryChild(0);
  859. if (hasUnknownTransform(child))
  860. return NULL;
  861. IHqlExpression * grandchild = child->queryChild(0);
  862. IHqlExpression * active = queryActiveTableSelector();
  863. try
  864. {
  865. OwnedMapper mapper = getMapper(child);
  866. HqlExprArray args;
  867. args.append(*LINK(grandchild));
  868. ForEachChildFrom(i, expr, 1)
  869. {
  870. IHqlExpression * cur = expr->queryChild(i);
  871. args.append(*expandFields(mapper, cur, active, active, NULL));
  872. }
  873. OwnedHqlExpr newPreserve = expr->clone(args);
  874. OwnedHqlExpr newProject = replaceChild(child, newPreserve);
  875. decUsage(child);
  876. if (!alreadyHasUsage(newProject))
  877. incUsage(newPreserve);
  878. return newProject.getClear();
  879. }
  880. catch (IException * e)
  881. {
  882. //Can possibly occur if the field has been optimized away. (see bug #76896)
  883. e->Release();
  884. return NULL;
  885. }
  886. }
  887. IHqlExpression * CTreeOptimizer::hoistFilterOverProject(IHqlExpression * transformed, bool onlyKeyed)
  888. {
  889. IHqlExpression * child = transformed->queryChild(0);
  890. //Should be able to move filters over count projects, as long as not filtering on the count fields.
  891. //Would need to add a containsCounter() test in the expandFields code - cannot just test filterExpr
  892. //because counter may be there (e.g., countindex3.hql)
  893. if (child->hasAttribute(_countProject_Atom) || child->hasAttribute(prefetchAtom) || isAggregateDataset(child))
  894. return NULL;
  895. if (hasUnknownTransform(child))
  896. return NULL;
  897. HqlExprArray expanded, unexpanded;
  898. if (expandFilterCondition(expanded, unexpanded, transformed, true, onlyKeyed))
  899. {
  900. if (optimizeFilterConditions(errorProcessor, expanded))
  901. return getOptimizedFilter(transformed, expanded);
  902. OwnedHqlExpr filterExpr = createFilterCondition(expanded);
  903. if (unexpanded.ordinality())
  904. DBGLOG("Optimizer: Move %d/%d filters over %s", expanded.ordinality(), expanded.ordinality()+unexpanded.ordinality(), queryNode1Text(child));
  905. else
  906. DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(transformed), queryNode1Text(child));
  907. IHqlExpression * newGrandchild = child->queryChild(0);
  908. OwnedHqlExpr newFilter = createDataset(no_filter, LINK(newGrandchild), LINK(filterExpr));
  909. newFilter.setown(transformed->cloneAllAnnotations(newFilter));
  910. OwnedHqlExpr ret = replaceChild(child, newFilter);
  911. if (!alreadyHasUsage(ret))
  912. incUsage(newFilter);
  913. noteUnused(child);
  914. if (unexpanded.ordinality() == 0)
  915. return ret.getClear();
  916. unexpanded.add(*LINK(child), 0);
  917. OwnedHqlExpr unhoistedFilter = transformed->clone(unexpanded);
  918. OwnedHqlExpr newUnhoistedFilter = replaceChild(unhoistedFilter, ret);
  919. if (!alreadyHasUsage(newUnhoistedFilter))
  920. incUsage(ret);
  921. return newUnhoistedFilter.getClear();
  922. }
  923. return NULL;
  924. }
  925. IHqlExpression * CTreeOptimizer::getHoistedFilter(IHqlExpression * transformed, bool canHoistLeft, bool canMergeLeft, bool canHoistRight, bool canMergeRight, unsigned conditionIndex)
  926. {
  927. HqlExprArray conds;
  928. unwindFilterConditions(conds, transformed);
  929. IHqlExpression * child = transformed->queryChild(0);
  930. IHqlExpression * left = child->queryChild(0);
  931. IHqlExpression * right = queryJoinRhs(child);
  932. IHqlExpression * seq = querySelSeq(child);
  933. OwnedHqlExpr leftSelector = createSelector(no_left, left, seq);
  934. OwnedHqlExpr rightSelector = createSelector(no_right, right, seq);
  935. OwnedHqlExpr activeLeft = ensureActiveRow(left);
  936. OwnedHqlExpr activeRight = ensureActiveRow(right);
  937. OwnedMapper mapper = getMapper(child);
  938. HqlExprArray expanded, unexpanded, leftFilters, rightFilters;;
  939. ForEachItemIn(i, conds)
  940. {
  941. ExpandComplexityMonitor expandMonitor(*this);
  942. IHqlExpression * cur = &conds.item(i);
  943. OwnedHqlExpr expandedFilter = mapper->expandFields(cur, child, NULL, NULL, &expandMonitor);
  944. bool matched = false;
  945. if (expandedFilter->isConstant())
  946. {
  947. expandedFilter.setown(foldHqlExpression(errorProcessor, expandedFilter));
  948. IValue * value = expandedFilter->queryValue();
  949. if (value)
  950. {
  951. if (!value->getBoolValue())
  952. return getOptimizedFilter(transformed, false);
  953. else
  954. matched = true;
  955. }
  956. }
  957. if (!matched && !expandMonitor.isComplex())
  958. {
  959. OwnedHqlExpr leftMappedFilter = replaceSelector(expandedFilter, leftSelector, activeLeft);
  960. OwnedHqlExpr rightMappedFilter = replaceSelector(expandedFilter, rightSelector, activeRight);
  961. //MORE: Could also take join conditions into account to sent filter up both sides;
  962. if (rightMappedFilter==expandedFilter)
  963. {
  964. //Only contains LEFT.
  965. if (canHoistLeft)
  966. {
  967. leftFilters.append(*LINK(leftMappedFilter));
  968. matched = true;
  969. }
  970. else if (canMergeLeft && (conditionIndex != NotFound))
  971. {
  972. expanded.append(*LINK(expandedFilter));
  973. matched = true;
  974. }
  975. //If the filter expression is invariant of left and right then hoist up both paths.
  976. if (leftMappedFilter==expandedFilter && canHoistRight)
  977. {
  978. rightFilters.append(*LINK(expandedFilter));
  979. matched = true;
  980. }
  981. }
  982. else if (leftMappedFilter==expandedFilter)
  983. {
  984. //Only contains RIGHT.
  985. if (canHoistRight)
  986. {
  987. rightFilters.append(*LINK(rightMappedFilter));
  988. matched = true;
  989. }
  990. else if (canMergeRight && (conditionIndex != NotFound))
  991. {
  992. expanded.append(*LINK(expandedFilter));
  993. matched = true;
  994. }
  995. }
  996. else if (canMergeLeft && canMergeRight && conditionIndex != NotFound)
  997. {
  998. expanded.append(*LINK(expandedFilter));
  999. matched = true;
  1000. }
  1001. }
  1002. if (!matched)
  1003. unexpanded.append(*LINK(cur));
  1004. }
  1005. if (leftFilters.ordinality() || rightFilters.ordinality() || expanded.ordinality())
  1006. {
  1007. LinkedHqlExpr ret = child;
  1008. //first insert filters on the left/right branches
  1009. if (leftFilters.ordinality())
  1010. ret.setown(createHoistedFilter(ret, leftFilters, 0, conds.ordinality()));
  1011. if (rightFilters.ordinality())
  1012. ret.setown(createHoistedFilter(ret, rightFilters, 1, conds.ordinality()));
  1013. //extend the join condition where appropriate
  1014. if (expanded.ordinality())
  1015. {
  1016. DBGLOG("Optimizer: Merge filters(%d/%d) into %s condition", expanded.ordinality(), conds.ordinality(), queryNode1Text(child));
  1017. OwnedITypeInfo boolType = makeBoolType();
  1018. HqlExprArray args;
  1019. unwindChildren(args, ret);
  1020. expanded.add(OLINK(args.item(conditionIndex)), 0);
  1021. args.replace(*createBalanced(no_and, boolType, expanded), conditionIndex);
  1022. ret.setown(ret->clone(args));
  1023. }
  1024. if (ret != child)
  1025. noteUnused(child);
  1026. //Now add the item that couldn't be hoisted.
  1027. if (unexpanded.ordinality())
  1028. {
  1029. if (ret != child)
  1030. incUsage(ret);
  1031. unexpanded.add(*LINK(child), 0);
  1032. OwnedHqlExpr unhoistedFilter = transformed->clone(unexpanded);
  1033. ret.setown(replaceChild(unhoistedFilter, ret));
  1034. }
  1035. return ret.getClear();
  1036. }
  1037. else if (unexpanded.ordinality() == 0)
  1038. //All filters expanded to true => remove the filter
  1039. return getOptimizedFilter(transformed, true) ;
  1040. return NULL;
  1041. }
  1042. IHqlExpression * CTreeOptimizer::createHoistedFilter(IHqlExpression * expr, HqlExprArray & conditions, unsigned childIndex, unsigned maxConditions)
  1043. {
  1044. IHqlExpression * grand = expr->queryChild(childIndex);
  1045. DBGLOG("Optimizer: Hoisting filter(%d/%d) over %s.%d", conditions.ordinality(), maxConditions, queryNode0Text(expr), childIndex);
  1046. conditions.add(*LINK(grand), 0);
  1047. OwnedHqlExpr hoistedFilter = createDataset(no_filter, conditions);
  1048. OwnedHqlExpr ret = insertChildDataset(expr, hoistedFilter, childIndex);
  1049. if (!alreadyHasUsage(ret))
  1050. incUsage(hoistedFilter);
  1051. return ret.getClear();
  1052. }
  1053. IHqlExpression * CTreeOptimizer::queryPromotedFilter(IHqlExpression * expr, node_operator side, unsigned childIndex)
  1054. {
  1055. IHqlExpression * child = expr->queryChild(0);
  1056. IHqlExpression * grand = child->queryChild(childIndex);
  1057. OwnedMapper mapper = getMapper(child);
  1058. HqlExprArray conds;
  1059. unwindFilterConditions(conds, expr);
  1060. HqlExprArray hoisted, unhoisted;
  1061. OwnedHqlExpr mapParent = createSelector(side, grand, querySelSeq(child));
  1062. ForEachItemIn(i1, conds)
  1063. {
  1064. IHqlExpression & cur = conds.item(i1);
  1065. bool ok = false;
  1066. OwnedHqlExpr collapsed = mapper->collapseFields(&cur, child, grand, mapParent, &ok);
  1067. if (ok)
  1068. hoisted.append(*collapsed.getClear());
  1069. else
  1070. unhoisted.append(OLINK(cur));
  1071. }
  1072. if (hoisted.ordinality() == 0)
  1073. return NULL;
  1074. DBGLOG("Optimizer: Hoisting filter(%d/%d) over %s", hoisted.ordinality(), hoisted.ordinality()+unhoisted.ordinality(), queryNode0Text(child));
  1075. OwnedHqlExpr newChild = createHoistedFilter(child, hoisted, childIndex, conds.ordinality());
  1076. noteUnused(child);
  1077. if (unhoisted.ordinality() == 0)
  1078. return newChild.getLink();
  1079. unhoisted.add(*LINK(child), 0);
  1080. OwnedHqlExpr unhoistedFilter = createDataset(no_filter, unhoisted);
  1081. OwnedHqlExpr newUnhoistedFilter = replaceChild(unhoistedFilter, newChild);
  1082. if (!alreadyHasUsage(newUnhoistedFilter))
  1083. incUsage(newChild);
  1084. return newUnhoistedFilter.getClear();
  1085. }
  1086. bool CTreeOptimizer::extractSingleFieldTempTable(IHqlExpression * expr, SharedHqlExpr & retField, SharedHqlExpr & retValues)
  1087. {
  1088. IHqlExpression * record = expr->queryRecord();
  1089. IHqlExpression * field = NULL;
  1090. ForEachChild(i, record)
  1091. {
  1092. IHqlExpression * cur = record->queryChild(i);
  1093. switch (cur->getOperator())
  1094. {
  1095. case no_record:
  1096. case no_ifblock:
  1097. return false;
  1098. case no_field:
  1099. if (cur->queryRecord() || field)
  1100. return false;
  1101. field = cur;
  1102. break;
  1103. }
  1104. }
  1105. if (!field)
  1106. return false;
  1107. OwnedHqlExpr values = normalizeListCasts(expr->queryChild(0));
  1108. switch (values->getOperator())
  1109. {
  1110. case no_null:
  1111. break;
  1112. case no_recordlist:
  1113. {
  1114. HqlExprArray args;
  1115. ITypeInfo * fieldType = field->queryType();
  1116. ForEachChild(i, values)
  1117. {
  1118. IHqlExpression * cur = values->queryChild(i);
  1119. if (cur->getOperator() != no_rowvalue)
  1120. return false;
  1121. args.append(*ensureExprType(cur->queryChild(0), fieldType));
  1122. }
  1123. values.setown(createValue(no_list, makeSetType(LINK(fieldType)), args));
  1124. }
  1125. break;
  1126. default:
  1127. if (values->queryType()->getTypeCode() != type_set)
  1128. return false;
  1129. break;
  1130. }
  1131. retField.set(field);
  1132. retValues.setown(values.getClear());
  1133. return true;
  1134. }
  1135. IHqlExpression * mapJoinConditionToFilter(IHqlExpression * expr, IHqlExpression * search, IHqlExpression * replace)
  1136. {
  1137. switch (expr->getOperator())
  1138. {
  1139. case no_and:
  1140. case no_or:
  1141. {
  1142. HqlExprArray args;
  1143. ForEachChild(i, expr)
  1144. {
  1145. IHqlExpression * mapped = mapJoinConditionToFilter(expr->queryChild(i), search, replace);
  1146. if (!mapped)
  1147. return NULL;
  1148. args.append(*mapped);
  1149. }
  1150. return expr->clone(args);
  1151. }
  1152. case no_eq:
  1153. {
  1154. IHqlExpression * l = expr->queryChild(0);
  1155. IHqlExpression * r = expr->queryChild(1);
  1156. if (l == search)
  1157. return createValue(no_in, makeBoolType(), LINK(r), LINK(replace));
  1158. if (r == search)
  1159. return createValue(no_in, makeBoolType(), LINK(l), LINK(replace));
  1160. break;
  1161. }
  1162. }
  1163. OwnedHqlExpr temp = replaceExpression(expr, search, replace);
  1164. if (temp != expr)
  1165. return NULL;
  1166. return LINK(expr);
  1167. }
  1168. IHqlExpression * splitJoinFilter(IHqlExpression * expr, HqlExprArray * leftOnly, HqlExprArray * rightOnly)
  1169. {
  1170. node_operator op = expr->getOperator();
  1171. switch (op)
  1172. {
  1173. case no_assertkeyed:
  1174. case no_and:
  1175. {
  1176. HqlExprArray args;
  1177. ForEachChild(i, expr)
  1178. {
  1179. IHqlExpression * next = splitJoinFilter(expr->queryChild(i), leftOnly, rightOnly);
  1180. if (next)
  1181. args.append(*next);
  1182. }
  1183. unsigned numRealArgs = args.ordinality() - numAttributes(args);
  1184. if (numRealArgs == 0)
  1185. return NULL;
  1186. if ((numRealArgs == 1) && (op == no_and))
  1187. return LINK(&args.item(0));
  1188. return cloneOrLink(expr, args);
  1189. }
  1190. }
  1191. HqlExprCopyArray scopeUsed;
  1192. expr->gatherTablesUsed(NULL, &scopeUsed);
  1193. if (scopeUsed.ordinality() == 1)
  1194. {
  1195. node_operator scopeOp = scopeUsed.item(0).getOperator();
  1196. if (leftOnly && scopeOp == no_left)
  1197. {
  1198. leftOnly->append(*LINK(expr));
  1199. return NULL;
  1200. }
  1201. if (rightOnly && scopeOp == no_right)
  1202. {
  1203. rightOnly->append(*LINK(expr));
  1204. return NULL;
  1205. }
  1206. }
  1207. return LINK(expr);
  1208. }
  1209. IHqlExpression * CTreeOptimizer::optimizeJoinCondition(IHqlExpression * expr)
  1210. {
  1211. //Look at the join condition and move any conditions just on left/right further up the tree
  1212. //can help after other constant folding....
  1213. if (!isSimpleInnerJoin(expr) || expr->hasAttribute(keyedAtom) || expr->hasAttribute(atmostAtom))
  1214. return NULL;
  1215. IHqlExpression * cond = expr->queryChild(2);
  1216. IHqlExpression * seq = querySelSeq(expr);
  1217. HqlExprArray leftOnly, rightOnly;
  1218. OwnedHqlExpr newCond = splitJoinFilter(cond, &leftOnly, isKeyedJoin(expr) ? (HqlExprArray *)NULL : &rightOnly);
  1219. if ((leftOnly.ordinality() == 0) && (rightOnly.ordinality() == 0))
  1220. return NULL;
  1221. HqlExprArray args;
  1222. unwindChildren(args, expr);
  1223. if (leftOnly.ordinality())
  1224. {
  1225. DBGLOG("Optimizer: Hoist %d LEFT conditions out of %s", leftOnly.ordinality(), queryNode0Text(expr));
  1226. IHqlExpression * lhs = expr->queryChild(0);
  1227. OwnedHqlExpr left = createSelector(no_left, lhs, seq);
  1228. OwnedHqlExpr leftFilter = createFilterCondition(leftOnly);
  1229. OwnedHqlExpr newFilter = replaceSelector(leftFilter, left, lhs->queryNormalizedSelector());
  1230. args.replace(*createDataset(no_filter, LINK(lhs), LINK(newFilter)), 0);
  1231. incUsage(&args.item(0));
  1232. }
  1233. if (rightOnly.ordinality())
  1234. {
  1235. DBGLOG("Optimizer: Hoist %d RIGHT conditions out of %s", rightOnly.ordinality(), queryNode0Text(expr));
  1236. IHqlExpression * rhs = expr->queryChild(1);
  1237. OwnedHqlExpr right = createSelector(no_right, rhs, seq);
  1238. OwnedHqlExpr rightFilter = createFilterCondition(rightOnly);
  1239. OwnedHqlExpr newFilter = replaceSelector(rightFilter, right, rhs->queryNormalizedSelector());
  1240. args.replace(*createDataset(no_filter, LINK(rhs), LINK(newFilter)), 1);
  1241. incUsage(&args.item(1));
  1242. }
  1243. if (!newCond)
  1244. newCond.setown(createConstant(true));
  1245. if (!queryAttribute(_conditionFolded_Atom, args))
  1246. args.append(*createAttribute(_conditionFolded_Atom));
  1247. args.replace(*newCond.getClear(), 2);
  1248. return expr->clone(args);
  1249. }
  1250. //DISTRIBUTE(DEDUP(ds, x, y, all), hash(trim(x)))
  1251. //It is likely that the following would be better since it removes one distribute:
  1252. //DEDUP(DISTRIBUTE(ds, hash(trim(x))), x, y, all, LOCAL)
  1253. IHqlExpression * CTreeOptimizer::optimizeDistributeDedup(IHqlExpression * expr)
  1254. {
  1255. IHqlExpression * child = expr->queryChild(0);
  1256. if (!child->hasAttribute(allAtom) || child->hasAttribute(localAtom) || isGrouped(child))
  1257. return NULL;
  1258. DedupInfoExtractor info(child);
  1259. if (info.equalities.ordinality() == 0)
  1260. return NULL;
  1261. IHqlExpression * dist = expr->queryChild(1);
  1262. if (!matchDedupDistribution(dist, info.equalities))
  1263. return NULL;
  1264. DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(expr), queryNode1Text(child));
  1265. OwnedHqlExpr distn;
  1266. if (expr->hasAttribute(manyAtom))
  1267. {
  1268. //DEDUP(DISTRIBUTE(DEDUP(ds, x, y, all, local), hash(trim(x))), x, y, all, LOCAL)
  1269. HqlExprArray localDedupArgs;
  1270. unwindChildren(localDedupArgs, child);
  1271. localDedupArgs.append(*createLocalAttribute());
  1272. localDedupArgs.append(*createAttribute(hashAtom));
  1273. OwnedHqlExpr localDedup = child->clone(localDedupArgs);
  1274. distn.setown(replaceChildDataset(expr, localDedup, 0));
  1275. }
  1276. else
  1277. {
  1278. //DEDUP(DISTRIBUTE(ds, hash(trim(x))), x, y, all, LOCAL)
  1279. distn.setown(replaceChildDataset(expr, child->queryChild(0), 0));
  1280. }
  1281. HqlExprArray args;
  1282. args.append(*LINK(distn));
  1283. unwindChildren(args, child, 1);
  1284. args.append(*createLocalAttribute());
  1285. //We would have generated a global hash dedup, so adding hash to the local dedup makes sense.
  1286. args.append(*createAttribute(hashAtom));
  1287. OwnedHqlExpr ret = child->clone(args);
  1288. if (!alreadyHasUsage(ret))
  1289. incUsage(distn);
  1290. return ret.getClear();
  1291. }
  1292. IHqlExpression * CTreeOptimizer::optimizeProjectInlineTable(IHqlExpression * transformed, bool childrenAreShared)
  1293. {
  1294. IHqlExpression * child = transformed->queryChild(0);
  1295. IHqlExpression * values = child->queryChild(0);
  1296. //MORE If trivial projection then might be worth merging with multiple items, but unlikely to occur in practice
  1297. if (!isPureInlineDataset(child) || transformed->hasAttribute(prefetchAtom))
  1298. return NULL;
  1299. bool onlyFoldConstant = false;
  1300. if (values->numChildren() != 1)
  1301. {
  1302. if (options & HOOfoldconstantdatasets)
  1303. {
  1304. if (!isConstantDataset(child))
  1305. return NULL;
  1306. onlyFoldConstant = true;
  1307. }
  1308. else
  1309. return NULL;
  1310. }
  1311. if (childrenAreShared)
  1312. {
  1313. if (!isConstantDataset(child))
  1314. return NULL;
  1315. }
  1316. IHqlExpression * transformedCountProject = transformed->queryAttribute(_countProject_Atom);
  1317. IHqlExpression * seq = querySelSeq(transformed);
  1318. node_operator projectOp = transformed->getOperator();
  1319. OwnedHqlExpr oldSelector = (projectOp == no_hqlproject) ? createSelector(no_left, child, seq) : LINK(child->queryNormalizedSelector());
  1320. IHqlExpression * curTransform = queryNewColumnProvider(transformed);
  1321. if (!isKnownTransform(curTransform))
  1322. return NULL;
  1323. ExpandSelectorMonitor monitor(*this);
  1324. HqlExprArray newValues;
  1325. ForEachChild(i, values)
  1326. {
  1327. TableProjectMapper mapper;
  1328. mapper.setMapping(values->queryChild(i), NULL);
  1329. OwnedHqlExpr next = expandFields(&mapper, curTransform, oldSelector, NULL, &monitor);
  1330. //Expand counter inline!
  1331. if (transformedCountProject)
  1332. {
  1333. OwnedHqlExpr counter = createConstant(createIntValue(i+1, 8, false));
  1334. next.setown(replaceExpression(next, transformedCountProject->queryChild(0), counter));
  1335. }
  1336. if (!next || monitor.isComplex())
  1337. return NULL;
  1338. if (onlyFoldConstant)
  1339. {
  1340. next.setown(foldScopedHqlExpression(errorProcessor, NULL, next));
  1341. if (!isConstantTransform(next))
  1342. return NULL;
  1343. }
  1344. newValues.append(*ensureTransformType(next, no_transform));
  1345. }
  1346. DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child));
  1347. HqlExprArray args;
  1348. args.append(*createValue(no_transformlist, makeNullType(), newValues));
  1349. if (projectOp == no_newusertable)
  1350. args.append(*LINK(transformed->queryChild(1)));
  1351. else
  1352. args.append(*LINK(transformed->queryRecord()));
  1353. unwindChildren(args, child, 2);
  1354. noteUnused(child);
  1355. OwnedHqlExpr ret = child->clone(args);
  1356. return transformed->cloneAllAnnotations(ret);
  1357. }
  1358. void CTreeOptimizer::analyseExpr(IHqlExpression * expr)
  1359. {
  1360. if (incUsage(expr))
  1361. return;
  1362. switch (expr->getOperator())
  1363. {
  1364. case no_filepos:
  1365. case no_file_logicalname:
  1366. case no_sizeof:
  1367. case no_offsetof:
  1368. return;
  1369. case no_table:
  1370. //only look at the filename - not the parent files.
  1371. analyseExpr(expr->queryChild(0));
  1372. return;
  1373. }
  1374. PARENT::analyseExpr(expr);
  1375. }
  1376. bool CTreeOptimizer::noteUnused(IHqlExpression * expr)
  1377. {
  1378. // return false;
  1379. return decUsage(expr);
  1380. }
  1381. bool CTreeOptimizer::decUsage(IHqlExpression * expr)
  1382. {
  1383. OptTransformInfo * extra = queryBodyExtra(expr);
  1384. #ifdef TRACE_USAGE
  1385. if (expr->isDataset() || expr->isDatarow())
  1386. DBGLOG("%lx dec %d [%s]", (unsigned)expr, extra->useCount, queryNode0Text(expr));
  1387. #endif
  1388. if (extra->useCount)
  1389. return extra->useCount-- == 1;
  1390. return false;
  1391. }
  1392. bool CTreeOptimizer::alreadyHasUsage(IHqlExpression * expr)
  1393. {
  1394. OptTransformInfo * extra = queryBodyExtra(expr);
  1395. return (extra->useCount != 0);
  1396. }
  1397. bool CTreeOptimizer::incUsage(IHqlExpression * expr)
  1398. {
  1399. OptTransformInfo * extra = queryBodyExtra(expr);
  1400. #ifdef TRACE_USAGE
  1401. if (expr->isDataset() || expr->isDatarow())
  1402. DBGLOG("%lx inc %d [%s]", (unsigned)expr, extra->useCount, queryNode0Text(expr));
  1403. #endif
  1404. return (extra->useCount++ != 0);
  1405. }
  1406. IHqlExpression * CTreeOptimizer::inheritUsage(IHqlExpression * newExpr, IHqlExpression * oldExpr)
  1407. {
  1408. OptTransformInfo * newExtra = queryBodyExtra(newExpr);
  1409. OptTransformInfo * oldExtra = queryBodyExtra(oldExpr);
  1410. if (oldExtra->getStopHoist())
  1411. newExtra->setStopHoist();
  1412. #ifdef TRACE_USAGE
  1413. if (newExpr->isDataset() || newExpr->isDatarow())
  1414. DBGLOG("%lx inherit %d,%d (from %lx) [%s]", (unsigned)newExpr, newExtra->useCount, oldExtra->useCount, (unsigned)oldExpr, queryNode0Text(newExpr));
  1415. //assertex(extra->useCount);
  1416. if ((oldExtra->useCount == 0) && (newExpr->isDataset() || newExpr->isDatarow()))
  1417. DBGLOG("Inherit0: %lx inherit %d,%d (from %lx)", (unsigned)newExpr, newExtra->useCount, oldExtra->useCount, (unsigned)oldExpr);
  1418. #endif
  1419. newExtra->useCount += oldExtra->useCount;
  1420. return newExpr;
  1421. }
  1422. bool CTreeOptimizer::isComplexTransform(IHqlExpression * transform)
  1423. {
  1424. ExpandComplexityMonitor monitor(*this);
  1425. monitor.analyseTransform(transform);
  1426. return monitor.isComplex();
  1427. }
  1428. IHqlExpression * CTreeOptimizer::expandProjectedDataset(IHqlExpression * child, IHqlExpression * transform, IHqlExpression * childSelector, IHqlExpression * expr)
  1429. {
  1430. if (hasUnknownTransform(child))
  1431. return NULL;
  1432. OwnedMapper mapper = getMapper(child);
  1433. ExpandSelectorMonitor monitor(*this);
  1434. OwnedHqlExpr expandedTransform = expandFields(mapper, transform, childSelector, NULL, &monitor);
  1435. IHqlExpression * onFail = child->queryAttribute(onFailAtom);
  1436. OwnedHqlExpr newOnFail;
  1437. if (onFail)
  1438. {
  1439. IHqlExpression * oldFailTransform = onFail->queryChild(0);
  1440. OwnedMapper onFailMapper = createProjectMapper(oldFailTransform, NULL);
  1441. OwnedHqlExpr onFailTransform = expandFields(onFailMapper, transform, childSelector, NULL, &monitor);
  1442. if (onFailTransform)
  1443. newOnFail.setown(createExprAttribute(onFailAtom, ensureTransformType(onFailTransform, oldFailTransform->getOperator())));
  1444. }
  1445. if (expandedTransform && (!onFail || newOnFail) && !monitor.isComplex())
  1446. {
  1447. unsigned transformIndex = queryTransformIndex(child);
  1448. IHqlExpression * oldTransform = child->queryChild(transformIndex);
  1449. expandedTransform.setown(ensureTransformType(expandedTransform, oldTransform->getOperator()));
  1450. DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(expr), queryNode1Text(child));
  1451. HqlExprArray args;
  1452. unwindChildren(args, child);
  1453. args.replace(*expandedTransform.getClear(), transformIndex);
  1454. if (onFail)
  1455. args.replace(*newOnFail.getClear(), args.find(*onFail));
  1456. noteUnused(child);
  1457. return child->clone(args);
  1458. }
  1459. return NULL;
  1460. }
  1461. IHqlExpression * CTreeOptimizer::optimizeAggregateCompound(IHqlExpression * transformed)
  1462. {
  1463. //Keep in sync with code in CompoundSourceTransformer
  1464. IHqlExpression * child = transformed->queryChild(0);
  1465. if (isLimitedDataset(child, true))
  1466. return NULL;
  1467. IHqlExpression * tableExpr = queryRoot(transformed);
  1468. node_operator modeOp = queryTableMode(tableExpr);
  1469. if (modeOp == no_csv || modeOp == no_xml)
  1470. return NULL;
  1471. if (isLimitedDataset(child) && !isSimpleCountExistsAggregate(transformed, true, false))
  1472. return NULL;
  1473. node_operator newOp = no_none;
  1474. node_operator childOp = child->getOperator();
  1475. if (queryRealChild(transformed, 3))
  1476. {
  1477. //Grouped aggregate
  1478. switch (childOp)
  1479. {
  1480. case no_compound_diskread:
  1481. case no_compound_disknormalize:
  1482. newOp = no_compound_diskgroupaggregate;
  1483. break;
  1484. case no_compound_indexread:
  1485. case no_compound_indexnormalize:
  1486. newOp = no_compound_indexgroupaggregate;
  1487. break;
  1488. case no_compound_childread:
  1489. case no_compound_childnormalize:
  1490. newOp = no_compound_childgroupaggregate;
  1491. break;
  1492. }
  1493. }
  1494. else
  1495. {
  1496. switch (childOp)
  1497. {
  1498. case no_compound_diskread:
  1499. case no_compound_disknormalize:
  1500. newOp = no_compound_diskaggregate;
  1501. break;
  1502. case no_compound_indexread:
  1503. case no_compound_indexnormalize:
  1504. newOp = no_compound_indexaggregate;
  1505. break;
  1506. case no_compound_childread:
  1507. case no_compound_childnormalize:
  1508. newOp = no_compound_childaggregate;
  1509. break;
  1510. case no_compound_inline:
  1511. newOp = no_compound_inline;
  1512. break;
  1513. }
  1514. }
  1515. if (newOp)
  1516. return createDataset(newOp, removeChildNode(transformed));
  1517. return NULL;
  1518. }
  1519. bool CTreeOptimizer::childrenAreShared(IHqlExpression * expr)
  1520. {
  1521. if (expr->isDataset() || expr->isDatarow())
  1522. {
  1523. switch (getChildDatasetType(expr))
  1524. {
  1525. case childdataset_none:
  1526. return false;
  1527. case childdataset_dataset:
  1528. case childdataset_datasetleft:
  1529. case childdataset_left:
  1530. case childdataset_same_left_right:
  1531. case childdataset_top_left_right:
  1532. case childdataset_dataset_noscope:
  1533. {
  1534. IHqlExpression * ds = expr->queryChild(0);
  1535. //Don't restrict the items that can be combined with no_null.
  1536. return isShared(ds);
  1537. }
  1538. case childdataset_leftright:
  1539. return isShared(expr->queryChild(0)) || isShared(expr->queryChild(1));
  1540. case childdataset_evaluate:
  1541. case childdataset_if:
  1542. case childdataset_case:
  1543. case childdataset_map:
  1544. case childdataset_nway_left_right:
  1545. return true; // stop any folding of these...
  1546. case childdataset_many_noscope:
  1547. case childdataset_many:
  1548. {
  1549. ForEachChild(i, expr)
  1550. {
  1551. IHqlExpression * cur = expr->queryChild(i);
  1552. if (!cur->isAttribute() && isShared(cur))
  1553. return true;
  1554. }
  1555. return false;
  1556. }
  1557. default:
  1558. UNIMPLEMENTED;
  1559. }
  1560. }
  1561. switch (expr->getOperator())
  1562. {
  1563. case no_select:
  1564. if (!isNewSelector(expr))
  1565. return false;
  1566. return isShared(expr->queryChild(0));
  1567. case NO_AGGREGATE:
  1568. return isShared(expr->queryChild(0));
  1569. }
  1570. return false;
  1571. }
  1572. bool CTreeOptimizer::isWorthMovingProjectOverLimit(IHqlExpression * project)
  1573. {
  1574. if (queryBodyExtra(project)->getStopHoist())
  1575. return false;
  1576. IHqlExpression * expr = project->queryChild(0);
  1577. loop
  1578. {
  1579. switch (expr->getOperator())
  1580. {
  1581. case no_limit:
  1582. case no_keyedlimit:
  1583. case no_choosen:
  1584. expr = expr->queryChild(0);
  1585. break;
  1586. case no_compound_diskread:
  1587. case no_compound_disknormalize:
  1588. case no_compound_indexread:
  1589. case no_compound_indexnormalize:
  1590. case no_compound_childread:
  1591. case no_compound_childnormalize:
  1592. case no_compound_selectnew:
  1593. case no_compound_inline:
  1594. //if (options & HOOcompoundproject)
  1595. return true;
  1596. case no_join:
  1597. if (isKeyedJoin(expr))
  1598. return false;
  1599. case no_selfjoin:
  1600. case no_fetch:
  1601. case no_normalize:
  1602. case no_newparse:
  1603. case no_newxmlparse:
  1604. return true;
  1605. case no_null:
  1606. return true;
  1607. case no_newusertable:
  1608. if (isAggregateDataset(expr))
  1609. return false;
  1610. //fallthrough.
  1611. case no_hqlproject:
  1612. if (!isPureActivity(expr) || expr->hasAttribute(_countProject_Atom) || expr->hasAttribute(prefetchAtom))
  1613. return false;
  1614. return true;
  1615. default:
  1616. return false;
  1617. }
  1618. if (isShared(expr))
  1619. return false;
  1620. }
  1621. }
  1622. IHqlExpression * CTreeOptimizer::moveProjectionOverSimple(IHqlExpression * transformed, bool noMoveIfFail, bool errorIfFail)
  1623. {
  1624. IHqlExpression * child = transformed->queryChild(0);
  1625. IHqlExpression * grandchild = child->queryChild(0);
  1626. IHqlExpression * newProject = replaceChild(transformed, grandchild);
  1627. HqlExprArray args;
  1628. args.append(*newProject);
  1629. OwnedMapper mapper = getMapper(transformed);
  1630. ForEachChild(idx, child)
  1631. {
  1632. if (idx != 0)
  1633. {
  1634. bool ok = true;
  1635. IHqlExpression * cur = child->queryChild(idx);
  1636. IHqlExpression * collapsed;
  1637. //NB: Attributes are generally independent of the input dataset, so they shouldn't be reverse mapped,
  1638. //otherwise if a input-invariant expression is projected it can cause problems (jholt44.eclxml)
  1639. if (cur->isAttribute())
  1640. collapsed = LINK(cur);
  1641. else
  1642. collapsed = mapper->collapseFields(cur, grandchild, newProject, &ok);
  1643. if (!ok)
  1644. {
  1645. ::Release(collapsed);
  1646. if (errorIfFail)
  1647. {
  1648. StringBuffer cause;
  1649. if (cur->getOperator() == no_sortlist)
  1650. {
  1651. ForEachChild(i, cur)
  1652. {
  1653. IHqlExpression * elem = cur->queryChild(i);
  1654. OwnedHqlExpr collapsed = mapper->collapseFields(elem, grandchild, newProject, &ok);
  1655. if (!ok)
  1656. {
  1657. cause.append(" expression: ");
  1658. getExprECL(elem, cause);
  1659. break;
  1660. }
  1661. }
  1662. }
  1663. throwError1(HQLERR_BadProjectOfStepping, cause.str());
  1664. }
  1665. if (noMoveIfFail)
  1666. return LINK(transformed);
  1667. //NB: Always succeed for distributed/sorted/grouped, because it is needed for the disk read/index read processing.
  1668. if (cur->getOperator() == no_sortlist)
  1669. collapsed = createValue(no_sortlist, makeSortListType(NULL), createAttribute(unknownAtom));
  1670. else
  1671. collapsed = createAttribute(unknownAtom);
  1672. }
  1673. args.append(*collapsed);
  1674. }
  1675. }
  1676. DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(transformed), queryNode1Text(child));
  1677. OwnedHqlExpr swapped = child->clone(args);
  1678. if (!alreadyHasUsage(swapped))
  1679. incUsage(newProject);
  1680. noteUnused(child);
  1681. return swapped.getClear();
  1682. }
  1683. IHqlExpression * CTreeOptimizer::moveProjectionOverLimit(IHqlExpression * transformed)
  1684. {
  1685. IHqlExpression * child = transformed->queryChild(0);
  1686. IHqlExpression * grandchild = child->queryChild(0);
  1687. IHqlExpression * newProject = replaceChild(transformed, grandchild);
  1688. HqlExprArray args;
  1689. args.append(*newProject);
  1690. ExpandSelectorMonitor monitor(*this);
  1691. ForEachChildFrom(idx, child, 1)
  1692. {
  1693. IHqlExpression * cur = child->queryChild(idx);
  1694. if (cur->isAttribute() && cur->queryName() == onFailAtom)
  1695. {
  1696. IHqlExpression * oldFailTransform = cur->queryChild(0);
  1697. if (!isKnownTransform(oldFailTransform))
  1698. return LINK(transformed);
  1699. OwnedMapper onFailMapper = createProjectMapper(oldFailTransform, NULL);
  1700. IHqlExpression * projectionTransformer = queryNewColumnProvider(transformed);
  1701. OwnedHqlExpr parentSelector = getParentDatasetSelector(transformed);
  1702. OwnedHqlExpr onFailTransform = expandFields(onFailMapper, projectionTransformer, parentSelector, NULL, &monitor);
  1703. args.append(*createExprAttribute(onFailAtom, ensureTransformType(onFailTransform, oldFailTransform->getOperator())));
  1704. }
  1705. else
  1706. args.append(*LINK(cur));
  1707. }
  1708. if (monitor.isComplex())
  1709. return LINK(transformed);
  1710. DBGLOG("Optimizer: Swap %s and %s", queryNode0Text(transformed), queryNode1Text(child));
  1711. OwnedHqlExpr swapped = child->clone(args);
  1712. if (!alreadyHasUsage(swapped))
  1713. incUsage(newProject);
  1714. noteUnused(child);
  1715. return swapped.getClear();
  1716. }
  1717. IHqlExpression * CTreeOptimizer::insertChild(IHqlExpression * expr, IHqlExpression * newChild)
  1718. {
  1719. return insertChildDataset(expr, newChild, 0);
  1720. }
  1721. IHqlExpression * CTreeOptimizer::replaceChild(IHqlExpression * expr, IHqlExpression * newChild)
  1722. {
  1723. return replaceChildDataset(expr, newChild, 0);
  1724. }
  1725. void CTreeOptimizer::unwindReplaceChild(HqlExprArray & args, IHqlExpression * expr, IHqlExpression * newChild)
  1726. {
  1727. HqlMapTransformer mapper;
  1728. mapper.setMapping(expr->queryChild(0), newChild);
  1729. mapper.setSelectorMapping(expr->queryChild(0), newChild);
  1730. ForEachChild(idx, expr)
  1731. args.append(*mapper.transformRoot(expr->queryChild(idx)));
  1732. }
  1733. ANewTransformInfo * CTreeOptimizer::createTransformInfo(IHqlExpression * expr)
  1734. {
  1735. return CREATE_NEWTRANSFORMINFO(OptTransformInfo, expr);
  1736. }
  1737. IHqlExpression * CTreeOptimizer::expandFields(TableProjectMapper * mapper, IHqlExpression * expr, IHqlExpression * oldDataset, IHqlExpression * newDataset, IExpandCallback * _expandCallback)
  1738. {
  1739. OwnedHqlExpr expandedFilter = mapper->expandFields(expr, oldDataset, newDataset, _expandCallback);
  1740. //There used to be code to constant fold filters here - but it can cause dataset expressions to become duplicated
  1741. //causing code to be duplicated. Only fold expressions that are reduced to constants.
  1742. return expandedFilter.getClear();
  1743. }
  1744. IHqlExpression * CTreeOptimizer::inheritSkips(IHqlExpression * newTransform, IHqlExpression * oldTransform, IHqlExpression * oldSelector, IHqlExpression * newSelector)
  1745. {
  1746. HqlExprArray args;
  1747. ForEachChild(i, oldTransform)
  1748. {
  1749. IHqlExpression * cur = oldTransform->queryChild(i);
  1750. if (cur->getOperator() == no_skip)
  1751. args.append(*replaceSelector(cur, oldSelector, newSelector));
  1752. }
  1753. if (args.ordinality() == 0)
  1754. return LINK(newTransform);
  1755. unwindChildren(args, newTransform);
  1756. return newTransform->clone(args);
  1757. }
  1758. IHqlExpression * CTreeOptimizer::createTransformed(IHqlExpression * expr)
  1759. {
  1760. node_operator op = expr->getOperator();
  1761. switch (op)
  1762. {
  1763. case no_field:
  1764. case no_record:
  1765. return LINK(expr);
  1766. }
  1767. //Do this first, so that any references to a child dataset that changes are correctly updated, before proceeding any further.
  1768. OwnedHqlExpr dft = defaultCreateTransformed(expr);
  1769. updateOrphanedSelectors(dft, expr);
  1770. OwnedHqlExpr ret = doCreateTransformed(dft, expr);
  1771. if (ret->queryBody() == expr->queryBody())
  1772. return ret.getClear();
  1773. inheritUsage(ret, expr);
  1774. if (ret == dft)
  1775. return ret.getClear();
  1776. return transform(ret);
  1777. }
  1778. IHqlExpression * CTreeOptimizer::getOptimizedFilter(IHqlExpression * transformed, bool alwaysTrue)
  1779. {
  1780. if (alwaysTrue)
  1781. return removeParentNode(transformed);
  1782. else
  1783. {
  1784. noteUnused(transformed->queryChild(0));
  1785. //MORE: Really wants to walk down the entire chain until we hit something that is shared.
  1786. IHqlExpression * ret = createNullDataset(transformed);
  1787. DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(transformed), queryNode1Text(ret));
  1788. return ret;
  1789. }
  1790. }
  1791. IHqlExpression * CTreeOptimizer::getOptimizedFilter(IHqlExpression * transformed, HqlExprArray const & filters)
  1792. {
  1793. return getOptimizedFilter(transformed, filters.ordinality() == 0);
  1794. }
  1795. void CTreeOptimizer::recursiveDecUsage(IHqlExpression * expr)
  1796. {
  1797. if (decUsage(expr))
  1798. recursiveDecChildUsage(expr);
  1799. }
  1800. void CTreeOptimizer::recursiveDecChildUsage(IHqlExpression * expr)
  1801. {
  1802. switch (getChildDatasetType(expr))
  1803. {
  1804. case childdataset_none:
  1805. break;
  1806. case childdataset_dataset:
  1807. case childdataset_datasetleft:
  1808. case childdataset_left:
  1809. case childdataset_same_left_right:
  1810. case childdataset_top_left_right:
  1811. case childdataset_dataset_noscope:
  1812. recursiveDecUsage(expr->queryChild(0));
  1813. break;
  1814. case childdataset_leftright:
  1815. recursiveDecUsage(expr->queryChild(0));
  1816. recursiveDecUsage(expr->queryChild(0));
  1817. break;
  1818. case childdataset_if:
  1819. recursiveDecUsage(expr->queryChild(1));
  1820. if (expr->queryChild(2))
  1821. recursiveDecUsage(expr->queryChild(2));
  1822. break;
  1823. case childdataset_evaluate:
  1824. case childdataset_case:
  1825. case childdataset_map:
  1826. case childdataset_nway_left_right:
  1827. break; // who knows?
  1828. case childdataset_many_noscope:
  1829. case childdataset_many:
  1830. {
  1831. ForEachChild(i, expr)
  1832. recursiveDecUsage(expr->queryChild(i));
  1833. break;
  1834. }
  1835. default:
  1836. UNIMPLEMENTED;
  1837. }
  1838. }
  1839. IHqlExpression * CTreeOptimizer::replaceWithNull(IHqlExpression * transformed)
  1840. {
  1841. IHqlExpression * ret = createNullExpr(transformed);
  1842. DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(transformed), queryNode1Text(ret));
  1843. recursiveDecChildUsage(transformed);
  1844. return ret;
  1845. }
  1846. IHqlExpression * CTreeOptimizer::replaceWithNullRow(IHqlExpression * expr)
  1847. {
  1848. IHqlExpression * ret = createRow(no_null, LINK(expr->queryRecord()));
  1849. DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(expr), queryNode1Text(ret));
  1850. recursiveDecChildUsage(expr);
  1851. return ret;
  1852. }
  1853. IHqlExpression * CTreeOptimizer::replaceWithNullRowDs(IHqlExpression * expr)
  1854. {
  1855. assertex(!isGrouped(expr));
  1856. IHqlExpression * ret = createDatasetFromRow(createRow(no_null, LINK(expr->queryRecord())));
  1857. DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(expr), queryNode1Text(ret));
  1858. recursiveDecChildUsage(expr);
  1859. return ret;
  1860. }
  1861. IHqlExpression * CTreeOptimizer::transformExpanded(IHqlExpression * expr)
  1862. {
  1863. return transform(expr);
  1864. }
  1865. IHqlExpression * CTreeOptimizer::queryMoveKeyedExpr(IHqlExpression * transformed)
  1866. {
  1867. //Need to swap with these, regardless of whether the input is shared, because the keyed limit only makes sense
  1868. //inside a compound source
  1869. IHqlExpression * child = transformed->queryChild(0);
  1870. node_operator childOp = child->getOperator();
  1871. switch(childOp)
  1872. {
  1873. case no_compound_indexread:
  1874. case no_compound_diskread:
  1875. case no_assertsorted:
  1876. case no_assertdistributed:
  1877. case no_section: // no so sure...
  1878. case no_sectioninput:
  1879. case no_executewhen:
  1880. return swapNodeWithChild(transformed);
  1881. case no_compound:
  1882. return swapNodeWithChild(transformed, 1);
  1883. case no_if:
  1884. return swapIntoIf(transformed, true);
  1885. case no_nonempty:
  1886. case no_addfiles:
  1887. case no_chooseds:
  1888. return swapIntoAddFiles(transformed, true);
  1889. //Force the child to be keyed if it is surrounded by something that needs to be keyed, to ensure both migrate up the tree
  1890. case no_hqlproject:
  1891. case no_newusertable:
  1892. case no_aggregate:
  1893. case no_newaggregate:
  1894. case no_choosen:
  1895. case no_limit:
  1896. case no_keyedlimit:
  1897. case no_sorted:
  1898. case no_stepped:
  1899. case no_distributed:
  1900. case no_preservemeta:
  1901. case no_grouped:
  1902. case no_nofold:
  1903. case no_nohoist:
  1904. case no_filter:
  1905. {
  1906. OwnedHqlExpr newChild = queryMoveKeyedExpr(child);
  1907. if (newChild)
  1908. {
  1909. OwnedHqlExpr moved = replaceChildDataset(transformed, newChild, 0);
  1910. decUsage(child);
  1911. if (!alreadyHasUsage(moved))
  1912. incUsage(newChild);
  1913. return moved.getClear();
  1914. }
  1915. }
  1916. }
  1917. return NULL;
  1918. }
  1919. IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transformed, IHqlExpression * _expr)
  1920. {
  1921. OwnedHqlExpr folded = foldNullDataset(transformed);
  1922. if (folded && folded != transformed)
  1923. return folded.getClear();
  1924. node_operator op = transformed->getOperator();
  1925. IHqlExpression * child = transformed->queryChild(0);
  1926. //Any optimizations that remove the current node, or modify the current node don't need to check if the children are shared
  1927. //Removing child nodes could be included, but it may create more spillers/spliters - which may be significant in thor.
  1928. switch (op)
  1929. {
  1930. case no_if:
  1931. {
  1932. OwnedHqlExpr ret = optimizeIf(transformed);
  1933. if (ret)
  1934. return ret.getClear();
  1935. //Processed hereThis won't split shared nodes, but one of the children may be shared - so proce
  1936. if (transformed->isDataset())
  1937. return optimizeDatasetIf(transformed);
  1938. break;
  1939. }
  1940. case no_keyedlimit:
  1941. {
  1942. IHqlExpression * ret = queryMoveKeyedExpr(transformed);
  1943. if (ret)
  1944. return ret;
  1945. break;
  1946. }
  1947. case no_filter:
  1948. if (filterIsKeyed(transformed))
  1949. {
  1950. IHqlExpression * ret = queryMoveKeyedExpr(transformed);
  1951. if (ret)
  1952. return ret;
  1953. }
  1954. break;
  1955. case no_hqlproject:
  1956. {
  1957. IHqlExpression * counterAttr = transformed->queryAttribute(_countProject_Atom);
  1958. if (counterAttr && !transformContainsCounter(transformed->queryChild(1), counterAttr->queryChild(0)))
  1959. return removeAttribute(transformed, _countProject_Atom);
  1960. //fallthrough
  1961. }
  1962. case no_newusertable:
  1963. if (transformed->hasAttribute(keyedAtom))
  1964. {
  1965. IHqlExpression * ret = queryMoveKeyedExpr(transformed);
  1966. if (ret)
  1967. return ret;
  1968. }
  1969. break;
  1970. case no_join:
  1971. {
  1972. #ifdef MIGRATE_JOIN_CONDITIONS
  1973. OwnedHqlExpr ret = optimizeJoinCondition(transformed);
  1974. if (ret)
  1975. return ret.getClear();
  1976. #endif
  1977. //Unfortunately you cannot convert a keyed join to an index read because the input dataset could contain duplicates
  1978. //That would generate duplicates in the output which would be missing from a index read.
  1979. //MORE:
  1980. //If left outer join, and transform doesn't reference RIGHT, and only one rhs record could match each lhs record (e.g., it was rolled
  1981. //up, or a non-many lookup join, then the join could be converted into a project
  1982. //Can occur once fields get implicitly removed from transforms etc. - e.g., bc10.xhql, although that code has since been fixed.
  1983. //There is no point in distributing the rhs of a global lookup join => remove it.
  1984. if (transformed->hasAttribute(lookupAtom) && !transformed->hasAttribute(localAtom))
  1985. {
  1986. IHqlExpression * rhs = transformed->queryChild(1);
  1987. if (rhs->getOperator() == no_distribute)
  1988. {
  1989. DBGLOG("Optimizer: Remove %s from RHS of global LOOKUP JOIN", queryNode0Text(rhs));
  1990. return ::replaceChild(transformed, 1, rhs->queryChild(0));
  1991. }
  1992. }
  1993. break;
  1994. }
  1995. case no_dedup:
  1996. {
  1997. node_operator childOp = child->getOperator();
  1998. switch(childOp)
  1999. {
  2000. case no_dedup:
  2001. {
  2002. DedupInfoExtractor dedup1(transformed); // slightly costly to create
  2003. DedupInfoExtractor dedup2(child);
  2004. switch (dedup1.compareWith(dedup2))
  2005. {
  2006. //In roxie this would probably be better, in thor it may create extra spills
  2007. //case DedupInfoExtractor::DedupDoesAll:
  2008. // return removeChildNode(transformed);
  2009. case DedupInfoExtractor::DedupDoesNothing:
  2010. return removeParentNode(transformed);
  2011. }
  2012. break;
  2013. }
  2014. }
  2015. break;
  2016. }
  2017. case no_aggregate:
  2018. case no_newaggregate:
  2019. {
  2020. node_operator childOp = child->getOperator();
  2021. if (transformed->hasAttribute(keyedAtom))
  2022. {
  2023. IHqlExpression * moved = NULL;
  2024. switch(childOp)
  2025. {
  2026. case no_compound_diskread:
  2027. case no_compound_disknormalize:
  2028. case no_compound_indexread:
  2029. case no_compound_indexnormalize:
  2030. case no_compound_childread:
  2031. case no_compound_childnormalize:
  2032. if (!isGrouped(queryRoot(child)) && (options & HOOhascompoundaggregate))
  2033. moved = optimizeAggregateCompound(transformed);
  2034. break;
  2035. default:
  2036. moved = queryMoveKeyedExpr(transformed);
  2037. break;
  2038. }
  2039. if (moved)
  2040. return moved;
  2041. }
  2042. IHqlExpression * folded = NULL;
  2043. switch(childOp)
  2044. {
  2045. case no_thisnode:
  2046. return swapNodeWithChild(transformed);
  2047. case no_inlinetable:
  2048. if ((options & HOOfoldconstantdatasets) && isPureInlineDataset(child))
  2049. folded = queryOptimizeAggregateInline(transformed, child->queryChild(0)->numChildren());
  2050. break;
  2051. default:
  2052. if ((options & HOOfoldconstantdatasets) && hasSingleRow(child))
  2053. folded = queryOptimizeAggregateInline(transformed, 1);
  2054. break;
  2055. }
  2056. if (folded)
  2057. {
  2058. recursiveDecUsage(child);
  2059. return folded;
  2060. }
  2061. //MORE: The OHOinsidecompound isn't really good enough - because might remove projects from
  2062. //nested child aggregates which could benifit from them. Probably not as long as all compound
  2063. //activities support aggregation. In fact test should be removable everywhere once all
  2064. //engines support the new activities.
  2065. if (isGrouped(transformed->queryChild(0)) || (queryRealChild(transformed, 3) && !(options & HOOinsidecompound)))
  2066. break;
  2067. OwnedHqlExpr ret = optimizeAggregateDataset(transformed);
  2068. if (ret != transformed)
  2069. return ret.getClear();
  2070. break;
  2071. }
  2072. case NO_AGGREGATE:
  2073. return optimizeAggregateDataset(transformed);
  2074. case no_selectnth:
  2075. {
  2076. node_operator childOp = child->getOperator();
  2077. switch(childOp)
  2078. {
  2079. case no_inlinetable:
  2080. {
  2081. __int64 index = getIntValue(transformed->queryChild(1), -1);
  2082. if (index == -1)
  2083. break;
  2084. IHqlExpression * values = child->queryChild(0);
  2085. if (!values->isPure())
  2086. break;
  2087. if (index < 1 || index > values->numChildren())
  2088. return replaceWithNull(transformed);
  2089. //MORE If trivial projection then might be worth merging with multiple items, but unlikely to occur in practice
  2090. OwnedHqlExpr ret = createRow(no_createrow, LINK(values->queryChild((unsigned)index-1)));
  2091. noteUnused(child);
  2092. DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(transformed), queryNode1Text(ret));
  2093. return ret.getClear();
  2094. }
  2095. case no_datasetfromrow:
  2096. {
  2097. __int64 index = getIntValue(transformed->queryChild(1), -1);
  2098. if (index == -1)
  2099. break;
  2100. if (index != 1)
  2101. return replaceWithNull(transformed);
  2102. IHqlExpression * ret = child->queryChild(0);
  2103. noteUnused(child);
  2104. decUsage(ret); // will inherit later
  2105. DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(transformed), queryNode1Text(ret));
  2106. return LINK(ret);
  2107. }
  2108. #if 0
  2109. //This works (with either condition used), but I don't tink it is worth the cycles..
  2110. case no_choosen:
  2111. {
  2112. __int64 index = getIntValue(transformed->queryChild(1), -1);
  2113. __int64 choosenMax = getIntValue(child->queryChild(1), -1);
  2114. //choosen(x,<n>)[m] == x[m] iff n >= m
  2115. // if ((index == 1) && (choosenMax == 1) && !queryRealChild(child, 2))
  2116. if ((index > 0) && (choosenMax >= index) && !queryRealChild(child, 2) && !isGrouped(child->queryChild(0)))
  2117. return removeChildNode(transformed);
  2118. }
  2119. break;
  2120. #endif
  2121. }
  2122. break;
  2123. }
  2124. case no_select:
  2125. {
  2126. if (transformed->hasAttribute(newAtom))
  2127. {
  2128. node_operator childOp = child->getOperator();
  2129. switch (childOp)
  2130. {
  2131. case no_createrow:
  2132. {
  2133. OwnedHqlExpr match = getExtractSelect(child->queryChild(0), transformed->queryChild(1), false);
  2134. if (match)
  2135. {
  2136. IHqlExpression * cur = match;
  2137. while (isCast(cur))
  2138. cur = cur->queryChild(0);
  2139. if (cur->isPure())
  2140. {
  2141. //This test should not be required, but it avoids problems with elements from rows
  2142. //being used conditionally within transforms. See HPCC-11018 for details.
  2143. if (isIndependentOfScope(match))
  2144. {
  2145. DBGLOG("Optimizer: Extract value %s from %s", queryNode0Text(cur), queryNode1Text(transformed));
  2146. noteUnused(child);
  2147. return match.getClear();
  2148. }
  2149. switch (cur->getOperator())
  2150. {
  2151. case no_createrow:
  2152. case no_constant:
  2153. case no_select:
  2154. case no_null:
  2155. case no_getresult:
  2156. case no_getgraphresult:
  2157. DBGLOG("Optimizer: Extract value %s from %s", queryNode0Text(match), queryNode1Text(transformed));
  2158. noteUnused(child);
  2159. return match.getClear();
  2160. }
  2161. }
  2162. }
  2163. }
  2164. break;
  2165. case no_datasetfromrow:
  2166. {
  2167. HqlExprArray args;
  2168. args.append(*LINK(child->queryChild(0)));
  2169. unwindChildren(args, transformed, 1);
  2170. noteUnused(child);
  2171. return transformed->clone(args);
  2172. }
  2173. break;
  2174. case no_inlinetable:
  2175. {
  2176. IHqlExpression * values = child->queryChild(0);
  2177. if (values->numChildren() == 1)
  2178. {
  2179. IHqlExpression * transform = values->queryChild(0);
  2180. OwnedHqlExpr match = getExtractSelect(transform, transformed->queryChild(1), false);
  2181. if (match)
  2182. {
  2183. IHqlExpression * cur = match;
  2184. while (isCast(cur))
  2185. cur = cur->queryChild(0);
  2186. switch (cur->getOperator())
  2187. {
  2188. case no_constant:
  2189. case no_select:
  2190. case no_null:
  2191. case no_getresult:
  2192. case no_getgraphresult:
  2193. case no_inlinetable:
  2194. case no_left:
  2195. case no_right:
  2196. {
  2197. DBGLOG("Optimizer: Extract value %s from %s", queryNode0Text(match), queryNode1Text(transformed));
  2198. noteUnused(child);
  2199. return match.getClear();
  2200. }
  2201. }
  2202. }
  2203. }
  2204. }
  2205. break;
  2206. }
  2207. }
  2208. }
  2209. break;
  2210. case no_extractresult:
  2211. {
  2212. //Very similar to the transform above, but needs to be done separately because of the new representation of no_extractresult.
  2213. //extract(inline-table(single-row), somefield) -> single-row.somefield if simple valued.
  2214. node_operator childOp = child->getOperator();
  2215. switch (childOp)
  2216. {
  2217. case no_inlinetable:
  2218. {
  2219. IHqlExpression * extracted = transformed->queryChild(1);
  2220. if ((extracted->getOperator() == no_select) && (extracted->queryChild(0) == child->queryNormalizedSelector()))
  2221. {
  2222. IHqlExpression * values = child->queryChild(0);
  2223. if (values->numChildren() == 1)
  2224. {
  2225. IHqlExpression * transform = values->queryChild(0);
  2226. OwnedHqlExpr match = getExtractSelect(transform, extracted->queryChild(1), false);
  2227. if (match)
  2228. {
  2229. IHqlExpression * cur = match;
  2230. while (isCast(cur))
  2231. cur = cur->queryChild(0);
  2232. switch (cur->getOperator())
  2233. {
  2234. case no_constant:
  2235. case no_select:
  2236. case no_null:
  2237. case no_getresult:
  2238. case no_getgraphresult:
  2239. {
  2240. DBGLOG("Optimizer: Extract value %s from %s", queryNode0Text(match), queryNode1Text(transformed));
  2241. noteUnused(child);
  2242. HqlExprArray args;
  2243. args.append(*match.getClear());
  2244. unwindChildren(args, transformed, 2);
  2245. return createValue(no_setresult, makeVoidType(), args);
  2246. }
  2247. }
  2248. }
  2249. }
  2250. }
  2251. }
  2252. break;
  2253. }
  2254. }
  2255. break;
  2256. case no_keyeddistribute:
  2257. case no_distribute:
  2258. {
  2259. if (transformed->hasAttribute(skewAtom))
  2260. break;
  2261. //If distribution matches existing and grouped then don't distribute, but still remove grouping.
  2262. IHqlExpression * distn = queryDistribution(transformed);
  2263. if (distn == queryDistribution(child))
  2264. {
  2265. assertex(isGrouped(child)); // not grouped handled already.
  2266. OwnedHqlExpr ret = createDataset(no_group, LINK(child));
  2267. DBGLOG("Optimizer: replace %s with %s", queryNode0Text(transformed), queryNode1Text(ret));
  2268. return transformed->cloneAllAnnotations(ret);
  2269. }
  2270. break;
  2271. }
  2272. case no_choosen:
  2273. {
  2274. IValue * num = transformed->queryChild(1)->queryValue();
  2275. if (num && (num->getIntValue() >= 1) && !queryRealChild(transformed, 2))
  2276. {
  2277. if (hasNoMoreRowsThan(child, 1))
  2278. return removeParentNode(transformed);
  2279. }
  2280. break;
  2281. }
  2282. case no_preservemeta:
  2283. {
  2284. node_operator childOp = child->getOperator();
  2285. switch(childOp)
  2286. {
  2287. case no_hqlproject:
  2288. case no_newusertable:
  2289. {
  2290. IHqlExpression * ret = hoistMetaOverProject(transformed);
  2291. if (ret)
  2292. return ret;
  2293. break;
  2294. }
  2295. //more; iterate, join? others?
  2296. case no_compound_diskread:
  2297. case no_compound_disknormalize:
  2298. case no_compound_indexread:
  2299. case no_compound_indexnormalize:
  2300. case no_compound_childread:
  2301. case no_compound_childnormalize:
  2302. case no_compound_selectnew:
  2303. case no_compound_inline:
  2304. return swapNodeWithChild(transformed);
  2305. }
  2306. break;
  2307. }
  2308. case no_temptable:
  2309. {
  2310. if (child->getOperator() == no_list)
  2311. {
  2312. ECLlocation dummyLocation(0, 0, 0, NULL);
  2313. OwnedHqlExpr inlineTable = convertTempTableToInlineTable(errorProcessor, dummyLocation, transformed);
  2314. if (transformed != inlineTable)
  2315. return inlineTable.getClear();
  2316. }
  2317. break;
  2318. }
  2319. case no_normalize:
  2320. //Convert NORMALIZE(ds, 0, t(LEFT, COUNTER)) to empty dataset
  2321. if (matchesConstantValue(transformed->queryChild(1), 0))
  2322. return replaceWithNull(transformed);
  2323. //Convert NORMALIZE(ds, 1, t(LEFT, COUNTER)) to PROJECT(ds, t(LEFT, 1));
  2324. if (matchesConstantValue(transformed->queryChild(1), 1))
  2325. {
  2326. IHqlExpression * counter = queryAttributeChild(transformed, _countProject_Atom, 0);
  2327. HqlExprArray args;
  2328. unwindChildren(args, transformed, 0, 1);
  2329. IHqlExpression * transform = transformed->queryChild(2);
  2330. if (counter)
  2331. {
  2332. OwnedHqlExpr one = createConstant(counter->queryType()->castFrom(false, I64C(1)));
  2333. //Remove the annotations from the transform, otherwise it may say t(LEFT,COUNTER) which is confusing.
  2334. args.append(*replaceExpression(transform->queryBody(), counter, one));
  2335. }
  2336. else
  2337. args.append(*LINK(transform));
  2338. DBGLOG("Optimizer: Convert %s(,1) into PROJECT", queryNode0Text(transformed));
  2339. unwindChildren(args, transformed, 3);
  2340. //This is not a count project.. so remove the attribute.
  2341. removeAttribute(args, _countProject_Atom);
  2342. return createDataset(no_hqlproject, args);
  2343. }
  2344. break;
  2345. case no_split:
  2346. node_operator childOp = child->getOperator();
  2347. if (childOp == no_split)
  2348. {
  2349. //Don't convert an unbalanced splitter into a balanced splitter
  2350. //- best would be to set unbalanced on the child, but that would require more complication.
  2351. if (transformed->hasAttribute(balancedAtom) || !child->hasAttribute(balancedAtom))
  2352. return removeParentNode(transformed);
  2353. }
  2354. //This would remove splits only used once, but dangerous if we ever get the usage counting wrong...
  2355. //if (queryBodyExtra(transformed)->useCount == 1)
  2356. // return removeParentNode(transformed);
  2357. break;
  2358. }
  2359. bool shared = childrenAreShared(transformed);
  2360. if (shared)
  2361. {
  2362. bool okToContinue = false;
  2363. switch (op)
  2364. {
  2365. case no_filter:
  2366. {
  2367. node_operator childOp = child->getOperator();
  2368. switch(childOp)
  2369. {
  2370. case no_hqlproject:
  2371. case no_newusertable:
  2372. {
  2373. IHqlExpression * ret = hoistFilterOverProject(transformed, true);
  2374. if (ret)
  2375. return ret;
  2376. break;
  2377. }
  2378. case no_inlinetable:
  2379. //shared is checked within the code below....
  2380. okToContinue = true;
  2381. break;
  2382. }
  2383. break;
  2384. }
  2385. case no_hqlproject:
  2386. {
  2387. node_operator childOp = child->getOperator();
  2388. switch(childOp)
  2389. {
  2390. case no_inlinetable:
  2391. okToContinue = true;
  2392. break;
  2393. }
  2394. break;
  2395. }
  2396. case no_addfiles:
  2397. //It is generally worth always combining inlinetable + inlinetable because it opens the scope
  2398. //for more optimizations (e.g., filters on inlinetables) and the counts also become a known constant.
  2399. okToContinue = true;
  2400. break;
  2401. }
  2402. if (!okToContinue)
  2403. return LINK(transformed);
  2404. }
  2405. switch (op)
  2406. {
  2407. case no_choosen:
  2408. {
  2409. //worth moving a choosen over an activity that doesn't read a record at a time.
  2410. //also worth moving if it brings two projects closer togther, if
  2411. //that doesn't mess up a projected disk read.
  2412. IHqlExpression * const1 = transformed->queryChild(1);
  2413. IValue * val1 = const1->queryValue();
  2414. if (val1)
  2415. {
  2416. __int64 limit = val1->getIntValue();
  2417. if ((limit == CHOOSEN_ALL_LIMIT) && !transformed->queryChild(2))
  2418. return removeParentNode(transformed);
  2419. //if (limit == 0)
  2420. //.,..
  2421. }
  2422. node_operator childOp = child->getOperator();
  2423. switch(childOp)
  2424. {
  2425. case no_choosen:
  2426. {
  2427. //Too complicated to process the grouped variants.
  2428. if (isGrouped(child) || isGrouped(transformed))
  2429. break;
  2430. if (transformed->queryChild(2) || child->queryChild(2))
  2431. {
  2432. //choosen(choosen(x, a, b), c, d))
  2433. //could generate choosen(x, (b+d-1), min(c, a)) but I doubt it is worth it....
  2434. break;
  2435. }
  2436. IHqlExpression * const2 = child->queryChild(1);
  2437. IValue * val2 = const2->queryValue();
  2438. if (val1 && val2)
  2439. {
  2440. __int64 ival1 = val1->getIntValue();
  2441. __int64 ival2 = val2->getIntValue();
  2442. IHqlExpression * newLimit;
  2443. if (ival1 < ival2)
  2444. newLimit = const1;
  2445. else
  2446. newLimit = const2;
  2447. DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child));
  2448. return createDataset(no_choosen, LINK(child->queryChild(0)), LINK(newLimit));
  2449. //don't bother to transform
  2450. }
  2451. break;
  2452. }
  2453. //This can be done, but I think it makes matters worse. The choosen() will short circuit the reading anyway,
  2454. //so no advantage of swapping with the project, and makes things worse, since stops projects commoning up.
  2455. case no_hqlproject:
  2456. case no_newusertable:
  2457. case no_transformascii:
  2458. case no_transformebcdic:
  2459. {
  2460. if (isPureActivity(child) && !isAggregateDataset(child))
  2461. {
  2462. //Don't move a choosen with a start value over a count project - we could if we also adjust the counter
  2463. if (child->queryAttribute(_countProject_Atom))
  2464. {
  2465. //Don't swap with a grouped project with counter - it changes the meaning of the counter
  2466. if (!isGrouped(child) && !queryRealChild(transformed, 2))
  2467. return forceSwapNodeWithChild(transformed);
  2468. }
  2469. else
  2470. return forceSwapNodeWithChild(transformed);
  2471. }
  2472. break;
  2473. }
  2474. case no_fetch: //NB: Not filtered fetch
  2475. {
  2476. if (!containsSkip(child->queryChild(3)))
  2477. return swapNodeWithChild(transformed, 1);
  2478. break;
  2479. }
  2480. case no_if:
  2481. return swapIntoIf(transformed);
  2482. case no_nonempty:
  2483. case no_chooseds:
  2484. return swapIntoAddFiles(transformed);
  2485. case no_sort:
  2486. //If the sort is grouped then this can't be converted to a topn.
  2487. if (!isGrouped(child))
  2488. {
  2489. unsigned __int64 topNLimit = 1000;
  2490. OwnedHqlExpr topn = queryConvertChoosenNSort(transformed, topNLimit);
  2491. if (topn)
  2492. {
  2493. noteUnused(child);
  2494. return topn.getClear();
  2495. }
  2496. }
  2497. break;
  2498. }
  2499. break;
  2500. }
  2501. case no_limit:
  2502. {
  2503. node_operator childOp = child->getOperator();
  2504. switch(childOp)
  2505. {
  2506. case no_hqlproject:
  2507. case no_newusertable:
  2508. {
  2509. if (isPureActivity(child) && !isAggregateDataset(child) && !transformed->hasAttribute(onFailAtom))
  2510. return forceSwapNodeWithChild(transformed);
  2511. break;
  2512. }
  2513. case no_fetch:
  2514. {
  2515. if (isPureActivity(child))
  2516. return swapNodeWithChild(transformed, 1);
  2517. break;
  2518. }
  2519. case no_if:
  2520. return swapIntoIf(transformed);
  2521. case no_nonempty:
  2522. case no_chooseds:
  2523. return swapIntoAddFiles(transformed);
  2524. case no_limit:
  2525. {
  2526. //Could be cleverer... but this is safer
  2527. if (transformed->queryAttribute(skipAtom) != child->queryAttribute(skipAtom))
  2528. break;
  2529. if (transformed->queryAttribute(onFailAtom) != child->queryAttribute(onFailAtom))
  2530. break;
  2531. OwnedHqlExpr parentLimit = foldHqlExpression(errorProcessor, transformed->queryChild(1));
  2532. OwnedHqlExpr childLimit = foldHqlExpression(errorProcessor, child->queryChild(1));
  2533. if (parentLimit == childLimit)
  2534. return removeParentNode(transformed);
  2535. IValue * parentLimitValue = parentLimit->queryValue();
  2536. IValue * childLimitValue = childLimit->queryValue();
  2537. if (parentLimitValue && childLimitValue)
  2538. {
  2539. if (parentLimitValue->getIntValue() <= childLimitValue->getIntValue())
  2540. return removeParentNode(transformed);
  2541. }
  2542. break;
  2543. }
  2544. case no_compound_indexread:
  2545. case no_compound_diskread:
  2546. if (!isLimitedDataset(child))
  2547. {
  2548. if (transformed->hasAttribute(skipAtom) || transformed->hasAttribute(onFailAtom))
  2549. {
  2550. //only merge if roxie
  2551. }
  2552. else
  2553. {
  2554. if ((options & HOOnoclonelimit) || ((options & HOOnocloneindexlimit) && (childOp == no_compound_indexread)))
  2555. return swapNodeWithChild(transformed);
  2556. OwnedHqlExpr childLimit = ::replaceChild(transformed, 0, child->queryChild(0));
  2557. OwnedHqlExpr localLimit = appendLocalAttribute(childLimit);
  2558. OwnedHqlExpr newCompound = ::replaceChild(child, 0, localLimit);
  2559. incUsage(localLimit);
  2560. incUsage(newCompound);
  2561. decUsage(child);
  2562. return ::replaceChild(transformed, 0, newCompound);
  2563. }
  2564. }
  2565. break;
  2566. case no_choosen:
  2567. {
  2568. OwnedHqlExpr parentLimit = foldHqlExpression(errorProcessor, transformed->queryChild(1));
  2569. OwnedHqlExpr childLimit = foldHqlExpression(errorProcessor, child->queryChild(1));
  2570. if (getIntValue(parentLimit, 0) > getIntValue(childLimit, I64C(0x7fffffffffffffff)))
  2571. return removeParentNode(transformed);
  2572. break;
  2573. }
  2574. case no_topn:
  2575. {
  2576. OwnedHqlExpr parentLimit = foldHqlExpression(errorProcessor, transformed->queryChild(1));
  2577. OwnedHqlExpr childLimit = foldHqlExpression(errorProcessor, child->queryChild(2));
  2578. if (getIntValue(parentLimit, 0) > getIntValue(childLimit, I64C(0x7fffffffffffffff)))
  2579. return removeParentNode(transformed);
  2580. break;
  2581. }
  2582. }
  2583. break;
  2584. }
  2585. case no_dedup:
  2586. {
  2587. node_operator childOp = child->getOperator();
  2588. switch(childOp)
  2589. {
  2590. case no_dedup:
  2591. {
  2592. DedupInfoExtractor dedup1(transformed); // slightly costly to create
  2593. DedupInfoExtractor dedup2(child);
  2594. switch (dedup1.compareWith(dedup2))
  2595. {
  2596. case DedupInfoExtractor::DedupDoesAll:
  2597. return removeChildNode(transformed);
  2598. }
  2599. break;
  2600. }
  2601. }
  2602. break;
  2603. }
  2604. case no_filter:
  2605. {
  2606. node_operator childOp = child->getOperator();
  2607. switch(childOp)
  2608. {
  2609. case no_filter:
  2610. {
  2611. DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child));
  2612. HqlExprArray args;
  2613. unwindChildren(args, child);
  2614. unwindChildren(args, transformed, 1);
  2615. OwnedHqlExpr combined = child->clone(args);
  2616. return transformed->cloneAllAnnotations(combined);
  2617. }
  2618. case no_hqlproject:
  2619. case no_newusertable:
  2620. {
  2621. IHqlExpression * ret = hoistFilterOverProject(transformed, false);
  2622. if (ret)
  2623. return ret;
  2624. break;
  2625. }
  2626. //more; iterate, join? others?
  2627. case no_compound_diskread:
  2628. case no_compound_disknormalize:
  2629. case no_compound_indexread:
  2630. case no_compound_indexnormalize:
  2631. case no_compound_childread:
  2632. case no_compound_childnormalize:
  2633. case no_compound_selectnew:
  2634. case no_compound_inline:
  2635. if (!isLimitedDataset(child))
  2636. return swapNodeWithChild(transformed);
  2637. break;
  2638. case no_sorted:
  2639. case no_stepped:
  2640. case no_distributed:
  2641. case no_distribute:
  2642. case no_group:
  2643. case no_grouped:
  2644. case no_keyeddistribute:
  2645. case no_sort:
  2646. case no_subsort:
  2647. case no_preload:
  2648. case no_assertsorted:
  2649. case no_assertgrouped:
  2650. case no_assertdistributed:
  2651. return swapNodeWithChild(transformed);
  2652. case no_keyedlimit:
  2653. {
  2654. //It is ugly this is forced.... but ensures filters get combined
  2655. OwnedHqlExpr ret = swapNodeWithChild(transformed);
  2656. //Need to add the filter as a skip on the onFail() transform
  2657. IHqlExpression * onFail = ret->queryAttribute(onFailAtom);
  2658. if (!onFail)
  2659. return ret.getClear();
  2660. IHqlExpression * limitTransform = onFail->queryChild(0);
  2661. if (!isKnownTransform(limitTransform))
  2662. return ret.getClear();
  2663. NewProjectMapper2 mapper;
  2664. mapper.setMapping(limitTransform);
  2665. HqlExprArray filterArgs;
  2666. unwindChildren(filterArgs, transformed, 1);
  2667. OwnedITypeInfo boolType = makeBoolType();
  2668. OwnedHqlExpr cond = createBalanced(no_and, boolType, filterArgs);
  2669. OwnedHqlExpr skipFilter = mapper.expandFields(cond, child, NULL, NULL, NULL);
  2670. OwnedHqlExpr skip = createValue(no_skip, makeVoidType(), getInverse(skipFilter));
  2671. OwnedHqlExpr newTransform = appendOwnedOperand(limitTransform, skip.getClear());
  2672. OwnedHqlExpr newOnFail = createExprAttribute(onFailAtom, newTransform.getClear());
  2673. return replaceOwnedAttribute(ret, newOnFail.getClear());
  2674. }
  2675. case no_if:
  2676. return swapIntoIf(transformed);
  2677. case no_nonempty:
  2678. case no_chooseds:
  2679. return swapIntoAddFiles(transformed);
  2680. case no_fetch:
  2681. if (isPureActivity(child) && !hasUnknownTransform(child))
  2682. {
  2683. IHqlExpression * ret = getHoistedFilter(transformed, false, false, true, true, NotFound);
  2684. if (ret)
  2685. return ret;
  2686. }
  2687. break;
  2688. case no_iterate:
  2689. //Should be possible to move a filter over a iterate, but only really same if the filter fields match the grouping criteria
  2690. #if 0
  2691. if (isPureActivity(child))
  2692. {
  2693. OwnedHqlExpr ret = queryPromotedFilter(transformed, no_right, 0);
  2694. if (ret)
  2695. return ret.getClear();
  2696. }
  2697. #endif
  2698. break;
  2699. case no_rollup:
  2700. //I don't think you can't move a filter over a rollup because it might affect the records rolled up.
  2701. //unless the filter fields match the grouping criteria
  2702. #if 0
  2703. if (isPureActivity(child))
  2704. {
  2705. OwnedHqlExpr ret = queryPromotedFilter(transformed, no_left, 0);
  2706. if (ret)
  2707. return ret.getClear();
  2708. }
  2709. #endif
  2710. break;
  2711. case no_selfjoin:
  2712. if (isPureActivity(child) && !hasUnknownTransform(child) && !isLimitedJoin(child) && !child->hasAttribute(fullouterAtom) && !child->hasAttribute(fullonlyAtom) && !child->hasAttribute(_countProject_Atom))
  2713. {
  2714. //Strictly speaking, we could hoist conditions that can be hoisted for left only (or even full) joins etc. if the fields that are filtered
  2715. //are based on equalities in the join condition. However, that can wait.... (same for join below...)
  2716. bool canHoistLeft = !child->hasAttribute(rightouterAtom) && !child->hasAttribute(rightonlyAtom) &&
  2717. !child->hasAttribute(leftouterAtom) && !child->hasAttribute(leftonlyAtom);
  2718. bool canMergeLeft = isInnerJoin(child);
  2719. bool canHoistRight = false;
  2720. bool canMergeRight = canMergeLeft;
  2721. IHqlExpression * ret = getHoistedFilter(transformed, canHoistLeft, canMergeLeft, canHoistRight, canMergeRight, 2);
  2722. if (ret)
  2723. return ret;
  2724. }
  2725. break;
  2726. case no_join:
  2727. if (isPureActivity(child) && !hasUnknownTransform(child) && !isLimitedJoin(child) && !child->hasAttribute(fullouterAtom) && !child->hasAttribute(fullonlyAtom) && !child->hasAttribute(_countProject_Atom))
  2728. {
  2729. bool canHoistLeft = !child->hasAttribute(rightouterAtom) && !child->hasAttribute(rightonlyAtom);
  2730. bool canMergeLeft = isInnerJoin(child);
  2731. bool canHoistRight = !child->hasAttribute(leftouterAtom) && !child->hasAttribute(leftonlyAtom) && !isKeyedJoin(child);
  2732. bool canMergeRight = canMergeLeft;
  2733. IHqlExpression * ret = getHoistedFilter(transformed, canHoistLeft, canMergeLeft, canHoistRight, canMergeRight, 2);
  2734. if (ret)
  2735. return ret;
  2736. }
  2737. break;
  2738. case no_select:
  2739. {
  2740. IHqlExpression * ret = moveFilterOverSelect(transformed);
  2741. if (ret)
  2742. return ret;
  2743. }
  2744. break;
  2745. case no_inlinetable:
  2746. if (options & HOOfoldconstantdatasets)
  2747. {
  2748. HqlExprArray conditions;
  2749. unwindChildren(conditions, transformed, 1);
  2750. OwnedITypeInfo boolType = makeBoolType();
  2751. OwnedHqlExpr filterCondition = createBalanced(no_and, boolType, conditions);
  2752. HqlExprArray filtered;
  2753. IHqlExpression * values = child->queryChild(0);
  2754. unsigned numValues = values->numChildren();
  2755. unsigned numOk = 0;
  2756. //A vague rule of thumb for the maximum proportion to retain if the dataset is shared.
  2757. unsigned maxSharedFiltered = (numValues >= 10) ? numValues / 10 : 1;
  2758. ForEachChild(i, values)
  2759. {
  2760. IHqlExpression * curTransform = values->queryChild(i);
  2761. if (!isKnownTransform(curTransform))
  2762. break;
  2763. NewProjectMapper2 mapper;
  2764. mapper.setMapping(curTransform);
  2765. OwnedHqlExpr expandedFilter = mapper.expandFields(filterCondition, child, NULL, NULL);
  2766. //This can prematurely ignore some expressions e.g., x and (' ' = ' '), but saves lots of
  2767. //additional constant folding on non constant expressions, so worthwhile.
  2768. if (!expandedFilter->isConstant())
  2769. break;
  2770. OwnedHqlExpr folded = foldHqlExpression(errorProcessor, expandedFilter);
  2771. IValue * value = folded->queryValue();
  2772. if (!value)
  2773. break;
  2774. if (value->getBoolValue())
  2775. {
  2776. filtered.append(*LINK(curTransform));
  2777. //Only break sharing on an inline dataset if it generates something significantly smaller.
  2778. if (shared && (filtered.ordinality() > maxSharedFiltered))
  2779. break;
  2780. }
  2781. numOk++;
  2782. }
  2783. if (numOk == numValues)
  2784. {
  2785. if (filtered.ordinality() == 0)
  2786. return replaceWithNull(transformed);
  2787. if (filtered.ordinality() == values->numChildren())
  2788. return removeParentNode(transformed);
  2789. DBGLOG("Optimizer: Node %s reduce values in child: %s from %d to %d", queryNode0Text(transformed), queryNode1Text(child), values->numChildren(), filtered.ordinality());
  2790. HqlExprArray args;
  2791. args.append(*values->clone(filtered));
  2792. unwindChildren(args, child, 1);
  2793. decUsage(child);
  2794. return child->clone(args);
  2795. }
  2796. }
  2797. break;
  2798. }
  2799. break;
  2800. }
  2801. case no_keyedlimit:
  2802. {
  2803. node_operator childOp = child->getOperator();
  2804. switch(childOp)
  2805. {
  2806. case no_distributed:
  2807. case no_sorted:
  2808. case no_stepped:
  2809. case no_limit:
  2810. case no_choosen:
  2811. case no_compound_indexread:
  2812. case no_compound_diskread:
  2813. case no_assertsorted:
  2814. case no_assertdistributed:
  2815. return swapNodeWithChild(transformed);
  2816. case no_if:
  2817. return swapIntoIf(transformed);
  2818. case no_nonempty:
  2819. case no_chooseds:
  2820. return swapIntoAddFiles(transformed);
  2821. }
  2822. break;
  2823. }
  2824. case no_hqlproject:
  2825. {
  2826. node_operator childOp = child->getOperator();
  2827. IHqlExpression * transformedCountProject = transformed->queryAttribute(_countProject_Atom);
  2828. if (transformed->hasAttribute(prefetchAtom))
  2829. break; // play safe
  2830. IHqlExpression * transformKeyed = transformed->queryAttribute(keyedAtom);
  2831. IHqlExpression * transform = transformed->queryChild(1);
  2832. switch(childOp)
  2833. {
  2834. case no_if:
  2835. if (isComplexTransform(transform))
  2836. break;
  2837. return swapIntoIf(transformed);
  2838. case no_nonempty:
  2839. case no_chooseds:
  2840. if (isComplexTransform(transform))
  2841. break;
  2842. return swapIntoAddFiles(transformed);
  2843. case no_newusertable:
  2844. if (isAggregateDataset(child))
  2845. break;
  2846. case no_hqlproject:
  2847. {
  2848. if (!isPureActivityIgnoringSkip(child) || hasUnknownTransform(child))
  2849. break;
  2850. IHqlExpression * childTransform = queryNewColumnProvider(child);
  2851. if (assignsContainSkip(childTransform))
  2852. break;
  2853. IHqlExpression * childCountProject = child->queryAttribute(_countProject_Atom);
  2854. //Don't merge two count projects - unless we go through and replace counter instances.
  2855. if (transformedCountProject && childCountProject)
  2856. break;
  2857. IHqlExpression * childKeyed = child->queryAttribute(keyedAtom);
  2858. if (childKeyed && !transformKeyed)
  2859. break;
  2860. OwnedMapper mapper = getMapper(child);
  2861. IHqlExpression * transformedSeq = querySelSeq(transformed);
  2862. OwnedHqlExpr oldLeft = createSelector(no_left, child, transformedSeq);
  2863. OwnedHqlExpr newLeft = createSelector(no_left, child->queryChild(0), transformedSeq);
  2864. ExpandSelectorMonitor monitor(*this);
  2865. OwnedHqlExpr expandedTransform = expandFields(mapper, transform, oldLeft, newLeft, &monitor);
  2866. if (expandedTransform && !monitor.isComplex())
  2867. {
  2868. expandedTransform.setown(inheritSkips(expandedTransform, child->queryChild(1), mapper->queryTransformSelector(), newLeft));
  2869. DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child));
  2870. //NB: Merging a project with a count project can actually remove the count project..
  2871. IHqlExpression * countProjectAttr = transformedCountProject;
  2872. if (childCountProject && transformContainsCounter(expandedTransform, childCountProject->queryChild(0)))
  2873. countProjectAttr = childCountProject;
  2874. noteUnused(child);
  2875. HqlExprArray args;
  2876. args.append(*LINK(child->queryChild(0)));
  2877. args.append(*expandedTransform.getClear());
  2878. if (countProjectAttr)
  2879. args.append(*LINK(countProjectAttr));
  2880. args.append(*LINK(transformedSeq));
  2881. if (transformKeyed)
  2882. args.append(*LINK(transformKeyed));
  2883. unwindHintAttrs(args, transformed);
  2884. unwindHintAttrs(args, child);
  2885. OwnedHqlExpr ret = createDataset(op, args);
  2886. ret.setown(child->cloneAllAnnotations(ret));
  2887. return transformed->cloneAllAnnotations(ret);
  2888. }
  2889. break;
  2890. }
  2891. case no_join:
  2892. if (isKeyedJoin(child))
  2893. break;
  2894. //fall through
  2895. case no_selfjoin:
  2896. case no_fetch:
  2897. case no_normalize:
  2898. case no_newparse:
  2899. case no_newxmlparse:
  2900. case no_rollupgroup:
  2901. {
  2902. if (!isPureActivity(child) || !isPureActivity(transformed) || transformedCountProject)
  2903. break;
  2904. IHqlExpression * transformedSeq = querySelSeq(transformed);
  2905. OwnedHqlExpr oldLeft = createSelector(no_left, child, transformedSeq);
  2906. IHqlExpression * ret = expandProjectedDataset(child, transform, oldLeft, transformed);
  2907. if (ret)
  2908. return ret;
  2909. break;
  2910. }
  2911. case no_preload:
  2912. if (!transformedCountProject)
  2913. return swapNodeWithChild(transformed);
  2914. break;
  2915. case no_sort:
  2916. case no_subsort:
  2917. if (transformedCountProject)
  2918. break;
  2919. if (increasesRowSize(transformed))
  2920. break;
  2921. return moveProjectionOverSimple(transformed, true, false);
  2922. case no_distribute:
  2923. //Cannot move a count project over anything that changes the order of the records.
  2924. if (transformedCountProject)
  2925. break;
  2926. if (increasesRowSize(transformed))
  2927. break;
  2928. return moveProjectionOverSimple(transformed, true, false);
  2929. case no_distributed:
  2930. case no_sorted:
  2931. case no_grouped:
  2932. if (transformedCountProject)
  2933. break;
  2934. return moveProjectionOverSimple(transformed, false, false);
  2935. case no_stepped:
  2936. return moveProjectionOverSimple(transformed, true, false);
  2937. case no_keyedlimit:
  2938. if (isWorthMovingProjectOverLimit(transformed))
  2939. {
  2940. if (child->hasAttribute(onFailAtom))
  2941. return moveProjectionOverLimit(transformed);
  2942. return swapNodeWithChild(transformed);
  2943. }
  2944. break;
  2945. case no_catchds:
  2946. //could treat like a limit, but not at the moment
  2947. break;
  2948. case no_limit:
  2949. case no_choosen:
  2950. if (isWorthMovingProjectOverLimit(transformed))
  2951. {
  2952. //MORE: Later this is going to be worth moving aggregates.... when we have a compound aggregates.
  2953. if (isPureActivity(transformed) && !isAggregateDataset(transformed) && !transformedCountProject)
  2954. {
  2955. if (child->hasAttribute(onFailAtom))
  2956. return moveProjectionOverLimit(transformed);
  2957. return swapNodeWithChild(transformed);
  2958. }
  2959. }
  2960. break;
  2961. case no_inlinetable:
  2962. {
  2963. if (transformContainsSkip(transform))
  2964. break;
  2965. IHqlExpression * ret = optimizeProjectInlineTable(transformed, shared);
  2966. if (ret)
  2967. return ret;
  2968. break;
  2969. }
  2970. case no_compound_diskread:
  2971. case no_compound_disknormalize:
  2972. case no_compound_indexread:
  2973. case no_compound_indexnormalize:
  2974. case no_compound_childread:
  2975. case no_compound_childnormalize:
  2976. case no_compound_selectnew:
  2977. case no_compound_inline:
  2978. if (!transformedCountProject)
  2979. return swapNodeWithChild(transformed);
  2980. break;
  2981. case no_addfiles:
  2982. if (transformedCountProject || isComplexTransform(transform))
  2983. break;
  2984. return swapIntoAddFiles(transformed);
  2985. }
  2986. break;
  2987. }
  2988. case no_projectrow:
  2989. {
  2990. node_operator childOp = child->getOperator();
  2991. switch(childOp)
  2992. {
  2993. case no_if:
  2994. if (isComplexTransform(transformed->queryChild(1)))
  2995. break;
  2996. return swapIntoIf(transformed);
  2997. case no_createrow:
  2998. case no_projectrow:
  2999. {
  3000. if (!isPureActivity(child) || !isPureActivity(transformed) || hasUnknownTransform(child))
  3001. break;
  3002. IHqlExpression * transform = transformed->queryChild(1);
  3003. IHqlExpression * transformedSeq = querySelSeq(transformed);
  3004. OwnedHqlExpr oldLeft = createSelector(no_left, child, transformedSeq);
  3005. OwnedMapper mapper = getMapper(child);
  3006. ExpandSelectorMonitor monitor(*this);
  3007. OwnedHqlExpr expandedTransform = expandFields(mapper, transform, oldLeft, NULL, &monitor);
  3008. if (expandedTransform && !monitor.isComplex())
  3009. {
  3010. DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child));
  3011. HqlExprArray args;
  3012. unwindChildren(args, child);
  3013. args.replace(*expandedTransform.getClear(), queryTransformIndex(child));
  3014. noteUnused(child);
  3015. return createRow(child->getOperator(), args);
  3016. }
  3017. break;
  3018. }
  3019. }
  3020. break;
  3021. }
  3022. case no_selectfields:
  3023. case no_usertable:
  3024. //shouldn't really have any, because we can't really process them properly.
  3025. break;
  3026. case no_newusertable:
  3027. {
  3028. node_operator childOp = child->getOperator();
  3029. switch(childOp)
  3030. {
  3031. case no_if:
  3032. if (isComplexTransform(transformed->queryChild(2)))
  3033. break;
  3034. return swapIntoIf(transformed);
  3035. case no_nonempty:
  3036. case no_chooseds:
  3037. if (isComplexTransform(transformed->queryChild(2)))
  3038. break;
  3039. return swapIntoAddFiles(transformed);
  3040. case no_newusertable:
  3041. if (isAggregateDataset(child))
  3042. break;
  3043. //fallthrough.
  3044. case no_hqlproject:
  3045. {
  3046. if (!isPureActivity(child) || hasUnknownTransform(child))
  3047. break;
  3048. if (child->hasAttribute(_countProject_Atom) || child->hasAttribute(prefetchAtom))
  3049. break;
  3050. IHqlExpression * transformKeyed = transformed->queryAttribute(keyedAtom);
  3051. IHqlExpression * childKeyed = child->queryAttribute(keyedAtom);
  3052. if (childKeyed && !transformKeyed)
  3053. break;
  3054. IHqlExpression * grandchild = child->queryChild(0);
  3055. OwnedMapper mapper = getMapper(child);
  3056. HqlExprArray args;
  3057. args.append(*LINK(grandchild));
  3058. args.append(*LINK(transformed->queryChild(1)));
  3059. ExpandSelectorMonitor monitor(*this);
  3060. IHqlExpression * transformExpr = transformed->queryChild(2);
  3061. HqlExprArray assigns;
  3062. ForEachChild(idxt, transformExpr)
  3063. {
  3064. IHqlExpression * cur = transformExpr->queryChild(idxt);
  3065. if (cur->getOperator() == no_assign)
  3066. {
  3067. IHqlExpression * tgt = cur->queryChild(0);
  3068. IHqlExpression * src = cur->queryChild(1);
  3069. assigns.append(*createAssign(LINK(tgt), expandFields(mapper, src, child, grandchild, &monitor)));
  3070. }
  3071. else
  3072. {
  3073. assigns.append(*LINK(cur));
  3074. }
  3075. }
  3076. OwnedHqlExpr expandedTransform = transformExpr->clone(assigns);
  3077. args.append(*LINK(expandedTransform));
  3078. unsigned max = transformed->numChildren();
  3079. for(unsigned idx=3; idx < max; idx++)
  3080. args.append(*expandFields(mapper, transformed->queryChild(idx), child, grandchild, &monitor));
  3081. if (!monitor.isComplex())
  3082. {
  3083. DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child));
  3084. removeAttribute(args, _internal_Atom);
  3085. noteUnused(child);
  3086. return transformed->clone(args);
  3087. }
  3088. break;
  3089. }
  3090. case no_join:
  3091. if (isKeyedJoin(child))
  3092. break;
  3093. //fall through
  3094. case no_selfjoin:
  3095. case no_fetch:
  3096. case no_normalize:
  3097. case no_newparse:
  3098. case no_newxmlparse:
  3099. case no_rollupgroup:
  3100. {
  3101. if (!isPureActivity(child) || !isPureActivity(transformed))
  3102. break;
  3103. IHqlExpression * transform = transformed->queryChild(2);
  3104. IHqlExpression * ret = expandProjectedDataset(child, transform, child, transformed);
  3105. if (ret)
  3106. return ret;
  3107. break;
  3108. }
  3109. case no_preload:
  3110. return swapNodeWithChild(transformed);
  3111. case no_distribute:
  3112. case no_sort:
  3113. case no_subsort:
  3114. if (increasesRowSize(transformed))
  3115. break;
  3116. return moveProjectionOverSimple(transformed, true, false);
  3117. case no_distributed:
  3118. case no_sorted:
  3119. case no_grouped:
  3120. return moveProjectionOverSimple(transformed, false, false);
  3121. case no_stepped:
  3122. return moveProjectionOverSimple(transformed, false, true);
  3123. case no_keyedlimit:
  3124. case no_limit:
  3125. case no_choosen:
  3126. if (isWorthMovingProjectOverLimit(transformed))
  3127. {
  3128. if (isPureActivity(transformed) && !isAggregateDataset(transformed))
  3129. {
  3130. if (child->hasAttribute(onFailAtom))
  3131. return moveProjectionOverLimit(transformed);
  3132. return swapNodeWithChild(transformed);
  3133. }
  3134. }
  3135. break;
  3136. case no_compound_diskread:
  3137. case no_compound_disknormalize:
  3138. case no_compound_indexread:
  3139. case no_compound_indexnormalize:
  3140. case no_compound_childread:
  3141. case no_compound_childnormalize:
  3142. case no_compound_selectnew:
  3143. case no_compound_inline:
  3144. if (!isAggregateDataset(transformed))
  3145. return swapNodeWithChild(transformed);
  3146. break;
  3147. case no_addfiles:
  3148. if (isComplexTransform(transformed->queryChild(2)))
  3149. break;
  3150. return swapIntoAddFiles(transformed);
  3151. case no_inlinetable:
  3152. {
  3153. IHqlExpression * ret = optimizeProjectInlineTable(transformed, shared);
  3154. if (ret)
  3155. return ret;
  3156. break;
  3157. }
  3158. }
  3159. break;
  3160. }
  3161. case no_group:
  3162. {
  3163. switch (child->getOperator())
  3164. {
  3165. case no_group:
  3166. {
  3167. IHqlExpression * newChild = child;
  3168. bool isLocal = transformed->hasAttribute(localAtom);
  3169. while (newChild->getOperator() == no_group)
  3170. {
  3171. if (newChild->queryAttribute(allAtom))
  3172. break;
  3173. if (queryRealChild(newChild, 1))
  3174. {
  3175. //Don't allow local groups to remove non-local groups.
  3176. if (isLocal && !newChild->hasAttribute(localAtom))
  3177. break;
  3178. }
  3179. noteUnused(newChild);
  3180. newChild = newChild->queryChild(0);
  3181. }
  3182. if (child == newChild)
  3183. break;
  3184. if (queryGrouping(transformed) == queryGrouping(newChild))
  3185. {
  3186. decUsage(newChild); // since will inherit usage on return
  3187. return LINK(newChild);
  3188. }
  3189. return replaceChild(transformed, newChild);
  3190. }
  3191. case no_hqlproject:
  3192. case no_newusertable:
  3193. //Move ungroups() over projects to increase the likely hood of combining projects and removing groups
  3194. // if (!queryRealChild(transformed, 1) && !child->hasAttribute(_countProject_Atom) && !isAggregateDataset(child))
  3195. // return swapNodeWithChild(transformed);
  3196. break;
  3197. }
  3198. break;
  3199. }
  3200. //GH->Ilka no_enth now has a different format, may want to do something with that as well.
  3201. case no_sample:
  3202. {
  3203. IValue * const1 = transformed->queryChild(1)->queryValue();
  3204. if (const1)
  3205. {
  3206. __int64 val1 = const1->getIntValue();
  3207. if (val1 == 1)
  3208. return removeParentNode(transformed);
  3209. node_operator childOp = child->getOperator();
  3210. switch(childOp)
  3211. {
  3212. case no_hqlproject:
  3213. case no_newusertable:
  3214. if (isPureActivity(child) && !child->hasAttribute(_countProject_Atom) && !child->hasAttribute(prefetchAtom) && !isAggregateDataset(child))
  3215. return swapNodeWithChild(transformed);
  3216. break;
  3217. }
  3218. }
  3219. break;
  3220. }
  3221. case no_sort:
  3222. {
  3223. switch(child->getOperator())
  3224. {
  3225. case no_sort:
  3226. case no_subsort:
  3227. if (!isLocalActivity(transformed) || isLocalActivity(child))
  3228. return removeChildNode(transformed);
  3229. break;
  3230. case no_distributed:
  3231. case no_distribute:
  3232. case no_keyeddistribute:
  3233. if (!isLocalActivity(transformed))
  3234. return removeChildNode(transformed); // no transform()
  3235. break;
  3236. }
  3237. break;
  3238. }
  3239. case no_subsort:
  3240. {
  3241. switch(child->getOperator())
  3242. {
  3243. case no_sort:
  3244. {
  3245. if (isGrouped(transformed))
  3246. break;
  3247. //Convert subsort(sort) back into a single sort. Do not convert if it would change the distribution.
  3248. if (!isAlwaysLocal() && (!isLocalActivity(transformed) || !isLocalActivity(child)))
  3249. break;
  3250. OwnedHqlExpr sortOrder = getExistingSortOrder(transformed, true, true);
  3251. //A weird user defined SUBSORT could create an unknown sort order
  3252. if (!sortOrder)
  3253. break;
  3254. OwnedHqlExpr newOrder = replaceSelector(sortOrder, queryActiveTableSelector(), child->queryNormalizedSelector());
  3255. decUsage(child);
  3256. DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child));
  3257. return ::replaceChild(child, 1, newOrder);
  3258. }
  3259. case no_subsort:
  3260. //This should almost certainly be improved, but it might be a bit tricky!
  3261. break;
  3262. }
  3263. break;
  3264. }
  3265. case no_keyeddistribute:
  3266. case no_distribute:
  3267. {
  3268. if (transformed->hasAttribute(skewAtom))
  3269. break;
  3270. //If distribution matches existing and grouped then don't distribute, but still remove grouping.
  3271. IHqlExpression * distn = queryDistribution(transformed);
  3272. switch(child->getOperator())
  3273. {
  3274. case no_distributed:
  3275. case no_distribute:
  3276. case no_keyeddistribute:
  3277. case no_sort:
  3278. case no_subsort:
  3279. if (!transformed->hasAttribute(mergeAtom))
  3280. return removeChildNode(transformed);
  3281. break;
  3282. case no_dedup:
  3283. {
  3284. IHqlExpression * ret = optimizeDistributeDedup(transformed);
  3285. if (ret)
  3286. return ret;
  3287. break;
  3288. }
  3289. case no_addfiles:
  3290. if ((distn == queryDistribution(child->queryChild(0))) ||
  3291. (distn == queryDistribution(child->queryChild(1))))
  3292. return swapIntoAddFiles(transformed);
  3293. break;
  3294. }
  3295. break;
  3296. }
  3297. case no_distributed:
  3298. {
  3299. switch(child->getOperator())
  3300. {
  3301. case no_distribute:
  3302. case no_distributed:
  3303. if (transformed->queryChild(1) == child->queryChild(1))
  3304. return removeParentNode(transformed);
  3305. break;
  3306. case no_compound_diskread:
  3307. case no_compound_disknormalize:
  3308. case no_compound_indexread:
  3309. case no_compound_indexnormalize:
  3310. return swapNodeWithChild(transformed);
  3311. }
  3312. break;
  3313. }
  3314. case no_sorted:
  3315. {
  3316. switch(child->getOperator())
  3317. {
  3318. case no_compound_diskread:
  3319. case no_compound_disknormalize:
  3320. case no_compound_indexread:
  3321. case no_compound_indexnormalize:
  3322. return swapNodeWithChild(transformed);
  3323. }
  3324. break;
  3325. }
  3326. case no_aggregate:
  3327. case no_newaggregate:
  3328. {
  3329. node_operator childOp = child->getOperator();
  3330. switch(childOp)
  3331. {
  3332. case no_if:
  3333. return swapIntoIf(transformed);
  3334. case no_nonempty:
  3335. case no_chooseds:
  3336. return swapIntoAddFiles(transformed);
  3337. case no_compound_diskread:
  3338. case no_compound_disknormalize:
  3339. case no_compound_indexread:
  3340. case no_compound_indexnormalize:
  3341. case no_compound_childread:
  3342. case no_compound_childnormalize:
  3343. if (!isGrouped(child) && (options & HOOhascompoundaggregate) && !transformed->hasAttribute(localAtom))
  3344. {
  3345. IHqlExpression * ret = optimizeAggregateCompound(transformed);
  3346. if (ret)
  3347. return ret;
  3348. }
  3349. break;
  3350. case no_thisnode:
  3351. return swapNodeWithChild(transformed);
  3352. }
  3353. //MORE: The OHOinsidecompound isn't really good enough - because might remove projects from
  3354. //nested child aggregates which could benifit from them. Probably not as long as all compound
  3355. //activities support aggregation. In fact test should be removable everywhere once all
  3356. //engines support the new activities.
  3357. if (isGrouped(transformed->queryChild(0)) || (queryRealChild(transformed, 3) && !(options & HOOinsidecompound)))
  3358. break;
  3359. return optimizeAggregateDataset(transformed);
  3360. }
  3361. case NO_AGGREGATE:
  3362. return optimizeAggregateDataset(transformed);
  3363. case no_fetch:
  3364. {
  3365. //NB: Required for fetch implementation
  3366. node_operator childOp = child->getOperator();
  3367. switch(childOp)
  3368. {
  3369. case no_newusertable:
  3370. if (isAggregateDataset(child))
  3371. break;
  3372. //fallthrough.
  3373. case no_hqlproject:
  3374. if (!hasUnknownTransform(child))
  3375. {
  3376. OwnedMapper mapper = getMapper(child);
  3377. IHqlExpression * selSeq = querySelSeq(transformed);
  3378. OwnedHqlExpr oldLeft = createSelector(no_left, child, selSeq);
  3379. OwnedHqlExpr newLeft = createSelector(no_left, child->queryChild(0), selSeq);
  3380. IHqlExpression * expanded = expandFields(mapper, transformed->queryChild(3), oldLeft, newLeft);
  3381. if (expanded)
  3382. {
  3383. DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child));
  3384. HqlExprArray args;
  3385. args.append(*LINK(child->queryChild(0)));
  3386. args.append(*LINK(transformed->queryChild(1)));
  3387. args.append(*LINK(transformed->queryChild(2)));
  3388. args.append(*expanded);
  3389. args.append(*LINK(selSeq));
  3390. return transformed->clone(args);
  3391. }
  3392. }
  3393. break;
  3394. }
  3395. break;
  3396. }
  3397. case no_addfiles:
  3398. {
  3399. //MORE: This is possibly worth doing even if the children are shared.
  3400. HqlExprArray allTransforms;
  3401. bool ok = true;
  3402. ForEachChild(i, transformed)
  3403. {
  3404. IHqlExpression * cur = transformed->queryChild(i);
  3405. if (!cur->isAttribute())
  3406. {
  3407. if (cur->getOperator() != no_inlinetable)
  3408. {
  3409. ok = false;
  3410. break;
  3411. }
  3412. cur->queryChild(0)->unwindList(allTransforms, no_transformlist);
  3413. }
  3414. }
  3415. if (!ok)
  3416. break;
  3417. DBGLOG("Optimizer: Merge inline tables for %s", queryNode0Text(transformed));
  3418. HqlExprArray args;
  3419. args.append(*createValue(no_transformlist, makeNullType(), allTransforms));
  3420. args.append(*LINK(child->queryRecord()));
  3421. ForEachChild(i2, transformed)
  3422. {
  3423. IHqlExpression * cur = transformed->queryChild(i2);
  3424. if (!cur->isAttribute())
  3425. decUsage(cur);
  3426. }
  3427. OwnedHqlExpr ret = createDataset(no_inlinetable, args);
  3428. return transformed->cloneAllAnnotations(ret);
  3429. }
  3430. #if 0
  3431. //Something like the following might theoretically be useful, but seems to cause problems not commoning up
  3432. case no_select:
  3433. if (transformed->hasAttribute(newAtom) && !childrenAreShared(child))
  3434. {
  3435. OwnedHqlExpr ret = transformTrivialSelectProject(transformed);
  3436. if (ret)
  3437. {
  3438. DBGLOG("Optimizer: Select %s from %s optimized", ret->queryChild(1)->queryName()->str(), queryNode1Text(child));
  3439. noteUnused(child);
  3440. return ret.getClear();
  3441. }
  3442. }
  3443. break;
  3444. #endif
  3445. case no_datasetfromrow:
  3446. {
  3447. node_operator childOp = child->getOperator();
  3448. switch (childOp)
  3449. {
  3450. case no_createrow:
  3451. {
  3452. DBGLOG("Optimizer: Merge %s and %s to Inline table", queryNode0Text(transformed), queryNode1Text(child));
  3453. HqlExprArray args;
  3454. args.append(*createValue(no_transformlist, makeNullType(), LINK(child->queryChild(0))));
  3455. args.append(*LINK(child->queryRecord()));
  3456. OwnedHqlExpr ret = createDataset(no_inlinetable, args);
  3457. ret.setown(child->cloneAllAnnotations(ret));
  3458. return transformed->cloneAllAnnotations(ret);
  3459. }
  3460. }
  3461. break;
  3462. }
  3463. case no_join:
  3464. {
  3465. if (isKeyedJoin(transformed) || transformed->hasAttribute(lookupAtom))
  3466. {
  3467. node_operator childOp = child->getOperator();
  3468. switch (childOp)
  3469. {
  3470. case no_newusertable:
  3471. case no_hqlproject:
  3472. {
  3473. if (!isPureActivity(child) || child->queryAttribute(_countProject_Atom) || child->hasAttribute(prefetchAtom))
  3474. break;
  3475. IHqlExpression * transform = queryNewColumnProvider(child);
  3476. if (transformContainsSkip(transform) || !isSimpleTransformToMergeWith(transform))
  3477. break;
  3478. OwnedMapper mapper = getMapper(child);
  3479. IHqlExpression * transformedSeq = querySelSeq(transformed);
  3480. OwnedHqlExpr oldLeft = createSelector(no_left, child, transformedSeq);
  3481. OwnedHqlExpr newLeft = createSelector(no_left, child->queryChild(0), transformedSeq);
  3482. bool ok = true;
  3483. HqlExprArray args;
  3484. args.append(*LINK(child->queryChild(0)));
  3485. args.append(*LINK(transformed->queryChild(1)));
  3486. ExpandSelectorMonitor monitor(*this);
  3487. ForEachChildFrom(i, transformed, 2)
  3488. {
  3489. OwnedHqlExpr expanded = expandFields(mapper, transformed->queryChild(i), oldLeft, newLeft, &monitor);
  3490. if (expanded && !monitor.isComplex())
  3491. {
  3492. args.append(*expanded.getClear());
  3493. }
  3494. else
  3495. {
  3496. ok = false;
  3497. break;
  3498. }
  3499. }
  3500. if (ok)
  3501. {
  3502. //If expanding the project removed all references to left (very silly join....) make it an all join
  3503. if (transformed->hasAttribute(lookupAtom) && !exprReferencesDataset(&args.item(2), newLeft))
  3504. args.append(*createAttribute(allAtom));
  3505. DBGLOG("Optimizer: Merge %s and %s", queryNode0Text(transformed), queryNode1Text(child));
  3506. noteUnused(child);
  3507. OwnedHqlExpr merged = transformed->clone(args);
  3508. //Substituting constants into LEFT join expression can cause problems for the ATMOST join
  3509. //Only keyed joins currently support it.
  3510. if (transformed->hasAttribute(atmostAtom) && !isKeyedJoin(transformed))
  3511. {
  3512. if (joinHasRightOnlyHardMatch(merged, false))
  3513. merged.clear();
  3514. }
  3515. if (merged)
  3516. return merged.getClear();
  3517. }
  3518. break;
  3519. }
  3520. }
  3521. }
  3522. break;
  3523. }
  3524. case no_selectnth:
  3525. {
  3526. node_operator childOp = child->getOperator();
  3527. switch(childOp)
  3528. {
  3529. case no_sort:
  3530. {
  3531. IHqlExpression * index = transformed->queryChild(1);
  3532. if (getIntValue(index, 99999) <= 100 && !isGrouped(child))
  3533. {
  3534. HqlExprArray topnArgs;
  3535. unwindChildren(topnArgs, child);
  3536. topnArgs.add(*LINK(index), 2);
  3537. OwnedHqlExpr topn = createDataset(no_topn, topnArgs);
  3538. incUsage(topn);
  3539. DBGLOG("Optimizer: Replace %s with %s", queryNode0Text(child), queryNode1Text(topn));
  3540. HqlExprArray selectnArgs;
  3541. selectnArgs.append(*child->cloneAllAnnotations(topn));
  3542. unwindChildren(selectnArgs, transformed, 1);
  3543. return transformed->clone(selectnArgs);
  3544. }
  3545. break;
  3546. }
  3547. case no_compound_indexread:
  3548. {
  3549. //If we reach here the index read isn't shared, so different indices won't duplicate the index read.
  3550. if (!isLimitedDataset(child))
  3551. {
  3552. //Add a choosen() within the index read to minimize the records returned remotely - convert ir[1] to choosen(ir,1)[1]
  3553. //Make it local because that is the thor semantics (roxie is happy with local or non local)
  3554. OwnedHqlExpr limited = createDataset(no_choosen, LINK(child->queryChild(0)), createComma(LINK(transformed->queryChild(1)), createLocalAttribute()));
  3555. OwnedHqlExpr newIndexRead = replaceChild(child, limited);
  3556. return replaceChild(transformed, newIndexRead);
  3557. }
  3558. break;
  3559. }
  3560. }
  3561. }
  3562. }
  3563. return LINK(transformed);
  3564. }
  3565. IHqlExpression * CTreeOptimizer::defaultCreateTransformed(IHqlExpression * expr)
  3566. {
  3567. return PARENT::createTransformed(expr);
  3568. }
  3569. TableProjectMapper * CTreeOptimizer::getMapper(IHqlExpression * expr)
  3570. {
  3571. return new TableProjectMapper(expr);
  3572. }
  3573. bool CTreeOptimizer::isShared(IHqlExpression * expr)
  3574. {
  3575. switch (expr->getOperator())
  3576. {
  3577. case no_null:
  3578. return false;
  3579. case no_spillgraphresult:
  3580. case no_spill:
  3581. case no_split:
  3582. case no_throughaggregate:
  3583. case no_commonspill:
  3584. return true;
  3585. }
  3586. return (queryBodyExtra(expr)->useCount > 1);
  3587. }
  3588. bool CTreeOptimizer::isSharedOrUnknown(IHqlExpression * expr)
  3589. {
  3590. switch (expr->getOperator())
  3591. {
  3592. case no_null:
  3593. return false;
  3594. case no_spillgraphresult:
  3595. case no_spill:
  3596. case no_split:
  3597. case no_throughaggregate:
  3598. case no_commonspill:
  3599. return true;
  3600. }
  3601. OptTransformInfo * extra = queryBodyExtra(expr);
  3602. return (extra->useCount != 1);
  3603. }
  3604. IHqlExpression * optimizeHqlExpression(IErrorReceiver & errorProcessor, IHqlExpression * expr, unsigned options)
  3605. {
  3606. //The no_compound can get very heavily nested => unwind to save stack traversal. We really should support nary no_compound
  3607. HqlExprArray args, newArgs;
  3608. unwindCommaCompound(args, expr);
  3609. optimizeHqlExpression(errorProcessor, newArgs, args, options);
  3610. OwnedHqlExpr optimized = createActionList(newArgs);
  3611. if (expr == optimized)
  3612. return optimized.getClear();
  3613. //If the graph was optimized then it is highly likely there are now constant folding opportunities
  3614. return foldHqlExpression(errorProcessor, optimized);
  3615. }
  3616. void optimizeHqlExpression(IErrorReceiver & errorProcessor, HqlExprArray & target, HqlExprArray & source, unsigned options)
  3617. {
  3618. CTreeOptimizer optimizer(errorProcessor, options);
  3619. optimizer.analyseArray(source, 0);
  3620. optimizer.transformRoot(source, target);
  3621. }
  3622. /*
  3623. Implementation issues:
  3624. 1. References to transformed items.
  3625. x := project(w, ...);
  3626. y := filter(x, ...);
  3627. z := distibute(y, x.fx);
  3628. when x and y are switched, all references to x need to be replaced by x'
  3629. y' := filter(w, ...);
  3630. x' := project(y', ...);
  3631. z := distibute(x', x'.fx);
  3632. Need to map an selector, where selector->queryNormalized() == oldDataset->queryNormalized() and replace with newDataset->queryNormalized()
  3633. However, the mapping is context dependant - depending on what the parent dataset is.
  3634. Could either have transformed[parentDataset] or could post process the transformed expression.
  3635. So to process efficiently, we need:
  3636. a) transformedSelector[parentCtx];
  3637. b) transformed[parentCtx]
  3638. c) on dataset transform, set dataset->queryNormalizedSelector()->transformedSelector[ctx] to newDataset->queryNormalizedSelector();
  3639. d) on mapping, replace with i) queryTransformed(x) or queryNomalizedSelector()->transformedSelector[ctx];
  3640. Could either have
  3641. expr->queryExtra()->transformedSelector[parentCtx]
  3642. or
  3643. ::transformSelector[parentCtx, expr]
  3644. First is not likely to affect many nodes - since only will be set on datasets.
  3645. Second is likely to use much less memory, and probably as quick - trading an extra indirection+construction time with an assign to a structure.
  3646. Have a noComma(top-ds, prev-ctx) to mark the current context.
  3647. *** Only need to change if dataset is visible inside the arguments to the ECL syntax ***
  3648. Use an array of ctx, where tos is current don't seed with a dummy value - because will cause commas to be created
  3649. The idea of the transformedSelector should also be generalized:
  3650. if (!transformed) try transformedSelector, and set transformedSelector to result.
  3651. - should we replace the boolean flags in CHqlExpression with a mask?
  3652. i) would make anding /oring more efficient.
  3653. ii) would make adding code generator helpers much less painful - use 32bits and allocate from top down for the code generator.
  3654. Useful flags
  3655. - context free - not getresults or access to fields in unrelated tables.
  3656. - unconditional?
  3657. - look at transforms and see what causes pain.
  3658. 2. optimizing shared items.
  3659. * When is it worthwhile?
  3660. o removing duplicate sorts?
  3661. o when it only removes a node e.g., count(project).
  3662. o when would enable operation to be done more efficiently. ??Eg.
  3663. * Need to differentiate between a use and a reference - only link count former.
  3664. */