From a89d19ca6c5b0c8a6cc37b5c327dbee084f5a162 Mon Sep 17 00:00:00 2001 From: _AZ_ Date: Fri, 29 Mar 2019 15:19:46 +0100 Subject: [PATCH 1/6] feature: add couchbase query runner --- .../app/assets/images/db-logos/couchbase.png | Bin 0 -> 12997 bytes redash/query_runner/couchbase.py | 173 ++++++++++++++++++ redash/settings/__init__.py | 1 + 3 files changed, 174 insertions(+) create mode 100644 client/app/assets/images/db-logos/couchbase.png create mode 100644 redash/query_runner/couchbase.py diff --git a/client/app/assets/images/db-logos/couchbase.png b/client/app/assets/images/db-logos/couchbase.png new file mode 100644 index 0000000000000000000000000000000000000000..d8e444e964d88d70b7a35fe908bee45677dde553 GIT binary patch literal 12997 zcmeHu^;;WJw>1(Zr7aX%yah^dE$&j>DelnX4#Ay3DW!O@;ts`Kf|f#o;OmGDM(9dd7~dL12ZUf1J+KjN?W!Q%k5>&-!{rl?4X&m&r5`$ zhrXDjT(9+t_@GNoF~q)Z#7FtE=j#a&50GBnHTN|PpMH);V4ah|ypPhJ9nX!au>b}d zgqDv742{dRKRlHcNAr*?aCf_BcHY`dEl*EhtlV`R-OiSkUJa1a0|+G0(Eoe*uMhq& z-{kgv(Ms=drb4J)Ti<1>jj_(muDavGa&VC!%=P%(PSozob%r_$PxbB69i&D--P z_k+2!6Xgj6H!esf7!8`3MopzjEKu)%^9QWCm=Ivj4*h+CGXc`$pxAXY?B!p3qo3f^ z#;f-=`a4sV&m~DY&d(5p${EGKVy(eNC-@z4>J@kDU8tRUTb0`)W~-GrPWdZHFOWcj z+b9l1bLB7(act1-H`LsRu(yBE<0=b-!k zxc`%7wZp;8%O!e)Mu4FE2=q2^v&w1=Mlq{%)+Fk;XC&>Uy!Cj{q{ryMV zDQ%}587{w$a$7JuMn8gmC=&}}EGn9*qHXJ{cV}JeYFPeur+yS$a}y9qBER%XunAcR z?g}ioA54DiE$mM_XO%#MyB+fXI!GT8uG&4wr`5d+Tn|!+G-qRr!IsRnYG{zyfCI10 zbf@R`%fg4Bc9&Xj`fbJj7~(1$qMp-{OUy+G(#|p?+Q-p+HMm}CJ*yUz?c}p!UmhF- z*G>c#h{LNg>!?h4s&5*5i~1MNL8u#QI`UVS$Cew-Pg^`YZpgZ#hY9^s;M04d9IdUU zE1_1u9}LyDccSo2vQN3Xa!x%G(t8JqK=~!&eOp%J?~bJXN*=yzm$Hi)o3*jilAln9 z9C6RL;>x}k$H~68=~5kctWrY^Rap~>v>5Vfb}XNL0&H#;EGuNLnz$wow842xdoHxf zaFVfj$OUDD(Z_)8pA{XO2OS-&^y0G!&i~vAG^6;+pm=MzNX+RGv|bVOeGM=|GlDZZ?d9+JWA}=A%R~j0b;p- z6k4M@O4()lW*HyfJkDNuR^6N<5m_0%OcHn?)rZ#whkOopk9# zR(c7r3$M2bmL<72o6<6EukQ@@e1bs->DCJi=(GwD%)?j7)_1;}gJl_4rC(Vs+g!v*v z6K`>LyE;0|mYTke7@y~fB&Kqyss$7GBipz-kz+rmN1e_d3@x4)(mj`h-t1**pkMsc zCRF-n`Tg3y1^c!`pb)=CogH8ULFA-6Vpmzn4x@2dtBv_kr(`oT3?hkt;xGaGN5 z7FtWUhhdC7VHr>~bPGl0c~hIwfh?A1MJI1yiEcERn02PBlWd=5#Hds5_;|^VzXg=d zw%NC2Ti-Z5bHQavzYwa>I6N{r@2kl2s`LL<@rNwdc7Dt8^YZ3Y^fy4_l_%2qMt)^L z%|{KDRx)Vgv0I{iL|1LDir4bx)5Yp9^56u_f+Jq48OMtkHK+MYuqjMwJ z#2%mEXR+*+Ex`xE`>ek+Ci`4Jf(l=2RqaylOiSe`3lgESTlt@cW*ptBm9-47aOd{F zfLO{vbgPLARQu~*`19_|WZ(*^02Dd&# zMGk?pbWNNyMg1M)wLS{7m%~pSorE_mg6JSQk3gH7v#{x+MrZkxYpFxap|8IkfIFhI zWg{>2G<_c19v>^}8cb#q9MDBcdJ{3xy;giwlD?EJ4Ti48hwjC+eP8gB_bsntP5+aE z&tcfec=kNbj1?7RdUN?|?yD}l3&f+XANv}=e9U=;u~6z*cNqe9TP)6i35e8Ae|Oij z2BT^Syd?75mh_$PYtdtlozE+rUvxy+b0np|Jw0s(EDNsy6)&*_aG<9`ffidWef)-A z#Rp8R?AC4NuQ|vtHvJ0e$fnocWIh;nrbZdXN6O-kVc34P`#4j=|Mje*6=ufPMN8uW z4R-)&$iv#2;M|wou^E$fLim;=_GCb6vdSA+<4G>xzH$Fn3t1)NE3wKQNrORaxjIs; zVOL}_am)c)v8ox2cIJmcf3fMOTNS9^u;Pi+`EE2S%y1H_c2Qx#%h5_VYz@byCM&zz&bTWpLllW~ zJUVF7?CK)_1EWHWcl} zf9pubwrMfnq^-Z-LDSc^_Y|TK@%ZF?9P^xnmqaEX3ktDguaCY5cA>6G4Slaw5XIJO zxL3O32UcGiB8exKA1L$Q$?#BW&$}x@-sp`43~QRC3c??+fft{F^rL$RtNx~{KQiH8 zCNLEI8g7q_xhw!(w1*cz^Y3R>e=YKj!te)%vF5M~n~!WE1 zZGhu0?twes(2u1U>}|h zO96aNT*Pa2Tq!1d^xPelStdTIF(4+m!}temTIA2#iy`|K z1K&&{5!J5l10*o&3eUC%;-`5F&OW9jlF-n468e|aaOr`(k^fW}Kg|T(fe~!ZryLu> zz<(3Pet(Q0pbiG*kryQcs{)Yv;N2s0n9AW)n;x?$MvEqB`{Ex-#nTxQk(NOyp@Do` zJAp)CJ3Q1tPM8QjzQSX||yUsQtk%W}Euk#u3eKfpQfVQQ9g5el{6 zr0{Fl-?fclBLa!Ap}#dwWx~Sn)2TW-&47tG>@m=Ewm`^N)dd5}pY#Ra?hRT=JR(o8 z#TP*&4t#@+xh0IpgSw+B4z4iv0i;YrpPLO(HL6+>dXkqYFbN-pvbv5wiJFcTONuq;A z>kzripJvfO$|u>Vmd1`X|O7V zX%{2iBD>vwYk`W-?~RpCc#u?4sw6SL)vw}hc-Ze&-OI7GO##g{S-I#= zt7u!leM9Bb+HIx%eAlM>%FBz?ZjSc%OZGALY620ff(+q>29L-WMGu&>d^~>BU6S0s zliNdc6;BRP0A%&_+}LY16Q2Y?rH0w#eja4FDf6D)4GPNXBlt!_(A+n9CDGy9?ax_~%ooxx+SLfrzr&31X!1X;0N6X$d7DxSmH zppPT2?lK7-2LXHV&m=wLGe3&HPpZm{y1uqV)?wilJ&dOY=IqvPr^2!yaLh-OeP_E< zUa9(WEeqZqh%PT%%l%|k7?wbhdCYo7@!BSbkp$#BDvwosQ3vfdB;jP>GETlYRaes) zqDs$O?EONHf%jR$P)2OP)X&SAg=s^E?NS;yck2?kZ)AX>x2zPQSz#UhZ#9n@MvD6P ztF3xMdT(f#-TSA-9)Gy0+?hTl8Hr9epTB)UXx>oAsP6xdTX{dIQSju~ z82=xTf*m%h)|2JP+`QVoJ2irFa!)ASdDQpIcAIvHOt>qsred&F`DHR?JT)&UTzs$9 z!_!@<^K_cy46Fc40~3boQPqEP*Zcyg67y)Lf_!R{*?$AEHE=QPt>s12TyUBsJe zw*{3!dlR;8x;%RvT{^bWw4`A_d{LSjP7CE@`_V)#2>q(4wvdj;PApTYf*%Wi=XKcU z?ap5lpiBhb>ukS>(`43&gFS(V-od1BneFt*gsOy9NbCz2LJGoRdld@ zx-6gZy={SVj?0QUZ96%Oh`ar;`AZH%Qwk)DrHM~JJ31)0p{;ZKk&f&3s70P*H3@CQ z!;`d1tXJcsW513F#>!b?@1D2SE0uL00`ruV$RyrZbP8OPC>E+r#9I2-l5(+cfTmm~ zzEJ{6pp$Nddf8^apXQ+>Lx7EIr~YdCtf;yB`qZtTYP<%)XcIQ;WR;zi-fAh5qt@5E zsR7DrMg}ls7!aa><*s*)%ds;iGSv}%RFd#7JhcXu&AwEHz`4z$g|k&|5PU1|U2b{f z7m!V~`)N~+Miis}bzGhaq8ywnWk9tpJO4zx#|1UH1t1Am7=?*S)xGtm=U|nukp*h3 zcY^f6pz4^=kj~zL_IqSs_g9H80Os*nU_~eTuKLd4@n_>Nl>PnrD3Hl95h8oA?Jdz{ z$gm@U5CCwe0!%9wQCXOBS76^V0SsfZMO{@e{T(64Ze&E~X9OG~(;uh{o+Ork?#Bx* z_S)67-&s!k%|V3@f+W4~<}Z(H)n?$dwqTmY0KWvq;N}J7g1(kt=kUq?B&3rF1CIp)fHtD%l~2ic0fwftxi8s*{Z=spL`Jzi#pj64v71z z{r#NTtw1AzUt`DL1;X;V)iR7L-f;;EmN9L^yHOLP@NAUcBLcCvjYy6JIuxCZg*SP7H~vV<0lIUx`Wo zqiYZ|iPg24>^wI~I$5RNU^rJ@TeCri65p(PW}5M_W*P^J-p^{T-=BA*eOWZvA}*CN z|8d64HgMR`n{DXhgq$fAQd>>9VHiI5=vdM*NtqP)G4^A>@}RAj#`4!uWPVDl}sxVYae7w!x=wbav~HWv}?%PVH9hP)hFjmK%ys?9cez z8qF5&zs!#1D85>cyI2%-Gk-AoivD4QQjeu+&_WR!d~f#?O1HLYDPu<1Q_qvkSE4b3GP^*{odq1Stw}IB$V_B zs=fR?jwd?ujQUXXS&#QiM550|v>xi!)|_Q$G<*2!Bm*I!XFZD;3VHG?=hA2jbNs&z zyrTRdpmL3VRh3|3+|{o@6kveVuAPZx%i(HmJ={l>@nRU87_K-3Bt6_&BBsHb&rpa^ zAR48B)J<;44cKTr|VQibglr>LM;7?R{t=-Fr-eMHBYiX@Oy);fXM}O<=TcP5R*6*b`X4#2 zW|(bZ09|m!FA?=qGpWR|zCW3pd>|Zj*F27gLr(FdCRX zmvp5G%Xv_bK+86?Cs+*{K@=xENU=YJ88Jvo#s!E21Ol0-YXWWSsiF$OHrJpNiugoU znWwF>%wHcinYHEcKuoJ4o9C|v`Sz}#pWItO+8rHnGI%iqds5E!3cI^@ej%@;1EpMR zG2Ec^YqE&eSEn$T3+C2P*T0a2Oy;5jR189OXrbBXe2&cU533dN+45H+|KX`dwyY3l z3nm?OviDst6!@Rt#7GN<34aW@8bXQE=ANHHIXBaDTCQSbyb!eOeg7}x%Ne<9cNnA{PLFF4V0-DKmmms}~5O5n0qyRq`UCzU4 zY?1kfSSGvq%xW8!*l}*W3wz^ukXDI5D956Hdz9H5-T&5-R)m8jOCsgfYs>zVY#BoX zjr3BKE>)`Wieq+qmLIfuf`j+UbkIIhM)PsJed5`TWe*)atl$}{CrPhy_Me<9Dw40$ z8XAHJi zIs7af!Nq^(=yjRHGW+i@_LWb`MmfXqI5-1G?KatrY!2>yYPKT3TC=akP=0`TQZ6?x zYaK1xD2iXlmptCi_C`nl1&`#L5H2(BW;ycyGU>3XJ{G>X5usN@ts}Jw3DU@c-!HGW zf`_jRM2|OWn(wS^ZfjGRr54Ey6T8VB9eaga2fZUnD4gMhn^gj{-h(OO=LaEJy@s5o zA(8iR@FyxMar6(d90ip;1kIQ@-JNT{1BnjEp2?Ao1%C?gE`F|3%Wy#X3(^Dkd07_D zw#9YSykE+=ZKw#LKpO8V;9=vC{Q-5YuI}JF(Ua0f=qKQsE^jY&!Z-icY^$ji3}DON z{rH;TkQ4HVPK3OK$Fl?3&(xmke20uezBxLltc}|pEY*Sf-AP-2b$dq^{LQOK_Th3~ z5iuVXQd-Goi9MSYle;DFZ@D->BdKKA$mO=fQW|@LApa)kR6rIJdA(DjU$Sh(^sg6o zhpV{g@jZs)!(q>S*d_lQrbyIyWdkjin7!ox?Ze8EGz|-QAoc zK}6VRH% zo#_k}ArpS6dpBCooTMmL1toXGQ3YOA-UU8rl2UnpgF_0czli(Ta}bZG$YA z%~Gs1^}N%yYnw-po-Yg(SH7@%2ko25K8Yi<+|dTA=*Br@4KsQUh$0BsA}cH6c_B`= z&Udgb%BoXagU^3`OFA;^10GQ@RT{%ZsAcbKe_9vn!&D+QN!oLzhl(O2w-J z@-Bm#4^Fu!?u`7hQ;f&9Q}?isAX)dm1z6B~p$SLJ>e2Z{1iX-`|7faCz_pwIw4vNq zL_|3t^MbCMIJiD!kHR{WW3-Qdx}h*8sOk}RyP1!-M+E=7%fIi+m22~LTSpE|aF3|@a_8p#*bi1=1;z9^ zU0Sj^@6J^&l1lWOf=&-h~D4{Etd2BC>5+i*MJamu@ufwn`OyaM9GB@@ApN0Sbq*m!xf)cgdRjbEBO&aAX3<61~c`2Mol-&9vGUC9VLLzFxR#3fE& z<~_8BZ~Yh>b-ghfV2(M+(#X-j3`1b-SFx>(cU~mooyxvP@w|xgG&!bl% z{kxgo{5Kd2G07t}gm;F)IR%&N@4OAxUw@dw4#Qblv8?wk2>dr|Mr-<$t(63EnK6C!WC-Ng{Km#E> ziL!7es4s>M|8BBrT;2)S~5q3ut zq6bWXawxK(+}#}{_s2Y2>1|nz5@>?swAxj~_)ZIN1-wKVCN;GkEp!0&7oT2+R1F;; zTFyaV2hpO`*UC1|eYrLYxs1CP?UR!x@i*|!r7ZobHgYiJ!6i^Ja~O(J9Zjk-{rIVB zw6An02y;Vy2llx*=2I2}$jCn3-c}wO-kxjxD^E^9gF@1bn;rKLxt?FXWy`>WOE_A! z;x1^s^Xv+`t-RlLNa3L>qz$&n$=Z7DYld-^0%jSb)w026=E;{CQxYxMqTzRQr&yt}a>6SWr`KX)jw-ST zWg{46jDyiMQu@d8sS?E*^Dmr0-z|P=Xc{D*jA^fVzVMrFn)gE{Z!7B)QU_a<@bwMm z={6^3`nbtd4t%Np`&`FG@yBbE5gHO>?M$CL!H1=NTDobz3I{@oqQ6O5VJXiYZc>q# z?J=&k6PniggSKUqEzr!L=_L40FkXAhd=Xw$)O_m%M&gXL1WsW!?c=8cA&gkalilxl zfgg;VeAQ4~Pp2NkK|k*zMx-J44NJ0j;Q19|Vx>>f`J#W4p$jF{uVsy-(dc@N8ge#Y zq@aX)wFp~?MO&qClj9hl01=@2;&jJ1H@rt zGJL*x8)_It4mdg#T6zB_>nl0j>0LdMJ=3p1_fv^u$9Xn7x-z_K(Gn$#d)M)`nNg*u zxY#}XjLl@Ss)L_DinkXmKwpCd*V6aD3}C=zS${WzzMKdoxLHZL23@UlG~TYTY`uH$ zY86(`O`nsAwN={b_b_o1xqUcSBX0gTU!a&E2Mhe{C+kR~mHE8yG(X(Y7kFhDP;tK&)cqj!rS$7_I>W@ZPp)0G z9Tg@XmyM2?WX$9;sUax7`AlBl&_dsIRZ7zAF_IFzW8R!lKJDbbHNHl3bt$0!VOkeX z>OhgNl$r>AkJ`$ks3M&a8`H&xuY=(gw)EAGqPtTrDjR}8LS2CG!b3}8E%}{KmCs*C zy#HUptK8qB#{I06+|AN#Ni{@X$FAXYYix~1%IL+Qc0iSBv*X~~Xtns)P-fKRcEi$% zRWXlj;#3s?U9ITFuJ!WoW11PtbAwB;{K6l8#&S?( z{@~_FS=qw`#3$-nVb)P)opb&hvmr2Q_x3v$=jUY7$s|O-g%lLz<@Nu2)GiyjQeNkq zS1(T~tIv~%@@Txz2^X-$F?WVzRqc`QJX|GOq&x8G1ry7MLMH2dVLey;bcO+^dc4v@u2h`+ksk24)ZNo`)!CXKYbF^B-IKFUJ@N%}Xa?5lW#t~M5q^7|0(u61-X_UZ zvj75KPaI7d(kKP28i^VB-)-co$R#9-pFt^6VFccLUn4l*RJ*M>>YJTBBhSDvOJt~0 z2mRpaI&c%S4pt8c>N7iLu*B>#=`QcLa-##pIn3;rr`V}or7;kT`Xywjw@ZZL8Yby8 z!~}#1@fplFKh{t;e@ob^(UoK7`n`UrrKNeFI%Tro*V{IT5Sz`uvTO=%rRX(fs7u0J z#{Jq|gL5QYxo7shRPP(1rf+hHN+0+uNy_d=rG z4`iRNXe0fvJ}fQ@6We0TvreOA0xexr|J2oX-;2#3Bb_FfM%kLloBCX@75!6bwrufi z%A`WWqx$d56bafI+^VncCQX1;{p~1UUztKuF`JK$k}*vt7cI?^PPWlQ#k^c@gN)+( z`=mPvW=Frx8fd%IO*j5gBv-)1Kn!Y-um_o;!e#l6k4D1wL-g_dAO;*O<*rxfO|gm| z$MAGnIS9O@%XQRljA?i*a){$i1mQ+4xs9OpENT+PVd_0Xjy!_w0=yjSYLvFdzK@&z zdEFWO*xxYy33}%xU+8}ypMQBgbG+;idd!Fqtw%NG6DFa+!Kb4)`#Qu9Cr2UHrYzwN z2E1`Do}doJ(oB#SxPIV15)g~eK{|I`A$7MZhdj}$5`rltKe zVp?o$;`h4*l7FF6 zWPt}qdv+X+!vTfP?e>VsCP{op+sLnc@S&cIzA}hZacR8(LUt=#c;Vn_^9%>Z$}hd% z`3>bBa#dvvA}s8Jr+)v=U9kO9HvCiNHV@oW9aF($Bcv@$OF|fZZ>yOuqHUxlySenP ze5qmc5-T(mhv5N@`;|=`8hU|bb-AY^hUe2j0|`9k>Q2n9a*p9%f$|g^T#oOPh%05&O+5Z^NHgsD|=d$|k#oy^W{3nS2 z&KpQW3Vc1SVIf5_G=g$_^!|qML2?g9^MX9dAhn6i{5TmJ% zMfmB3#TPC}fT*Z-YH2{}QtCtDnO6zr@v&`97KuFRc(Q`dNa~JY^zAwm8M~>;{9$j4 zAENK3DxdFq9J;cS3-|vSF0q7qm}dK6WHE@d-f7v$c`EC;ZSBiwn$lL}9bkTkeJZ-j z3EbcTO%B$;C%yfZVC3s3U7520+GrOl59OypWT%{#9z3jbVCC1V3cL4{XjvzRWv zdWr^WI0(Qs@#tLi#Qbp|eKiG%<6;YH58Y^`6{_C%;7xUIM~pI(Nw2wMfSc`MZjT0< zFW9TIe4;3^4QMHL-;FL{)xL6`IA`QiYaDCH$ur#J{1uh0JHD;?f~$dd8cZ8L6IE%j zZCf3eLm)O_)8xyW`p#AubbO^0w`iB?Gv<3FW_a=Lr}*`n=go8U{NDa2rZ;29qC^;} zL~&;7-rn=KKEq)H&-QVadv69UCIc&LB%2(DS}n~xCHRgiYTKQYEDg89$21a2d|OR2 z_qwF1_QeSUrr0`6tXN~sCyL@|J|27rVEed-BW^qe)wmgB7fT`YyJY-&r=gI_X)bWjzD{ujH-8H0NBnaBfiT`XXk#&&*5vGP&tS?{KMu p(J=md_^%KCuj7Do5aWqz(mlm+8F*)g`Yr`cK}J=&@}pVE{{thJGeH0V literal 0 HcmV?d00001 diff --git a/redash/query_runner/couchbase.py b/redash/query_runner/couchbase.py new file mode 100644 index 0000000000..468c0e4d31 --- /dev/null +++ b/redash/query_runner/couchbase.py @@ -0,0 +1,173 @@ +import datetime +import logging + +from dateutil.parser import parse + +from redash.query_runner import * +from redash.utils import JSONEncoder, json_dumps, json_loads, parse_human_time +import json + +logger = logging.getLogger(__name__) +try: + import requests + import httplib2 +except ImportError as e: + logger.error('Failed to import: ' + str(e)) + + +TYPES_MAP = { + str: TYPE_STRING, + unicode: TYPE_STRING, + int: TYPE_INTEGER, + long: TYPE_INTEGER, + float: TYPE_FLOAT, + bool: TYPE_BOOLEAN, + datetime.datetime: TYPE_DATETIME, + datetime.datetime: TYPE_STRING +} + +def _get_column_by_name(columns, column_name): + for c in columns: + if "name" in c and c["name"] == column_name: + return c + + return None +def parse_results(results): + rows = [] + columns = [] + + for row in results: + parsed_row = {} + for key in row: + if isinstance(row[key], dict): + for inner_key in row[key]: + column_name = u'{}.{}'.format(key, inner_key) + if _get_column_by_name(columns, column_name) is None: + columns.append({ + "name": column_name, + "friendly_name": column_name, + "type": TYPES_MAP.get(type(row[key][inner_key]), TYPE_STRING) + }) + + parsed_row[column_name] = row[key][inner_key] + + else: + if _get_column_by_name(columns, key) is None: + columns.append({ + "name": key, + "friendly_name": key, + "type": TYPES_MAP.get(type(row[key]), TYPE_STRING) + }) + + parsed_row[key] = row[key] + + rows.append(parsed_row) + + return rows, columns +class Couchbase(BaseQueryRunner): + + noop_query = 'Select 1' + + @classmethod + def configuration_schema(cls): + return { + 'type': 'object', + 'properties': { + 'host': { + 'type': 'string', + }, + 'port': { + 'type': 'string', + 'title':'Port (Defaults: 8095 - Analytics, 8093 - N1QL)', + 'default': '8095' + }, + 'user': { + 'type': 'string', + }, + 'password': { + 'type': 'string', + }, + }, + 'required': ['host', 'user', 'password'], + "order": ['host', 'port', 'user', 'password'], + "secret": ["password"] + } + + def __init__(self, configuration): + super(Couchbase, self).__init__(configuration) + + @classmethod + def enabled(cls): + return True + + @classmethod + def annotate_query(cls): + return False + + def test_connection(self): + result = self.call_service(self.noop_query, '') + + + def get_buckets(self, query, nameParam): + defaultColumns = [ + 'meta().id' + ] + result = self.call_service(query, "").json()['results'] + schema = {} + for row in result: + table_name = row.get(nameParam) + schema[table_name] = {'name': table_name, 'columns': defaultColumns} + + return schema.values() + + def get_schema(self, get_stats=False): + + try: + # Try fetch from Analytics + return self.get_buckets("SELECT ds.GroupName as name FROM Metadata.`Dataset` ds where ds.DataverseName <> 'Metadata'", "name") + except: + # Try fetch from N1QL + return self.get_buckets("select name from system:keyspaces", "name") + + + + def call_service(self, query, user): + try: + user = self.configuration.get("user") + password = self.configuration.get("password") + host = self.configuration.get("host") + port = self.configuration.get('port', 8095) + params = {'statement' : query} + + url = "http://%s:%s/query/service" % ( host, port); + + r = requests.post(url, params=params, auth=(user, password)) + r.raise_for_status() + return r + except requests.exceptions.HTTPError as err: + if (err.response.status_code == 401): + raise Exception("Wrong username/password") + raise Exception("Couchbase connection error") + + + def run_query(self, query, user): + try: + result = self.call_service(query, user) + + rows, columns = parse_results(result.json()['results']) + data = { + "columns": columns, + "rows": rows + } + + return json_dumps(data), None + except KeyboardInterrupt: + return None, "Query cancelled by user." + + @classmethod + def name(cls): + return "Couchbase" + +register(Couchbase) + + diff --git a/redash/settings/__init__.py b/redash/settings/__init__.py index 404b2baa91..6f68c08f8e 100644 --- a/redash/settings/__init__.py +++ b/redash/settings/__init__.py @@ -168,6 +168,7 @@ def all_settings(): 'redash.query_runner.google_spreadsheets', 'redash.query_runner.graphite', 'redash.query_runner.mongodb', + 'redash.query_runner.couchbase', 'redash.query_runner.mysql', 'redash.query_runner.pg', 'redash.query_runner.url', From 54e71343e6c80f2543ee7f1c771131197a2b82b6 Mon Sep 17 00:00:00 2001 From: _AZ_ Date: Fri, 29 Mar 2019 17:10:01 +0100 Subject: [PATCH 2/6] fix style --- redash/query_runner/couchbase.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/redash/query_runner/couchbase.py b/redash/query_runner/couchbase.py index 468c0e4d31..4004cfd7b9 100644 --- a/redash/query_runner/couchbase.py +++ b/redash/query_runner/couchbase.py @@ -26,12 +26,14 @@ datetime.datetime: TYPE_STRING } + def _get_column_by_name(columns, column_name): for c in columns: if "name" in c and c["name"] == column_name: return c - return None + + def parse_results(results): rows = [] columns = [] @@ -62,8 +64,9 @@ def parse_results(results): parsed_row[key] = row[key] rows.append(parsed_row) - return rows, columns + + class Couchbase(BaseQueryRunner): noop_query = 'Select 1' @@ -78,7 +81,7 @@ def configuration_schema(cls): }, 'port': { 'type': 'string', - 'title':'Port (Defaults: 8095 - Analytics, 8093 - N1QL)', + 'title': 'Port (Defaults: 8095 - Analytics, 8093 - N1QL)', 'default': '8095' }, 'user': { @@ -107,7 +110,6 @@ def annotate_query(cls): def test_connection(self): result = self.call_service(self.noop_query, '') - def get_buckets(self, query, nameParam): defaultColumns = [ 'meta().id' @@ -124,7 +126,8 @@ def get_schema(self, get_stats=False): try: # Try fetch from Analytics - return self.get_buckets("SELECT ds.GroupName as name FROM Metadata.`Dataset` ds where ds.DataverseName <> 'Metadata'", "name") + return self.get_buckets( + "SELECT ds.GroupName as name FROM Metadata.`Dataset` ds where ds.DataverseName <> 'Metadata'", "name") except: # Try fetch from N1QL return self.get_buckets("select name from system:keyspaces", "name") From 95c80f11561a62ab342e3a294b43685b416ec5f1 Mon Sep 17 00:00:00 2001 From: _AZ_ Date: Fri, 29 Mar 2019 17:12:42 +0100 Subject: [PATCH 3/6] fix style --- redash/query_runner/couchbase.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/redash/query_runner/couchbase.py b/redash/query_runner/couchbase.py index 4004cfd7b9..98c53d0f01 100644 --- a/redash/query_runner/couchbase.py +++ b/redash/query_runner/couchbase.py @@ -128,21 +128,19 @@ def get_schema(self, get_stats=False): # Try fetch from Analytics return self.get_buckets( "SELECT ds.GroupName as name FROM Metadata.`Dataset` ds where ds.DataverseName <> 'Metadata'", "name") - except: + except Exception: # Try fetch from N1QL return self.get_buckets("select name from system:keyspaces", "name") - - def call_service(self, query, user): try: user = self.configuration.get("user") password = self.configuration.get("password") host = self.configuration.get("host") port = self.configuration.get('port', 8095) - params = {'statement' : query} + params = {'statement': query} - url = "http://%s:%s/query/service" % ( host, port); + url = "http://%s:%s/query/service" % (host, port) r = requests.post(url, params=params, auth=(user, password)) r.raise_for_status() @@ -152,7 +150,6 @@ def call_service(self, query, user): raise Exception("Wrong username/password") raise Exception("Couchbase connection error") - def run_query(self, query, user): try: result = self.call_service(query, user) @@ -172,5 +169,3 @@ def name(cls): return "Couchbase" register(Couchbase) - - From dc2f936396244b32ba697f094a8bc923b9003c08 Mon Sep 17 00:00:00 2001 From: _AZ_ Date: Fri, 29 Mar 2019 17:19:24 +0100 Subject: [PATCH 4/6] fix style --- redash/query_runner/couchbase.py | 1 + 1 file changed, 1 insertion(+) diff --git a/redash/query_runner/couchbase.py b/redash/query_runner/couchbase.py index 98c53d0f01..a485a763af 100644 --- a/redash/query_runner/couchbase.py +++ b/redash/query_runner/couchbase.py @@ -168,4 +168,5 @@ def run_query(self, query, user): def name(cls): return "Couchbase" + register(Couchbase) From 92f198e604774957b9e3ae9b595eca834ee16bd8 Mon Sep 17 00:00:00 2001 From: _AZ_ Date: Tue, 16 Apr 2019 18:57:07 +0200 Subject: [PATCH 5/6] fix naming due to convention --- redash/query_runner/couchbase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/redash/query_runner/couchbase.py b/redash/query_runner/couchbase.py index a485a763af..47731a972e 100644 --- a/redash/query_runner/couchbase.py +++ b/redash/query_runner/couchbase.py @@ -110,14 +110,14 @@ def annotate_query(cls): def test_connection(self): result = self.call_service(self.noop_query, '') - def get_buckets(self, query, nameParam): + def get_buckets(self, query, name_param): defaultColumns = [ 'meta().id' ] result = self.call_service(query, "").json()['results'] schema = {} for row in result: - table_name = row.get(nameParam) + table_name = row.get(name_param) schema[table_name] = {'name': table_name, 'columns': defaultColumns} return schema.values() From ae8756e2718dcc799a7dfb12e6bf60a60cdfae61 Mon Sep 17 00:00:00 2001 From: _AZ_ Date: Tue, 16 Apr 2019 19:31:17 +0200 Subject: [PATCH 6/6] extracting protocol as parameter --- redash/query_runner/couchbase.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/redash/query_runner/couchbase.py b/redash/query_runner/couchbase.py index 47731a972e..f6e839fb0b 100644 --- a/redash/query_runner/couchbase.py +++ b/redash/query_runner/couchbase.py @@ -76,6 +76,10 @@ def configuration_schema(cls): return { 'type': 'object', 'properties': { + 'protocol': { + 'type': 'string', + 'default': 'http' + }, 'host': { 'type': 'string', }, @@ -92,8 +96,8 @@ def configuration_schema(cls): }, }, 'required': ['host', 'user', 'password'], - "order": ['host', 'port', 'user', 'password'], - "secret": ["password"] + 'order': ['protocol', 'host', 'port', 'user', 'password'], + 'secret': ['password'] } def __init__(self, configuration): @@ -136,11 +140,12 @@ def call_service(self, query, user): try: user = self.configuration.get("user") password = self.configuration.get("password") + protocol = self.configuration.get("protocol", "http") host = self.configuration.get("host") - port = self.configuration.get('port', 8095) + port = self.configuration.get("port", 8095) params = {'statement': query} - url = "http://%s:%s/query/service" % (host, port) + url = "%s://%s:%s/query/service" % (protocol, host, port) r = requests.post(url, params=params, auth=(user, password)) r.raise_for_status()