From a3d0ec40eb4286dec8e2245561bbd31598177339 Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Fri, 4 Oct 2024 14:47:03 -0400 Subject: [PATCH 01/44] DOCSP-42308: Deployment and connection string (#52) * DOCSP-42308: Deployment and connection string * add file --- source/get-started.txt | 9 +-- .../create-a-connection-string.txt | 65 ++++++++++++++++++ source/get-started/create-a-deployment.txt | 36 ++++++++++ .../atlas_connection_copy_string_scala.png | Bin 0 -> 128335 bytes .../atlas_connection_select_cluster.png | Bin 0 -> 35481 bytes source/includes/get-started/troubleshoot.rst | 6 ++ 6 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 source/get-started/create-a-connection-string.txt create mode 100644 source/get-started/create-a-deployment.txt create mode 100644 source/includes/figures/atlas_connection_copy_string_scala.png create mode 100644 source/includes/figures/atlas_connection_select_cluster.png create mode 100644 source/includes/get-started/troubleshoot.rst diff --git a/source/get-started.txt b/source/get-started.txt index 4a8d921..e7cc264 100644 --- a/source/get-started.txt +++ b/source/get-started.txt @@ -13,10 +13,5 @@ Get Started .. toctree:: - /get-started/primer/ - /get-started/quickstart/ - /get-started/qs-case-class/ - -- :ref:`scala-primer` -- :ref:`scala-quickstart` -- :ref:`scala-case-class-qs` + /get-started/create-a-deployment + /get-started/create-a-connection-string diff --git a/source/get-started/create-a-connection-string.txt b/source/get-started/create-a-connection-string.txt new file mode 100644 index 0000000..e18462b --- /dev/null +++ b/source/get-started/create-a-connection-string.txt @@ -0,0 +1,65 @@ +.. _scala-connection-string: + +========================== +Create a Connection String +========================== + +.. facet:: + :name: genre + :values: tutorial + +.. meta:: + :keywords: uri, atlas + +You can connect to your MongoDB deployment by providing a +**connection URI**, also called a *connection string*, which +instructs the driver on how to connect to a MongoDB deployment +and how to behave while connected. + +The connection string includes the hostname or IP address and +port of your deployment, the authentication mechanism, user credentials +when applicable, and connection options. + +.. TODO: + To connect to an instance or deployment not hosted on Atlas, see + :ref:`scala-connection-targets`. + +.. procedure:: + :style: connected + + .. step:: Find your MongoDB Atlas Connection String + + To retrieve your connection string for the deployment that + you created in the :ref:`previous step `, + log in to your Atlas account and navigate to the + :guilabel:`Database` section and click the :guilabel:`Connect` button + for your new deployment. + + .. figure:: /includes/figures/atlas_connection_select_cluster.png + :alt: The connect button in the clusters section of the Atlas UI + + Then, select the :guilabel:`Drivers` option under the :guilabel:`Connect to your application` + header. Select "Scala" from the :guilabel:`Driver` selection + menu and the version that best matches the version you installed + from the :guilabel:`Version` selection menu. + + .. step:: Copy your Connection String + + Click the button on the right of the connection string to copy it + to your clipboard, as shown in the following screenshot: + + .. figure:: /includes/figures/atlas_connection_copy_string_scala.png + :alt: The copy button next to the connection string in the Atlas UI + + .. step:: Update the Placeholders + + Paste this connection string into a file in your preferred text editor + and replace the ```` and ```` placeholders with + your database user's username and password. + + Save this file to a safe location for use in the next step. + +After completing these steps, you have a connection string that +corresponds to your Atlas cluster. + +.. include:: /includes/get-started/troubleshoot.rst \ No newline at end of file diff --git a/source/get-started/create-a-deployment.txt b/source/get-started/create-a-deployment.txt new file mode 100644 index 0000000..2de8e02 --- /dev/null +++ b/source/get-started/create-a-deployment.txt @@ -0,0 +1,36 @@ +.. _scala-create-deployment: + +=========================== +Create a MongoDB Deployment +=========================== + +.. facet:: + :name: genre + :values: tutorial + +.. meta:: + :keywords: cloud, host, atlas + +You can create a free tier MongoDB deployment on MongoDB Atlas +to store and manage your data. MongoDB Atlas hosts and manages +your MongoDB database in the cloud. + +.. procedure:: + :style: connected + + .. step:: Create a free MongoDB deployment on Atlas + + Complete the :atlas:`Get Started with Atlas ` + guide to set up a new Atlas account and load sample data into a new free + tier MongoDB deployment. + + .. step:: Save your credentials + + After you create your database user, save that user's + username and password to a safe location for use in an upcoming step. + +After you complete these steps, you have a new free tier MongoDB +deployment on Atlas, database user credentials, and sample data loaded +into your database. + +.. include:: /includes/get-started/troubleshoot.rst \ No newline at end of file diff --git a/source/includes/figures/atlas_connection_copy_string_scala.png b/source/includes/figures/atlas_connection_copy_string_scala.png new file mode 100644 index 0000000000000000000000000000000000000000..56cb97e1c5cc2f4becf48386484803caea4adb83 GIT binary patch literal 128335 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⪼3*zG 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%0A%Q4QS7-@ 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 IzW@Ax0I5#`!2kdN literal 0 HcmV?d00001 diff --git a/source/includes/figures/atlas_connection_select_cluster.png b/source/includes/figures/atlas_connection_select_cluster.png new file mode 100644 index 0000000000000000000000000000000000000000..52d827d67bf9a46e30671df001d144f4538c4695 GIT binary patch literal 35481 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

4N?~)Xc)h9V|#8Xnx^ocodOvO(h zXH!0&&gY#g6&x42HKRI};Zjhv3V^!O=y%et$#)G^r625mwXP(K&FA%r5gGI+d&UBy zDA+1`&%#{bcR~iV_^kLvb}A?s0g=^Ky)r27jp)8skm@XsZTovOeS8CSPgoP}y+C?Y zWaRF3mdrz+d@T|bzCNG?h0x$t&44U?UMACvxy0})ePTk{FF_$F(*S_;B3!1~=2wNx z;hRT7X+6*EFL0$~D*ap~up|_>TzJX}DSHR>7qls!qOI)PPk0VSDJwpw$mv^A*(xQ2 zba+WcZe-u^Hka(iw#v%*`iM`)sRyU7+M;YXaoJeEPk(o@vBvZFXC4xjRiG{|sUCe^ zd+(M)QanL`U#?fKs;xun?XCQa_vr_hYx`rL235yvvwvgz^7Ek3sY#304gT2jU$v@r z7yjcCSL+dQ8XRSDDD_*RMv~P01^c908brThdl=l?bod7~4x=MzXw&Rp*uMM%D7d#> zM=go!MqObdm)*83Y@k;3o$q2(`rOQmI&VWKum+oYahue}=Ju)7o;dYeO>MSqZjLww z3k~(QYvL=nVePnIG2R`WZP$JG1FHo`47H%6)YiUcuhmGy%)70;T7DfHF zQ$zaLliS9Itu}!q-QoU)?aQx#BDXfBIbXLZrH%Nn%dJms8cBOPHcqrnlblRsY^-f< zudPp~Cc_$yc<$H zzMcPKd7s~p){u#wK(Q?;>OQ(Ju@t$y`|{0_0%m`AG!4`^;!3oj*&&tJz5jzCBHI8! z(%d_!*nI?;C?eS-li>71M)a~A2_tk`#IM`A?Tsyk&^98}c1FhkdhwjJxKH7q@jm$@ zR3ln*?40kCKIA|iI5{E1u&yD$sW***@eO;Q(ywS`TYaA}GL|VEeU^L)RHFHb{2~^FKhMGpIaS(^!3U zgq&(2Uu>t__uTMhY$Mk}kIc2>9bQHWj0B2VcV*<{(e!ZY_&&Vc^5?k2;>T=?U{Va{ z^AK>69ZSLNwlMdjnQP8Ru*Jg&@Vx5b5)Ab2Na5=gcD7`Z2UKW1)5Km@v}^9 zov^;%#ud2z$J;tDVJ~i5!;o;yg2&7e6v}({6{G#KZE=Rp@550s5C7}f*~j-k`wadO zP(V{Pqd!hzIiPoxJV@sJ2V%`>{dhW?6p}E_aDq%E8NbNR(-IWK%-vEi&~YRpBqHQQ z22NxngoXGBC-`}riDhTjIYxol*cKGZ9S2z^@FEJ7a?Os49|1*};6POIan=3#bTK*x zC-mv8Np#S1JPj6=dyBjl0Tdc`(g0y+WAF~b4iOY@(5Mr7iUM&MGI2uBBu}J+N$YcvEW_b)wK@i;lcbQKxZ%V{P)r1hk%R7N z2o5s811O5H`a>0Zz=Bg^gNX*H;vh!h*!NQBdT_eR*O^w8IR0zL(4E=io|k0m=`q7T zg@4BT)CHp=ND6Jt<)W_v45!m|xSYbu5LP};7tpEz6hUJ;o<0tj(+NOvJq;(IyH8`oixIi%<`%($tj1t54Gx?Pk2E#P1*f1=-t{@12yZLR2pqPR+ z4nP6A|9{xK5+1jC9Vp4x6)RE_ErK8@lK=lNABU2U9J_Vjw{03!Ds~;8*#aDMD3QZM z(QF1Tj_L*#UwVqNNFgf#^!%3p^d0#cnMq$WxSNTJ$;RZO-md-p)^aNlXiV;aQ`8jMB`A1{nJ1z~$0C`(8_+)CI0fQ7CEEiv zAW_$G<(oiZ*MowFCOCwk2^rxI6f)BAHqFmqww0dJr!G9axfvF6g$>g4lYC%9$%~cA zN}~R2pm?FDK$vYJ)+s)+HY!jU{ecMMvfp=P^!pzl9~K&ROq4!!#(Kt{Hky966kdQw zIeoLF&1vJus`0_e9VPRrG-92WwkKKhrTqV_dd`8iT@4D@o+0YwbRb}C^BsXonMQXH ziY`C3>y&04j3`IP;$6%?H&!o1j`uJTS&H=;e4THgc&(>E3ZLc*2dqDk`4byrg>95V zDAOrJ1PdAjh%Ahes>4+V+Vc3Ph;B)}T*NCmvgSBvai~cGq+(+hu&J3Jce+xey7SuE zC$bGbMW8@C2e);3%i4GgJMa87fizFD%KVf*g@vu9RC|cgZ<$w-{Vbih<5OAswVm(=DH&;5qhYoSY*i#3vKd6*3)p)>Say_91{Yu zb~pUO>=Vy9ZMUpunTbG!+eL~L7&pXhy4cYUn||O|Karg-B_E0@`;a1lU};N)3!G8n zV32`41M;6$dVAkxEW6MW)TXLt%3QYRdUl zghSEUlv8pyksoeA?X`@C9%j`?pVy8$ApGvro5zn#)wc856tv z&&;LzhP=q3V1#yvuG*pfrQMbQO{V{+iURot7%Ep#{&e7!*ueR4QN)}9EhE_a?U#1B{* zzSC;njS&~hZ0YC~FrWe6?@QaS{OB%LYxA?JvA{BxM(WeL# zjxy8hp4^7ZLHG!|L(f3QaXmpGh=PDY`*cd+LORqjGost*3~?-aHvQN{z=LA~#0XgU zyogEe1eqHs{?IzbC{;5#iaR?m>K*gW%`a;|L4~XZ*W74wKzK6Mog#kWGE~&6@+D){ zkt#>5;*CMw-TIgU%M^(xV|75l&2vuE|1*c;N+P|gIhMd18>Z~Rqc97ap|yq}**X|1 zwjo&$s+9r(EhaMeLixK?;sgQ}u_c+|d)Sh@1PVo-0uanV55AnBlqFC@*i0aD?5HSk zU^46sEOqQJGCF*>xX!Pk%2iiX!e!;8!DB2-!B|e_rMe)hN3V}5WQAMGE z^9NbIpf55uJdF#Hwc@7$2G5EH1PU+K&pvTfk++v^##%*k`sNLrN?Jbv$FqJL3abiD?qW(s4>&3^@_ntP8AQ5dDbZN zQAE#Vd0>$_lc?ykwG_O7#!91+t?1Y0K6#eorgt}%?ux6hvpw+(-!n4-JSk!GXJ75GRe3#T6U?V+GeBmLqjTG6W_;tUn28!PS3UjAwb018YY5J+8HO*B; z^)A?pRhxkB0TKuhO5W*E{VeyXM%w`uBKd+` z4{O>M{`VpsUl)8S@NnlQiM5{sM`o))PddrNVtg{gykD7KcnNlT(Yeq zRztG28n?%KLnKf{>&hW~u`Te_6%_b-L@V?_rgC%--7n}b>T>7nQbz;Dp9Do^Fr${t zXQ1%Yr>SDq*w38D6up~TVo{;s3p28#ax4Rf(Z!$2A7MraA#horC0p8=_}W6(!dKiX z3VL!KbxNlsY6D&c9t0jY? zgbD?p{wBp;f?L%;!MZt7R{SIrv4P?>F~y_-C|7eQ`#8T_eSx1>Kc@PN@w(Adm>QEj zgz)l{a#T(TyeAnFsCZ@3k*J`!KN|D5XmFHV_y&rXg2MhfB))q*1qEl&CGTtJ#_=!( zB)PoRUkM7I9{=2_boM`ELNrhDYEam}6BKM5N!%xbix&&ujIHV(Pe}JYrQ~I@teJ2F z#Z&S$vWS~jcF_KKamn^mhakkaxi11<14RSHbCTj*hz}pXpQrvWR*TJdktlJy}r6Kg0ww?EOQUa{XIDU}^ zKGo(eYV=G4MH_Diim@WDR4~BywKUS9-Ce1YljzP;>Oy^Ov{kBzBwx=Bz{(yVz{PUW zl@dGepUdJjP_*&)gQ64waq*}}y*;;?m!&F5IK{=)LQfrpC4<5l=xsP@P=gJTKyH$U zp7dyBG1b*IP_*%OpfJ@_6n#K%xMj9w6EF3sk{i5p2UQJeBbLPTLPnz$J{nDzk$dq1 zZbLg;1UYKw;6|U?c)yr}V&vjhS=aX{L$uh;tC;>1aYpK=HtAUJP39lj8&s~E- zqy|M;(5#9%K*Jvz{k4^F14SF}2MTT{(Qg)`H+AaC4E^9MI`$ND9(MR_x$zJpEoK} z*Z?0gpwIjHkbCqrJsuTyubV4coGKh+P@K@;@pRZ^h2Ge76)rv;ddS1d7HGHehM!TYk2gYH^&Vbd_SP+U{KhU<==3UY&ZoW~ zwD6dmZJ=o5-9RBNYkPw}!Bh0`0E6&C^BNuP5G-(Pv)L;qN{7RdJDb8u5GXG6xi^@U z(u(O*kF&U`u2?iswDESH!eH^u4-^wzE|)~{GkFSM7O}nF?M~+dd4luieBKVAPXq_P z=6X2nfD-tJBU%cpVZ4f>jPvHO)8uZPZ1ky(_XCAS%7zDhD!qmfQ+R74=lq?{C+13^ zNIgR8>j^=&qL_g~X{Amhm+DVHE=-^35ts9hNY?+@yB6j~bscPzm2KSwxGb63-ARVi z|Nkqm9)6LgE$P`pIv2MIc@S`jKTDq^OC&0s---6#zN7jiZfN-Le|{!E1*tW``N(Pp49GUC2^QsFZ=O_Rx>0K6IDD23}75*KMN zvFS5`h3>)o1Ox#7h~q;Vu6n#DciKQwu$vB=AOFlj!9+v6cd|5MUJlZE_i9a@wWZJ< zt6qPVUq1*6N!W^Or3#C3qRj0`QZ{9i1&k6x!0_j`MQ~(+;e7rA0*V0p0NsdF@Xr>S zQuLjzt3tD-hAAwl)`HbO3?V=dTY(}e9T-RES7!_aC~A=0IX*jGHyY569-~NyjDuhN z_lxf+tn*4BY>o*A;Hx?u&oEO;1q;GRE--io&;TMUC2PB2&FE(*d0Zrat zbjSao*vLscZQxK!0H+e-B7peyP7`3Tz$ifD!#eZDo!{@?`cYN78>2s^9T#3W0I0OvZbSxUT`xpgcD9P4C9rm%=oyavfypooe`K!${^8OB#b zB0gjSmQtUis!cNuDOsGPnP`dnGuw;VMQ}6o)!w!M1v<&X;Znw?MwVhroQG>t;sFaE zh>2U3$*v8GsEZry4h#u18?Rpv3N~(g=GY)&f+0~MA{m)Xk|zO^RZ3N?87xfLv+ZvS z3Xu<*#0I0fBrAg|S@AtG1ag@#qPd4|RkwG+gz-+SsD#T(N+c2;ZAtk9VjvUUb%qO7 zqSPGAb?bLnY~;}vHsNsAzI4L>5>Ui?Mu)XSLE;d{;<%ncLd{A66a;fnFmH$iP;bNt)cIKB!LGIj(LSVr7>4N7S-yoH1~wF^UVt_qBr zmn18&P*j8l!VZe9K%p!mF^ZHFEy|5=!71XvVoaEoF`*Gk5S{4NbgxXpvK4lus} zrj#|NkC#s)0=2Urd2nl8sG_a?yu zXQn0bTze7Ggu-$&>spzt}fCDZMqNy#X5-+^drIK(EHDC{5@oT)!3qu)W% zmLSC7a{hxGtE)!K7F;nZcAM;0plDLiD<{4zs#Brb-vAUr{1aVB!*j) z9=nxIPHYEOhh3HvFL5;2wZVwJzpJ+b>3Vb?H0EfSKYIa%&6c`;4(H&P~BAJ`O{2N+P zC<+IB>=Co;ONz(Zma!lAKoJE+x1Q-q#(Z%=(D^Glq3>$g5`K>O3+OV?7!_g1#LAq) z=}J_$v^?FDfr6C{c4n_8C`3nd6(}$h0F04Myuw=i?4O{=;^7Y2oW(~~uip-eRZX(8 zprU~etnUKdfs{v;nvUSLPOz7{UA%&O%YgFNpopd?c2cZ(%}mH^PaR-16^WdP;T(|Q z@EP+{5`UMZKuNGiW$;?(OJZ$$)_?mHBbMF&p%|!_UKg{Ra~itf7F<6{Yk90{N}+}u~X7N+SuIN1@avih5;GuZ0O)R zNb7ll_PM7QtW>iL&VnZT01_e81wsVSJ#CzO5=jA^c;R5yo2aUQZ=w!G_b26_n1rh= zw#+OZJ-!d>4hOG?7FH4R!tE6?Q66vycoUu?m#;B6?<6kimhQ9+(}o6`4Gn3*!`RS< zlV5U~Q>QlvT=mpSW&;4ri}M>B_!a*opa8QnD5^+T4)3@`k}ai@!MWPCCjlCtIzA@{ z@$uL%`A1)sSbz*VQ3)}^6`mnwq2W$p2&rrE0LuL`RB%R{09?$JK{1Wf)U>Cj8PDf_ z>VdYcZ2R^+HBHm@?Z~ekBvY(=3Q2)$ONwZXmFY9nVND4e%s91wq0uGhoSX6B7yLU% zeY6u}HOJDe-=Y)udWA_wFe1kB!LPFj6jI!aL2%YRhI%a>a19My^97ptxR1 zg(7Cbj}OCmuI&%r!qAd5xgMI4b*m@g6#RwP1%+6ii2ORdm8nO%mLz+F z=R(XbCFEV+yzjgr@-|L9Uu9N-9C?8B=iOROI{^^G9;XoqA{O1o&2z7kRtnTBqPsMW zgpWQ4*74U_C@DDJiS;Lu6fHmK`+-2xTqoU<;-oZuQm~wdbN^)O(;`sRM77kD2q{O@ zU3qY8)2l!MF2%EoCz~2g@G_S>wqqmakQlabzj>n{%UQ8<(^H2o$^tiz*(}STP&i2N z!Fxyh@J#Nl_g#D+V`l^wZ9rcepV`)~m@f2^~=*pR~UzTyvq)1pI+Cbx<^rE3Z3|#wen2vEvU^pHR2YT2u z`{>WZ0k((@d<6tPC#swmQkRHVCy*PEXdae8s#l}3+;O6M{ty(>H z=N9MF6b;S)mO2q6kS0tg$%ltIRiv(02Iu{|=kzj1$FD6$8-7u3H>l@IYG%G1^UITZ zox1MulxO_uRIU4&xuEN##Rgu#&O%VgPSG{tAGYkB1oB>kz0dqiQ8N`X{~;onHUQj)TKkSm*=v^EbgFB3d< zA~yPJP({AQay*h?_1*9Xkw1Awr;F>Gt)!$Zf!77taTu5Ha@%WXP3#r$1+NRIl-unR zIXw=Rud@UcjCqn2?m7{GY2+Wz!_ZzwR+VH+418eF480@LO?2D3Zv+a7y$(=k)iA;2 zGFrY`N1b3%bgbf;j)?{!FF`HVAJZHnNA!D;@E`&r-N&p|2Ck|1ql%#=&E%P zg;&(Yh++Z`lh{V`V&`1oG1*@lv1$1!gcdB8X>Or<`G%wrP?#i(F0Ra?H^KWeJr+L? ziaT%d@Ke8CXfcKUWDwcXqYqXZ%;FU|k;+2d84>*enCk~fQO)O6_@E&rn6Eqxt) z02ExY$Z5K98!V|!<9NQ3IyLYXI>$~uU}^+TMBgU9yU&243Xlm^4yH8zK?9i}l+X>6 z3p$z`ifvN4q6mvxP{{6W6IFL#Qp_!gFUEVIXj&mXmzR_Vsly*{h-g= zLzGWhV?l-=)2~(|;B3l$v=o4t=~YQp{7SW}ds?q#sMmaI=LHltg5rPFT@7=iIuZm0 z2`D2l1)<(mF!%odue|Oa35a9oHrcJ(UGD}P4AK~;=SwpZq|n);`actG02K9C%_mZ# zRHwR!2^H6Met~}AdnXM+58pE*q-NUSACeIG1E9DMHWkeyQ265PV&4}qyr1N9_b~E+ z^PcJyT~j7dJa{@Mu6Xx#`w$c%D(O(O7$_*a8)nIX3GY$%9supb2_7FVP4yfUp6r(f zw_~p1-$Mv>nH}Iy|7E2qFNAK(>9@DJ+s9Z*mdKsOAy70a{`6*wBF|VCl68J&h4yZ> z9IZ+9b@SG!(GphZ@o=;`m1KEzm!Rv`YZ4CL~DVrk4_SJvyC~h znn2OoJ*-9allp3X6^pNdA}ErriyIk9HN_r32{1A!+q$6mz{jTiOF&_apO;iT=YDg* z(s9+gYFV7H6T&H6~5*S12fad=~rGC2e+nZX-P0Ip(1LFbhd&Er-$BLtr{#fjzc z)A-}KibGSv^B@V9|6$5WfD$cQdKc|V{Llz#WQ;UiCJ2T&%?{m-hS}FF-Z6hw;o(HL z)%BRwmF8S(52^%Cv8JpTIu5N2KXgJP{{twRFM?t>`PwH@3j0_7zWMpTWj+}uhBuqX zvM`6)RbX$uDtF^XCY0CW?o1i>%kAb=U5AIC15$3$6*t$kO*mw@C3dsjcu49%t5$hTevads}vpfxo)hLBSA9aSY#qLp|L1VG8tryXj?{(S|Q-CX|31 z0(hXbj3wnd`)Q!%Tc*(mv>%!o)`k+CL7l7u2358hfDjY0BA_?t5q&%b1zgHung-`o zN>Jbs-WnpB9B(!Ri7}I$yz2Y~SM&}acpK2i9Nn~bwAIx!e>q{v452?%`)akweu_K#|S7-?);?H#CLjiy8DkMTt~CElk{Q6#Um4 zHHBoE^8^7A;?hCyu?IE6PrlMMudjrJ^8#qPAQoiZka26{*aUt0sv{DGuTR z56%Q;@L~*xy2`@jcL13l+!pt`}lbr=CN0zKe#4Xf(BW#EIHPS4ySSU zl*Uc&7cU$lR)_)9HXhf`BLnWS^B;i%B7IQO&*EDyGEJ@5P%VMmYvQRE zL#k$z3yKzj#Ecr}V%N&ZUS}K|JaZ>WMlT=M5zizNSV_rOjol#p@O=vKdr)M1l{tJR z8szO2B0O(${qvxxO%1fq0^02lAbfz)sb&!p`2Y>AK3QZjnPVGu<5N^ zktSS_5KqYXy0@ITkD#FO)s1lg==3|(G6~S7!8Jsxv#K2G9j?y~Vy0nQ$0_#)P$)ry z`?}Uka%`i~Yd0y~ub^USUnYz011OFF7(+r3V0Qb}Qh&F6^*2HBi5@6_|5{()@hOUy znBdrO`(~5*VM9ac>gYIEGNepf`~!c%tg0%RR>@wfw12~1WjcfrsP^9!kTp=y{)*WT z!`o$MG8ts;*m<|jv4fP3gWk7kQgw=|Rlq!5oVORZtz)#&KXW(lKv9$qJV70S=Ef;Z z5lo|Y2NuBKy^j-oV1Q!1xK*IIyzAF((-6ls4kvnIH$^ls#F-4P(7%;rcb-a-OnSyO6XHcXYprgHRz=oTH@6UAW zqA5-OUEhNur;N2Ax?w>OXz$QyaMPwh>8}CBAN=~3rf6+l(pCy1&0{7%LXla>mc>9p z=Yq=MrhRIPTg5#plWR7%D(|^v%Wr$^X)isEdmaV^-MS`04IUU*mio@JBE+%eS?tdc zpA@7x(#^89{64DtK~n?(91N+l7Ftl$Mbrxr+?Q#Dl|D`YD6lxr+W5=6ew`oZiCs^2 zeDXXFRdzA!W}uZLFoiuj@IX^QF?DE&nQ^>sGwQ|Tv;J6g18OcJ4lWfwG54DK!`sQ1 z42qejoNoY*qyGtHDw$8`wv6^7olOI z!P)<5B5{{W#@;|L$7|Fp^^ia@sf1_00X~jqS>4fHS%blnf**pSRX)4N*<(k!FuSss z6BGQ;YKm}|cljW7ZO6}Fr74pA^cGYc1Wt3}a`5Z&`rm5;#g*QIybJD;^u8RS2eY*CO+Adx@CQ!tD1Lpk(c%$1Tx}Ey626H*d z1M|tKErE)TQ*Jn#WWMisJL;bR#q%I*+n%%bPa{$7-{Mmc6!@Fotn2~&YpUXQ6$0E2 z3IgROd+8=n@W_Ib;A&$pG>+FSipj!O%9`R-p%g90NAd%1{2WO_2S9^;Z09TC_Inw(&sFT4!L8d?TyMy95 z?oCq+K8g6sgu7y-e8BJGJSo=f=~GnL;5vr1oH_#}R$`Mb0^p8zyQKqhz6`Ut@M^5Q~|9PLAByQEC;XCsX78R)B^c$PV`=k3?$ z$W!~rfZ}saA2~Uas}^md~LdQMy|m+Ti+h|DI~@ z&d3k(2E~Q^x`ALeRB z*}w_8+@!WOupfklewm}4w0Zp}tRMV6O`%D=ZAiQ33cg+uK#Mco7Mf6Vm7-3!>SAwB z07yf8ix(&wZ>YFdnrZxVP(Yye(W3KI3(2we<%={0t>4b6GKG;NI;_wZ;AOpI?5B# z9>L;5lhTnMfPypU$uJ^{hcX!TQaK&NVx)!#Qs*Tn6;Kdi9Yi&MY8t1oQ@Z^qJ%7J` zq+1Q{Ci}hl^pzBrUkHk4dCGsuIE7}LRmnr!gq#%%TvV`+oNs(Z=fYKRTC0kOm;bSM zEzF7IOi*%GuSzhNGF4YsNm2X%f90p{89i)FmL>VXyTNyx5bz7oXl69sJ^!#$_v4@Z z{EzzAkdJlTYj7-%uNg(HfD&Y%=jZ1f(U6V8DnQJ60W0PRfV0OMo}Q$T@WRy@6bmZ% zE$y`1SHnIDR20R9h$;t!PO;|68UqUeAcwPfz}gjWzzMDs>MJo%ws9DqvGXYVk3e8p z9`3M+IXp8*a5kM9g$_y-3Z>qVPnSJPe(>a^%#qJDpeR1%yBh4X%K<*17JzxUy%a|s z?ugg3eBx>3!~8zH7B~bE^5R+OE+&N>RI3 zjc~;W4$-)53LeTF_;v^ZbbMKskpPvJ|p#vS-1df4@G^G9AqQ8E<{#Xu_#yvZx~T1JQzqM<5G^*jX#tW_c}pg$UHEk?R_(z*B0%M8WGqN!W5sLgxrG ze2#wrSY7iJIwrX=g{c8&7rYg&$IBpogScKK`YDH*mZL^(D8cseq2#NNK>%QoHW1Pp z3@HC0B#@*hgwk>TU_uA%yO~k=k6TnI?5WY8I`zA)dy1;D2#1hV1~!^b^Ob7UjaH|f zfZK~g$iNYWyXQ-=uWX9WC~ER$1-UKYX=@c$O-ex&Y<7})l%vlHj>g!+Vol&vRK1Y5 zG-zOkQDhXQO^7B=f+vtQArNCElSh=Xh)N3Ah^}7o&G|QFl8EpI#tAosIbY(q!caQn zDT38kX-F_=UAUCX%W%dZmi+=aI2ieY!3I@{pju~T7a<1WpFrvhYTVZQRy6OYjUxHq zFbbz4BmznZKwWa=$9gN6QGhjzh#EyCNytkRt_?Jhy(Vdl8hD1_|d z0Ff7Y0=V43sl=0l;h5UEl#_)BmY8cGkD>|Y zm6lQd6K|w}5znr3tpR`a}^7tOu)y9-xmOlyOQ$NOQ-M)6!0WZ#l6avq_|XZpU9YAXYFsSe_+u3#g3{&IcY z6H1rB^cR1+vm;sh#TSL%Q>;*LPB_v%_h6=7u*T$U(jBvV6)oA63+1%EFxXd%caQ+6{&|T zob#5!C*>r?t{AbZUclHzr)nz%JpHkV`Tc+z!?dpEiH7 zJOwcCd7#5xpd|$;W5luS-d_)SFru{cWB-a;CHw_2mIu#odHXrVNf)5LJ$(6Hs}N;j zcBTOpe4I-{b#&AuX?*q+@i-;#RNS!%@hjjyw2{-ztkTEASIK^IRO7He+ENSo@8=Y) z$W~C!qaGK^bNQnogRL5e7rxry=Q3go3(!)*%6yOW=Ol7E#-JLb{@=;HxclX(X{2sH?7)fdU+m()m|5=gc+oy_Gd%^`OFwp zUJMOqndCH6h1h_p;#p|g5?UXKEBa)F%MJ_S!=3Qa)USbEazvZ#{6+PAdp2#k&?vIU z&)?%aLH&nAZAmd8ry!hdcOVTvXV+v+WuwTuVPALa)-Bg~3RhEvsG=N2V!8DF?X;e{ z{VZXCIW9#rPb4&(7+O~c`D~{|6hsm$BvrpzEj~`Is19XM9zWVe2+KKjc33RSCF6^g%jDfeX+m9@}l^0}<+Boq{3^z`h@lVH`m_-+@eyjC%J9;HX0v zylfOyCjT>5pT7F@sy0#EG`>aO#QQ&gdeN>;U7MuXuZ}&2>@B`zRuDykCk{x`pGGIW zu^Navl67L(<`AvVDr&u?N)-L!>a8WC6SM5~WPt=Z~zN^KjV`dNpt4%Zw zo%k?{+l5c428v7@(2LnBC^smgp!uf}5GSFY!2l3E0-hXH7cqQKPtm2?|1zsE@iQ}d zcN767gvS;M9pCyeJ#6AOUovEI34gUCF}(!%N5Sp_sUHZmX#6v{IHilqt&Q9Hk zr*Ng|MRgS>oiuv6)+pHNqT0mIqzWYNa~F7Qzh@MRNemIlcXCaD5EeKio&XReiW)lM z@Z3Wz#@YDYa*C)-J^tuBL0&tPyG1hMJ(U(p@FaynU)oQ)$B4a&QS{)(-z5#*xL3ty za*C)0aB&s7LIkytt0!_9{Q4-dsaLIm7~@^+DYcL7W=0{sCw|K&ZX7|~-&1tW<-yPD zmWa%iGkG~8HL#(cRdDH4Bk>ASe2mwddy2CN-17*>`;&}r?-X~%u^5Fo24DJ@3Np}Nb2g)nf+o@Ygd`Nc8O~!t_D)9OPmH2iLe(+}_wyH9 zs8hrlSrypWvM$TH-CuC@>Jt!{n#E3?Us%A~AKPt=Vojf@vf-Nu|4Jd_M6hR_^ZO;f z<&39Dw>64-;lkFdzCwCxSKUq5Q=FDrL4D*a*iXw7r8)N_gSfX*u=&B-T2eELUJ|gg zj#ELc6+hy?#J6n70nrIA_9)szNaXca;me43UUh8aEGbI>U%vnV38YCxK~&H@p%TM` zDM)m66K&n;VH6w7Df)n_N&KYVR?75me?z_GFENVQEujB}Mm-qLse(RIuR0hP9-<-$ zT`V0R+ew;y+hd4s(I~t&ocDeq!|1-sdp=nZKFo9^b}QjYZf5QGKXL3ss0_FqV$8AS%vgqW!kObk_oYCgP2Z)szy#4 zI$_yK1w0?A^;V6dC?1AIs(j)n{PLM^VH7&F(>(l<^0sM-He5tIpi8->_gFIt#9!W- zCa3e*Hfj{(aJ3$PlB18Xqv)H1iaO|YjCk0{_Z!9agE+?PKnwRlj0;jHu zozsb6TEg+z?qn3_wLm*L#vT#X>J=%?1F92q`a)l^MWdh>i1S>zc#G7h52izbt0FBe zq8uY`BA60&JrIC*G>TvJrw|qG|Fw4|x{d2FFj5|Mz_tv-28u-kWB>mvABU22G}$)0 zO=CMIyNz=w!2;D}NJ^T4vooX(oX9&z_=N0mAeFK%yDv)1u&Tk4R{03@qe%3g#fe7F zBYkhL`7KaHEZ&(q?Pra;#_f3?lAf0Hj(hwm z$S31%kEn||mG>c<79uxL-An{?oZusL2_fqld^)sZ_9T-z(R8-Y_#Xg;VWx=leXV3! zCQ<7@tC$5Cf{W6n-DUft_?~Ys5HZ!~rHM;f`d_JHu0QFW{uEAZDY_oso3Lw!2Vvj2 z>>?Ok*1!=%HA|^?E)A^_Szxi46_3Mw()%ZvBF&mUg()wZ5~6ch@NCb>c#ravYH0|HeT{_0whnIer4sMuu!czv@2aR-%=fyq#6cB}~x{=EcJ9bUOX;yVO zj6>JisvGz!@|puN82GuCLBSZvDzpvA2Oovhi~^a5gF+moXHB0@Hmcc~o5HmAa54R_ zW6G`Mz@0l|hx0ul`n^3z_vs#g3RkS|M!fuA-&NzmigQbYD^K`_fzk3W1EIAd=g(Js z6vSr-9yE3o5-dJ`3KVmFspS7fk)VlLCSGcb(r&$ti(kj~RD_qhn2*IHKkQ_)Eh$UP z6<_&NQ1Pb_10%RpAld=LdF&t$2Oh)o{$R!Yi=UUPT!5>gkvao4yfZ&kQ_T8}dMu5z zbb*_WAiYmLrjj?k{W_HxxhoZ}tc#CJdo=lJXw1@m`pln#9O%N}F%PS|y5h(b(!=8m z4=Z9&5SJKa(}^i+d=$`zf}-uj3(Wk);@!;Y(*-obR1btuj?%@c-&CSBpmL-3R)%z5 zVJjx)0mWVZ6i#M)t~!u5aY@d<9bm-Qk(mN`XsT-9VRhG6oPF^}?Ky3#X&4<3%1d|; z?>^CDKykZOD=KDXouaH%Om39gUs`NS_6~oFkkcouYu z&V%+C+uOnv6X8R{@K3rr{L$UcyG#LoN*~(EV-KU zYDAqZmL#xR;rOkxh>@V}&-`Qgj>jO_;G!M-J?Z|HF@=OUauq1}(;C>nF z6r!RWa6crM)%lah5;Yd61EHNO5HeWWqshVojV7t90R`MpkTr@|t|`9Aqog_Ik9Nfh ziY2E{U-(ln8jR1o{2&O=!&BMX7v*ao2~mJpKbozf2$ai)ad}%LNzY)p?%=#mTVsmL z_ALe7ys?ILim&`Bj2IWCyusDPgf(!uRp3q(^9t`%5E8-mYkL$!+HYh&#rw|Ro_&ZA^D1kK$@^MUrZs}%@BJz6I*D!Kxw7B}nIZ%w zhV?Rq`IkdL`TX_q5+^ZclW%>z)Wz9k_vyU2&?&dF^yyFj6xW_!XFu1nndS%yG-RWB zmUQ9xg* zh(BK9Y$G*AP&=ZPg5qlS`@h!xD~ohd{^fqwql${PcAvt>Q>8lZ*2NS@ucAxQvl%Lg zS7Qo|)qvvTmwo(LS2(RHG&T+je@9bx!|tdWTLr~ZQK3ws@pMppvUf?DLSw6i_B8!Z zM6q;GC{t+sG$?NQwJ1|)Y!DQu)w$d0lhza(TLOiNo;)O!MH$@S)G3bOMI17M_@3;5 zm7}RRRrkH)q{2)CZI)G`7GL!5bPx{nAhn zABhMJ59SYm#3k`zA<@z#r%a*o@}Pk3ig9(<6IB1;;057*rUcLeGei)T59mS-&|tR8 z6dJFeK0&X8YN-Cz6;Yq?vuvrGev-Fg!oJh zw%T9VPl%n7W0b({&K;U1fokXQ)sLM6xb6hdik718PVg1H?{1N4Pr47LyBTp5zQ_dWeSbg2Zihihdh(a z_b5yu?iAU#MIPa1m!DLo(AYrwba|r4FF2GbG@cHMn@WE3sbnfsXe67sLe zkWZmZp|QF2srXA2j|s8)&62RwjWUJCwm`A)P^QpOP$*Mq%mIqH@Q#x*g~nn)VWKjH lhNe%-6dD=|3Js0x_#cuE@z*ejqV50y002ovPDHLkV1jmgud@IE literal 0 HcmV?d00001 diff --git a/source/includes/get-started/troubleshoot.rst b/source/includes/get-started/troubleshoot.rst new file mode 100644 index 0000000..31b6b41 --- /dev/null +++ b/source/includes/get-started/troubleshoot.rst @@ -0,0 +1,6 @@ +.. note:: + + If you run into issues on this step, ask for help in the + :community-forum:`MongoDB Community Forums ` + or submit feedback by using the :guilabel:`Rate this page` + tab on the right or bottom right side of this page. \ No newline at end of file From 3e39306de94b63bcf88fe55d8ea4e0f6465e29b7 Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Fri, 11 Oct 2024 13:22:51 -0400 Subject: [PATCH 02/44] DOCSP-42306: Get Started and Install (#51) * Get Started and Install * build * edits * wording * troubleshooting file * version * JS feedback --- snooty.toml | 2 + source/get-started.txt | 39 +++++++++-- source/get-started/download-and-install.txt | 71 +++++++++++++++++++++ 3 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 source/get-started/download-and-install.txt diff --git a/snooty.toml b/snooty.toml index 0d28fee..ced98b2 100644 --- a/snooty.toml +++ b/snooty.toml @@ -14,6 +14,7 @@ toc_landing_pages = [ "/tutorials/connect", "/tutorials/write-ops", "/builders", + "/get-started" ] [constants] @@ -21,6 +22,7 @@ driver-short = "Scala driver" driver-long = "MongoDB Scala Driver" version = "5.2" full-version = "{+version+}.0" +language-version = "2.13.15" api = "https://mongodb.github.io/mongo-java-driver/{+version+}/apidocs/mongo-scala-driver" driver-source-gh = "https://github.com/mongodb/mongo-java-driver" rs-docs = "https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/org/reactivestreams" diff --git a/source/get-started.txt b/source/get-started.txt index e7cc264..4b369c5 100644 --- a/source/get-started.txt +++ b/source/get-started.txt @@ -1,17 +1,44 @@ .. _scala-get-started: -=========== -Get Started -=========== +================================= +Get Started with the Scala Driver +================================= + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol .. facet:: :name: genre - :values: reference - + :values: tutorial + .. meta:: - :keywords: runnable app, basic concepts + :description: Learn how to create an app to connect to MongoDB deployment by using the Scala driver. + :keywords: quick start, tutorial, basics .. toctree:: + /get-started/download-and-install/ /get-started/create-a-deployment /get-started/create-a-connection-string + +Overview +-------- + +The {+driver-long+} is an asynchronous API built upon the Java Reactive Streams driver, +which you can use to connect to MongoDB and interact with data stored in your deployment. +This guide shows you how to create an application that uses the {+driver-short+} to +connect to a MongoDB cluster hosted on MongoDB Atlas and query data in your cluster. + +.. tip:: + + MongoDB Atlas is a fully managed cloud database service that hosts your MongoDB + deployments. You can create your own free (no credit card required) MongoDB Atlas + deployment by following the steps in this guide. + +Follow this guide to connect a sample Scala application to a MongoDB Atlas +deployment. If you prefer to connect to MongoDB using a different driver or +programming language, see our :driver:`list of official drivers <>`. + diff --git a/source/get-started/download-and-install.txt b/source/get-started/download-and-install.txt new file mode 100644 index 0000000..a3cff9a --- /dev/null +++ b/source/get-started/download-and-install.txt @@ -0,0 +1,71 @@ +.. _scala-quick-start-download-and-install: + +==================== +Download and Install +==================== + +.. facet:: + :name: genre + :values: tutorial + +.. meta:: + :keywords: installation, setup, code example + +.. procedure:: + :style: connected + + .. step:: Install dependencies + + Before you being developing, ensure you have the following dependencies + installed in your development environment: + + - `JDK `__ version 8 or later + - `sbt `__ version 1 or later + + .. step:: Create a project directory + + Run the following command in your shell to create a directory + called ``scala-quickstart`` for this project: + + .. code-block:: bash + + mkdir scala-quickstart + + Select the tab corresponding to your operating system and run the following commands + to create a ``build.sbt`` file in the ``scala-quickstart`` directory: + + .. tabs:: + + .. tab:: macOS / Linux + :tabid: create-file-mac-linux + + .. code-block:: bash + + cd scala-quickstart + touch build.sbt + + .. tab:: Windows + :tabid: create-file-windows + + .. code-block:: bash + + cd scala-quickstart + type nul > build.sbt + + .. step:: Configure your project to use the {+driver-short+} + + Navigate to your ``build.sbt`` file and add the following code to use + the {+driver-short+} in your application: + + .. code-block:: none + + ThisBuild / scalaVersion := "{+language-version+}" + libraryDependencies += "org.mongodb.scala" %% "mongo-scala-driver" % "{+full-version+}" + + This code configures your application to use Scala version {+language-version+} and + {+driver-short+} version {+full-version+}. + +After you complete these steps, you have a new project directory with the driver +dependencies installed. + +.. include:: /includes/get-started/troubleshoot.rst From bd8fdc5569a4d4b1d91137015ceb4fce3150fb1f Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Mon, 14 Oct 2024 11:43:26 -0400 Subject: [PATCH 03/44] DOCSP-42310: Connect to MongoDB (#53) * DOCSP-42310: Connect to MongoDB * edits * code highlight * reduce scroll --- source/get-started.txt | 1 + source/get-started/connect-to-mongodb.txt | 108 ++++++++++++++++++++++ source/includes/get-started/Helpers.scala | 33 +++++++ source/includes/get-started/Main.scala | 25 +++++ 4 files changed, 167 insertions(+) create mode 100644 source/get-started/connect-to-mongodb.txt create mode 100644 source/includes/get-started/Helpers.scala create mode 100644 source/includes/get-started/Main.scala diff --git a/source/get-started.txt b/source/get-started.txt index 4b369c5..32d3e65 100644 --- a/source/get-started.txt +++ b/source/get-started.txt @@ -23,6 +23,7 @@ Get Started with the Scala Driver /get-started/download-and-install/ /get-started/create-a-deployment /get-started/create-a-connection-string + /get-started/connect-to-mongodb Overview -------- diff --git a/source/get-started/connect-to-mongodb.txt b/source/get-started/connect-to-mongodb.txt new file mode 100644 index 0000000..4a8d0a5 --- /dev/null +++ b/source/get-started/connect-to-mongodb.txt @@ -0,0 +1,108 @@ +.. _scala-connect-to-mongodb: + +================== +Connect to MongoDB +================== + +.. facet:: + :name: genre + :values: tutorial + +.. meta:: + :keywords: test connection, runnable, code example + +After retrieving the connection string for your MongoDB Atlas deployment, +you can connect to the deployment from your Scala application and query +the Atlas sample datasets. + +.. procedure:: + :style: connected + + .. step:: Create an application file + + Navigate to the ``scala-quickstart`` directory that you made in the + :ref:`scala-quick-start-download-and-install` step of this guide and + create the ``src/main/scala/quickstart`` nested directories. + + Select the tab corresponding to your operating system and run the following + commands to create a ``Main.scala`` file in the ``quickstart`` subdirectory: + + .. tabs:: + + .. tab:: macOS / Linux + :tabid: create-file-mac-linux + + .. code-block:: bash + + cd src/main/scala/quickstart + touch Main.scala + + .. tab:: Windows + :tabid: create-file-windows + + .. code-block:: bash + + cd src/main/scala/quickstart + type nul > Main.scala + + .. step:: Add helper methods + + Add the following ``Helpers.scala`` file from the `driver source code + `__ + to the ``src/main/scala/quickstart`` directory: + + .. literalinclude:: /includes/get-started/Helpers.scala + :language: scala + + This file allows you to access helper methods for printing query results. + + .. step:: Add your application code + + Copy and paste the following code into the ``Main.scala`` file, which queries + the ``movies`` collection in the ``sample_mflix`` database: + + .. literalinclude:: /includes/get-started/Main.scala + :language: scala + + .. step:: Assign the connection string + + Replace the ```` placeholder with the + connection string that you copied from the :ref:`scala-quick-start-connection-string` + step of this guide. + + .. step:: Run your Scala application + + In your project root directory, run the following commands to start the sbt shell + and run your application: + + .. code-block:: bash + + sbt + run + + The command line output contains details about the retrieved movie + document: + + .. code-block:: none + :copyable: false + + {"_id": {"$oid": "..."}, ... , "genres": ["Crime", "Drama"], "rated": "R", + "metacritic": 80, "title": "The Shawshank Redemption", ... } + + If you encounter an error or see no output, ensure that you specified the + proper connection string in the ``Main.scala`` file and that you loaded + the sample data. + + .. tip:: + + You can exit the sbt shell by running the following command: + + .. code-block:: none + + exit + +After you complete these steps, you have a Scala application that +connects to your MongoDB deployment, runs a query on the sample +data, and returns a matching document. + +.. include:: /includes/get-started/troubleshoot.rst diff --git a/source/includes/get-started/Helpers.scala b/source/includes/get-started/Helpers.scala new file mode 100644 index 0000000..666ce00 --- /dev/null +++ b/source/includes/get-started/Helpers.scala @@ -0,0 +1,33 @@ +package quickstart + +import java.util.concurrent.TimeUnit + +import scala.concurrent.Await +import scala.concurrent.duration.Duration + +import org.mongodb.scala._ + +object Helpers { + + implicit class DocumentObservable[C](val observable: Observable[Document]) extends ImplicitObservable[Document] { + override val converter: (Document) => String = (doc) => doc.toJson + } + + implicit class GenericObservable[C](val observable: Observable[C]) extends ImplicitObservable[C] { + override val converter: (C) => String = (doc) => Option(doc).map(_.toString).getOrElse("") + } + + trait ImplicitObservable[C] { + val observable: Observable[C] + val converter: (C) => String + + def results(): Seq[C] = Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) + def headResult() = Await.result(observable.head(), Duration(10, TimeUnit.SECONDS)) + def printResults(initial: String = ""): Unit = { + if (initial.length > 0) print(initial) + results().foreach(res => println(converter(res))) + } + def printHeadResult(initial: String = ""): Unit = println(s"${initial}${converter(headResult())}") + } + +} diff --git a/source/includes/get-started/Main.scala b/source/includes/get-started/Main.scala new file mode 100644 index 0000000..3a4d57e --- /dev/null +++ b/source/includes/get-started/Main.scala @@ -0,0 +1,25 @@ +package quickstart + +import org.mongodb.scala._ +import org.mongodb.scala.model.Filters._ +import Helpers._ + +object Main { + + def main(args: Array[String]): Unit = { + + val mongoClient = MongoClient("") + + val database: MongoDatabase = + mongoClient.getDatabase("sample_mflix") + val collection: MongoCollection[Document] = + database.getCollection("movies") + + val filter = equal("title", "The Shawshank Redemption") + collection.find(filter).printResults() + + mongoClient.close() + + } + +} From 47ce5288480d44af79d784329bf0b305826b0779 Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Mon, 14 Oct 2024 15:59:32 -0400 Subject: [PATCH 04/44] DOCSP-42305: Index page + repo cleanup (#56) --- snooty.toml | 1 + source/bson.txt | 49 -- source/bson/documents.txt | 242 --------- source/bson/extended-json.txt | 149 ------ source/bson/macros.txt | 161 ------ source/builders.txt | 38 -- source/builders/aggregates.txt | 240 --------- source/builders/filters.txt | 291 ---------- source/builders/indexes.txt | 141 ----- source/builders/projections.txt | 156 ------ source/builders/sorts.txt | 95 ---- source/builders/updates.txt | 320 ----------- source/compatibility.txt | 2 +- source/get-started/primer.txt | 114 ---- source/get-started/qs-case-class.txt | 228 -------- source/get-started/quickstart.txt | 502 ------------------ source/includes/connect-section.rst | 16 - .../dns_seedlist_connection_string_parts.png | Bin 9609 -> 0 bytes source/includes/install-note.rst | 4 - source/includes/obs-note.rst | 4 - source/includes/prereq-restaurants.rst | 8 - .../security/crypt-maven-versioned.rst | 9 - .../includes/security/crypt-sbt-versioned.rst | 3 - source/index.txt | 103 ++-- source/installation.txt | 83 --- source/reference.txt | 22 - source/reference/logging.txt | 50 -- source/reference/monitoring.txt | 238 --------- source/reference/observables.txt | 192 ------- source/tutorials.txt | 40 -- source/tutorials/aggregation.txt | 118 ---- source/tutorials/bulk-writes.txt | 53 -- source/tutorials/change-stream.txt | 140 ----- source/tutorials/command.txt | 70 --- source/tutorials/connect.txt | 352 ------------ source/tutorials/connect/authentication.txt | 327 ------------ source/tutorials/connect/compression.txt | 137 ----- source/tutorials/connect/tls.txt | 250 --------- source/tutorials/db-coll.txt | 278 ---------- source/tutorials/encrypt.txt | 28 - source/tutorials/geospatial.txt | 76 --- source/tutorials/gridfs.txt | 212 -------- source/tutorials/indexes.txt | 258 --------- source/tutorials/read-ops.txt | 316 ----------- source/tutorials/text-search.txt | 128 ----- source/tutorials/write-ops.txt | 370 ------------- source/validate-signatures.txt | 3 - 47 files changed, 67 insertions(+), 6550 deletions(-) delete mode 100644 source/bson.txt delete mode 100644 source/bson/documents.txt delete mode 100644 source/bson/extended-json.txt delete mode 100644 source/bson/macros.txt delete mode 100644 source/builders.txt delete mode 100644 source/builders/aggregates.txt delete mode 100644 source/builders/filters.txt delete mode 100644 source/builders/indexes.txt delete mode 100644 source/builders/projections.txt delete mode 100644 source/builders/sorts.txt delete mode 100644 source/builders/updates.txt delete mode 100644 source/get-started/primer.txt delete mode 100644 source/get-started/qs-case-class.txt delete mode 100644 source/get-started/quickstart.txt delete mode 100644 source/includes/connect-section.rst delete mode 100644 source/includes/dns_seedlist_connection_string_parts.png delete mode 100644 source/includes/install-note.rst delete mode 100644 source/includes/obs-note.rst delete mode 100644 source/includes/prereq-restaurants.rst delete mode 100644 source/includes/security/crypt-maven-versioned.rst delete mode 100644 source/includes/security/crypt-sbt-versioned.rst delete mode 100644 source/installation.txt delete mode 100644 source/reference.txt delete mode 100644 source/reference/logging.txt delete mode 100644 source/reference/monitoring.txt delete mode 100644 source/reference/observables.txt delete mode 100644 source/tutorials.txt delete mode 100644 source/tutorials/aggregation.txt delete mode 100644 source/tutorials/bulk-writes.txt delete mode 100644 source/tutorials/change-stream.txt delete mode 100644 source/tutorials/command.txt delete mode 100644 source/tutorials/connect.txt delete mode 100644 source/tutorials/connect/authentication.txt delete mode 100644 source/tutorials/connect/compression.txt delete mode 100644 source/tutorials/connect/tls.txt delete mode 100644 source/tutorials/db-coll.txt delete mode 100644 source/tutorials/encrypt.txt delete mode 100644 source/tutorials/geospatial.txt delete mode 100644 source/tutorials/gridfs.txt delete mode 100644 source/tutorials/indexes.txt delete mode 100644 source/tutorials/read-ops.txt delete mode 100644 source/tutorials/text-search.txt delete mode 100644 source/tutorials/write-ops.txt delete mode 100644 source/validate-signatures.txt diff --git a/snooty.toml b/snooty.toml index ced98b2..ea86361 100644 --- a/snooty.toml +++ b/snooty.toml @@ -22,6 +22,7 @@ driver-short = "Scala driver" driver-long = "MongoDB Scala Driver" version = "5.2" full-version = "{+version+}.0" +language = "Scala" language-version = "2.13.15" api = "https://mongodb.github.io/mongo-java-driver/{+version+}/apidocs/mongo-scala-driver" driver-source-gh = "https://github.com/mongodb/mongo-java-driver" diff --git a/source/bson.txt b/source/bson.txt deleted file mode 100644 index 9232562..0000000 --- a/source/bson.txt +++ /dev/null @@ -1,49 +0,0 @@ -.. _scala-bson: - -=================== -BSON Implementation -=================== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: documents, storage, codec - -.. toctree:: - - /bson/documents/ - /bson/macros/ - /bson/extended-json/ - -The BSON library comprehensively supports `BSON -`__, the data storage and network transfer format -that MongoDB uses for documents. BSON, short -for Binary `JSON `__, is a -binary-encoded serialization of JSON-like documents. - -For everyday usage there are type aliases and companion objects -available from the ``org.mongodb.scala.bson`` package. To learn more, -see the `API documentation -<{+api+}/org/mongodb/scala/bson/index.html>`__ for the Scala BSON -implementation. - -The following sections describe aspects of the driver's BSON -implementation: - -- :ref:`scala-documents`: describes the driver’s support for BSON document - representations -- :ref:`scala-macros`: describes the case classes you can use to - represent documents in a collection -- :ref:`scala-ejson`: describes the driver’s support for MongoDB Extended - JSON - -For advanced usage you might need to use ``org.bson`` directly. See the -Java Sync driver documentation to learn about the following topics: - -- :driver:`Readers and Writers `: describes - the driver’s support for stream-based reading and writing of BSON documents -- :driver:`Codec and CodecRegistry `: describes - the driver’s Codec API, an abstraction for producing and consuming BSON document representations - using the stream-based readers and writers diff --git a/source/bson/documents.txt b/source/bson/documents.txt deleted file mode 100644 index 063d811..0000000 --- a/source/bson/documents.txt +++ /dev/null @@ -1,242 +0,0 @@ -.. _scala-documents: - -========= -Documents -========= - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: representation, storage, codec - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Overview --------- - -The {+driver-short+} includes two Scala-specific representations for BSON -documents. Following the convention from the Scala collections library, -there are immutable and mutable implementations of the ``Document`` type. The -underlying implementations of ``Document`` use the type-safe -`BsonDocument <{+api+}/bson/org/bson/BsonDocument.html>`__ class. The BSON classes are available from the -``org.mongodb.scala.bson`` namespace, which includes type aliases and -companion objects. These objects should suffice for many use cases, but -for advanced use cases you may need to use classes from the ``org.bson`` -namespace directly. - -.. important:: Duplicate Key Names - - The server’s behavior regarding duplicate key names in a document is - undefined. When a document with duplicate key names is decoded, the - driver will assign the last value associated with the duplicate key. - Storing such a document will cause the other values to be lost. - -.. note:: - - The Scala ``Document`` classes implement ``TraversableLike[(String, - BsonValue)]`` and the general API mirrors that of a ``Map[String, - BsonValue]`` value. However, unlike ``Map``, implementations of ``TraversableLike`` - enable strict type safety as there is no variance in the value type. - -``BsonValue`` is the type-safe representation of a BSON type from the -``org.bson`` library and represents specific value types. The most commonly -used value types are as follows: - -.. list-table:: - :header-rows: 1 - :class: compatibility-large - - * - BSON Type - - Scala Type - - * - ``Document`` - - ``org.mongodb.scala.bson.Document`` - - * - ``Array`` - - ``List`` - - * - ``Date`` - - ``Date`` or ``int`` (milliseconds since epoch) - - * - ``Boolean`` - - ``Boolean`` - - * - ``Double`` - - ``Double`` - - * - ``Int32`` - - ``Integer`` - - * - ``Int64`` - - ``Long`` - - * - ``String`` - - ``String`` - - * - ``Binary`` - - ``Array[Byte]`` - - * - ``ObjectId`` - - ``ObjectId`` - - * - ``Null`` - - ``None`` - -It is possible to change or extend these mappings, a process described -in the following sections. - -The following sections describe the two main ``Document`` classes. - -Immutable Documents -------------------- - -Similar to the Scala collections library, the immutable class is the preferred -class. For convenience, it is aliased to ``org.mongodb.scala.Document`` and -``org.mongodb.scala.bson.Document`` as well as being available from -``org.mongodb.scala.bson.collection.immutable.Document``. - -Instances of this -type are guaranteed to be immutable for everyone. Such a collection will -never change after it is created. Therefore, you can rely on the fact -that accessing the same collection value repeatedly at different points -in time will always yield a collection with the same elements. - -.. code-block:: scala - - import org.mongodb.scala.bson._ - - val doc1 = Document("AL" -> BsonString("Alabama")) - val doc2 = doc1 + ("AK" -> BsonString("Alaska")) - val doc3 = doc2 ++ Document("AR" -> BsonString("Arkansas"), "AZ" -> BsonString("Arizona")) - -Mutable Documents ------------------ - -To get the mutable ``Document`` type, you need to import it explicitly -from ``org.mongodb.scala.collections.mutable.Document``. The mutable -``Document`` can be updated or extended in place. This means you can change, -add, or remove elements of the ``Document`` as a side effect. Similar to Scala -collections, when dealing with mutable types you need to understand -which code changes which collection and when. - -.. code-block:: scala - - import org.mongodb.scala.bson._ - import org.mongodb.scala.bson.collection.mutable.Document - - val doc = Document("AL" -> BsonString("Alabama")) - val doc1 = doc + ("AK" -> BsonString("Alaska")) // doc not mutated but new doc created - doc1 ++= Document("AR" -> BsonString("Arkansas"), - "AZ" -> BsonString("Arizona")) // doc1 mutated as ++= changes in place. - -Implicit Conversions --------------------- - -For many of the ``BsonValue`` types, there are obvious direct mappings from a -Scala type. For example, a ``String`` maps to ``BsonString``, an ``Int`` maps to -``BsonInt32`` and a ``Long`` maps to a ``BsonInt64``. For convenience, these types -can be used directly with ``Document`` types and they are converted by the -contract traits in the ``BsonMagnets`` object. As long as there is an -implicit ``BsonTransformer`` in scope for any given type, then that type can -be converted into a ``BsonValue``. - -The following ``BsonTransformers`` are in scope by default: - -.. list-table:: - :header-rows: 1 - :class: compatibility-large - - * - Scala Type - - BsonValue - - * - ``Boolean`` - - ``BsonBoolean`` - - * - ``String`` - - ``BsonString`` - - * - ``Array[Byte]`` - - ``BsonBinary`` - - * - ``Regex`` - - ``BsonRegex`` - - * - ``Date`` - - ``BsonDateTime`` - - * - ``ObjectId`` - - ``BsonObjectId`` - - * - ``Int`` - - ``BsonInt32`` - - * - ``Long`` - - ``BsonInt64`` - - * - ``Double`` - - ``BsonDouble`` - - * - ``immutable.Document`` - - ``BsonDocument`` - - * - ``mutable.Document`` - - ``BsonDocument`` - - * - ``Option[T]`` - - ``BsonValue`` where ``T`` has a ``BsonTransformer`` - - * - ``Seq[(String, T)]`` - - ``BsonDocument`` where ``T`` has a ``BsonTransformer`` - - * - ``Seq[T]`` - - ``BsonArray`` where ``T`` has a ``BsonTransformer`` - - * - ``BsonValue`` - - ``BsonValue`` - -.. code-block:: scala - - import org.mongodb.scala.Document - - val doc1 = Document("AL" -> "Alabama") - val doc2 = doc1 + ("AK" -> "Alaska") - val doc3 = doc2 ++ Document("AR" -> "Arkansas", "population" -> 2.966) - -This is achieved by making use of the **Magnet Pattern**, which you can -learn more about in the `Magnet Pattern blog post on spray.io -`__. - -In the API where we would normally expect a single value or a key-value -pair or many key value pairs, such as ``BsonValue``, (``String``, ``BsonValue``) or -``Iterable[(String, BsonValue)]``, we require anything that can become those -types via ``CanBeX`` traits that handle the implicit conversions necessary -to conform to the correct types. These traits are ``CanBeBsonValue``, -``CanBeBsonElement`` and ``CanBeBsonElements``. - -One such example is adding a key-value pair to a ``Document`` or a list of -values: - -.. code-block:: scala - - val doc1 = Document("AL" -> "Alabama") - val doc2 = Document("codes" -> List("AL", "AK", "AR")) - -Bson -~~~~ - -The driver also contains a small but powerful interface called ``Bson``. Any -class that represents a BSON document, whether included in the driver -itself or from a third party, can implement this interface and can then -be used any place in the high-level API where a BSON document is -required. For example: - -.. code-block:: scala - - collection.find(Document("x" -> 1)) - collection.find(Filters.eq("x", 1)) diff --git a/source/bson/extended-json.txt b/source/bson/extended-json.txt deleted file mode 100644 index b901e6b..0000000 --- a/source/bson/extended-json.txt +++ /dev/null @@ -1,149 +0,0 @@ -.. _scala-ejson: - -============= -Extended JSON -============= - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: documents, storage, codec, registry - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Overview --------- - -The {+driver-short+} supports reading and writing of BSON documents -represented as MongoDB Extended JSON. Both of the following variants are supported: - -- Strict Mode: representations of BSON types that conform to the `JSON - RFC `__. This is the format that - :dbtools:`mongoexport ` produces and - :dbtools:`mongoimport ` consumes. - -- Shell Mode: a superset of JSON that the :mongosh:`MongoDB shell ` can parse. - -Furthermore, the ``Document`` type provides two sets of convenience -methods for this purpose: - -- ``Document.toJson()``: a set of overloaded methods that convert a - ``Document`` instance to a JSON string -- ``Document()``: a set of overloaded static factory methods that - convert a JSON string to a ``Document`` instance - -Writing JSON ------------- - -Consider the task of implementing a ``mongoexport``-like tool by using -the driver: - -.. code-block:: scala - - val fileName = // initialize to the path of the file to write to - val collection = // initialize the collection from which you want to query - - val writer: PrintWriter = new PrintWriter(fileName) - collection.find().subscribe( - (doc: Document) => output.write(s"${doc.toJson}\r\n"), - (t: Throwable) => // handle failure, - () => output.close()) - -The ``Document.toJson()`` method constructs an instance of a ``JsonWriter`` with -its default settings, which writes in strict mode with no new lines -or indentation. - -You can override this default behavior by using one of the overloads of -``toJson()``. As an example, consider the task of writing a JSON string -that can be copied and pasted into the MongoDB shell: - -.. code-block:: scala - - import java.text.SimpleDateFormat - - val fmt = new SimpleDateFormat("dd/MM/yy") - val first = fmt.parse("01/01/2014") - val second = fmt.parse("01/01/2015") - val doc = Document("startDate" -> Document("$gt" -> first, "$lt" -> second)) - println(doc.toJson(new JsonWriterSettings(JsonMode.SHELL))) - -This code snippet will print out MongoDB shell-compatible JSON, which -can then be pasted into the shell: - -.. code-block:: shell - - { "startDate" : { "$gt" : ISODate("2014-01-01T05:00:00.000Z"), "$lt" : ISODate("2015-01-01T05:00:00.000Z") } } - -Reading JSON ------------- - -Consider the task of implementing a ``mongoimport``-like tool by using -driver: - -.. code-block:: scala - - import scala.io.Source - val fileName = // initialize to the path of the file to read from - val collection = // initialize the collection from which you want to import to - - try { - for (json <- Source.fromFile(fileName).getLines()) { - collection.insertOne(Document(json)).head() - } - } catch { - case ex: Exception => println("Bummer, an exception happened.") - } - -The ``Document()`` companion helper method constructs an instance of a -``JsonReader`` with the given string and returns an instance of an -equivalent ``Document`` instance. ``JsonReader`` automatically detects the JSON -flavor in the string, so you do not need to specify it. - -Reading and Writing JSON Directly -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you do not need a document and only want to deal with JSON, you can -use a ``JsonObject`` to read and write JSON directly. ``JsonObject`` is a -wrapper class that takes in a ``String`` in the constructor and returns the -``String`` in the ``getJson()`` method. Reading and writing JSON directly is -more efficient than constructing a ``Document`` first and then calling -``toJson()``, and it is also more efficient than calling -``Document#parse()``. - -The codec responsible for reading/writing JSON, ``JsonObjectCodec``, is part of -the default registry, so doing this is very simple and demonstrated by -the following example: - -.. code-block:: scala - - val database: MongoDatabase = mongoClient.getDatabase("mydb") - val collection: MongoCollection[JsonObject] = database.getCollection("test") - collection.insertOne(new JsonObject("{hello: 1}")).printResults() - val jsonObject: SingleObservable[JsonObject] = collection.find.first() - -Reading and Writing JSON with CustomSettings -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can also provide custom ``JsonWriterSettings`` to the ``JsonObjectCodec``, -by constructing the codec yourself and then creating a registry: - -.. code-block:: scala - - val codecRegistry = - fromRegistries( - fromCodecs(new JsonObjectCodec(JsonWriterSettings - .builder() - .outputMode(JsonMode.EXTENDED) - .build())), - DEFAULT_CODEC_REGISTRY - ) - val database: MongoDatabase = mongoClient.getDatabase("mydb").withCodecRegistry(codecRegistry) - val collection: MongoCollection[JsonObject] = database.getCollection("test") - collection.insertOne(new JsonObject("{hello: 1}")).printResults() - val jsonObject: SingleObservable[JsonObject] = collection.find.first() diff --git a/source/bson/macros.txt b/source/bson/macros.txt deleted file mode 100644 index 7d3cc9f..0000000 --- a/source/bson/macros.txt +++ /dev/null @@ -1,161 +0,0 @@ -.. _scala-macros: - -====== -Macros -====== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: representation, storage, codec - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Overview --------- - -The {+driver-short+} allows you to use case classes to represent -documents in a collection by using the ``Macros`` helper. Simple case classes and -nested case classes are supported. Hierarchical modeling can be achieved -by using a sealed trait or class and then by having case classes implement the -parent trait. - -Many simple Scala types are supported and can be marshaled into their -corresponding ``BsonValue`` type. The following list describes Scala types and their -type-safe BSON representation: - -.. list-table:: - :header-rows: 1 - :class: compatibility-large - - * - Scala Type - - BSON Type - - * - Case class - - Document - - * - ``Iterable`` - - Array - - * - ``Date`` - - Date - - * - ``Boolean`` - - Boolean - - * - ``Double`` - - Double - - * - ``Int`` - - Int32 - - * - ``Long`` - - Int64 - - * - ``String`` - - String - - * - ``Array[Byte]`` - - Binary - - * - ``None`` - - Null - -Creating Codecs ---------------- - -To create a codec for your case class, use the ``Macros`` object helper -methods. You should use the -``Macros.createCodecProvider()`` method to create a ``CodecProvider``. A -``CodecProvider`` passes the configured ``CodecRegistry`` to the underlying -``Codec`` and provides access to all the configured codecs. - -To create a ``CodecProvider``, set the case class type when calling -``createCodecProvider()`` as shown in the following code: - -.. code-block:: scala - - import org.mongodb.scala.bson.codecs.Macros - - case class Person(firstName: String, secondName: String) - - val personCodecProvider = Macros.createCodecProvider[Person]() - -The ``personCodecProvider`` can then be used when converted into a -``CodecRegistry`` by using the ``CodecRegistries`` static helpers. The -following code creates a new codec registry, combining the new ``personCodecProvider`` -and the default codec registry: - -.. code-block:: scala - - import org.mongodb.scala.bson.codecs.DEFAULT_CODEC_REGISTRY - import org.bson.codecs.configuration.CodecRegistries.{fromRegistries, fromProviders} - - val codecRegistry = fromRegistries( fromProviders(personCodecProvider), DEFAULT_CODEC_REGISTRY ) - -The ``Macros`` helper also has an implicit ``createCodecProvider()`` method that -takes the ``Class[T]`` and creates a ``CodecProvider`` from that. This method -is more concise especially when defining multiple providers: - -.. code-block:: scala - - import org.mongodb.scala.bson.codecs.Macros._ - import org.mongodb.scala.bson.codecs.DEFAULT_CODEC_REGISTRY - import org.bson.codecs.configuration.CodecRegistries.{fromRegistries, fromProviders} - - case class Address(firstLine: String, secondLine: String, thirdLine: String, town: String, zipCode: String) - case class ClubMember(person: Person, address: Address, paid: Boolean) - - val codecRegistry = fromRegistries( fromProviders(classOf[ClubMember], classOf[Person], classOf[Address]), DEFAULT_CODEC_REGISTRY ) - -Sealed Classes and ADTs ------------------------ - -Hierarchical class structures are supported through sealed traits and -classes. Each subclass is handled specifically by the generated codec, -so you only need to create a ``CodecProvider`` for the parent sealed -trait or class. Internally an extra field (``_t``) is stored alongside the data -so that the correct subclass can be hydrated when decoding the data. -The following code is an example of a tree-like structure containing branch and leaf -nodes: - -.. code-block:: scala - - sealed class Tree - case class Branch(b1: Tree, b2: Tree, value: Int) extends Tree - case class Leaf(value: Int) extends Tree - - val codecRegistry = fromRegistries( fromProviders(classOf[Tree]), DEFAULT_CODEC_REGISTRY ) - -Options and None Values ------------------------ - -By default, ``Option`` values are always stored. In driver v2.1.0, new -helpers were added so that ``None`` values would not be stored in the -database. In the following example, the driver only stores an address if -if one is present: - -.. code-block:: scala - - import org.mongodb.scala.bson.codecs.Macros - - case class Person(firstName: String, secondName: String, address: Option[Address]) - - val personCodecProvider = Macros.createCodecProviderIgnoreNone[Person]() - -Alternative Field Names ------------------------ - -The ``BsonProperty`` annotation can be used to configure the BSON field -key to be used for a given property. The following example uses the -``BsonProperty`` annotation to change how the ``firstName`` field is stored: - -.. code-block:: scala - - case class Person(@BsonProperty("first_name") firstName: String, secondName: String) diff --git a/source/builders.txt b/source/builders.txt deleted file mode 100644 index 32c3750..0000000 --- a/source/builders.txt +++ /dev/null @@ -1,38 +0,0 @@ -.. _scala-builders: - -======== -Builders -======== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: helper methods, CRUD operations - -.. toctree:: - - /builders/filters/ - /builders/projections/ - /builders/sorts/ - /builders/aggregates/ - /builders/updates/ - /builders/indexes/ - -The {+driver-short+} provides the following classes that make it easier to use -the CRUD API: - -- :ref:`scala-builders-filters`: Support for building query filters -- :ref:`scala-builders-projections`: Support for building projections -- :ref:`scala-builders-sorts`: Support for building sort criteria -- :ref:`scala-builders-agg`: Support for building aggregation pipelines -- :ref:`scala-builders-updates`: Support for building updates -- :ref:`scala-builders-indexes`: Support for creating index keys - -.. important:: - - Builders make use of the ``Bson`` helper. This type, unlike the - ``Document`` type, is not type-safe. Instead, conversion to BSON is - done by using :driver:`Codecs and the CodecRegistry - `. \ No newline at end of file diff --git a/source/builders/aggregates.txt b/source/builders/aggregates.txt deleted file mode 100644 index 3acca28..0000000 --- a/source/builders/aggregates.txt +++ /dev/null @@ -1,240 +0,0 @@ -.. _scala-builders-agg: - -========== -Aggregates -========== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, create insights, computed fields - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -The `Aggregates <{+api+}/org/mongodb/scala/model/Aggregates$.html>`__ class provides static factory methods that build -:manual:`aggregation pipeline stages -`. Each method returns an -instance of the ``Bson`` type, which can in turn be passed to the -``MongoCollection.aggregate()`` method. - -You can import the methods of the ``Aggregates`` -class statically, as shown in the following code: - -.. code-block:: scala - - import org.mongodb.scala.model.Aggregates._ - -The examples in this guide assume this static import. - -Match ------ - -The ``$match`` pipeline stage passes all documents matching the -specified filter to the next stage. Though the filter can be an instance -of any class that implements ``Bson``, it’s convenient to use methods from -the :ref:`scala-builders-filters` class. - -The following example creates a pipeline stage that matches all -documents where the ``author`` field value is ``"Dave"``: - -.. code-block:: scala - - `match`(equal("author", "Dave")) - -.. note:: - - As ``match`` is a reserved word in Scala and has to be escaped by - backticks, you might prefer to use the ``filter()`` alias: - - .. code-block:: scala - - filter(equal("author", "Dave")) - -Project -------- - -The ``$project`` pipeline stage passes the projected fields of all -documents to the next stage. Though the projection can be an instance of -any class that implements ``Bson``, it’s convenient to use methods from the -:ref:`scala-builders-projections` class. - -The following example creates a pipeline stage that excludes the ``_id`` -field but includes the ``title`` and ``author`` fields: - -.. code-block:: scala - - project(fields(include("title", "author"), excludeId())) - -Computed Fields -~~~~~~~~~~~~~~~ - -The ``$project`` stage can project computed fields as well. - -The following example projects the ``qty`` field into a new field called -``quantity``. In other words, it renames the field: - -.. code-block:: scala - - project(computed("quantity", "$qty")) - -Sample ------- - -The ``$sample`` pipeline stage randomly select ``N`` documents from -input documents. The following example uses the ``sample()`` method to randomly select ``5`` -documents from the collection: - -.. code-block:: scala - - sample(5) - -Sort ----- - -The ``$sort`` pipeline stage passes all documents to the next stage, sorted -by the specified sort criteria. Though the sort criteria can be an -instance of any class that implements ``Bson``, it’s convenient to use methods -from the :ref:`scala-builders-sorts` class. - -The following example creates a pipeline stage that sorts in descending order -according to the value of the ``age`` field and then in ascending order -according to the value of the ``posts`` field: - -.. code-block:: scala - - sort(orderBy(descending("age"), ascending("posts"))) - -Skip ----- - -The ``$skip`` pipeline stage skips over the specified number of documents -that pass into the stage and passes the remaining documents to the next -stage. - -The following example skips the first ``5`` documents: - -.. code-block:: scala - - skip(5) - -Limit ------ - -The ``$limit`` pipeline stage limits the number of documents passed to -the next stage. - -The following example limits the number of documents to 10: - -.. code-block:: scala - - limit(10) - -Lookup ------- - -The ``$lookup`` pipeline stage performs a left outer join with another -collection to filter in documents from the joined collection for -processing. - -The following example performs a left outer join on the ``fromCollection`` -collection, joining the ``local`` field to the ``from`` field and outputted -in the ``joinedOutput`` field: - -.. code-block:: scala - - lookup("fromCollection", "local", "from", "joinedOutput") - -Group ------ - -The ``$group`` pipeline stage groups documents by some specified expression -and outputs a document for each distinct grouping to the next stage. A -group consists of an ``_id`` which specifies the expression on which to -group, and zero or more accumulators which are evaluated for each -grouping. - -To simplify the expression of accumulators, the driver -includes an ``Accumulators`` singleton object with factory methods for each -of the supported accumulators. - -The following example groups documents by the value of the ``customerId`` field, and -for each group accumulates the sum and average of the values of the -quantity field into the ``totalQuantity`` and ``averageQuantity`` fields, -respectively: - -.. code-block:: scala - - group("$customerId", sum("totalQuantity", "$quantity"), avg("averageQuantity", "$quantity")) - -Unwind ------- - -The ``$unwind`` pipeline stage deconstructs an array field from the -input documents to output a document for each element. - -The following example outputs, for each document, a document for each element in -the ``sizes`` array: - -.. code-block:: scala - - unwind("$sizes") - -The following example also includes any documents that have missing or null -values for the ``sizes`` field or where the ``sizes`` list is empty: - -.. code-block:: scala - - unwind("$sizes", UnwindOptions().preserveNullAndEmptyArrays(true)) - -The following example unwinds the ``sizes`` array and also outputs the array -index into the ``position`` field: - -.. code-block:: scala - - unwind("$sizes", UnwindOptions().includeArrayIndex("$position")) - -Set Window Fields ------------------ - -The ``$setWindowFields`` pipeline stage allows using window operators. This -stage partitions the input documents similarly to the ``$group`` pipeline -stage, optionally sorts them, computes fields in the documents by -computing window functions over windows specified per function, and -outputs the documents. A window is a subset of a partition. - -The important difference from the ``$group`` pipeline stage is that documents belonging to -the same partition or window are not folded into a single document. - -The driver includes the ``WindowedComputations`` singleton object with -factory methods for supported window operators. - -The following example computes the accumulated rainfall and the average -temperature over the past month per each locality from more fine-grained -measurements presented in the ``rainfall`` and ``temperature`` fields: - -.. code-block:: scala - - val pastMonth: Window = Windows.timeRange(-1, MongoTimeUnit.MONTH, Windows.Bound.CURRENT) - - setWindowFields(Some("$localityId"), Some(Sorts.ascending("measurementDateTime")), - WindowedComputations.sum("monthlyRainfall", "$rainfall", Some(pastMonth)), - WindowedComputations.avg("monthlyAvgTemp", "$temperature", Some(pastMonth))) - -Assembling a Pipeline ---------------------- - -Pipeline operators are typically combined into a list and passed to the -``aggregate()`` method of a ``MongoCollection``: - -.. code-block:: scala - - collection.aggregate(List(filter(equal("author", "Dave")), - group("$customerId", sum("totalQuantity", "$quantity"), - avg("averageQuantity", "$quantity")), - out("authors"))) diff --git a/source/builders/filters.txt b/source/builders/filters.txt deleted file mode 100644 index 0c382f1..0000000 --- a/source/builders/filters.txt +++ /dev/null @@ -1,291 +0,0 @@ -.. _scala-builders-filters: - -======= -Filters -======= - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, match, criteria - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -The `Filters <{+api+}/org/mongodb/scala/model/Filters$.html>`__ class provides static factory methods for the -:manual:`MongoDB query operators `. Each method returns an instance of the ``Bson`` -type, which can in turn be passed to any method that expects a query -filter. - -You can import the methods of the ``Filters`` -class statically, as shown in the following code: - -.. code-block:: scala - - import org.mongodb.scala.model.Filters._ - -The examples in this guide assume this static import. - -Comparison ----------- - -The comparison operator methods include the following: - -- ``eq``: Matches values that are equal to a specified value. Aliased to - ``equal`` as ``eq`` is a reserved word. -- ``gt``: Matches values that are greater than a specified value. -- ``gte``: Matches values that are greater than or equal to a specified value. -- ``lt``: Matches values that are less than a specified value. -- ``lte``: Matches values that are less than or equal to a specified value. -- ``ne``: Matches all values that are not equal to a specified value. - Aliased to ``notEqual`` as ``neq`` is a reserved word. -- ``in``: Matches any of the values specified in an array. -- ``nin``: Matches none of the values specified in an array. -- ``empty``: Matches all the documents. - -Examples -~~~~~~~~ - -The following example creates a filter that selects all documents where the value -of the ``qty`` field is ``20``: - -.. code-block:: scala - - `eq`("qty", 20) - equal("qty", 20) - -The following example creates a filter that selects all documents where -the value of the ``qty`` field is either ``5`` or ``15``: - -.. code-block:: scala - - in("qty", 5, 15) - -The following example creates a filter that selects all documents because the -filter is empty: - -.. code-block:: scala - - empty() - -Logical -------- - -The logical operator methods include the following: - -- ``and``: Joins filters with a logical ``AND`` and selects all documents - that match the conditions of both filters. -- ``or``: Joins filters with a logical ``OR`` and selects all documents - that match the conditions of either filter. -- ``not``: Inverts the effect of a query expression and selects - documents that do not match the filter. -- ``nor``: Joins filters with a logical ``NOR`` and selects all documents - that fail to match both filters. - -Examples -~~~~~~~~ - -The following example creates a filter that selects all documents where the value -of the ``qty`` field is greater than ``20`` and the value of the ``user`` field -is ``"jdoe"``: - -.. code-block:: scala - - and(gt("qty", 20), equal("user", "jdoe")) - -The ``and()`` method generates an ``$and`` operator only if necessary, as the query -language implicitly adds together all the elements in a filter. The -preceding example renders as the following: - -.. code-block:: json - - { - "qty" : { "$gt" : 20 }, - "user" : "jdoe" - } - -The following example creates a filter that selects all documents where the ``price`` -field value equals ``0.99`` or ``1.99``, and either the ``sale`` field value is -``true`` or the ``qty`` field value is less than ``20``: - -.. code-block:: scala - - and(or(equal("price", 0.99), equal("price", 1.99) - or(equal("sale", true), lt("qty", 20))) - -This query cannot be constructed using an implicit ``$and`` operation, -because it uses the ``$or`` operator more than once. This query renders -to the following: - -.. code-block:: json - - { - "$and" : - [ - { "$or" : [ { "price" : 0.99 }, { "price" : 1.99 } ] }, - { "$or" : [ { "sale" : true }, { "qty" : { "$lt" : 20 } } ] } - ] - } - -Arrays ------- - -The array operator methods include the following: - -- ``all``: Matches arrays that contain all elements specified in the query. -- ``elemMatch``: Selects documents if an element in the array field matches - all the specified ``$elemMatch`` conditions. -- ``size``: Selects documents if the array field is a specified size. - -Examples -~~~~~~~~ - -The following example selects documents with a ``tags`` array containing -both ``"ssl"`` and ``"security"``: - -.. code-block:: scala - - all("tags", "ssl", "security") - -Elements --------- - -The elements operator methods include the following: - -- ``exists``: Selects documents that have the specified field. -- ``type``: Selects documents if a field is of the specified type. - Aliased to ``bsonType`` as ``type`` is a reserved word. - -Examples -~~~~~~~~ - -The following example selects documents that contain a ``qty`` field -and the value of this field does not equal ``5`` or ``15``: - -.. code-block:: scala - - and(exists("qty"), nin("qty", 5, 15)) - -Evaluation ----------- - -The evaluation operator methods include the following: - -- ``mod``: Performs a modulo operation on the value of a field and - selects documents with a specified result. -- ``regex``: Selects documents where values match a specified regular expression. -- ``text``: Selects documents matching a full-text search expression. -- ``where``: Matches documents that satisfy a JavaScript expression. - -Examples -~~~~~~~~ - -The following example assumes a collection that has a text index in the field -``abstract``. It selects documents that have an ``abstract`` field containing the -term ``"coffee"``: - -.. code-block:: scala - - text("coffee") - -Text indexes allow case-sensitive searches. The following example selects -documents that have an ``abstract`` field containing the exact term ``"coffee"``: - -.. code-block:: scala - - text("coffee", TextSearchOptions().caseSensitive(true)) - -Text indexes allow diacritic-sensitive searches. The following example selects -documents that have an ``abstract`` field containing the exact term ``"café"``: - -.. code-block:: scala - - text("café", TextSearchOptions().diacriticSensitive(true)) - -Bitwise -------- - -The bitwise operator methods include the following: - -- ``bitsAllSet``: Selects documents where all the specified bits of a - field are set. -- ``bitsAllClear``: Selects documents where all the specified bits of a - field are clear. -- ``bitsAnySet``: Selects documents where at least one of the specified - bits of a field are set. -- ``bitsAnyClear``: Selects documents where at least one of the - specified bits of a field are clear. - -Examples -~~~~~~~~ - -The example selects documents that have a ``bitField`` field with bits set -at positions of the corresponding bitmask ``50`` (``00110010``): - -.. code-block:: scala - - bitsAllSet("bitField", 50) - -Geospatial ----------- - -The geospatial operator methods include the following: - -- ``geoWithin``: Selects all documents containing a field whose value is - a ``GeoJSON`` geometry that falls within a bounding ``GeoJSON`` - geometry. -- ``geoWithinBox``: Selects all documents containing a field with grid - coordinates data that exist entirely within the specified box. -- ``geoWithinPolygon``: Selects all documents containing a field with - grid coordinates data that exist entirely within the specified - polygon. -- ``geoWithinCenter``: Selects all documents containing a field with - grid coordinates data that exist entirely within the specified circle. -- ``geoWithinCenterSphere``: Selects geometries containing a field with - geospatial data (``GeoJSON`` or legacy coordinate pairs) that exist - entirely within the specified circle, using spherical geometry. -- ``geoIntersects``: Selects geometries that intersect with a - ``GeoJSON`` geometry. The ``2dsphere`` index supports ``$geoIntersects``. -- ``near``: Returns geospatial objects in proximity to a point. Requires - a geospatial index. The ``2dsphere`` and ``2d`` indexes support ``$near``. -- ``nearSphere``: Returns geospatial objects in proximity to a point on - a sphere. Requires a geospatial index. The ``2dsphere`` and ``2d`` indexes - support ``$nearSphere``. - -To make it easier to construct ``GeoJSON``-based filters, the driver -also includes a full ``GeoJSON`` class hierarchy: - -- ``Point``: Representation of a ``GeoJSON`` ``Point`` -- ``MultiPoint``: Representation of a ``GeoJSON`` ``MultiPoint`` -- ``LineString``: Representation of a ``GeoJSON`` ``LineString`` -- ``MultiLineString``: Representation of a ``GeoJSON`` ``MultiLineString`` -- ``Polygon``: Representation of a ``GeoJSON`` ``Polygon`` -- ``MultiPolygon``: Representation of a ``GeoJSON`` ``MultiPolygon`` -- ``GeometryCollection``: Representation of a ``GeoJSON`` ``GeometryCollection`` - -Examples -~~~~~~~~ - -The following example creates a filter that selects all documents where the ``geo`` -field contains a ``GeoJSON`` ``Geometry`` object that falls within the given -polygon: - -.. code-block:: scala - - val polygon: Polygon = Polygon(Seq(Position(0, 0), Position(4, 0), - Position(4, 4), Position(0, 4), - Position(0, 0))) - geoWithin("geo", polygon) - -The following example creates a filter that selects all documents -where the ``geo`` field contains a ``GeoJSON`` ``Geometry`` object that intersects -the given ``Point``: - -.. code-block:: scala - - geoIntersects("geo", Point(Position(4, 0))) diff --git a/source/builders/indexes.txt b/source/builders/indexes.txt deleted file mode 100644 index 544339c..0000000 --- a/source/builders/indexes.txt +++ /dev/null @@ -1,141 +0,0 @@ -.. _scala-builders-indexes: - -======= -Indexes -======= - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, optimize, geospatial, text search - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -The `Indexes <{+api+}/org/mongodb/scala/model/Indexes$.html>`__ class -provides static factory methods for the :manual:`MongoDB index key types -`. Each method returns an instance of the -``Bson`` type, which can in turn be used with the ``createIndex()`` methods. - -You can import the methods of the ``Indexes`` -class statically, as shown in the following code: - -.. code-block:: scala - - import org.mongodb.scala.model.Indexes._ - -The examples in this guide assume this static import. - -Ascending ---------- - -To specify an ascending index key, use one of the ``ascending()`` methods. - -The following example specifies an ascending index key for the ``quantity`` field: - -.. code-block:: scala - - ascending("quantity") - -The following example specifies a compound index key composed of the ``quantity`` -field sorted in ascending order and the ``totalAmount`` field sorted in -ascending order: - -.. code-block:: scala - - ascending("quantity", "totalAmount") - -Descending ----------- - -To specify a descending index key, use one of the ``descending()`` methods. - -The following example specifies a descending index key on the ``quantity`` field: - -.. code-block:: scala - - descending("quantity") - -The following example specifies a compound index key composed of the ``quantity`` -field sorted in descending order and the ``totalAmount`` field sorted in -descending order: - -.. code-block:: scala - - descending("quantity", "totalAmount") - -Compound Index --------------- - -To specify a compound index, use the ``compoundIndex()`` method. - -The following example specifies a compound index key composed of the ``quantity`` -field sorted in ascending order, followed by the ``totalAmount`` field -sorted in ascending order, followed by the ``orderDate`` field sorted in -descending order: - -.. code-block:: scala - - compoundIndex(ascending("quantity", "totalAmount"), descending("orderDate")) - -Text Index ----------- - -To specify a text index key, use the ``text()`` method. - -The following example specifies a text index key for the ``description`` field: - -.. code-block:: scala - - text("description") - -Hashed Index ------------- - -To specify a hashed index key, use the ``hashed()`` method. - -The following example specifies a hashed index key for the ``timestamp`` field: - -.. code-block:: scala - - hashed("timestamp") - -Geospatial Index ----------------- - -There are helpers for creating the index keys for the various geospatial -indexes supported by MongoDB. - -2dsphere -~~~~~~~~ - -To specify a 2dsphere index key, use one of the ``geo2dsphere()`` -methods. - -The following example specifies a ``2dsphere`` index on the ``location`` field: - -.. code-block:: scala - - geo2dsphere("location") - -2d -~~ - -To specify a ``2d`` index key, use the ``geo2d()`` method. - -.. important:: - - A ``2d`` index is for data stored as points on a two-dimensional plane - and is intended for legacy coordinate pairs used in MongoDB Server - version 2.2 and earlier. - -The following example specifies a ``2d`` index on the ``points`` field: - -.. code-block:: scala - - geo2d("points") \ No newline at end of file diff --git a/source/builders/projections.txt b/source/builders/projections.txt deleted file mode 100644 index b974a78..0000000 --- a/source/builders/projections.txt +++ /dev/null @@ -1,156 +0,0 @@ -.. _scala-builders-projections: - -=========== -Projections -=========== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, create fields, customize results - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -The `Projections <{+api+}/org/mongodb/scala/model/Projections$.html>`__ class provides static factory methods for the -:manual:`MongoDB projection operators `. Each method returns an instance of the -``Bson`` type, which can in turn be passed to any method that expects a -projection. - -You can import the methods of the ``Projections`` -class statically, as shown in the following code: - -.. code-block:: scala - - import org.mongodb.scala.model.Projections._ - -The examples in this guide assume this static import. - -Inclusion ---------- - -By default, all fields of each document are included in the results. To specify the -inclusion of one or more fields, which implicitly excludes all other -fields except ``_id``, use the ``include()`` method. - -The following example includes the ``quantity`` field and, implicitly, the -``_id`` field: - -.. code-block:: scala - - include("quantity") - -The following example includes the ``quantity`` and ``totalAmount`` -fields and, implicitly, the ``_id`` field: - -.. code-block:: scala - - include("quantity", "totalAmount") - -Exclusion ---------- - -To specify the exclusion of one or more fields, which implicitly -includes all other fields, use the ``exclude()`` method. - -The following example excludes the ``quantity`` field: - -exclude("quantity") - -The following example excludes the ``quantity`` and ``totalAmount`` fields: - -.. code-block:: scala - - exclude("quantity", "totalAmount") - -Exclusion of _id Field ----------------------- - -To specify the exclusion of the ``_id`` field, use the ``excludeId()`` -method: - -.. code-block:: scala - - excludeId() - -This is equivalent to the following code: - -.. code-block:: scala - - exclude("_id") - -Array Element Match with a Specified Filter -------------------------------------------- - -To specify a projection that includes only the first element of an array -that matches a supplied query filter, use the -``elemMatch()`` method that takes a field name and a filter. - -The following example projects the first element of the ``orders`` array field, -where the ``quantity`` field is greater than ``3``: - -.. code-block:: scala - - elemMatch("orders", Filters.gt("quantity", 3)) - -Array Element Match with an Implicit Filter -------------------------------------------- - -To specify a projection that includes only the first element of an array -that matches the filter supplied as part of the query, use the -``elemMatch()`` method that takes just a field name. - -The following example projects the first element of the ``orders`` array -that matches the query filter: - -.. code-block:: scala - - elemMatch("orders") - -Slice ------ - -To project a slice of an array, use one of the ``slice()`` methods. - -The following example projects the first ``7`` elements of the ``tags`` array: - -.. code-block:: scala - - slice("tags", 7) - -The following example skips the first ``2`` elements of the ``tags`` -array and projects the next ``5``: - -.. code-block:: scala - - slice("tags", 2, 5) - -Text Score ----------- - -To specify a projection of the score of a ``$text`` query, use the -``metaTextScore()`` method to specify the name of the projected field. - -The following example projects the text score as the value of the -``score`` field: - -.. code-block:: scala - - metaTextScore("score") - -Combining Projections ---------------------- - -To combine multiple projections, use the fields method. - -The following example includes the ``quantity`` and ``totalAmount`` -fields and excludes the ``_id`` field: - -.. code-block:: scala - - fields(include("quantity", "totalAmount"), excludeId()) diff --git a/source/builders/sorts.txt b/source/builders/sorts.txt deleted file mode 100644 index 6df7495..0000000 --- a/source/builders/sorts.txt +++ /dev/null @@ -1,95 +0,0 @@ -.. _scala-builders-sorts: - -===== -Sorts -===== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, consistent results, order results - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -The `Sorts <{+api+}/org/mongodb/scala/model/Sorts$.html>`__ class provides static factory methods for the MongoDB -sort criteria operators. Each method returns an instance of the ``Bson`` -type, which can in turn be passed to any method that expects sort -criteria. - -You can import the methods of the ``Sorts`` -class statically, as shown in the following code: - -.. code-block:: scala - - import org.mongodb.scala.model.Sorts._ - -The examples in this guide assume this static import. - -Ascending ---------- - -To specify an ascending sort, use one of the ``ascending()`` methods. - -The following example specifies an ascending sort on the ``quantity`` -field: - -.. code-block:: scala - - ascending("quantity") - -The following example specifies an ascending sort on the ``quantity`` field, followed -by an ascending sort on the ``totalAmount`` field: - -.. code-block:: scala - - ascending("quantity", "totalAmount") - -Descending ----------- - -To specify a descending sort, use one of the ``descending()`` methods. - -The following example specifies a descending sort on the ``quantity`` field: - -.. code-block:: scala - - descending("quantity") - -The following example specifies a descending sort on the ``quantity`` field, -followed by a ``descending`` sort on the ``totalAmount`` field: - -.. code-block:: scala - - descending("quantity", "totalAmount") - -Text Score ----------- - -To specify a sort on the score of a ``$text`` query, use the -``metaTextScore()`` method to specify the name of the projected field. - -The following example specifies a descending sort on the score of a ``$text`` query that will be -projected into the ``scoreValue`` field: - -.. code-block:: scala - - metaTextScore("scoreValue") - -Combining Sorts ---------------- - -To combine multiple sort criteria, use the ``orderBy()`` method. - -The following example specifies ascending sorts on the ``quantity`` and -``totalAmount`` fields, followed by a descending sort on the -``orderDate`` field: - -.. code-block:: scala - - orderBy(ascending("quantity", "totalAmount"), descending("orderDate")) diff --git a/source/builders/updates.txt b/source/builders/updates.txt deleted file mode 100644 index b3d50aa..0000000 --- a/source/builders/updates.txt +++ /dev/null @@ -1,320 +0,0 @@ -.. _scala-builders-updates: - -======= -Updates -======= - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, change data, operator - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -The `Updates <{+api+}/org/mongodb/scala/model/Updates$.html>`__ class provides static factory methods for the -:manual:`MongoDB update operators `. Each method returns an instance of the ``Bson`` -type, which can in turn be passed to any method that expects an update. - -You can import the methods of the ``Updates`` -class statically, as shown in the following code: - -.. code-block:: scala - - import org.mongodb.scala.model.Updates._ - -The examples in this guide assume this static import. - -Field Updates -------------- - -This section describes update operators that apply to the value of an -entire field. - -Set -~~~ - -The ``$set`` update operator sets the value of a field to the specified -value. - -The following example sets the value of the ``quantity`` field to ``11``: - -.. code-block:: scala - - set("quantity", 11) - -Unset -~~~~~ - -The ``$unset`` update operator deletes the field with the given name. - -The following example deletes the ``quantity`` field: - -.. code-block:: scala - - unset("quantity") - -Set On Insert -~~~~~~~~~~~~~ - -The ``$setOnInsert`` update operator sets the value of a field to the given -value, but only if the update is an upsert that results in an insert of -a document. - -The following example sets the value of the ``defaultQuantity`` field to ``10`` -if an upsert resulted in the insert of a document: - -.. code-block:: scala - - setOnInsert("defaultQuantity", 10) - -Increment -~~~~~~~~~ - -The ``$inc`` update operator increments the value of a numeric field by a specified value. - -The following example increments the value of the ``quantity`` field by ``5``: - -.. code-block:: scala - - inc("quantity", 5) - -Multiply -~~~~~~~~ - -The ``$mul`` update operator multiplies the value of a numeric field by a specified value. - -The following example multiplies the value of the ``price`` field by ``1.2``: - -.. code-block:: scala - - mul("price", 1.2) - -Rename -~~~~~~ - -The ``$rename`` update operator renames a field. - -The following example renames the ``qty`` field to ``quantity``: - -.. code-block:: scala - - rename("qty", "quantity") - -Min -~~~ - -The ``$min`` update operator updates the value of the field to a -specified value if the specified value is less than the current value of -the field. - -The following example sets the value of the ``lowScore`` field to the minimum of -its current value and ``150``: - -.. code-block:: scala - - min("lowScore", 150) - -Max -~~~ - -The ``$max`` update operator updates the value of the field to a -specified value if the specified value is greater than the current value -of the field. - -The following example sets the value of the ``highScore`` field to the maximum of -its current value and ``900``: - -.. code-block:: scala - - max("highScore", 900) - -Current Date -~~~~~~~~~~~~ - -The ``$currentDate`` update operator sets the value of the field with -the specified name to the current date, either as a BSON date or as a -BSON timestamp. - -The following example sets the value of the ``lastModified`` field to the current -date as a BSON date type: - -.. code-block:: scala - - currentDate("lastModified") - -The following example sets the value of the ``lastModified`` field to the current -date as a BSON timestamp type: - -.. code-block:: scala - - currentTimestamp("lastModified") - -Bit -~~~ - -The ``$bit`` update operator performs a bitwise update of the integral -value of a field. - -The following example performs a bitwise ``AND`` between the number ``10`` and -the integral value of the mask field: - -.. code-block:: scala - - bitwiseAnd("mask", 10) - -The following example performs a bitwise ``OR`` between the number ``10`` and the -integral value of the mask field: - -.. code-block:: scala - - bitwiseOr("mask", 10) - -The following example performs a bitwise ``XOR`` between the number ``10`` and -the integral value of the mask field: - -.. code-block:: scala - - bitwiseXor("mask", 10) - -Array Updates -------------- - -This section describes update operators that apply to the contents of -the array value of a field. - -Add to Set -~~~~~~~~~~ - -The ``$addToSet`` update operator adds a value to an array unless the -value is already present, in which case the operator does nothing to -that array. - -The following example adds the value ``"a"`` to the array value of the -``letters`` field: - -.. code-block:: scala - - addToSet("letters", "a") - -The following example adds each of the values ``"a"``, ``"b"``, and ``"c"`` to -the array value of the ``letters`` field: - -.. code-block:: scala - - addEachToSet("letters", Arrays.asList("a", "b", "c")) - -Pop -~~~ - -The ``$pop`` update operator removes the first or last element of an array. - -The following example removes the first element of the array value of the ``scores`` field: - -.. code-block:: scala - - popFirst("scores") - -The following example removes the last element of the array value of the ``scores`` field: - -.. code-block:: scala - - popLast("scores") - -Pull All -~~~~~~~~ - -The ``$pullAll`` update operator removes all instances of the specified -values from an existing array. - -The following example removes the scores ``0`` and ``5`` from the ``scores`` array: - -.. code-block:: scala - - pullAll("scores", Arrays.asList(0, 5)) - -Pull -~~~~ - -The ``$pull`` update operator removes from an existing array all -instances of a value or values that match a specified query. - -The following example removes the value ``0`` from the ``scores`` array: - -.. code-block:: scala - - pull("scores", 0) - -The following example removes all elements from the ``votes`` array that are greater -than or equal to ``6``: - -.. code-block:: scala - - pullByFilter(Filters.gte("votes", 6)) - -Push -~~~~ - -The ``$push`` update operator appends a specified value to an array. - -The following example pushes the value ``89`` to the ``scores`` array: - -.. code-block:: scala - - push("scores", 89) - -The following example pushes the values ``89``, ``90``, and ``92`` to the -``scores`` array: - -.. code-block:: scala - - pushEach("scores", 89, 90, 92) - -The following example pushes the values ``89``, ``90``, and ``92`` to the -start of the ``scores`` array: - -.. code-block:: scala - - pushEach("scores", new PushOptions().position(0), 89, 90, 92) - -The following example pushes the values ``89``, ``90``, and ``92`` to the ``scores`` -array, sorts the array in descending order, and removes all but the -first ``5`` elements of the array: - -.. code-block:: scala - - pushEach("scores", new PushOptions().sort(-1).slice(5), 89, 90, 92) - -The following example pushes the documents ``{ wk: 5, score: 8 }``, ``{ wk: 6, -score: 7 }``, and ``{ wk: 7, score: 6 }`` to the quizzes array, sorts -the array in descending order by score, and removes all but the last ``3`` -elements of the array: - -.. code-block:: scala - - pushEach("quizzes", new PushOptions().sortDocument(Sorts.descending("score")).slice(-3), - Document("week" -> 5, "score" -> 8), - Document("week" -> 6, "score" -> 7), - Document("week" -> 7, "score" -> 6)) - -Combining Multiple Update Operators ------------------------------------ - -Often, an application must atomically update multiple fields of -a single document by combining two or more of the update operators -described in the preceding sections. - -The following example sets the value of the ``quantity`` field to ``11``, the value of -the ``total`` field to ``30.40``, and pushes the values ``4.99``, ``5.99``, and -``10.99`` to the array value of the ``prices`` field: - -.. code-block:: scala - - combine(set("quantity", 11), - set("total", 30.40), - pushEach("prices", 4.99, 5.99, 10.99)) diff --git a/source/compatibility.txt b/source/compatibility.txt index 5ff2813..b982658 100644 --- a/source/compatibility.txt +++ b/source/compatibility.txt @@ -1,4 +1,4 @@ -.. _scala-compatibility-tables: +.. _scala-compatibility: ============= Compatibility diff --git a/source/get-started/primer.txt b/source/get-started/primer.txt deleted file mode 100644 index e53155d..0000000 --- a/source/get-started/primer.txt +++ /dev/null @@ -1,114 +0,0 @@ -.. _scala-primer: - -====== -Primer -====== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, design, types, preparation - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -This guide provides background about the {+driver-short+} and its -asynchronous API before showing you how to use the driver and MongoDB in -the :ref:`Quick Start guide `. - -.. note:: - - See the :ref:`Installation guide ` - for instructions on how to install the driver. - -Reactive Streams ----------------- - -The {+driver-short+} is built upon the `MongoDB Java Reactive Streams -driver `__. -The reactive stream API consists of the following components: - -1. ``Observable``: a custom implementation of a `Publisher <{+rs-docs+}/Publisher.html>`__ -#. ``Observer``: a custom implementation of a `Subscriber <{+rs-docs+}/Subscriber.html>`__ -#. `Subscription <{+rs-docs+}/Subscription.html>`__ - -An ``Observable`` is a provider of a potentially unbounded number of -sequenced elements, published according to the demand received from its -``Observer`` or multiple instances of ``Observer``. - -In response to a call to ``Observable.subscribe(Observer)``, the -possible invocation sequences for methods on the ``Observer`` class -are given by the following protocol: - -.. code-block:: scala - :copyable: false - - onSubscribe onNext* (onError | onComplete)? - -This means that ``onSubscribe()`` is always -signaled, followed by a possibly unbounded number -of ``onNext()`` signals, as requested by -``Observer``. This is followed by an ``onError()`` signal -if there is a failure or an ``onComplete()`` signal -when no more elements are available, as long as -the ``Subscription`` is not canceled. - -.. tip:: - - To learn more about reactive streams, visit - the `Reactive Streams documentation `__. - -Observables ------------ - -The {+driver-short+} API mirrors the :driver:`Java Sync driver -` API and any methods that cause network -I/O to return an ``Observable`` type, where ``T`` is the type of response -for the operation. - -.. note:: - - All ``Observable`` types returned from the API are - `cold `__, - meaning that nothing happens until they are subscribed to. So just - creating an ``Observable`` won’t cause any network I/O. It’s not until - you call the ``Subscription.request()`` method that the driver executes - the operation. - - Publishers in this implementation are *unicast*. Each - ``Subscription`` to an ``Observable`` relates to a single MongoDB - operation, and the ``Observable`` instance's ``Observer`` receives its - own specific set of results. - -Back Pressure -~~~~~~~~~~~~~ - -By default, the ``Observer`` trait will request all the results from the -``Observer`` as soon as the ``Observable`` is subscribed to. Ensure that -the ``Observer`` can handle all the results from the -``Observable``. Custom implementations of the ``Observer.onSubscribe()`` -method can save the ``Subscription`` so that data is only -requested when the ``Observer`` has the capacity. - -Helpers Used in the in the Quick Start --------------------------------------- - -In the Quick Start, we have implemented custom implicit helpers defined -in the `Helpers.scala -<{+driver-source-gh+}/blob/master/driver-scala/src/integration/scala/tour/Helpers.scala>`__ -file in the driver source GitHub repository. -These helpers retrieve and print results. Though the Quick Start is an -artificial scenario for asynchronous code, the examples block on the -results of one example before starting the next, to ensure the -state of the database. The ``Helpers`` object provides the following -methods: - -- ``results()``: blocks until the ``Observable`` is completed and returns the collected results -- ``headResult()``: blocks until the first result of the ``Observable`` can be returned -- ``printResults()``: blocks until the ``Observable`` is completed, and prints out each result -- ``printHeadResult()``: blocks until the first result of the ``Observable`` is available and then prints it diff --git a/source/get-started/qs-case-class.txt b/source/get-started/qs-case-class.txt deleted file mode 100644 index 0e9b8ca..0000000 --- a/source/get-started/qs-case-class.txt +++ /dev/null @@ -1,228 +0,0 @@ -.. _scala-case-class-qs: - -================================= -Quick Start (Case Class Examples) -================================= - -.. facet:: - :name: genre - :values: tutorial - -.. meta:: - :keywords: code example, get started, connect, change data, case class - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Overview --------- - -This guide is similar to the :ref:`Quick Start guide -` but uses case classes to -model documents instead of the generic ``Document`` class. - -The code examples in this guide come from the `QuickTourCaseClass.scala -<{+driver-source-gh+}/blob/master/driver-scala/src/integration/scala/tour/QuickTourCaseClass.scala>`__ -file in the driver source code GitHub repository. - -.. important:: - - See the :ref:`BSON Macros ` documentation for information about - using macros for configuring case class support with your - ``MongoCollection`` instance. - -First, create the case class you want to use to represent the -documents in the collection. The following code creates a ``Person`` case -class and companion object: - -.. code-block:: scala - - import org.mongodb.scala.bson.ObjectId - - object Person { - def apply(firstName: String, lastName: String): Person = - Person(new ObjectId(), firstName, lastName) - } - case class Person(_id: ObjectId, firstName: String, lastName: String) - -.. note:: - - In the companion object, the ``apply()`` method can automatically - assign an ``_id`` value when creating new instances that don’t include one. In - MongoDB the ``_id`` field represents the primary key for a document, so by - having an ``_id`` field in the case class, you allow access to the primary - key. - -Configuring Case Classes ------------------------- - -When using ``Person`` with a collection, there must be a ``Codec`` that can -convert it to and from BSON. The ``org.mongodb.scala.bson.codecs.Macros`` -companion object provides macros that can automatically generate a codec -for case classes at compile time. The following example creates a -new ``CodecRegistry`` that includes a codec for the ``Person`` case -class: - -.. code-block:: scala - - import org.mongodb.scala.bson.codecs.Macros._ - import org.mongodb.scala.MongoClient.DEFAULT_CODEC_REGISTRY - import org.bson.codecs.configuration.CodecRegistries.{fromRegistries, fromProviders} - - val codecRegistry = fromRegistries(fromProviders(classOf[Person]), DEFAULT_CODEC_REGISTRY ) - -Once the ``CodecRegistry`` is configured, the next step is to create a -``MongoCollection[Person]``. The following example uses the ``test`` collection from -the ``mydb`` database: - -.. code-block:: scala - - val mongoClient: MongoClient = MongoClient() - val database: MongoDatabase = mongoClient.getDatabase("mydb").withCodecRegistry(codecRegistry) - val collection: MongoCollection[Person] = database.getCollection("test") - -.. note:: - - The ``CodecRegistry`` can be set when creating a ``MongoClient``, at the - database level, or at the collection level. The API is flexible, allowing - for different ``CodecRegistry`` instances as required. - -Insert a Person ---------------- - -With the correctly configured ``MongoCollection``, inserting ``Person`` -instances into the collection is simple: - -.. code-block:: scala - - val person: Person = Person("Ada", "Lovelace") - collection.insertOne(person).results() - -Insert Multiple Person Instances --------------------------------- - -To insert multiple ``Person`` instances, use the ``insertMany()`` -method. The following example uses the ``printResults()`` implicit and -blocks until the observer is completed and then prints each result: - -.. code-block:: scala - - val people: Seq[Person] = Seq( - Person("Charles", "Babbage"), - Person("George", "Boole"), - Person("Gertrude", "Blanch"), - Person("Grace", "Hopper"), - Person("Ida", "Rhodes"), - Person("Jean", "Bartik"), - Person("John", "Backus"), - Person("Lucy", "Sanders"), - Person("Tim", "Berners Lee"), - Person("Zaphod", "Beeblebrox") - ) - - collection.insertMany(people).printResults() - -This code outputs the following message: - -.. code-block:: none - - The operation completed successfully - -Querying the Collection ------------------------ - -Use the ``find()`` method to query a collection. - -Find the First Matching Person -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Querying the collection uses the same API used in the Quick Start: - -.. code-block:: scala - - collection.find().first().printHeadResult() - -The example prints the first ``Person`` in the collection: - -.. code-block:: none - - Person(58dd0a68218de22333435fa4, Ada, Lovelace) - -Return All Documents -~~~~~~~~~~~~~~~~~~~~ - -To retrieve all the documents in the collection, use the ``find()`` method. The -``find()`` method returns a ``FindObservable`` instance that provides a fluent -interface for chaining or controlling find operations. The following -uses prints all the documents in the collection as ``Person`` instances: - -.. code-block:: scala - - collection.find().printResults() - -Retrieve a Person By Using a Query Filter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To return a subset of the documents in our collection, pass a filter to -the ``find()`` method. For example, the following example returns the first -``Person`` whose first name is ``"Ida"``: - -.. code-block:: scala - - import org.mongodb.scala.model.Filters._ - - collection.find(equal("firstName", "Ida")).first().printHeadResult() - -This example outputs the following result: - -.. code-block:: none - - Person(58dd0a68218de22333435fa4, Ida, Rhodes) - -.. tip:: - - You can use the ``Filters``, ``Sorts``, ``Projections`` and - ``Updates`` helpers to enable simple and concise ways of building queries. - -Find Matching Person Instances -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The following filter finds all ``Person`` instances where the -``firstName`` starts with ``"G"``, sorted by ``lastName``: - -.. code-block:: scala - - collection.find(regex("firstName", "^G")).sort(ascending("lastName")).printResults() - -This example prints out the ``Person`` instances for ``"Gertrude"``, ``"George"`` and ``"Grace"``. - -Update Documents ----------------- - -There are many :manual:`update operators -` supported by MongoDB. Use the ``Updates`` -helpers to help update documents in a collection. - -The following update corrects the hyphenation for ``"Tim Berners-Lee"``: - -.. code-block:: scala - - collection.updateOne(equal("lastName", "Berners Lee"), set("lastName", "Berners-Lee")) - .printHeadResult("Update Result: ") - -The update methods return an ``UpdateResult``, which provides -information about the operation, including the number of documents -modified by the update. - -Delete Documents ----------------- - -To delete at most a single document, or none if no documents match the -filter, use the ``deleteOne()`` method: - -.. code-block:: scala - - collection.deleteOne(equal("firstName", "Zaphod")).printHeadResult("Delete Result: ") diff --git a/source/get-started/quickstart.txt b/source/get-started/quickstart.txt deleted file mode 100644 index 0f9801a..0000000 --- a/source/get-started/quickstart.txt +++ /dev/null @@ -1,502 +0,0 @@ -.. _scala-quickstart: - -=========== -Quick Start -=========== - -.. facet:: - :name: genre - :values: tutorial - -.. meta:: - :keywords: code example, get started, connect, change data - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Overview --------- - -The code examples in this guide come from the `QuickTour.scala -<{+driver-source-gh+}/blob/master/driver-scala/src/integration/scala/tour/QuickTour.scala>`__ -file in the driver source code GitHub repository. - -.. include:: /includes/install-note.rst - -.. include:: /includes/obs-note.rst - -Prerequisites -------------- - -You must set up the following components to run the code examples in -this guide: - -- MongoDB server running on the default port for MongoDB (``27017``) -- Driver dependency installed in your project -- The following import statements: - - .. code-block:: scala - - import org.mongodb.scala._ - import org.mongodb.scala.model.Aggregates._ - import org.mongodb.scala.model.Filters._ - import org.mongodb.scala.model.Projections._ - import org.mongodb.scala.model.Sorts._ - import org.mongodb.scala.model.Updates._ - import org.mongodb.scala.model._ - - import scala.collection.JavaConverters._ - -Make a Connection ------------------ - -Use the `MongoClient <{+api+}/org/mongodb/scala/MongoClient$.html>`__ -companion object to make a connection to a running MongoDB deployment. - -The ``MongoClient`` instance represents a pool of connections to the -database. You need only one instance of ``MongoClient`` even -when using concurrent operation threads. - -.. important:: - - Typically, you create only one ``MongoClient`` instance for a given - MongoDB deployment, such as a standalone deployment, replica set, or a sharded - cluster, and use the client across your application. However, if you do create - multiple instances, keep the following in mind: - - - All resource-usage limits (for example, max connections) apply to - each ``MongoClient`` instance. - - To dispose of an instance, call the ``MongoClient.close()`` method - to clean up resources. - -Connect to a Single MongoDB Deployment -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The following example shows several ways to connect to a single -MongoDB deployment. - -You can connect to a single MongoDB deployment in the following ways: - -- Instantiate a ``MongoClient`` object without any parameters to - connect to a MongoDB server running on localhost on port ``27017``: - - .. code-block:: scala - - val mongoClient: MongoClient = MongoClient() - -- Explicitly specify the ``hostname`` to connect to a MongoDB - instance running on the specified host on port ``27017``: - - .. code-block:: scala - - val mongoClient: MongoClient = MongoClient( - MongoClientSettings.builder() - .applyToClusterSettings((builder: ClusterSettings.Builder) => builder.hosts(List(new ServerAddress("hostOne")).asJava)) - .build()) - -- Explicitly specify the ``hostname`` and the ``port``: - - .. code-block:: scala - - val mongoClient: MongoClient = MongoClient( - MongoClientSettings.builder() - .applyToClusterSettings((builder: ClusterSettings.Builder) => builder.hosts(List(new ServerAddress("hostOne", 27017)).asJava)) - .build()) - -- Specify the ``ConnectionString``: - - .. code-block:: scala - - val mongoClient: MongoClient = MongoClient("mongodb://hostOne:27017") - -Access a Database ------------------ - -Once you have a ``MongoClient`` instance connected to a MongoDB -deployment, use the ``MongoClient.getDatabase()`` method to access a -database. - -Specify the name of the database to the ``getDatabase()`` method. If a -database does not exist, MongoDB creates the database when you first -store data for that database. - -The following example accesses the ``mydb`` database: - -.. code-block:: scala - - val database: MongoDatabase = mongoClient.getDatabase("mydb") - -``MongoDatabase`` instances are immutable. - -Access a Collection -------------------- - -Once you have a ``MongoDatabase`` instance, use the -``getCollection()`` method to access a collection. - -Specify the name of the collection to the ``getCollection()`` method. If -a collection does not exist, MongoDB creates the collection when you -first store data for that collection. - -For example, using the database instance, the following statement -accesses the collection named ``test`` in the ``mydb`` database: - -.. code-block:: scala - - val collection: MongoCollection[Document] = database.getCollection("test") - -``MongoCollection`` instances are immutable. - -Create a Document ------------------ - -To create the document by using the driver, use the `Document -<{+api+}/org/mongodb/scala/bson/collection/immutable/Document.html>`__ -class. - -For example, consider the following JSON document: - -.. code-block:: json - - { - "name" : "MongoDB", - "type" : "database", - "count" : 1, - "info" : { x : 203, y : 102 } - } - -To create the document by using the driver, instantiate a ``Document`` -object: - -.. code-block:: scala - - val doc: Document = Document("_id" -> 0, "name" -> "MongoDB", "type" -> "database", - "count" -> 1, "info" -> Document("x" -> 203, "y" -> 102)) - -Insert a Document ------------------ - -Once you have the ``MongoCollection`` object, you can insert documents -into the collection. - -Insert One Document -~~~~~~~~~~~~~~~~~~~ - -To insert a document into a collection, use the ``insertOne()`` method. -Then, use the ``results()`` implicit helper to block until the observer is -completed: - -.. code-block:: scala - - collection.insertOne(doc).results() - -.. warning:: - - The driver provides two document types, an immutable ``Document`` and - a mutable ``Document``. - - When using an immutable document, you should explicitly add an ``_id`` - value, if you need to know that ``_id`` value in the future. - - If no top-level ``_id`` field is specified in the document, MongoDB - automatically generates a value and adds this field to the inserted - document, but that ``_id`` will not be passed back to the user. - -.. important:: - - In the API, all methods returning an ``Observable`` instance are cold - streams, meaning that nothing happens until they are subscribed to. - - For example, the example below does nothing: - - .. code-block:: scala - - val observable: Observable[InsertOneResult] = collection.insertOne(doc) - - The operation happens only when a ``Observable`` is subscribed to and - data is requested. - - .. code-block:: scala - - observable.subscribe(new Observer[InsertOneResult] { - - override def onSubscribe(subscription: Subscription): Unit = subscription.request(1) - - override def onNext(result: InsertOneResult): Unit = println(s"onNext $result") - - override def onError(e: Throwable): Unit = println("Failed") - - override def onComplete(): Unit = println("Completed") - }) - - Once the document has been inserted, the ``onNext()`` method is called - and prints ``onNext`` followed by the result. Finally, the - ``onComplete()`` method prints ``Completed``. If there was an error - for any reason, the ``onError()`` method would print ``Failed``. - -Insert Multiple Documents -~~~~~~~~~~~~~~~~~~~~~~~~~ - -To insert multiple documents, you can use the collection's -``insertMany()`` method, which takes a list of documents to insert. - -The following example adds multiple documents in the following form: - -.. code-block:: json - - { "i" : value } - -Create the documents in a loop and add them to the ``documents`` list: - -.. code-block:: scala - - val documents = (1 to 100) map { i: Int => Document("i" -> i) } - -To insert these documents into the collection, pass the list of -documents to the ``insertMany()`` method: - -.. code-block:: scala - - val insertObservable = collection.insertMany(documents) - -The preceding example blocks on the ``Observable`` to finish. This ensures -that the data is in the database before the next operation runs. - -Count Documents in A Collection -------------------------------- - -To count the number of documents in a collection, you can use the -collection's ``count()`` method. The following code should -print ``101``, describing the 100 inserted documents by using -``insertMany()`` and the 1 inserted by using ``insertOne()``. - -Chain the two operations together using a ``for`` comprehension. The -following code inserts the documents, then counts the number of -documents, and prints the results: - -.. code-block:: scala - - val insertAndCount = for { - insertResult <- insertObservable - countResult <- collection.countDocuments() - } yield countResult - - println(s"Total # of documents: ${insertAndCount.headResult()}") - -Query the Collection --------------------- - -To query the collection, you can use the collection's ``find()`` -method. You can call the method without any arguments to query all -documents in a collection or pass a filter to query for documents that -match the filter criteria. - -Find the First Document in a Collection -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To return the first document in the collection, use the ``find()`` -method without any parameters and chain the ``first()`` method. - -.. tip:: - - The ``find().first()`` construct is useful for queries that should - only match a single document or if you are interested only in the - first matching document. - -The following example prints the first document found in the -collection: - -.. code-block:: scala - - collection.find().first().printHeadResult() - -The example should print the following document: - -.. code-block:: json - - { - "_id" : { "$oid" : "551582c558c7b4fbacf16735" }, - "name" : "MongoDB", - "type" : "database", - "count" : 1, - "info" : { "x" : 203, "y" : 102 } - } - -.. note:: - - The ``_id`` element has been added automatically by MongoDB to your - document and your value will differ from the one shown. MongoDB - reserves field names that start with ``_`` and ``$`` for internal use. - -Find All Documents in a Collection -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To retrieve all of the documents in the collection, use the -``find()`` method. The ``find()`` method returns a ``FindObservable`` -instance that provides a fluent interface for chaining or controlling -find operations. The following code retrieves and prints all documents in the -collection: - -.. code-block:: scala - - collection.find().printResults() - -Specify a Query Filter ----------------------- - -To query for documents that match certain conditions, pass a filter -object to the ``find()`` method. To facilitate creating filter -objects, the driver provides ``Filters`` helper methods. - -Get A Single Document That Matches a Filter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To find the first document in which the field ``i`` has the -value ``71``, pass an ``eq()`` filter definition to specify the equality -condition: - -.. code-block:: scala - - collection.find(equal("i", 71)).first().printHeadResult() - -The example prints one document: - -.. code-block:: json - - { "_id" : { "$oid" : "..." }, "i" : 71 } - -Get All Documents That Match a Filter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The following example returns and prints all documents in which the -value of ``i`` is greater than ``50``: - -.. code-block:: scala - - collection.find(gt("i", 50)).printResults() - -To specify a filter for a range, such as ``50 < i <= 100``, you can use -the ``and()`` helper: - -.. code-block:: scala - - collection.find(and(gt("i", 50), lte("i", 100))).printResults() - -Update Documents ----------------- - -To update documents in a collection, you can use the collection's -``updateOne()`` and ``updateMany()`` methods. - -Pass the following parameters to the methods: - -- Filter object to determine the document or documents to update. To - facilitate creating filter objects, the driver provides - ``Filters`` helper methods. To specify an empty filter and match all - documents in the collection, use an empty ``Document`` object. -- Update document that specifies the modifications. To view a list of - the available operators, see :manual:`Update Operators - ` in the Server manual. - -The update methods return an ``UpdateResult`` type that provides -information about the operation, including the number of documents -modified by the update. - -Update a Single Document -~~~~~~~~~~~~~~~~~~~~~~~~ - -To update a single document, use the ``updateOne()`` method. - -The following example updates the first document in which -``i`` is ``10`` and sets the value of ``i`` to ``110``: - -.. code-block:: scala - - collection.updateOne(equal("i", 10), set("i", 110)).printHeadResult("Update Result: ") - -Update Multiple Documents -~~~~~~~~~~~~~~~~~~~~~~~~~ - -To update all documents matching a query filter, use the -``updateMany()`` method. - -The following example increments the value of ``i`` by ``100`` in all -documents in which the value of ``i`` is less than ``100``: - -.. code-block:: scala - - collection.updateMany(lt("i", 100), inc("i", 100)).printHeadResult("Update Result: ") - -Delete Documents ----------------- - -To delete documents from a collection, you can use the collection's -``deleteOne()`` and ``deleteMany()`` methods. - -Pass a filter object to determine the document or -documents to delete. To facilitate creating filter objects, the driver -provides ``Filters`` helper methods. To specify an empty filter and -match all documents in the collection, use an empty ``Document`` object. - -The delete methods return a ``DeleteResult`` that provides -information about the operation, including the number of documents -deleted. - -Delete a Single Document That Matches a Filter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To delete a single document that matches the filter, use the -``deleteOne()`` method. - -The following example deletes the first document in which the value of -``i`` equals ``110``: - -.. code-block:: scala - - collection.deleteOne(equal("i", 110)).printHeadResult("Delete Result: ") - -Delete All Documents That Match a Filter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To delete all documents that match the filter, use the ``deleteMany()`` -method. - -The following example deletes all documents in which the value of ``i`` -is greater or equal to ``100``: - -.. code-block:: scala - - collection.deleteMany(gte("i", 100)).printHeadResult("Delete Result: ") - -Create Indexes --------------- - -To create an index on a field or fields, pass an index specification -document to the ``createIndex()`` method. An index key specification -document contains the fields to index and the index type for each -field, as modeled by the following document: - -.. code-block:: scala - - Document( -> , , , ...) - -To create an ascending index type, specify ``1`` for ````. -To create a descending index type, specify ``-1`` for ````. - -The following example creates an ascending index on the ``i`` field: - -.. code-block:: scala - - collection.createIndex(Document("i" -> 1)).printHeadResult("Create Index Result: %s") - -To view a list of other index types, see the :ref:`scala-indexes` guide. - -Additional Information ----------------------- - -The :ref:`scala-case-class-qs` guide demonstrates how to use the driver with case classes. - -You can find additional tutorials in the :ref:`scala-tutorials` section. diff --git a/source/includes/connect-section.rst b/source/includes/connect-section.rst deleted file mode 100644 index b036014..0000000 --- a/source/includes/connect-section.rst +++ /dev/null @@ -1,16 +0,0 @@ -First, connect to a MongoDB deployment, then declare and define -``MongoDatabase`` and ``MongoCollection`` instances. - -The following code connects to a standalone -MongoDB deployment running on ``localhost`` on port ``27017``. Then, it -defines the ``database`` variable to refer to the ``test`` database and -the ``collection`` variable to refer to the ``restaurants`` collection: - -.. code-block:: scala - - val mongoClient: MongoClient = MongoClient() - val database: MongoDatabase = mongoClient.getDatabase("test") - val collection: MongoCollection[Document] = database.getCollection("restaurants") - -To learn more about connecting to MongoDB deployments, -see the :ref:`scala-connect` tutorial. \ No newline at end of file diff --git a/source/includes/dns_seedlist_connection_string_parts.png b/source/includes/dns_seedlist_connection_string_parts.png deleted file mode 100644 index d57165511438d2730a511d8d24f44a7bafda3dab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9609 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 E06j{~lK=n! diff --git a/source/includes/install-note.rst b/source/includes/install-note.rst deleted file mode 100644 index e6d4584..0000000 --- a/source/includes/install-note.rst +++ /dev/null @@ -1,4 +0,0 @@ -.. note:: - - For instructions about how to install the {+driver-short+}, - see the :ref:`Installation ` guide. \ No newline at end of file diff --git a/source/includes/obs-note.rst b/source/includes/obs-note.rst deleted file mode 100644 index fe584cb..0000000 --- a/source/includes/obs-note.rst +++ /dev/null @@ -1,4 +0,0 @@ -.. note:: - - This guide uses the ``Observable`` implicits as covered in the - :ref:`Quick Start Primer `. \ No newline at end of file diff --git a/source/includes/prereq-restaurants.rst b/source/includes/prereq-restaurants.rst deleted file mode 100644 index add2719..0000000 --- a/source/includes/prereq-restaurants.rst +++ /dev/null @@ -1,8 +0,0 @@ -You must set up the following components to run the code examples in -this guide: - -- A ``test.restaurants`` collection populated with documents from the - ``restaurants.json`` file in the `documentation assets GitHub - `__. - -- The following import statements: \ No newline at end of file diff --git a/source/includes/security/crypt-maven-versioned.rst b/source/includes/security/crypt-maven-versioned.rst deleted file mode 100644 index 1a87d8a..0000000 --- a/source/includes/security/crypt-maven-versioned.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. code-block:: xml - - - - org.mongodb - mongodb-crypt - {+mongocrypt-version+} - - \ No newline at end of file diff --git a/source/includes/security/crypt-sbt-versioned.rst b/source/includes/security/crypt-sbt-versioned.rst deleted file mode 100644 index c5c2841..0000000 --- a/source/includes/security/crypt-sbt-versioned.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. code-block:: none - - libraryDependencies += "org.mongodb" % "mongodb-crypt" % "{+mongocrypt-version+}" diff --git a/source/index.txt b/source/index.txt index 1cbdafb..6872ae8 100644 --- a/source/index.txt +++ b/source/index.txt @@ -12,14 +12,8 @@ :titlesonly: :maxdepth: 1 - /installation /get-started /whats-new - /tutorials - /reference - /bson - /builders - /validate-signatures /compatibility View the Source API Documentation <{+api+}/index.html> @@ -28,53 +22,86 @@ Introduction ------------ -Welcome to the documentation site for the {+driver-long+}, the -official driver for asynchronous stream processing. Download -the driver by following the :ref:`scala-install` guide, then set up a -runnable project by following the :ref:`scala-quickstart` guide. +Welcome to the documentation site for the {+driver-long+}, the official MongoDB driver for +{+language+} applications. -- :ref:`scala-install` +Get Started +----------- -- :ref:`scala-get-started` +Learn how to install the driver, establish a connection to MongoDB, and begin +working with data in the :ref:`scala-get-started` tutorial. -- :ref:`scala-whats-new` +.. TODO +.. Connect to MongoDB +.. ------------------ -- :ref:`scala-tutorials` +.. Learn how to create and configure a connection to a MongoDB deployment +.. in the :ref:`scala-connect` section. -- :ref:`scala-reference` +.. TODO +.. Databases and Collections +.. ------------------------- +.. Learn how to use the {+driver-short+} to work with MongoDB databases and collections in the +.. :ref:`scala-databases-collections` section. -- :ref:`scala-bson` +What's New +---------- -- :ref:`scala-builders` +For a list of new features and changes in each version, see the :ref:`What's New ` +section. -- :ref:`Validate Driver Artifact Signatures ` +.. TODO +.. Write Data to MongoDB +.. --------------------- -- `Driver Source GitHub Repository `__ +.. Learn how you can write data to MongoDB in the :ref:`scala-write` section. -- `API Documentation <{+api+}/index.html>`__ +.. TODO +.. Read Data from MongoDB +.. ---------------------- -- `Previous Versions Documentation `__ +.. Learn how you can retrieve data from MongoDB in the :ref:`scala-read` section. -To access MongoDB by using Java instead of Scala, use the `Java Sync -Driver `__ or -the asynchronous `Java Reactive Streams Driver -`__. +.. TODO +.. Optimize Queries by Using Indexes +.. --------------------------------- -Compatibility -------------- +.. Learn how to work with common types of indexes in the :ref:`scala-indexes` +.. section. + +.. TODO +.. Transform Your Data with Aggregation +.. ------------------------------------ + +.. Learn how to use the {+driver-short+} to perform aggregation operations in the +.. :ref:`scala-aggregation` section. + +.. TODO +.. Configure Operations on Replica Sets +.. ------------------------------------ -Learn about compatibility between the {+driver-short+} and MongoDB, and -the driver and Scala in the :ref:`scala-compatibility-tables` section. +.. Learn how to configure read and write operations on a replica set in the +.. :ref:`scala-read-write-config` section. -How To Get Help ---------------- +.. TODO +.. Issues & Help +.. ------------- -- Ask questions on our :community-forum:`MongoDB Community Forums <>` -- Visit our :technical-support:`Support Channels ` -- See :driver:`Issues & Help ` +.. Learn how to report bugs, contribute to the driver, and find more resources for +.. asking questions and receiving help in the :ref:`Issues & Help ` section. + +.. TODO +.. Compatibility +.. ------------ + +.. To learn about the versions of the {+mdb-server+} and the C language that are compatible with +.. each version of the {+driver-short+}, see the :ref:`Compatibility ` section. + +Developer Hub +------------- -Validate Driver Artifact Signatures ------------------------------------ +The Developer Hub provides tutorials and social engagement for +developers. -Learn about how to validate signatures of {+driver-short+} artifacts -published on Maven in the :ref:`Validate Driver Artifact Signatures ` section. \ No newline at end of file +To ask questions and engage in discussions with fellow developers who +use the {+driver-short+}, see the `forums page `__. \ No newline at end of file diff --git a/source/installation.txt b/source/installation.txt deleted file mode 100644 index 1e5d939..0000000 --- a/source/installation.txt +++ /dev/null @@ -1,83 +0,0 @@ -.. _scala-install: - -============ -Installation -============ - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: dependency, reactive streams, add package, sbt, maven - -The recommended way to get started with the {+driver-short+} in your -project is by using a dependency management system. To manually -download the MongoDB Scala driver, see the `Scala driver Maven repository -`__. - -The following sections contain the Reactive Streams-based Scala -implementation for asynchronous stream processing with non-blocking back -pressure. - -Scala 2.12 Based Driver ------------------------ - -.. tabs:: - - .. tab:: Maven - :tabid: maven-installation - - If you are using `Maven `__ to manage your - packages, add the following entry to your ``pom.xml`` dependencies list: - - .. code-block:: xml - - - - org.mongodb.scala - mongo-scala-driver_2.12 - {+full-version+} - - - - .. tab:: sbt - :tabid: sbt-installation - - If you are using `sbt `__ to manage your - packages, add the following entry to your ``build.sbt`` file: - - .. code-block:: none - - libraryDependencies += "org.mongodb.scala" %% "mongo-scala-driver" % "{+full-version+}" - -Scala 2.11 Based Driver ------------------------ - -.. tabs:: - - .. tab:: Maven - :tabid: maven-installation - - If you are using `Maven `__ to manage your - packages, add the following entry to your ``pom.xml`` dependencies list: - - .. code-block:: xml - - - - org.mongodb.scala - mongo-scala-driver_2.11 - {+full-version+} - - - - .. tab:: sbt - :tabid: sbt-installation - - If you are using `sbt `__ to manage your - packages, add the following entry to your ``build.sbt`` file: - - .. code-block:: none - - libraryDependencies += "org.mongodb.scala" %% "mongo-scala-driver" % "{+full-version+}" diff --git a/source/reference.txt b/source/reference.txt deleted file mode 100644 index b4a718d..0000000 --- a/source/reference.txt +++ /dev/null @@ -1,22 +0,0 @@ -.. _scala-reference: - -========= -Reference -========= - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: performance, logging, monitoring - -.. toctree:: - - /reference/logging/ - /reference/monitoring/ - /reference/observables/ - -- :ref:`scala-logging` -- :ref:`scala-monitoring` -- :ref:`scala-observables` \ No newline at end of file diff --git a/source/reference/logging.txt b/source/reference/logging.txt deleted file mode 100644 index 1d1859e..0000000 --- a/source/reference/logging.txt +++ /dev/null @@ -1,50 +0,0 @@ -.. _scala-logging: - -======= -Logging -======= - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: record logs, log types - -By default, logging is enabled via the popular `SLF4J -`__ API. Logging is optional, so the driver -uses SLF4J if the driver detects the presence of SLF4J API (class -``org.slf4j.Logger``) in the classpath. - -Otherwise, the driver logs a single warning via JUL -(``java.util.logging``) and logging is otherwise disabled. - -The driver uses the following logger names: - -- ``org.mongodb.driver``: the root logger - - - ``cluster``: for logs related to monitoring of the MongoDB servers to - which the driver connects - - - ``connection``: for logs related to connections and connection pools - - - ``protocol``: for logs related to protocol messages sent to and - received from a MongoDB server - - - ``insert``: for logs related to insert messages and responses - - - ``update``: for logs related to update messages and responses - - - ``delete``: for logs related to delete messages and responses - - - ``query``: for logs related to query messages and responses - - - ``getmore``: for logs related to ``getmore`` messages and responses - - - ``killcursor``: for logs related to ``killcursor`` messages and responses - - - ``command``: for logs related to command messages and responses - - - ``uri``: for logs related to connection string parsing - - - ``management``: for logs related to JMX diff --git a/source/reference/monitoring.txt b/source/reference/monitoring.txt deleted file mode 100644 index 2872de3..0000000 --- a/source/reference/monitoring.txt +++ /dev/null @@ -1,238 +0,0 @@ -.. _scala-monitoring: - -========== -Monitoring -========== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, record messages, diagnostics - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -The driver uses `JMX -`__ to -create `MXBeans `__ -that allow you to monitor various aspects of the driver. - -The driver creates ``MXBean`` instances of a single -type, ``ConnectionPoolStatisticsMBean``. The driver registers one -``ConnectionPoolStatisticsMBean`` instance for each server it connects -to. For example, when connected to a replica set, the driver creates an -instance for each non-hidden member of the replica set. - -Each ``MXBean`` instance is required to be registered with a unique object -name, which consists of a domain and a set of named properties. All -``MXBean`` instances created by the driver are under the domain -``org.mongodb.driver``. Instances of ``ConnectionPoolStatisticsMBean`` -have the following properties: - -- ``clusterId``: a client-generated unique identifier, required to - ensure object name uniqueness in situations where an application has - multiple ``MongoClient`` instances connected to the same MongoDB server - deployment -- ``host``: the hostname of the server -- ``port``: the port on which the server is listening -- ``minSize``: the minimum allowed size of the pool, including idle and - in-use members -- ``maxSize``: the maximum allowed size of the pool, including idle and - in-use members -- ``size``: the current size of the pool, including idle and in-use - members -- ``checkedOutCount``: the current count of connections that are - currently in use - -JMX connection pool monitoring is disabled by default. To enable it -add a ``com.mongodb.management.JMXConnectionPoolListener`` instance -when creating a ``MongoClientSettings`` instance: - -.. code-block:: scala - - val settings: MongoClientSettings = - MongoClientSettings.builder() - .applyToConnectionPoolSettings((builder: ConnectionPoolSettings.Builder) => builder.addConnectionPoolListener(new JMXConnectionPoolListener())) - .build() - -Command Monitoring ------------------- - -The driver implements the command monitoring specification, allowing -an application to be notified when a command starts and when it either -succeeds or fails. - -An application registers command listeners with a ``MongoClient`` by -configuring a ``MongoClientSettings`` instance with instances of classes -that implement the ``CommandListener`` interface. The following example -is a simple implementation of the ``CommandListener`` interface: - -.. code-block:: scala - - case class TestCommandListener() extends CommandListener { - - override def commandStarted(event: CommandStartedEvent): Unit = { - println(s"""Sent command '${event.getCommandName}:${event.getCommand.get(event.getCommandName)}' - | with id ${event.getRequestId} to database '${event.getDatabaseName}' - | on connection '${event.getConnectionDescription.getConnectionId}' to server - | '${event.getConnectionDescription.getServerAddress}'""".stripMargin) - } - - override def commandSucceeded(event: CommandSucceededEvent): Unit = { - println(s"""Successfully executed command '${event.getCommandName}}' - | with id ${event.getRequestId} - | on connection '${event.getConnectionDescription.getConnectionId}' to server - | '${event.getConnectionDescription.getServerAddress}'""".stripMargin) - } - - override def commandFailed(event: CommandFailedEvent): Unit = { - println(s"""Failed execution of command '${event.getCommandName}}' - | with id ${event.getRequestId} - | on connection '${event.getConnectionDescription.getConnectionId}' to server - | '${event.getConnectionDescription.getServerAddress} - | with exception '${event.getThrowable}'""".stripMargin) - } - } - -The following example creates an instance of ``MongoClientSettings`` -configured with an instance of ``TestCommandListener``: - -.. code-block:: scala - - val settings: MongoClientSettings = MongoClientSettings.builder() - .addCommandListener(TestCommandListener()) - .build() - val client: MongoClient = MongoClient(settings) - -A ``MongoClient`` configured with these options prints a message to -``System.out`` before sending each command to a MongoDB server, and -prints another message upon either successful completion or failure of each -command. - -Cluster Monitoring ------------------- - -The driver implements the SDAM Monitoring specification, allowing an -application to be notified when the driver detects changes to the -topology of the MongoDB cluster to which it is connected. - -An application registers listeners with a ``MongoClient`` by configuring -``MongoClientSettings`` with instances of classes that implement any of -the ``ClusterListener``, ``ServerListener``, or -``ServerMonitorListener`` interfaces. - -The following code demonstrates how to create a cluster listener: - -.. code-block:: scala - - case class TestClusterListener(readPreference: ReadPreference) extends ClusterListener { - var isWritable: Boolean = false - var isReadable: Boolean = false - - override def clusterOpening(event: ClusterOpeningEvent): Unit = - println(s"Cluster with unique client identifier ${event.getClusterId} opening") - - override def clusterClosed(event: ClusterClosedEvent): Unit = - println(s"Cluster with unique client identifier ${event.getClusterId} closed") - - override def clusterDescriptionChanged(event: ClusterDescriptionChangedEvent): Unit = { - if (!isWritable) { - if (event.getNewDescription.hasWritableServer) { - isWritable = true - println("Writable server available!") - } - } else { - if (!event.getNewDescription.hasWritableServer) { - isWritable = false - println("No writable server available!") - } - } - - if (!isReadable) { - if (event.getNewDescription.hasReadableServer(readPreference)) { - isReadable = true - println("Readable server available!") - } - } else { - if (!event.getNewDescription.hasReadableServer(readPreference)) { - isReadable = false - println("No readable server available!") - } - } - } - } - -The following example creates an instance of ``MongoClientSettings`` -configured with an instance of ``TestClusterListener``: - -.. code-block:: scala - - val settings: MongoClientSettings = MongoClientSettings.builder() - .applyToClusterSettings((builder: ClusterSettings.Builder) => - builder.addClusterListener(TestClusterListener(ReadPreference.secondary()))) - .build() - val client: MongoClient = MongoClient(settings) - -A ``MongoClient`` configured with these options prints a message to -``System.out`` when the ``MongoClient`` is created with these options, and -when that ``MongoClient`` is closed. In addition, it prints a message -when the client enters any of the following states: - -- Has an available server that will accept writes -- Is without an available server that will accept writes -- Has an available server that will accept reads by using the configured - ``ReadPreference`` -- Is without an available server that will accept reads by using the - configured ``ReadPreference`` - -Connection Pool Monitoring --------------------------- - -The driver supports monitoring of connection pool-related events. - -An application registers listeners with a ``MongoClient`` by configuring -``MongoClientSettings`` with instances of classes that implement the -``ConnectionPoolListener`` interface. - -The following code demonstrates how to create a connection pool listener: - -.. code-block:: scala - - case class TestConnectionPoolListener() extends ConnectionPoolListener { - - override def connectionPoolOpened(event: ConnectionPoolOpenedEvent): Unit = println(event) - - override def connectionPoolClosed(event: ConnectionPoolClosedEvent): Unit = println(event) - - override def connectionCheckedOut(event: ConnectionCheckedOutEvent): Unit = println(event) - - override def connectionCheckedIn(event: ConnectionCheckedInEvent): Unit = println(event) - - override def waitQueueEntered(event: ConnectionPoolWaitQueueEnteredEvent): Unit = println(event) - - override def waitQueueExited(event: ConnectionPoolWaitQueueExitedEvent): Unit = println(event) - - override def connectionAdded(event: ConnectionAddedEvent): Unit = println(event) - - override def connectionRemoved(event: ConnectionRemovedEvent): Unit = println(event) - } - -The following example creates an instance of ``MongoClientSettings`` -configured with an instance of ``TestConnectionPoolListener``: - -.. code-block:: scala - - val settings: MongoClientSettings = MongoClientSettings.builder() - .applyToConnectionPoolSettings((builder: ConnectionPoolSettings.Builder) => - builder.addConnectionPoolListener(TestConnectionPoolListener())) - .build() - val client: MongoClient = MongoClient(settings) - -A ``MongoClient`` configured with these options prints a message to -``System.out`` for each connection pool-related event for each MongoDB -server to which the MongoClient is connected. diff --git a/source/reference/observables.txt b/source/reference/observables.txt deleted file mode 100644 index 4ac1b42..0000000 --- a/source/reference/observables.txt +++ /dev/null @@ -1,192 +0,0 @@ -.. _scala-observables: - -=========== -Observables -=========== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, subscribe, non-blocking - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -The {+driver-short+} is an asynchronous and non-blocking driver. -By implementing the ``Observable`` model, asynchronous events become simple, composable -operations that are free from the complexity of nested callbacks. - -For asynchronous operations, there are three interfaces: - -- `Observable <{+api+}/org/mongodb/scala/Observable.html>`__ -- `Subscription <{+api+}/org/mongodb/scala/Subscription.html>`__ -- `Observer <{+api+}/org/mongodb/scala/Observer.html>`__ - -.. note:: - - The driver is built upon the `MongoDB Reactive Streams - driver `__ - and is an implementation of the reactive streams specification. - ``Observable`` is an implementation of ``Publisher`` and ``Observer`` - is an implementation of ``Subscriber``. - - The following class naming conventions apply: - - - ``Observable``: custom implementation of a ``Publisher`` - - ``Observer``: custom implementation of a ``Subscriber`` - - ``Subscription`` - -Observable ----------- - -The ``Observable`` is an extended ``Publisher`` implementation, and in general -it represents a MongoDB operation which emits its results to the ``Observer`` -based on a request from the ``Subscription`` to the ``Observable``. - -.. important:: - - ``Observable`` can be thought of as a partial function. Like with partial - functions, nothing happens until they are called. An ``Observable`` can be - subscribed to multiple times, with each subscription potentially - causing new side effects, such as querying MongoDB or inserting data. - -SingleObservable -~~~~~~~~~~~~~~~~ - -The `SingleObservable -<{+api+}/org/mongodb/scala/SingleObservable.html>`__ trait is a -``Publisher`` implementation that returns only a single item. It can be -used in the same way as an ordinary ``Observable``. - -Subscription ------------- - -A ``Subscription`` represents a one-to-one lifecycle of an ``Observer`` -subscribing to an ``Observable``. A ``Subscription`` to an ``Observable`` can only -be used by a single ``Observer``. The purpose of a ``Subscription`` is to -control demand and to allow unsubscribing from the ``Observable``. - -Observer --------- - -An ``Observer`` provides the mechanism for receiving push-based -notifications from the ``Observable``. Demand for these events is signaled -by its ``Subscription``. - -Upon subscription to an ``Observable[TResult]``, the ``Observer`` will be passed -the ``Subscription`` though the ``onSubscribe(subscription: -Subscription)`` method. Demand for results is signaled through the -``Subscription`` and any results are passed to the ``onNext(result: -TResult)`` method. If there is an error for any reason the ``onError(e: -Throwable)`` method will be called and no more events -are passed to the ``Observer``. Alternatively, when the ``Observer`` has consumed -all the results from the ``Observable``, the ``onComplete()`` method will be -called. - -Back Pressure -------------- - -In the following example, the ``Subscription`` is used to control demand -when iterating an ``Observable``. The default ``Observer`` implementation -automatically requests all the data. Below we override the ``onSubscribe()`` -method custom so we can manage the demand-driven iteration of the -``Observable``: - -.. code-block:: scala - - collection.find().subscribe(new Observer[Document](){ - - var batchSize: Long = 10 - var seen: Long = 0 - var subscription: Option[Subscription] = None - - override def onSubscribe(subscription: Subscription): Unit = { - this.subscription = Some(subscription) - subscription.request(batchSize) - } - - override def onNext(result: Document): Unit = { - println(document.toJson()) - seen += 1 - if (seen == batchSize) { - seen = 0 - subscription.get.request(batchSize) - } - } - - override def onError(e: Throwable): Unit = println(s"Error: $e") - - override def onComplete(): Unit = println("Completed") - }) - -Observable Helpers ------------------- - -The ``org.mongodb.scala`` package provides improved interaction with -``Publisher`` types. The extended functionality includes simple subscription through -anonymous functions: - -.. code-block:: scala - - // Subscribe with custom onNext: - collection.find().subscribe((doc: Document) => println(doc.toJson())) - - // Subscribe with custom onNext and onError - collection.find().subscribe((doc: Document) => println(doc.toJson()), - (e: Throwable) => println(s"There was an error: $e")) - - // Subscribe with custom onNext, onError and onComplete - collection.find().subscribe((doc: Document) => println(doc.toJson()), - (e: Throwable) => println(s"There was an error: $e"), - () => println("Completed!")) - -The ``org.mongodb.scala`` package includes an implicit class that also provides the -following Monadic operators to make chaining and working with ``Publisher`` -or ``Observable`` instances simpler: - -.. code-block:: scala - - GenerateHtmlObservable().andThen({ - case Success(html: String) => renderHtml(html) - case Failure(t) => renderHttp500 - }) - -The following list describes the available Monadic operators: - -- ``andThen``: Allows the chaining of ``Observable`` instances. -- ``collect``: Collects all the results into a sequence. -- ``fallbackTo``: Allows falling back to an alternative ``Observable`` if there is a failure. -- ``filter``: Filters results of the ``Observable``. -- ``flatMap``: Creates a new ``Observable`` by applying a function to each result of the ``Observable``. -- ``foldLeft``: Creates a new ``Observable`` that contains the single result of the applied accumulator function. -- ``foreach``: Applies a function applied to each emitted result. -- ``head``: Returns the head of the ``Observable`` in a ``Future``. -- ``map``: Creates a new ``Observable`` by applying a function to each emitted result of the ``Observable``. -- ``observeOn``: Creates a new ``Observable`` that uses a specific ``ExecutionContext`` for future operations. -- ``recover``: Creates a new ``Observable`` that will handle any - matching throwable that this ``Observable`` might contain by assigning - it a value of another ``Observable``. -- ``recoverWith``: Creates a new ``Observable`` that will handle any - matching throwable that this ``Observable`` might contain. -- ``toFuture``: Collects the ``Observable`` results and converts them to a ``Future``. -- ``transform``: Creates a new ``Observable`` by applying the ``resultFunction`` function to each emitted result. -- ``withFilter``: Provides for-comprehensions support to ``Observable`` instances. -- ``zip``: Zips the values of this and another ``Observable``, and - creates a new ``Observable`` holding the tuple of their results. - -See the `BoxedPublisher -<{+api+}/org/mongodb/scala/ObservableImplicits$BoxedPublisher.html>`__ -API documentation to learn more about each operator. - -SingleObservable -~~~~~~~~~~~~~~~~ - -Because a ``SingleObservable[T]`` returns a single item, the -``toFuture()`` method returns a ``Future[T]`` in the same way as the -head method does. There is also an implicit converter that converts a -``Publisher`` to a ``SingleObservable``. diff --git a/source/tutorials.txt b/source/tutorials.txt deleted file mode 100644 index 972714e..0000000 --- a/source/tutorials.txt +++ /dev/null @@ -1,40 +0,0 @@ -.. _scala-tutorials: - -========= -Tutorials -========= - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: core concepts, learn by example - -.. toctree:: - - /tutorials/connect/ - /tutorials/db-coll/ - /tutorials/indexes/ - /tutorials/read-ops/ - /tutorials/encrypt/ - /tutorials/write-ops/ - /tutorials/aggregation/ - /tutorials/change-stream/ - /tutorials/text-search/ - /tutorials/geospatial/ - /tutorials/gridfs/ - /tutorials/command/ - -- :ref:`scala-connect` -- :ref:`scala-db-coll` -- :ref:`scala-indexes` -- :ref:`scala-read-operations` -- :ref:`scala-encrypt` -- :ref:`scala-write-ops` -- :ref:`scala-aggregation` -- :ref:`scala-changestream` -- :ref:`scala-text-search` -- :ref:`scala-geospatial` -- :ref:`scala-gridfs` -- :ref:`scala-run-command` diff --git a/source/tutorials/aggregation.txt b/source/tutorials/aggregation.txt deleted file mode 100644 index 3d123e0..0000000 --- a/source/tutorials/aggregation.txt +++ /dev/null @@ -1,118 +0,0 @@ -.. _scala-aggregation: - -===================== -Aggregation Framework -===================== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, create insights, change data - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -The aggregation pipeline is a framework for data aggregation, -modeled on the concept of data processing pipelines. - -To learn more about aggregation, see :manual:`Aggregation Pipeline -` in the Server manual. - -Prerequisites -------------- - -.. include:: /includes/prereq-restaurants.rst - -.. code-block:: scala - - import org.mongodb.scala._ - - import org.mongodb.scala.model.Aggregates._ - import org.mongodb.scala.model.Accumulators._ - import org.mongodb.scala.model.Filters._ - import org.mongodb.scala.model.Projections._ - -.. include:: /includes/obs-note.rst - -Connect to a MongoDB Deployment -------------------------------- - -.. include:: /includes/connect-section.rst - -Perform Aggregation -------------------- - -To perform aggregation, pass a list of aggregation stages to the -``MongoCollection.aggregate()`` method. The driver provides the -``Aggregates`` helper class that contains builders for aggregation -stages. - -In this example, the aggregation pipeline performs the -following tasks: - -- Uses a ``$match`` stage to filter for documents in which the - ``categories`` array field contains the element ``"Bakery"``. The example - uses ``Aggregates.filter()`` to build the ``$match`` stage. - -+ Uses a ``$group`` stage to group the matching documents by - the ``stars`` field, accumulating a count of documents for each distinct - value of ``stars``. The example uses ``Aggregates.group()`` to build the - ``$group`` stage and ``Accumulators.sum()`` to build the accumulator - expression. For the accumulator expressions for use within the - ``$group`` stage, the driver provides ``Accumulators`` helper - class. - -.. code-block:: scala - - collection.aggregate(Seq( - Aggregates.filter(Filters.equal("categories", "Bakery")), - Aggregates.group("$stars", Accumulators.sum("count", 1)) - )).printResults() - -Use Aggregation Expressions -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -For ``$group`` accumulator expressions, the driver provides the -``Accumulators`` helper class. For other aggregation expressions, -manually build the expression by using the ``Document`` class. - -In the following example, the aggregation pipeline uses a -``$project`` stage to return only the ``name`` field and the calculated -field ``firstCategory`` whose value is the first element in the -``categories`` array. The example uses ``Aggregates.project()`` and -various ``Projections`` class methods to build the ``$project`` stage: - -.. code-block:: scala - - collection.aggregate( - Seq( - Aggregates.project( - Projections.fields( - Projections.excludeId(), - Projections.include("name"), - Projections.computed( - "firstCategory", - Document("$arrayElemAt"-> Seq("$categories", 0)) - ) - ) - ) - ) - ).printResults() - -Explain an Aggregation -~~~~~~~~~~~~~~~~~~~~~~ - -To ``$explain`` an aggregation pipeline, call the -``AggregatePublisher.explain()`` method: - -.. code-block:: scala - - collection.aggregate( - Seq(Aggregates.filter(Filters.eq("categories", "Bakery")), - Aggregates.group("$stars", Accumulators.sum("count", 1))) - ).explain().printResults() \ No newline at end of file diff --git a/source/tutorials/bulk-writes.txt b/source/tutorials/bulk-writes.txt deleted file mode 100644 index 6a85d1a..0000000 --- a/source/tutorials/bulk-writes.txt +++ /dev/null @@ -1,53 +0,0 @@ -.. _scala-bulk-writes: - -===================== -Bulk Write Operations -===================== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, multiple changes - -There are two types of bulk operations, ordered and unordered bulk -operations: - -1. Ordered bulk operations execute all the operations in order and - error out on the first write error. -#. Unordered bulk operations execute all the operations and report any - the errors. Unordered bulk operations do not guarantee an order of - execution. - -.. include:: /includes/obs-note.rst - -The following code provides examples using ordered and unordered -operations: - -.. code-block:: scala - - import org.mongodb.scala._ - import org.mongodb.scala.model._ - - // Ordered bulk operation - order is guaranteed - collection.bulkWrite( - List(InsertOneModel(Document("_id" -> 4)), - InsertOneModel(Document("_id" -> 5)), - InsertOneModel(Document("_id" -> 6)), - UpdateOneModel(Document("_id" -> 1), Document("$set", Document("x" -> 2))), - DeleteOneModel(Document("_id" -> 2)), - ReplaceOneModel(Document("_id"-> 3), Document("_id" -> 3, "x" -> 4))) - ).printResults() - - - // Unordered bulk operation - no guarantee of order of operation - collection.bulkWrite( - List(InsertOneModel(Document("_id" -> 4)), - InsertOneModel(Document("_id" -> 5)), - InsertOneModel(Document("_id" -> 6)), - UpdateOneModel(Document("_id" -> 1), Document("$set", Document("x" -> 2))), - DeleteOneModel(Document("_id" -> 2)), - ReplaceOneModel(Document("_id"-> 3), Document("_id" -> 3, "x" -> 4))), - BulkWriteOptions().ordered(false) - ).printResults() diff --git a/source/tutorials/change-stream.txt b/source/tutorials/change-stream.txt deleted file mode 100644 index 2a2a615..0000000 --- a/source/tutorials/change-stream.txt +++ /dev/null @@ -1,140 +0,0 @@ -.. _scala-changestream: - -============== -Change Streams -============== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, watch for changes - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -MongoDB Server version 3.6 introduces the ``$changeStream`` aggregation pipeline -operator. - -Change streams provide a way to watch changes to documents in a -collection. To improve the usability of this new stage, the -``MongoCollection`` type includes the ``watch()`` method. The -``ChangeStreamObservable`` instance sets up the change stream and automatically -attempts to resume if it encounters a potentially recoverable error. - -Prerequisites -------------- - -.. include:: /includes/prereq-restaurants.rst - -.. code-block:: scala - - import java.util.concurrent.CountDownLatch - - import org.mongodb.scala._ - import org.mongodb.scala.model.Aggregates._ - import org.mongodb.scala.model.Filters._ - import org.mongodb.scala.model.changestream._ - -.. include:: /includes/obs-note.rst - -Connect to a MongoDB Deployment -------------------------------- - -.. include:: /includes/connect-section.rst - -Watch for Changes in a Collection ---------------------------------- - -To create a change stream use one of the ``MongoCollection.watch()`` -methods. - -In the following example, the change stream prints out all changes it -observes: - -.. code-block:: scala - - case class LatchedObserver() extends Observer[ChangeStreamDocument[Document]] { - val latch = new CountDownLatch(1) - - override def onSubscribe(subscription: Subscription): Unit = subscription.request(Long.MaxValue) // Request data - - override def onNext(changeDocument: ChangeStreamDocument[Document]): Unit = println(changeDocument) - - override def onError(throwable: Throwable): Unit = { - println(s"Error: '$throwable") - latch.countDown() - } - - override def onComplete(): Unit = latch.countDown() - - def await(): Unit = latch.await() - } - - val observer = LatchedObserver() - collection.watch().subscribe(observer) - observer.await() // Block waiting for the latch - -Watch for Changes on a Database -------------------------------- - -Applications can open a single change stream to watch all non-system -collections of a database. To create such a change stream, use one of the -``MongoDatabase.watch()`` methods. - -In the following example, the change stream prints out all the changes -it observes on the given database: - -.. code-block:: scala - - val observer = LatchedObserver() - database.watch().subscribe(observer) - observer.await() // Block waiting for the latch - -Watch for Changes on All Databases ----------------------------------- - -Applications can open a single change stream to watch all non-system -collections of all databases in a MongoDB deployment. To create such a -change stream, use one of the ``MongoClient.watch()`` methods. - -In the following example, the change stream prints out all the changes -it observes in the deployment to which the ``MongoClient`` is connected: - -.. code-block:: scala - - val observer = LatchedObserver() - client.watch().subscribe(observer) - observer.await() // Block waiting for the latch - -Filtering Content ------------------ - -You can pass a list of aggregation stages to the ``watch()`` method to -modify the data returned by the ``$changeStream`` operator. - -.. note:: - - Not all aggregation operators are supported. See - :manual:`Change Streams ` in the Server manual to learn more. - -In the following example, the change stream prints out all changes it -observes corresponding to ``insert``, ``update``, ``replace`` and -``delete`` operations. - -First, the pipeline includes a ``$match`` stage to filter for documents -where the ``operationType`` is either an ``insert``, ``update``, ``replace`` or -``delete``. Then, it sets the ``fullDocument`` to -``FullDocument.UPDATE_LOOKUP``, so that the document after the update is -included in the results: - -.. code-block:: scala - - val observer = LatchedObserver() - collection.watch(Seq(Aggregates.filter(Filters.in("operationType", Seq("insert", "update", "replace", "delete"))))) - .fullDocument(FullDocument.UPDATE_LOOKUP).subscribe(observer) - observer.await() // Block waiting for the latch diff --git a/source/tutorials/command.txt b/source/tutorials/command.txt deleted file mode 100644 index 457ea35..0000000 --- a/source/tutorials/command.txt +++ /dev/null @@ -1,70 +0,0 @@ -.. _scala-run-command: - -============ -Run Commands -============ - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, database command, server management - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Not all database commands have a specific helper method. However, you can -run any MongoDB command by using the ``MongoDatabase.runCommand()`` -method. - -To learn more about MongoDB commands, see :manual:`Database Commands ` -in the Server manual. - -Prerequisites -------------- - -.. include:: /includes/prereq-restaurants.rst - -.. code-block:: scala - - import org.mongodb.scala._ - -.. include:: /includes/obs-note.rst - -Connect to a MongoDB Deployment -------------------------------- - -First, connect to a MongoDB deployment, then declare and define -a ``MongoDatabase`` instance. - -The following code connects to a standalone -MongoDB deployment running on ``localhost`` on port ``27017``. Then, it -defines the ``database`` variable to refer to the ``test`` database: - -.. code-block:: scala - - val mongoClient: MongoClient = MongoClient() - val database: MongoDatabase = mongoClient.getDatabase("test") - -To learn more about connecting to MongoDB deployments, -see the :ref:`scala-connect` guide. - -Run the buildInfo Command -------------------------- - -To run the ``buildInfo`` command, construct a ``Document`` object that -specifies the command and pass it as a parameter to the ``runCommand()`` method. - -The following sample code runs the ``buildInfo`` command and prints -the results: - -.. code-block:: scala - - database.runCommand(Document("buildInfo" -> 1)).printResults() - -To view a list of available MongoDB commands, see :manual:`Database -Commands ` in the Server manual. diff --git a/source/tutorials/connect.txt b/source/tutorials/connect.txt deleted file mode 100644 index 0d1d3c5..0000000 --- a/source/tutorials/connect.txt +++ /dev/null @@ -1,352 +0,0 @@ -.. _scala-connect: - -================== -Connect to MongoDB -================== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, modify connection - -.. toctree:: - - /tutorials/connect/tls/ - /tutorials/connect/authentication/ - /tutorials/connect/compression/ - -.. contents:: On this page - :local: - :backlinks: none - :depth: 1 - :class: singlecol - -This guide describes how to use the {+driver-short+} to connect to -MongoDB. - -Use the ``MongoClient()`` method to make a connection to a -running MongoDB deployment. - -.. important:: - - The following examples do not provide an exhaustive list of - ways to instantiate a ``MongoClient``. For a complete list of the - ``MongoClient`` companion methods, see the `MongoClient API - documentation <{+api+}/org/mongodb/scala/MongoClient.html>`__. - -.. note:: - - We *strongly recommended* that system keep-alive settings should - be configured with shorter timeouts. - - See the :manual:`Does TCP keepalive time affect MongoDB Deployments? - ` - question and answer in the Server manual FAQ for more information. - -Prerequisites -------------- - -You must set up the following components to run the code examples in -this guide: - -- A running MongoDB deployment to connect to. For example, to - connect to a standalone deployment, you must have access to a running - standalone deployment. - -- Driver dependency installed in your project. To learn how to install - the driver, see :ref:`scala-install`. - -- The following import statements: - - .. code-block:: scala - - import org.mongodb.scala._ - import scala.collection.JavaConverters._ - -MongoClient ------------ - -A ``MongoClient`` instance represents a pool of connections to the -database. You need only one instance of ``MongoClient`` even -when running multiple concurrent operations. - -.. important:: - - Typically, you create only one ``MongoClient`` instance for a given - MongoDB deployment, such as a standalone deployment, replica set, or a sharded - cluster, and use the client across your application. However, if you do create - multiple instances, keep the following in mind: - - - All resource-usage limits (for example, max connections) apply to - each ``MongoClient`` instance. - - To dispose of an instance, call the ``MongoClient.close()`` method - to clean up resources. - -Connection URI --------------- - -The **connection URI** provides a set of instructions that the driver uses to -connect to a MongoDB deployment. It instructs the driver on how it should -connect to MongoDB and how it should behave while connected. The following -figure explains each part of a sample connection URI: - -.. figure:: /includes/dns_seedlist_connection_string_parts.png - :alt: An example of a connection string that demonstrates the protocol, credentials, hostname or IP, port, and connection options - -In this example, you connect to an Atlas MongoDB deployment that has a -DNS SRV record. For more details, see the :manual:`DNS Seed List -Connection Format -` -documentation. This format offers flexibility in deployment and the -ability to change the servers in rotation without reconfiguring clients. - -.. note:: - - If your deployment is on MongoDB Atlas, see the - :atlas:`Atlas driver connection guide ` - and select :guilabel:`Scala` from the language dropdown to retrieve your connection - string. - -If you are connecting to an instance or replica set that does not have a -DNS SRV address, you must use ``mongodb`` for the protocol, which specifies -the :manual:`Standard Connection String Format -`. - -After the protocol, the connection string contains your -credentials if you are using a password-based authentication mechanism. -Replace the value of ``user`` with your username and ``pass`` with your -password. If your authentication mechanism does not require credentials, -omit this part of the connection URI. - -The next part of the connection URI specifies the hostname or IP -address, followed by the port of your MongoDB instance. In the example, -``sample.host`` represents the hostname and ``27017`` is the port number. -Replace these values to refer to your MongoDB instance. - -The last part of the connection URI contains connection options as parameters. -The example sets two connection options: ``maxPoolSize=20`` and -``w=majority``. - -Connect to MongoDB Atlas ------------------------- - -You can use the following connection snippet to test your connection to -your MongoDB deployment on Atlas: - -.. code-block:: scala - :emphasize-lines: 14 - - import com.mongodb.{ServerApi, ServerApiVersion} - import org.mongodb.scala.{ConnectionString, MongoClient, MongoClientSettings} - import org.mongodb.scala.bson.Document - - import scala.concurrent.Await - import scala.concurrent.duration.DurationInt - import scala.util.Using - - object MongoClientConnectionExample { - - def main(args: Array[String]): Unit = { - - // Replace the placeholder with your Atlas connection string - val connectionString = ""; - - // Construct a ServerApi instance using the ServerApi.builder() method - val serverApi = ServerApi.builder.version(ServerApiVersion.V1).build() - - val settings = MongoClientSettings - .builder() - .applyConnectionString(ConnectionString(connectionString)) - .serverApi(serverApi) - .build() - - // Create a new client and connect to the server - Using(MongoClient(settings)) { mongoClient => - // Send a ping to confirm a successful connection - val database = mongoClient.getDatabase("admin") - val ping = database.runCommand(Document("ping" -> 1)).head() - - Await.result(ping, 10.seconds) - System.out.println("Pinged your deployment. You successfully connected to MongoDB!") - } - } - } - -This connection snippet uses the Stable API feature, which you can -enable when using the {+driver-short+} v4.3 and later to connect to MongoDB -Server v5.0 and later. When you use this feature, you can update your -driver or server without worrying about backward compatibility issues -with any commands covered by the Stable API. - -To learn more about the Stable API feature, see -:manual:`Stable API ` in the Server manual. - -Connect to a Local MongoDB Deployment -------------------------------------- - -You can connect to a local MongoDB deployment in the following ways: - -- Instantiate a ``MongoClient`` object without any parameters to - connect to a MongoDB server running on localhost on port ``27017``: - - .. code-block:: scala - - val mongoClient = MongoClient() - -- Explicitly specify the ``hostname`` to connect to a MongoDB - instance running on the specified host on port ``27017``: - - .. code-block:: scala - - val mongoClient = MongoClient("mongodb://host1") - -- Explicitly specify the ``hostname`` and the ``port``: - - .. code-block:: scala - - val mongoClient = MongoClient("mongodb://host1:27017") - -Connect to a Replica Set ------------------------- - -To connect to a replica set, you must specify one or more -members to the ``MongoClient`` apply method. To learn more about -replica sets, see :manual:`Replication ` in the Server -manual. - -.. note:: - - MongoDB auto-discovers the primary and secondary nodes in a replica - set. - -You can connect to a MongoDB replica set by specifying the members in -a ``ConnectionString``. - -The following example shows how to specify three members of the -replica set: - -.. code-block:: scala - - val mongoClient = MongoClient("mongodb://host1:27017,host2:27017,host3:27017") - -The following example shows how to specify members of the -replica set and the ``replicaSet`` option with the replica set -name: - -.. code-block:: scala - - val mongoClient = MongoClient("mongodb://host1:27017,host2:27017,host3:27017/?replicaSet=myReplicaSet") - -The following example shows how to specify a list of -``ServerAddress`` instances corresponding to all of the replica -set members: - -.. code-block:: scala - - val mongoClient = MongoClient( - MongoClientSettings.builder() - .applyToClusterSettings((builder: ClusterSettings.Builder) => builder.hosts(List( - new ServerAddress("host1", 27017), - new ServerAddress("host2", 27017), - new ServerAddress("host3", 27017)).asJava)) - .build()) - -Connect to a Sharded Cluster ----------------------------- - -To connect to a sharded cluster, specify the ``mongos`` instance or -instances to the ``MongoClient`` apply method. To learn more about -sharded clusters, see :manual:`Sharding ` in the Server -manual. - -You can connect to a single ``mongos`` instance in the following ways: - -- Specify the hostname and the port in a ``ConnectionString``: - - .. code-block:: scala - - val mongoClient = MongoClient( "mongodb://localhost:27017" ) - -- Exclude connection string if the ``mongos`` is running on - ``localhost:27017``: - - .. code-block:: scala - - val mongoClient = MongoClient() - -You can connect to multiple ``mongos`` instances in the following ways: - -- Specify the ``ConnectionString`` to contain their hostnames and ports: - - .. code-block:: scala - - val mongoClient = MongoClient("mongodb://host1:27017,host2:27017") - -- Specify a list of the ``ServerAddress`` objects corresponding to - each instance: - - .. code-block:: scala - - val mongoClient = MongoClient( - MongoClientSettings.builder() - .applyToClusterSettings((builder: ClusterSettings.Builder) => builder.hosts(List( - new ServerAddress("host1", 27017), - new ServerAddress("host2", 27017)).asJava)) - .build()) - -Connection Options ------------------- - -You can specify connection settings by using either the -``ConnectionString`` or ``MongoClientSettings`` types, or both. - -For example, you can specify TLS/SSL and authentication settings in the -connection string: - -.. code-block:: scala - - val mongoClient = MongoClient("mongodb://user1:pwd1@host1/?authSource=db1&ssl=true") - -You can also use a ``MongoClientSettings`` instance to specify TLS/SSL -and the ``MongoCredential`` type to store the authentication information: - -.. code-block:: scala - - val user: String = // the user name - val source: String = // the source where the user is defined - val password: Array[Char] = // the password as a character array - // ... - val credential = MongoCredential.createCredential(user, source, password) - - val mongoClient: MongoClient = MongoClient( - MongoClientSettings.builder() - .applyToSslSettings((builder: SslSettings.Builder) => builder.enabled(true)) - .applyToClusterSettings((builder: ClusterSettings.Builder) => builder.hosts(List(new ServerAddress("host1", 27017)).asJava)) - .credential(credential) - .build()) - -In some cases, you might need to combine a connection string -with programmatic configuration: - -.. code-block:: scala - - val connectionString = ConnectionString("mongodb://host1:27107,host2:27017/?ssl=true") - val myCommandListener: CommandListener = ??? - - val mongoClient = MongoClient( - MongoClientSettings.builder() - .addCommandListener(myCommandListener) - .applyConnectionString(connectionString) - .build()) - -Connection Tutorials --------------------- - -To learn how to implement other connection features, see the following -tutorials: - -- :ref:`scala-tls` -- :ref:`scala-auth` -- :ref:`scala-compression` diff --git a/source/tutorials/connect/authentication.txt b/source/tutorials/connect/authentication.txt deleted file mode 100644 index f31d546..0000000 --- a/source/tutorials/connect/authentication.txt +++ /dev/null @@ -1,327 +0,0 @@ -.. _scala-auth: - -============== -Authentication -============== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, verify, AWS, Kerberos - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -The driver supports all MongoDB authentication mechanisms, -including those available only in the MongoDB Enterprise Edition. - -MongoCredential ---------------- - -Include the following import statements: - -.. code-block:: scala - - import org.mongodb.scala._ - import scala.collection.JavaConverters._ - -An authentication credential is represented as an instance of the -``MongoCredential`` class. The ``MongoCredential`` class includes -helper methods for each of the supported authentication -mechanisms. - -Default Authentication Mechanism --------------------------------- - -In MongoDB 3.0, MongoDB changed the default authentication mechanism -from ``MONGODB-CR`` to ``SCRAM-SHA-1``. In MongoDB 4.0, support for -the deprecated ``MONGODB-CR`` mechanism was removed and -``SCRAM-SHA-256`` support was added. - -To create a credential that authenticates by using the default -authentication mechanism, regardless of server version, create a -credential by using the ``createCredential()`` helper method: - -.. code-block:: scala - - val user: String = ... // the user name - val source: String = ... // the source where the user is defined - val password: Array[Char] = ... // the password as a character array - // ... - val credential = MongoCredential.createCredential(user, source, password) - - val mongoClient: MongoClient = MongoClient( - MongoClientSettings.builder() - .applyToClusterSettings((builder: ClusterSettings.Builder) => builder.hosts(List(new ServerAddress("host1", 27017)).asJava)) - .credential(credential) - .build()) - -Or, you can use a connection string without explicitly specifying the -authentication mechanism: - -.. code-block:: scala - - val mongoClient: MongoClient = MongoClient("mongodb://user1:pwd1@host1/?authSource=db1") - -For challenge and response mechanisms, using the default -authentication mechanism is the recommended approach, as it makes -upgrading from MongoDB 2.6 to MongoDB 3.0 more simple, even after -upgrading the authentication schema. For MongoDB 4.0 users, using the -default authentication mechanism is also recommended as the mechanisms are -checked and the correct hashing algorithm is used. - -SCRAM-Based Mechanisms ----------------------- - -Salted Challenge-Response Authentication Mechanism (``SCRAM``) has been -the default authentication mechanism for MongoDB since 3.0. ``SCRAM`` is -based on the `IETF RFC 5802 -`__ standard that defines -best practices for implementation of challenge-response mechanisms for authenticating -users with passwords. - -MongoDB 3.0 introduced support for ``SCRAM-SHA-1``, which uses the -``SHA-1`` hashing function. MongoDB 4.0 introduced support for -``SCRAM-SHA-256`` which uses the ``SHA-256`` hashing function. - -SCRAM-SHA-256 -~~~~~~~~~~~~~ - -Using this mechanism requires MongoDB 4.0 and -``featureCompatibilityVersion`` to be set to 4.0. - -To explicitly create a credential of type ``SCRAM-SHA-256``, use -the ``createScramSha256Credential()`` method: - -.. code-block:: scala - - val user: String = ... // the user name - val source: String = ... // the source where the user is defined - val password: Array[Char] = ... // the password as a character array - // ... - val credential = MongoCredential.createScramSha256Credential(user, source, password) - - val mongoClient: MongoClient = MongoClient( - MongoClientSettings.builder() - .applyToClusterSettings((builder: ClusterSettings.Builder) => builder.hosts(List(new ServerAddress("host1", 27017)).asJava)) - .credential(credential) - .build()) - -Or, you can use a connection string that explicitly specifies -``authMechanism=SCRAM-SHA-256``: - -.. code-block:: scala - - val mongoClient: MongoClient = MongoClient("mongodb://user1:pwd1@host1/?authSource=db1&authMechanism=SCRAM-SHA-256") - -SCRAM-SHA-1 -~~~~~~~~~~~ - -To explicitly create a credential of type ``SCRAM-SHA-1``, use the -``createScramSha1Credential()`` method: - -.. code-block:: scala - - val user: String = ... // the user name - val source: String = ... // the source where the user is defined - val password: Array[Char] = ... // the password as a character array - // ... - val credential = MongoCredential.createScramSha1Credential(user, source, password) - - val mongoClient: MongoClient = MongoClient( - MongoClientSettings.builder() - .applyToClusterSettings((builder: ClusterSettings.Builder) => builder.hosts(List(new ServerAddress("host1", 27017)).asJava)) - .credential(credential) - .build()) - -Or, you can use a connection string that explicitly specifies -``authMechanism=SCRAM-SHA-1``: - -.. code-block:: scala - - val mongoClient: MongoClient = MongoClient("mongodb://user1:pwd1@host1/?authSource=db1&authMechanism=SCRAM-SHA-1") - -MONGODB-CR ----------- - -.. important:: - - Starting in version 4.0, MongoDB removes support for the deprecated - MongoDB Challenge-Response (``MONGODB-CR``) authentication mechanism. - - If your deployment has user credentials stored in a ``MONGODB-CR`` schema, - you must upgrade to use a ``SCRAM``-based mechanism before you - upgrade to version 4.0. - -To explicitly create a credential of type ``MONGODB-CR`` use the -``createMongCRCredential()`` helper method: - -.. code-block:: scala - - val user: String = ... // the user name - val source: String = ... // the source where the user is defined - val password: Array[Char] = ... // the password as a character array - // ... - val credential = MongoCredential.createMongoCRCredential(user, database, password) - - val mongoClient: MongoClient = MongoClient( - MongoClientSettings.builder() - .applyToClusterSettings((builder: ClusterSettings.Builder) => builder.hosts(List(new ServerAddress("host1", 27017)).asJava)) - .credential(credential) - .build()) - -Or, you can use a connection string that explicitly specifies -``authMechanism=MONGODB-CR``: - -.. code-block:: scala - - val mongoClient: MongoClient = MongoClient("mongodb://user1:pwd1@host1/?authSource=db1&authMechanism=MONGODB-CR") - -.. note:: - - After you upgrade the authentication schema from ``MONGODB-CR`` to ``SCRAM``, - ``MONGODB-CR`` credentials will fail to authenticate. - -X.509 ------ - -With the ``X.509`` mechanism, MongoDB uses the X.509 certificate presented -during SSL negotiation to authenticate a user whose name is derived -from the distinguished name of the X.509 certificate. - -X.509 authentication requires the use of SSL connections with -certificate validation. To create a credential of this type use the -``createMongoX509Credential()`` helper method: - -.. code-block:: scala - - val user: String = ... // The X.509 certificate derived user name, e.g. "CN=user,OU=OrgUnit,O=myOrg,..." - // ... - val credential = MongoCredential.createMongoX509Credential(user) - - val mongoClient: MongoClient = MongoClient( - MongoClientSettings.builder() - .applyToClusterSettings((builder: ClusterSettings.Builder) => builder.hosts(List(new ServerAddress("host1", 27017)).asJava)) - .credential(credential) - .build()) - -Or, you can use a connection string that explicitly specifies -``authMechanism=MONGODB-X509``: - -.. code-block:: scala - - val mongoClient: MongoClient = MongoClient("mongodb://subjectName@host1/?authMechanism=MONGODB-X509&ssl=true") - -See the :manual:`Use x.509 Certificates to Authenticate Clients ` -tutorial in the Server manual to learn more about -determining the subject name from the certificate. - -Kerberos (GSSAPI) ------------------ - -MongoDB Enterprise supports proxy authentication through the Kerberos -service. To create a credential of type Kerberos (GSSAPI), use the -``createGSSAPICredential()`` helper method: - -.. code-block:: scala - - val user: String = ... // The Kerberos user name, including the realm, e.g. "user1@MYREALM.ME" - // ... - val credential = MongoCredential.createGSSAPICredential(user) - - val mongoClient: MongoClient = MongoClient( - MongoClientSettings.builder() - .applyToClusterSettings((builder: ClusterSettings.Builder) => builder.hosts(List(new ServerAddress("host1", 27017)).asJava)) - .credential(credential) - .build()) - -Or, you can use a connection string that explicitly specifies -``authMechanism=GSSAPI``: - -.. code-block:: scala - - val mongoClient: MongoClient = MongoClient("mongodb://username%40REALM.ME@host1/?authMechanism=GSSAPI") - -.. note:: - - The method refers to the ``GSSAPI`` authentication mechanism instead of - ``Kerberos`` because the driver authenticates by using the ``GSSAPI`` SASL mechanism. - -To successfully authenticate by using Kerberos, the application typically -must specify several system properties so that the underlying GSSAPI -Java libraries can acquire a Kerberos ticket: - -.. code-block:: none - - java.security.krb5.realm=MYREALM.ME - java.security.krb5.kdc=mykdc.myrealm.me - -Depending on the Kerberos setup, additional property specifications -might be required, either within the application code or, in some cases, -by using the ``withMechanismProperty()`` method of the ``MongoCredential`` -instance: - -- ``SERVICE_NAME`` -- ``CANONICALIZE_HOST_NAME`` -- ``JAVA_SUBJECT`` -- ``JAVA_SASL_CLIENT_PROPERTIES`` - -The following code shows how to specify the ``SERVICE_NAME`` property within the -``MongoCredential`` object: - -.. code-block:: scala - - val credentialWithProperty = credential.withMechanismProperty(MongoCredential.SERVICE_NAME_KEY, "othername") - -Or, you can specify the ``SERVICE_NAME`` property within the ``ConnectionString``: - -.. code-block:: scala - - val uri = "mongodb://username%40MYREALM.com@myserver/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:othername" - -.. note:: - - On Windows, Oracles JRE uses `LSA - `__ - rather than `SSPI - `__ - in its implementation of GSSAPI, which limits interoperability with Windows - Active Directory and in particular the ability to implement single - sign-on. - -LDAP (PLAIN) ------------- - -MongoDB Enterprise supports proxy authentication through a -Lightweight Directory Access Protocol (LDAP) service. To create a -credential of type ``LDAP`` use the ``createPlainCredential()`` helper method: - -.. code-block:: scala - - val user: String = ... // The LDAP user name - val password: Array[Char] = ... // The LDAP password - // ... - val credential = MongoCredential.createPlainCredential(user, "$external", password) - val mongoClient: MongoClient = MongoClient( - MongoClientSettings.builder() - .applyToClusterSettings((builder: ClusterSettings.Builder) => builder.hosts(List(new ServerAddress("host1", 27017)).asJava)) - .credential(credential) - .build()) - -Or, you can use a connection string that explicitly specifies -``authMechanism=PLAIN``: - -.. code-block:: scala - - val mongoClient: MongoClient = MongoClient("mongodb://user1@host1/?authSource=$external&authMechanism=PLAIN") - -.. note:: - - The method refers to the ``PLAIN`` authentication mechanism instead of - ``LDAP`` because the driver authenticates by using the ``PLAIN`` - SASL mechanism. \ No newline at end of file diff --git a/source/tutorials/connect/compression.txt b/source/tutorials/connect/compression.txt deleted file mode 100644 index 59a9a1b..0000000 --- a/source/tutorials/connect/compression.txt +++ /dev/null @@ -1,137 +0,0 @@ -.. _scala-compression: - -=========== -Compression -=========== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, optimization, server - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -The {+driver-short+} supports compression of messages to and from MongoDB -servers. The driver implements the three algorithms that are supported -by MongoDB servers: - -- `Snappy `__: Snappy compression can - be used when connecting to MongoDB servers running version 3.4 and later. -- `Zlib `__: Zlib compression can be used when - connecting to MongoDB servers running version 3.6 and later. -- `Zstandard `__: Zstandard - compression can be used when connecting to MongoDB servers running - version 4.2 and later. - -The driver will negotiate which, if any, compression algorithm is used -based on capabilities advertised by the server in the ``hello`` command -response. - -Specify Compression By Using ConnectionString ---------------------------------------------- - -Include the following import statements: - -.. code-block:: scala - - import org.mongodb.scala._ - -To specify compression within a ``ConnectionString``, specify -compressors as part of the connection string. - -The following code specifies the Snappy compression algorithm: - -.. code-block:: scala - - val mongoClient: MongoClient = MongoClient("mongodb://localhost/?compressors=snappy") - -The following code specifies the Zlib compression algorithm: - -.. code-block:: scala - - val mongoClient: MongoClient = MongoClient("mongodb://localhost/?compressors=zlib") - -The following code specifies the Zstandard compression algorithm: - -.. code-block:: scala - - val mongoClient: MongoClient = MongoClient("mongodb://localhost/?compressors=zstd") - -The following code specifies multiple compression algorithms: - -.. code-block:: scala - - val mongoClient: MongoClient = MongoClient("mongodb://localhost/?compressors=snappy,zlib,zstd") - -In all cases, the driver uses the first compressor in the list for which -the server has support. - -Specify Compression By Using MongoClientSettings ------------------------------------------------- - -Include the following import statements: - -.. code-block:: scala - - import org.mongodb.scala._ - - import scala.collection.JavaConverters._ - -To specify compression within a ``MongoClientSettings`` instance, set the ``compressors`` -property to a list of ``MongoCompressor`` instances. - -The following code specifies the Snappy compression algorithm: - -.. code-block:: scala - - val settings = MongoClientSettings.builder() - .compressorList(List(MongoCompressor.createSnappyCompressor).asJava) - .build() - val client = MongoClient(settings) - -The following code specifies the Zlib compression algorithm: - -.. code-block:: scala - - val settings = MongoClientSettings.builder() - .compressorList(List(MongoCompressor.createZlibCompressor).asJava) - .build() - val client = MongoClient(settings) - -The following code specifies the Zstandard compression algorithm: - -.. code-block:: scala - - val settings = MongoClientSettings.builder() - .compressorList(List(MongoCompressor.createZstdCompressor).asJava) - .build() - val client = MongoClient(settings) - -The following code specifies multiple compression algorithms: - -.. code-block:: scala - - val settings = MongoClientSettings.builder() - .compressorList(List(MongoCompressor.createSnappyCompressor, - MongoCompressor.createZlibCompressor, - MongoCompressor.createZstdCompressor).asJava) - .build() - val client = MongoClient(settings) - -As with configuration that uses the connection string, the driver uses the first -compressor in the list for which the server has support. - -Dependencies ------------- - -As the JDK has no built-in support for Snappy or Zstandard, the driver -takes a dependency on existing open-source Snappy and Zstandard -implementations. See the `snappy-java GitHub repository -`__ and the `zstd-java GitHub -repository `__ for details. \ No newline at end of file diff --git a/source/tutorials/connect/tls.txt b/source/tutorials/connect/tls.txt deleted file mode 100644 index e7feb28..0000000 --- a/source/tutorials/connect/tls.txt +++ /dev/null @@ -1,250 +0,0 @@ -.. _scala-tls: - -======= -TLS/SSL -======= - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, certificate, authenticate - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -By default, the driver supports TLS/SSL connections to MongoDB -servers using the underlying support for TLS/SSL provided by the JDK. -This can be changed by utilizing the extensibility of the `Java SE -API `__. - -MongoClient API ---------------- - -You can configure the driver to use -TLS/SSL by specifying options in a ``ConnectionString`` or in a -``MongoClientSettings`` instance. - -Specify TLS/SSL in ConnectionString -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Include the following import statements: - -.. code-block:: scala - - import org.mongodb.scala._ - -To specify TLS/SSL in a ``ConnectionString``, specify ``ssl=true`` as -part of the connection string: - -.. code-block:: scala - - val mongoClient: MongoClient = MongoClient("mongodb://localhost/?ssl=true") - -Specify TLS/SSL in MongoClientSettings -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Include the following import statements: - -.. code-block:: scala - - import org.mongodb.scala._ - -To specify TLS/SSL in a ``MongoClientSettings`` instance, set the -``enabled`` property to ``true``: - -.. code-block:: scala - - val settings = MongoClientSettings.builder() - .applyToSslSettings((builder: SslSettings.Builder) => builder.enabled(true)) - .build() - val client = MongoClient(settings) - -Specify Java SE SSLContext in MongoClientSettings -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Include the following import statements: - -.. code-block:: scala - - import javax.net.ssl.SSLContext - -To specify the ``javax.net.ssl.SSLContext`` with -``MongoClientSettings``, set the ``sslContext`` property: - -.. code-block:: scala - - val sslContext: SSLContext = ... - val settings = MongoClientSettings.builder() - .applyToSslSettings((builder: SslSettings.Builder) => { - builder.enabled(true) - builder.context(sslContext) - }) - .build() - val client = MongoClient(settings) - -Disable Hostname Verification ------------------------------ - -By default, the driver ensures that the hostname included in the -server's SSL certificate matches the hostname provided when -constructing a ``MongoClient``. - -If your application needs to disable hostname verification, you must -explicitly indicate this in ``MongoClientSettings``: - -.. code-block:: scala - - val settings = MongoClientSettings.builder() - .applyToSslSettings((builder: SslSettings.Builder) => { - builder.enabled(true) - builder.invalidHostNameAllowed(true) - }) - .build() - -Common TLS/SSL Configuration Tasks ----------------------------------- - -This section is based on the documentation for `Oracle JDK -`__, so some -parts may be inapplicable to your JDK or the custom TLS/SSL -implementation that you use. - -Configure Trust Store and Key Store -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You might configure trust stores and key stores specific to the -client by using ``javax.net.ssl.SSLContext.init(KeyManager[] km, -TrustManager[] tm, SecureRandom random)``, or you might set the JVM default -ones. - -Set the Default Trust Store -``````````````````````````` - -A typical application will need to set several JVM system properties -to ensure that the client can *validate* the TLS/SSL -certificate presented by the server: - -- ``javax.net.ssl.trustStore``: the path to a trust store containing the - certificate of the signing authority -- ``javax.net.ssl.trustStorePassword``: the password to access this - trust store - -The trust store is typically created with the ``keytool`` command-line -program provided as part of the JDK: - -.. code-block:: bash - - keytool -importcert -trustcacerts -file - -keystore -storepass - -Set the Default Key Store -````````````````````````` - -A typical application will also need to set several JVM system -properties to ensure that the client *presents* a TLS/SSL client -certificate to the MongoDB server: - -- ``javax.net.ssl.keyStore``: the path to a key store containing the - clients TLS/SSL certificates -- ``javax.net.ssl.keyStorePassword``: the password to access this key - store - -The key store is typically created with the ``keytool`` or the -``openssl`` command-line program. For example, if you have a file with -the client certificate and its private key, and you want to create a key -store in the `PKCS #12 `__ -format, you can run the following command: - -.. code-block:: bash - - openssl pkcs12 -export -in - -out -passout pass: - -To learn more about configuring a Java application for TLS/SSL, -refer to the `JSSE Reference Guide -`__. - -Forcing TLS v1.2 -~~~~~~~~~~~~~~~~ - -Some applications might want to force only the TLS 1.2 protocol. To do -this, set the ``jdk.tls.client.protocols`` system property to ``TLSv1.2``. - -Java runtime environments before Java 8 started to enable the TLS -1.2 protocol only in later updates, as shown in the previous section. -For the driver to force the use of the TLS 1.2 protocol with a Java -runtime environment before Java 8, ensure that the update has TLS -1.2 enabled. - -OCSP -~~~~ - -.. note:: - - The driver cannot enable OCSP by default on an individual - ``MongoClient`` basis. - -Client-Driven OCSP -`````````````````` - -An application will need to set the following JVM system and security properties to -ensure that client-driven OCSP is enabled: - -- ``com.sun.net.ssl.checkRevocation``: when set to ``true``, this system - property enables revocation checking -- ``ocsp.enable``: When set to ``true``, this security property enables - client-driven OCSP - -To configure an application to use client-driven OCSP, the application -must already be set up to connect to a server using TLS. Setting these -system properties is required to enable client-driven OCSP. - -.. note:: - - The support for TLS provided by the JDK utilizes "hard fail" behavior - in the case of an unavailable OCSP responder in contrast to - ``mongosh`` and the drivers that utilize "soft fail" behavior. - -OCSP Stapling -````````````` - -.. important:: - - The following exception may occur when using OCSP stapling with Java - runtime environments that use the TLS 1.3 protocol (Java 11 and higher - use TLS 1.3 by default): - - .. code-block:: none - - javax.net.ssl.SSLHandshakeException: extension (5) should not be presented in certificate_request - - The exception is due to a known issue with TLS 1.3 in Java 11 and - higher. To avoid this exception when using Java runtime environments - that operate on the TLS 1.3 protocol, you can force the application to use the - TLS 1.2 protocol. To do this, set the ``jdk.tls.client.protocols`` - system property to ``TLSv1.2``. - -An application will need to set several JVM system properties to set -up OCSP stapling: - -- ``jdk.tls.client.enableStatusRequestExtension``: when set to ``true`` - (its default value), this enables OCSP stapling. -- ``com.sun.net.ssl.checkRevocation``: when set to ``true``, this enables - revocation checking. If this property is not set to ``true``, then the - connection will be allowed to proceed regardless of the presence or - status of the revocation information. - -To configure an application to use OCSP stapling, the application must -already be set up to connect to a server using TLS, and the server -must be set up to staple an OCSP response to the certificate it -returns as part of the TLS handshake. - -To learn more about configuring a Java application to use OCSP, -refer to `Client-Driven OCSP and OCSP Stapling -`__ -in the Java documentation. \ No newline at end of file diff --git a/source/tutorials/db-coll.txt b/source/tutorials/db-coll.txt deleted file mode 100644 index e154d80..0000000 --- a/source/tutorials/db-coll.txt +++ /dev/null @@ -1,278 +0,0 @@ -.. _scala-db-coll: - -========================= -Databases and Collections -========================= - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, validation, codec registry, write concern - -.. contents:: On this page - :local: - :backlinks: none - :depth: 1 - :class: singlecol - -MongoDB organizes data in a hierarchical structure. A MongoDB deployment -contains one or more databases, and each database contains one or more -collections. In each collection, MongoDB stores data as documents that -contain field-and-value pairs. - -Prerequisites -------------- - -You must include the following import statements in your program to run the -code examples in this guide: - -.. code-block:: scala - - import org.mongodb.scala._ - import org.mongodb.scala.model.Filters._ - -.. include:: /includes/obs-note.rst - -Connect to a MongoDB Deployment -------------------------------- - -First, connect to a running MongoDB deployment. - -The following code connects to a standalone MongoDB deployment running -on ``localhost`` on port ``27017``: - -.. code-block:: scala - - val mongoClient = MongoClient() - -To learn more about connecting to MongoDB deployments, -see the :ref:`scala-connect` tutorial. - -Access a Database ------------------ - -Once you have a ``MongoClient`` instance connected to a MongoDB -deployment, use the ```getDatabase()`` method to access a database. - -Pass the name of the database as a parameter to the ``getDatabase()`` -method. If a database does not exist, MongoDB creates it when -you insert any data into the database. - -The following example accesses the ``test`` database: - -.. code-block:: scala - - val database: MongoDatabase = mongoClient.getDatabase("test") - -.. note:: - - ``MongoDatabase`` instances are immutable. To learn more, see the - :ref:`Immutability ` section of this guide. - -Access a Collection -------------------- - -After you create a ``MongoDatabase`` instance, use the -``getCollection()`` method to access a collection from within that -database. - -Pass the name of the collection as a parameter to the ``getCollection()`` -method. - -Using the ``database`` instance created in the preceding section, the -following code accesses the collection named ``myTestCollection``: - -.. code-block:: scala - - val coll: MongoCollection[Document] = database.getCollection("myTestCollection") - -.. note:: - - ``MongoCollection`` instances are immutable. To learn more, see the - :ref:`Immutability ` section of this guide. - -If a collection with that name does not exist, MongoDB creates it when -you first insert data into that collection. - -You can also directly create a collection with various options, such -as setting the maximum size or creating documentation validation rules. - -Create a Collection -------------------- - -The driver provides the ``createCollection()`` method to -directly create a collection. When you create a -collection, you can specify various collection options, such as a -maximum size or documentation validation rules, with the -``CreateCollectionOptions`` class. - -If you are not specifying any options, you do not need to directly -create the collection since MongoDB automatically creates new -collections when you first insert data. - -Capped Collection -~~~~~~~~~~~~~~~~~ - -The following operation creates a capped collection limited to 1 -megabyte: - -.. code-block:: scala - - database.createCollection("cappedCollection", CreateCollectionOptions().capped(true).sizeInBytes(0x100000)) - .printResults() - -To learn more about capped collections, see :manual:`Capped Collections -` in the Server manual. - -Document Validation -~~~~~~~~~~~~~~~~~~~ - -MongoDB allows you to validate documents during -updates and inserts. Validation rules are specified on a collection -level by using the ``ValidationOptions`` class, which takes a -filter document that specifies the validation rules or expressions. - -The following example creates a collection with schema validation: - -.. code-block:: scala - - ValidationOptions collOptions = ValidationOptions().validator( - Filters.or(Filters.exists("email"), Filters.exists("phone"))) - - database.createCollection("contacts", CreateCollectionOptions().validationOptions(collOptions)) - .printResults() - -To learn more about document validation, see :manual:`Schema Validation -` in the Server manual. - -Get A List of Collections -------------------------- - -You can get a list of the collections in a database by using the -``MongoDatabase.listCollectionNames()`` method: - -.. code-block:: scala - - database.listCollectionNames().printResults() - -Drop a Collection ------------------ - -You can drop a collection and delete all of the data in the collection -by using the ``MongoCollection.drop()`` method: - -.. code-block:: scala - - val collection: MongoCollection[Document] = database.getCollection("contacts") - collection.drop().printResults() - -.. _scala-immutability: - -Immutability ------------- - -``MongoDatabase`` and ``MongoCollection`` instances are immutable. To -create new instances from existing instances that have different -properties, such as different :manual:`read concerns -`, :manual:`read preferences -`, and :manual:`write concerns -`, the ``MongoDatabase`` and -``MongoCollection`` class provides the following methods: - -- ``MongoDatabase.withReadConcern()`` -- ``MongoDatabase.withReadPreference()`` -- ``MongoDatabase.withWriteConcern()`` -- ``MongoCollection.withReadConcern()`` -- ``MongoCollection.withReadPreference()`` -- ``MongoCollection.withWriteConcern()`` - -To learn more, see the :ref:`scala-read-operations` and -:ref:`scala-write-ops` tutorials. - -CodecRegistry -------------- - -An overload of the ``getCollection()`` method allows you to specify a -different class for representing BSON documents. For example, you -might want to use the strict and type-safe ``BsonDocument`` class to -model your documents when performing CRUD operations: - -.. code-block:: scala - - import org.mongodb.scala.bson._ - - val collection: MongoCollection[BsonDocument] = database.getCollection[BsonDocument]("mycoll") - - // insert a document - val document = BsonDocument("{x: 1}") - collection.insertOne(document).printResults() - - document.append("x", BsonInt32(2)).append("y", BsonInt32(3)) - - // replace a document - collection.replaceOne(Filters.equal("_id", document.get("_id")), document) - .printResults() - - // find documents - collection.find().printResults() - -There are two requirements that any class must meet to be used in -this way: - -- ``Codec`` instance for the class must be registered in the - ``CodecRegistry`` for the ``MongoCollection``. - -- ``Codec`` instance must be one that encodes and decodes a full BSON - document, and not just, for example, a single BSON value like an - ``Int32``. - -By default, a ``MongoCollection`` is configured with ``Codec`` instances -for four classes: - -- ``Document`` (Scala ``BsonDocument`` wrapper) -- ``BsonDocument`` -- ``Document`` (Java driver's loosely typed ``Document`` class) -- ``BasicDBObject`` - -Applications are free to register ``Codec`` implementations -for other classes by customizing the ``CodecRegistry``. New -``CodecRegistry`` instances are configurable at the following levels: - -- In a ``MongoClient`` within ``MongoClientSettings`` -- In a ``MongoDatabase`` within its ``withCodecRegistry`` method -- In a ``MongoCollection`` within its ``withCodecRegistry`` method - -Consider the case of encoding and decoding instances of the ``UUID`` -class. The driver by default encodes instances of ``UUID`` by using a -byte ordering that is not compatible with other MongoDB drivers, and -changing the default would be dangerous. - -It is possible for new applications that require interoperability across -multiple drivers to be able to change that default, and they can do that -by specifying a ``CodecRegistry`` - -.. code-block:: scala - - // replaces the default UuidCodec with one that uses the new standard UUID representation - import org.bson.UuidRepresentation - import org.bson.codecs.UuidCodec - import org.bson.codecs.configuration.CodecRegistries - - val codecRegistry = CodecRegistries.fromRegistries( - CodecRegistries.fromCodecs(new UuidCodec(UuidRepresentation.STANDARD)), - MongoClient.DEFAULT_CODEC_REGISTRY) - - // globally - val settings = MongoClientSettings.builder() - .codecRegistry(codecRegistry).build() - val client = MongoClient(settings) - - // or at the database level - val database = client.getDatabase("mydb") - .withCodecRegistry(codecRegistry) - - // or at the collection level - val collection = database.getCollection("mycoll") - .withCodecRegistry(codecRegistry) diff --git a/source/tutorials/encrypt.txt b/source/tutorials/encrypt.txt deleted file mode 100644 index 9ba36cc..0000000 --- a/source/tutorials/encrypt.txt +++ /dev/null @@ -1,28 +0,0 @@ -.. _scala-encrypt: - -.. sharedinclude:: dbx/encrypt-fields.rst - - .. replacement:: driver-specific-content - - .. important:: Compatible Encryption Library Version - - The {+driver-short+} uses the `mongodb-crypt - `__ - encryption library for in-use encryption. This driver version - is compatible with ``mongodb-crypt`` v{+mongocrypt-version+}. - - Select from the following :guilabel:`Maven` and - :guilabel:`sbt` tabs to see how to add the ``mongodb-crypt`` - dependency to your project by using the specified manager: - - .. tabs:: - - .. tab:: Maven - :tabid: maven-dependency - - .. include:: /includes/security/crypt-maven-versioned.rst - - .. tab:: sbt - :tabid: sbt-dependency - - .. include:: /includes/security/crypt-sbt-versioned.rst diff --git a/source/tutorials/geospatial.txt b/source/tutorials/geospatial.txt deleted file mode 100644 index 1f712e2..0000000 --- a/source/tutorials/geospatial.txt +++ /dev/null @@ -1,76 +0,0 @@ -.. _scala-geospatial: - -================= -Geospatial Search -================= - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, search coordinates, location - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -To support geospatial queries, MongoDB provides geospatial -indexes and geospatial query operators. - -To learn more about performing geospatial queries, see -:manual:`Geospatial Queries ` in the -Server manual. - -Prerequisites -------------- - -.. include:: /includes/prereq-restaurants.rst - -.. code-block:: scala - - import org.mongodb.scala._ - import org.mongodb.scala.model.geojson._ - import org.mongodb.scala.model.Indexes - import org.mongodb.scala.model.Filters - -.. include:: /includes/obs-note.rst - -Connect to a MongoDB Deployment -------------------------------- - -.. include:: /includes/connect-section.rst - -Create a 2dsphere Index ------------------------ - -To create a ``2dsphere`` index, use the ``Indexes.geo2dsphere()`` -helper to create a specification for the ``2dsphere`` index. Pass the -specification to the ``MongoCollection.createIndex()`` method to create -the index. - -The following example creates a ``2dsphere`` index on the -``"contact.location"`` field in the collection: - -.. code-block:: scala - - collection.createIndex(Indexes.geo2dsphere("contact.location")).printResults() - -Query for Locations Near a GeoJSON Point ----------------------------------------- - -MongoDB provides various geospatial query operators. To facilitate -the creation of geospatial query filters, the driver provides -the ``Filters`` class and the ``com.mongodb.client.model.geojson`` -package. - -The following example returns documents that are at least ``1000.0`` meters -and at most ``5000.0`` meters from the specified GeoJSON ``Point`` instance, -automatically sorted from nearest to farthest: - -.. code-block:: scala - - val refPoint = Point(Position(-73.9667, 40.78)) - collection.find(Filters.near("contact.location", refPoint, 5000.0, 1000.0)).printResults() diff --git a/source/tutorials/gridfs.txt b/source/tutorials/gridfs.txt deleted file mode 100644 index 17e5bf4..0000000 --- a/source/tutorials/gridfs.txt +++ /dev/null @@ -1,212 +0,0 @@ -.. _scala-gridfs: - -====== -GridFS -====== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, large file, storage - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -GridFS is a specification for storing and retrieving files that -exceed the BSON document size limit of 16 MB. Instead of storing a large -file in a single document, GridFS divides a file into parts, or chunks, and -stores each of those chunks as separate documents. - -When you query a GridFS store for a file, the driver reassembles the -chunks as needed. - -The code examples in this guide come from the `GridFSTour.scala -<{+driver-source-gh+}/blob/master/driver-scala/src/integration/scala/tour/GridFSTour.scala>`__ -file in the driver source code GitHub repository. - -Prerequisites -------------- - -You must include the following import statements in your program to run the -code examples in this guide: - -.. code-block:: scala - - import java.nio.ByteBuffer - import java.nio.charset.StandardCharsets - - import org.mongodb.scala._ - import org.mongodb.scala.bson.BsonObjectId - import org.mongodb.scala.gridfs._ - import org.mongodb.scala.model.Filters - - import tour.Helpers._ - - import scala.util.Success - -.. include:: /includes/obs-note.rst - -Connect to a MongoDB Deployment -------------------------------- - -First, connect to a MongoDB deployment and declare and define -a ``MongoDatabase`` instance. - -The following code connects to a standalone -MongoDB deployment running on ``localhost`` on port ``27017``: - -.. code-block:: scala - - val mongoClient: MongoClient = MongoClient() - -To learn more about connecting to MongoDB deployments, -see the :ref:`scala-connect` guide. - -Create a GridFS Bucket ----------------------- - -GridFS stores files in two collections: - -- ``chunks``: stores the file chunks -- ``files``: stores file metadata - -The two collections are in a common bucket and the collection names -are prefixed with the bucket name. - -The driver provides the ``GridFSBucket()`` method to -create ``GridFSBucket`` instances: - -.. code-block:: scala - - val myDatabase = mongoClient.getDatabase("mydb") - - // Create a gridFSBucket using the default bucket name "fs" - val gridFSBucket = GridFSBucket(myDatabase) - -You can pass a bucket name to the ``GridFSBucket()`` method: - -.. code-block:: scala - - // Create a gridFSBucket with a custom bucket name "files" - val gridFSFilesBucket = GridFSBucket(myDatabase, "files") - -.. note:: - - GridFS automatically creates indexes on the ``files`` and ``chunks`` - collections when you upload data to the GridFS bucket. - -Upload to GridFS ----------------- - -The ``GridFSBucket.uploadFromObservable()`` method reads the contents -of an ``Observable[ByteBuffer]`` and saves it to the ``GridFSBucket`` instance. - -You can use the ``GridFSUploadOptions`` type to configure the chunk size -or include additional metadata. - -The following example uploads the contents of a -``Observable[ByteBuffer]`` into ``GridFSBucket``: - -.. code-block:: scala - - // Get the input stream - val observableToUploadFrom: Observable[ByteBuffer] = Observable( - Seq(ByteBuffer.wrap("MongoDB Tutorial".getBytes(StandardCharsets.UTF_8))) - ) - - // Create some custom options - val options: GridFSUploadOptions = new GridFSUploadOptions() - .chunkSizeBytes(358400) - .metadata(Document("type" -> "presentation")) - - val fileId: BsonObjectId = gridFSBucket - .uploadFromObservable("mongodb-tutorial", observableToUploadFrom, options) - .headResult() - -Find Files Stored in GridFS ---------------------------- - -To find the files stored in the ``GridFSBucket``, use the ``find()`` -method. - -The following example prints out the filename of each file stored: - -.. code-block:: scala - - gridFSBucket.find().results().foreach(file => println(s" - ${file.getFilename}")) - -You can also provide a custom filter to limit the results returned. -The following example prints out the filenames of all files in which the -``contentType`` value is an ``image/png`` value in the user-defined metadata -document: - -.. code-block:: scala - - gridFSBucket - .find(Filters.equal("metadata.contentType", "image/png")) - .results() - .foreach(file => println(s" > ${file.getFilename}")) - -Download from GridFS --------------------- - -The ``downloadToObservable()`` method returns an ``Observable[ByteBuffer]`` -that reads the contents from MongoDB. - -To download a file by its file ``_id``, pass the ``_id`` to the method. -The following example downloads a file by its file ``_id``: - -.. code-block:: scala - - val downloadById = gridFSBucket.downloadToObservable(fileId).results() - -If you don't know the ``_id`` of the file but know the filename, then you -can pass the filename to the ``downloadToObservable()`` method. By -default, it will download the latest version of the file. Use the -``GridFSDownloadOptions`` class to configure which version to download. - -The following example downloads the original version of the file named -``mongodb-tutorial``: - -.. code-block:: scala - - val downloadOptions: GridFSDownloadOptions = new GridFSDownloadOptions().revision(0) - val downloadByName = gridFSBucket.downloadToObservable("mongodb-tutorial", downloadOptions).results() - -Rename Files ------------- - -If you need to rename a file, then use the ``rename()`` method. - -The following example renames a file to ``mongodbTutorial``: - -.. code-block:: scala - - val fileId: ObjectId = ... // ObjectId of a file uploaded to GridFS - gridFSBucket.rename(fileId, "mongodbTutorial").printResults() - -.. note:: - - The ``rename()`` method requires an ``ObjectId`` rather than a ``filename`` to - ensure the correct file is renamed. - - To rename multiple revisions of the same filename, first retrieve the - full list of files. Then, for every file that should be renamed, - run ``rename()`` with the corresponding ``_id``. - -Delete Files ------------- - -To delete a file from the ``GridFSBucket``, use the ``delete()`` method. - -The following example deletes a file from the ``GridFSBucket``: - -.. code-block:: scala - - val fileId: ObjectId = ... //ObjectId of a file uploaded to GridFS - gridFSBucket.delete(fileId).printResults() diff --git a/source/tutorials/indexes.txt b/source/tutorials/indexes.txt deleted file mode 100644 index 8c98e08..0000000 --- a/source/tutorials/indexes.txt +++ /dev/null @@ -1,258 +0,0 @@ -.. _scala-indexes: - -============== -Create Indexes -============== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, optimize, covered query - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Indexes support the efficient execution of queries in MongoDB. To -create an index on a field or fields, pass an index specification -document to the ``MongoCollection.createIndex()`` method. - -The {+driver-short+} provides the :ref:`scala-builders-indexes` class that includes -static factory methods to create index specification documents for the -various MongoDB index key types. To learn more about index types, see -:manual:`Indexes ` in the Server manual. - -.. note:: - - MongoDB only creates an index if an index of the same specification - does not already exist. - -Prerequisites -------------- - -You must include the following import statements in your program to run the -code examples in this guide: - -.. code-block:: scala - - import org.mongodb.scala._ - - import org.mongodb.Indexes - import org.mongodb.IndexOptions - import org.mongodb.Filters - -.. include:: /includes/obs-note.rst - -Connect to a MongoDB Deployment -------------------------------- - -.. include:: /includes/connect-section.rst - -Ascending Index ---------------- - -To create a specification for an ascending index, use the -``Indexes.ascending()`` static helper method. - -Single Ascending Index -~~~~~~~~~~~~~~~~~~~~~~ - -The following example creates an ascending index on the ``name`` field: - -.. code-block:: scala - - collection.createIndex(Indexes.ascending("name")) - .printResults() - -Compound Ascending Index -~~~~~~~~~~~~~~~~~~~~~~~~ - -The following example creates an ascending compound index on the -``stars`` field and the ``name`` field: - -.. code-block:: scala - - collection.createIndex(Indexes.ascending("stars", "name")) - .printResults() - -To view an alternative way to create a compound index, see the :ref:`Compound -Indexes ` section. - -Descending Index ----------------- - -To create a specification of a descending index, use the -``Indexes.descending()`` static helper method. - -Single Descending Key Index -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The following example creates a descending index on the ``stars`` field: - -.. code-block:: scala - - collection.createIndex(Indexes.descending("stars")) - .printResults() - -Compound Descending Key Index -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The following example creates a descending compound index on the -``stars`` field and the ``name`` field: - -.. code-block:: scala - - collection.createIndex(Indexes.descending("stars", "name")) - .printResults() - -To view an alternative way to create a compound index, see the :ref:`Compound -Indexes ` section. - -.. _scala-compound-indexes: - -Compound Indexes ----------------- - -To create a specification for a compound index, use the -``Indexes.compoundIndex()`` static helper method. - -.. note:: - - To create a specification for a compound index where all the keys are - ascending, you can use the ``ascending()`` method. To create a - specification for a compound index where all the keys are descending, - you can use the ``descending()`` method. - -The following example creates a compound index on the ``stars`` field -in descending order and the ``name`` field in ascending order: - -.. code-block:: scala - - collection.createIndex( - Indexes.compoundIndex(Indexes.descending("stars"), - Indexes.ascending("name"))) - .printResults() - -Text Indexes ------------- - -MongoDB provides text indexes to support text search of string -content. Text indexes can include any field whose value is a string or -an array of string elements. To create a specification for a text -index, use the ``Indexes.text()`` helper method. - -The following example creates a text index on the ``name`` field: - -.. code-block:: scala - - collection.createIndex(Indexes.text("name")) - .printResults() - -To learn more about text indexes, see -:manual:`Text Indexes ` in the Server manual. - -Hashed Index ------------- - -To create a specification for a hashed index index, use the -``Indexes.hashed()`` static helper method. - -The following example creates a hashed index on the ``_id`` field: - -.. code-block:: scala - - collection.createIndex(Indexes.hashed("_id")) - .printResults() - -To learn more about hashed indexes, see -:manual:`Hashed Indexes ` in the Server manual. - -Geospatial Indexes ------------------- - -To support geospatial queries, MongoDB supports various geospatial -indexes. To learn more about geospatial indexes, see -:manual:`Geospatial Indexes ` in the Server manual. - -2dsphere -~~~~~~~~ - -To create a specification for a ``2dsphere`` index, use the -``Indexes.geo2dsphere()`` static helper method. - -The following example creates a ``2dsphere`` index on the -``contact.location`` field: - -.. code-block:: scala - - collection.createIndex(Indexes.geo2dsphere("contact.location")) - .printResults() - -IndexOptions ------------- - -In addition to the index specification document, the -``createIndex()`` method can take an index options document that -directs the driver to create unique indexes or partial indexes. - -The driver provides the ``IndexOptions`` class to specify various -index options. - -Add the following import statement to your code to create an -``IndexOptions`` instance. - -.. code-block:: scala - - import org.mongodb.scala.model.IndexOptions - -Unique Index -~~~~~~~~~~~~ - -The following code specifies the ``unique(true)`` option to create a -unique index on the ``name`` and ``stars`` fields: - -.. code-block:: scala - - val indexOptions = IndexOptions().unique(true) - collection.createIndex(Indexes.ascending("name", "stars"), indexOptions) - .printResults() - -To learn more about unique indexes, see -:manual:`Unique Indexes ` in the Server manual. - -Partial Index -~~~~~~~~~~~~~ - -To create a partial index, include the ``partialFilterExpression`` index -option. - -The following example creates a partial index on documents in which the -value of the ``status`` field is ``"A"``. - -.. code-block:: scala - - val partialFilterIndexOptions = IndexOptions() - .partialFilterExpression(Filters.exists("contact.email")) - collection.createIndex( - Indexes.descending("name", "stars"), partialFilterIndexOptions) - .printResults() - -To learn more about partial indexes, see -:manual:`Partial Indexes ` in the Server manual. - -Get a List of Indexes on a Collection -------------------------------------- - -Use the ``listIndexes()`` method to get a list of indexes. The following code -lists the indexes on the collection: - -.. code-block:: scala - - collection.listIndexes().printResults() - -To learn about other index options, see :manual:`Index Properties -` in the Server manual. \ No newline at end of file diff --git a/source/tutorials/read-ops.txt b/source/tutorials/read-ops.txt deleted file mode 100644 index 070e393..0000000 --- a/source/tutorials/read-ops.txt +++ /dev/null @@ -1,316 +0,0 @@ -.. _scala-read-operations: - -=============== -Read Operations -=============== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, access data, modify results - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Read operations retrieve documents or information about documents from a -collection. You can specify a filter to retrieve only those documents that -match the filter condition. - -Prerequisites -------------- - -.. include:: /includes/prereq-restaurants.rst - -.. code-block:: scala - - import org.mongodb.scala._ - import org.mongodb.scala.model.Filters._ - import org.mongodb.scala.model.Projections._ - import org.mongodb.scala.model.Sorts._ - -.. include:: /includes/obs-note.rst - -Connect to a MongoDB Deployment -------------------------------- - -.. include:: /includes/connect-section.rst - -Query a Collection ------------------- - -To query the collection, you can use the collection's ``find()`` -method. - -You can call the method without any arguments to query all documents -in a collection: - -.. code-block:: scala - - collection.find().printResults() - -Or, you can pass a filter to query for documents that match the filter -criteria: - -.. code-block:: scala - - collection.find(equal("name", "456 Cookies Shop")) - .printResults() - -Query Filters -------------- - -To query for documents that match certain conditions, pass a filter -document to the ``find()`` method. - -Empty Filter -~~~~~~~~~~~~ - -To specify an empty filter and match all documents in a collection, -use an empty ``Document`` object: - -.. code-block:: scala - - collection.find(Document()).printResults() - -.. tip:: - - When using the ``find()`` method, you can also call the method without - passing any filter object to match all documents in a collection. - - .. code-block:: scala - - collection.find().printResults() - -Filters Helper -~~~~~~~~~~~~~~ - -To facilitate the creation of filter documents, the driver -provides the ``Filters`` class that provides filter condition helper -methods. To learn about these methods, see the -:ref:`scala-builders-filters` guide. - -This example find operation includes a filter -``Document`` instance which specifies the following conditions: - -- ``stars`` field value is greater than or equal to ``2`` and less than ``5`` -- ``categories`` field equals ``"Bakery"``, or if ``categories`` is an - array field, contains the string ``"Bakery"`` as an element - -.. code-block:: scala - - collection.find( - Document("stars" -> Document("$gte" -> 2, "$lt"-> 5, "categories" -> "Bakery"))) - .printResults() - -The following example specifies the same filter condition using the -``Filters`` helper methods: - -.. code-block:: scala - - collection.find(and(gte("stars", 2), lt("stars", 5), equal("categories", "Bakery"))) - .printResults() - -To view a list of query filter operators, see :manual:`Query and -Projection Operators ` in the -Server manual. To view a list of ``Filters`` helpers, see the `Filters -API documentation <{+api+}/org/mongodb/scala/model/Filters$.html>`__. - -FindObservable --------------- - -The ``find()`` method returns an instance of the ``FindObservable`` -class. The class provides various methods that you can chain -to the ``find()`` method to modify the output or behavior of the query, -such as ``sort()`` or ``projection()``, as well as for iterating -the results via the ``subscribe()`` method. - -Projections -~~~~~~~~~~~ - -By default, queries in MongoDB return all fields in matching -documents. To specify the fields to return in the matching documents, -you can specify a projection document. - -This find operation example includes a projection -``Document`` which specifies that the matching documents include only the -``name``, ``stars``, and the ``categories`` fields: - -.. code-block:: scala - - collection.find(and(gte("stars", 2), lt("stars", 5), equal("categories", "Bakery"))) - .projection(Document("name" -> 1, "stars" -> 1, "categories" -> 1, "_id" -> 0)) - .printResults() - -To facilitate the creation of projection documents, the driver -provides the ``Projections`` class helper methods. To learn more about -these methods, see the :ref:`scala-builders-projections` guide. - -.. code-block:: scala - - collection.find(and(gte("stars", 2), lt("stars", 5), equal("categories", "Bakery"))) - .projection(fields(include("name", "stars", "categories"), excludeId())) - .printResults() - -In the projection document, you can also specify a projection -expression by using a projection operator. - -To view an example that uses the ``Projections.metaTextScore()`` method, see the -:ref:`scala-text-search` tutorial. - -Sorts -~~~~~ - -To sort documents, pass a sort specification document to the -``FindObservable.sort()`` method. The driver provides ``Sorts`` -helper methods to facilitate the creation of the sort specification -document. To learn how to build sort critera by using builders, see the -:ref:`scala-builders-sorts` guide. - -.. code-block:: scala - - collection.find(and(gte("stars", 2), lt("stars", 5), equal("categories", "Bakery"))) - .sort(ascending("name")) - .printResults() - -Sort with Projections -~~~~~~~~~~~~~~~~~~~~~ - -The ``FindObservable`` methods themselves return ``FindObservable`` -objects, and as such, you can append multiple ``FindObservable`` methods -to the ``find()`` method: - -.. code-block:: scala - - collection.find(and(gte("stars", 2), lt("stars", 5), eq("categories", "Bakery"))) - .explain() - .printResults() - -Explain -~~~~~~~ - -To explain a find operation, call the ``FindObservable.explain()`` -method: - -.. code-block:: scala - - collection.find(and(gte("stars", 2), lt("stars", 5), eq("categories", "Bakery"))) - .explain() - .printResults() - -Read Preference ---------------- - -For read operations on replica sets or sharded clusters, -you can configure the read preference at the following levels: - -- In a ``MongoClient`` in the following ways: - - - By creating a ``MongoClientSettings`` instance: - - .. code-block:: scala - - val mongoClient = MongoClient(MongoClientSettings.builder() - .applyConnectionString(ConnectionString("mongodb://host1,host2")) - .readPreference(ReadPreference.secondary()) - .build()) - - - By creating a ``ConnectionString`` instance: - - .. code-block:: scala - - val mongoClient = MongoClient("mongodb://host1:27017,host2:27017/?readPreference=secondary") - -- In a ``MongoDatabase`` by using the ``withReadPreference()`` method: - - .. code-block:: scala - - val database = mongoClient.getDatabase("test") - .withReadPreference(ReadPreference.secondary()) - -- In a ``MongoCollection`` by using the ``withReadPreference()`` method: - - .. code-block:: scala - - val collection = database.getCollection("restaurants") - .withReadPreference(ReadPreference.secondary()) - -``MongoDatabase`` and ``MongoCollection`` instances are immutable. Calling -``withReadPreference()`` on an existing ``MongoDatabase`` or -``MongoCollection`` instance returns a new instance and does not affect -the instance on which the method is called. - -In the following example, the ``collectionWithReadPref`` instance -has the read preference of ``primaryPreferred`` whereas the read -preference of the ``collection`` is unaffected: - -.. code-block:: scala - - val collectionWithReadPref = collection.withReadPreference(ReadPreference.primaryPreferred()) - -Read Concern ------------- - -For read operations on replica sets or sharded clusters, -applications can configure the read concern at the following levels: - -- In a ``MongoClient`` in the following ways: - - - By creating a ``MongoClientSettings`` instance: - - .. code-block:: scala - - val mongoClient = MongoClient(MongoClientSettings.builder() - .applyConnectionString(ConnectionString("mongodb://host1,host2")) - .readConcern(ReadConcern.MAJORITY) - .build()) - - - By creating a ``ConnectionString`` instance: - - .. code-block:: scala - - val mongoClient = MongoClient("mongodb://host1:27017,host2:27017/?readConcernLevel=majority") - -- In a ``MongoDatabase`` by using the ``withReadConcern()`` method: - - .. code-block:: scala - - val database = mongoClient.getDatabase("test") - .withReadConcern(ReadConcern.MAJORITY) - -- In a ``MongoCollection`` by using the ``withReadConcern()`` method: - - .. code-block:: scala - - val collection = database.getCollection("restaurants") - .withReadConcern(ReadConcern.MAJORITY) - -``MongoDatabase`` and ``MongoCollection`` instances are immutable. Calling -``withReadConcern()`` on an existing ``MongoDatabase`` or -``MongoCollection`` instance returns a new instance and does not affect -the instance on which the method is called. - -In the following example, the ``collWithReadConcern`` instance has -an ``AVAILABLE`` read concern whereas the read concern of the ``collection`` -is unaffected: - -.. code-block:: scala - - val collWithReadConcern = collection.withReadConcern(ReadConcern.AVAILABLE) - -You can build ``MongoClientSettings``, ``MongoDatabase``, or -``MongoCollection`` instances to include combinations of read concerns, read -preferences, and write concerns. - -For example, the following code sets all three at the collection level: - -.. code-block:: scala - - val collection = database.getCollection("restaurants") - .withReadPreference(ReadPreference.primary()) - .withReadConcern(ReadConcern.MAJORITY) - .withWriteConcern(WriteConcern.MAJORITY) \ No newline at end of file diff --git a/source/tutorials/text-search.txt b/source/tutorials/text-search.txt deleted file mode 100644 index 7a478bd..0000000 --- a/source/tutorials/text-search.txt +++ /dev/null @@ -1,128 +0,0 @@ -.. _scala-text-search: - -=========== -Text Search -=========== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, full text search, phrases, match score - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -MongoDB supports query operations that perform a text search on -string content in documents. To perform a text search, MongoDB uses a text index -and the ``$text`` query operator. To learn more about text searches, see -:manual:`Text Search ` in the Server manual. - -The driver provides the ``Filters.text()`` helper method to facilitate -the creation of text search query filters. - -Prerequisites -------------- - -.. include:: /includes/prereq-restaurants.rst - -.. code-block:: scala - - import org.mongodb.scala._ - import org.mongodb.scala.model._ - -.. include:: /includes/obs-note.rst - -Connect to a MongoDB Deployment -------------------------------- - -.. include:: /includes/connect-section.rst - -Create the Text Index ---------------------- - -To create a text index, use the ``Indexes.text()`` static helper to -create a specification for a text index and pass the specification to the -``MongoCollection.createIndex()`` method to create the index. - -The following example creates a text index on the ``name`` field in -documents in the collection: - -.. code-block:: scala - - collection.createIndex(Indexes.text("name")).printResults() - -Perform Text Search -------------------- - -To perform text search, use the ``Filters.text()`` helper method to specify -the text search query filter. - -For example, the following code performs a text search on the ``name`` -field to match the strings ``"bakery"`` or ``"coffee"``: - -.. io-code-block:: - :copyable: true - - .. input:: - :language: scala - - collection.countDocuments(Filters.text("bakery coffee")).printResults("Text search matches: ") - - .. output:: - :language: none - :visible: false - - Text search matches: [2] - -Text Score -~~~~~~~~~~ - -For each matching document, text search assigns a score that represents -the relevance of a document to the specified text search query filter. -To return and sort by score, use the ``$meta`` operator in the -projection document and the sort expression: - -.. code-block:: scala - - collection.find(Filters.text("bakery cafe")) - .projection(Projections.metaTextScore("score")) - .sort(Sorts.metaTextScore("score")) - .printResults() - -Specify a Text Search Option -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``Filters.text()`` helper can accept various text search -options. The driver provides the ``TextSearchOptions`` class to -specify these options. - -For example, the following text search specifies the text search -language option when performing a text search for the word ``"cafe"``: - - -.. io-code-block:: - :copyable: true - - .. input:: - :language: scala - - collection.countDocuments(Filters.text("cafe", TextSearchOptions().language("english"))) - .printResults("Text search matches (english): ") - - .. output:: - :language: none - :visible: false - - Text search matches (english): [1] - -To learn more about text search, see the following sections in -the MongoDB Server manual: - -- :manual:`$text ` -- :manual:`Text Indexes ` -- :manual:`Specify the Default Language for a Text Index ` diff --git a/source/tutorials/write-ops.txt b/source/tutorials/write-ops.txt deleted file mode 100644 index 6ee536d..0000000 --- a/source/tutorials/write-ops.txt +++ /dev/null @@ -1,370 +0,0 @@ -.. _scala-write-ops: - -================ -Write Operations -================ - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: code example, insert data, change data - -.. toctree:: - - /tutorials/bulk-writes/ - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -You can perform write operations to insert new documents, -update existing documents, replace an existing document, or delete -existing documents from a collection. - -Prerequisites -------------- - -.. include:: /includes/prereq-restaurants.rst - -.. code-block:: scala - - import org.mongodb.scala._ - import org.mongodb.scala.model._ - import org.mongodb.scala.model.Filters._ - import org.mongodb.scala.model.Updates._ - import org.mongodb.scala.model.UpdateOptions - import org.mongodb.scala.bson.BsonObjectId - -.. include:: /includes/obs-note.rst - -Connect to a MongoDB Deployment -------------------------------- - -.. include:: /includes/connect-section.rst - -Insert a Document ------------------ - -To insert a single document into the collection, you can use the -collection's ``insertOne()`` method: - -.. code-block:: scala - - val document = Document("name" -> "Café Con Leche" , - "contact" -> Document("phone" -> "228-555-0149", - "email" -> "cafeconleche@example.com", - "location" -> Seq(-73.92502, 40.8279556)), - "stars" -> 3, "categories" -> Seq("Bakery", "Coffee", "Pastries")) - - collection.insertOne(document).printResults() - -.. note:: - - If no top-level ``_id`` field is specified in the document, MongoDB - automatically generates a value and adds this field to the inserted - document. - -Insert Multiple Documents -------------------------- - -To insert multiple documents, you can use the collection's -``insertMany()`` method, which takes a list of documents to insert as a parameter. - -The following example inserts two documents into the collection: - -.. code-block:: scala - - val doc1 = Document("name" -> "Amarcord Pizzeria" , - "contact" -> Document("phone" -> "264-555-0193", - "email" -> "amarcord.pizzeria@example.net", - "location" -> Seq(-73.88502, 40.749556)), - "stars" -> 2, "categories" -> Seq("Pizzeria", "Italian", "Pasta")) - - val doc2 = Document("name" -> "Blue Coffee Bar" , - "contact" -> Document("phone" -> "604-555-0102", - "email" -> "bluecoffeebar@example.com", - "location" -> Seq(-73.97902, 40.8479556)), - "stars" -> 5, "categories" -> Seq("Coffee", "Pastries")) - - collection.insertMany(Seq(doc1, doc2)).printResults() - -.. note:: - - If no top-level ``_id`` field is specified in the document, MongoDB - automatically generates a value and adds this field to the inserted - document. - -Update Existing Documents -------------------------- - -To update existing documents in a collection, you can use the -collection's ``updateOne()`` or ``updateMany()`` methods. - -Filters -~~~~~~~ - -You can pass in a filter document to the methods to specify which -documents to update. The filter document specification is the same as -for read operations. To facilitate the creation of filter objects, the -driver provides the ``Filters`` helper class. - -To specify an empty filter and match all documents in a collection, -use an empty ``Document`` object as the filter. - -Update Operators -~~~~~~~~~~~~~~~~ - -To change a field in a document, MongoDB provides update operators. -To specify the modification to perform using the update operators, -create an update document. To learn more about update operators, see -:manual:`Update Operators ` in the Server manual. - -To facilitate the creation of update documents, the driver -provides the ``Updates`` helper class. To learn more about using -builders to specify updates, see the :ref:`scala-builders-updates` guide. - -.. important:: - - The ``_id`` field is immutable, so you cannot change the value of the - ``_id`` field in a document. - -Update a Single Document -~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``updateOne()`` method updates a single document, even -if the filter condition matches multiple documents in the collection. - -The following operation on the ``restaurants`` collection updates a -document in which the value of the ``_id`` field is -``BsonObjectId("57506d62f57802807471dd41")``: - -.. code-block:: scala - - collection.updateOne( - equal("_id", BsonObjectId("57506d62f57802807471dd41")), - combine(set("stars", 1), set("contact.phone", "228-555-9999"), currentDate("lastModified"))) - .printResults() - -Specifically, the operation uses the following methods: - -- ``Updates.set()`` to set the value of the ``stars`` field to ``1`` and - the ``contact.phone`` field to ``"228-555-9999"`` -- ``Updates.currentDate()`` to modify the ``lastModified`` field to the - current date. If the ``lastModified`` field does not exist, the operator - adds the field to the document. - -Update Multiple Documents -~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``updateMany()`` method updates all documents that match the -filter condition. - -The following operation on the ``restaurants`` collection updates all -documents in which the value of the ``stars`` field is ``2``: - -.. code-block:: scala - - collection.updateMany( - equal("stars", 2), - combine(set("stars", 0), currentDate("lastModified"))) - .println() - -Specifically, the operation uses the following methods: - -- ``Updates.set()`` to set the value of the ``stars`` field to ``0`` -- ``Updates.currentDate()`` to set the ``lastModified`` field to the - current date. If the ``lastModified`` field does not exist, the operator - adds the field to the document. - -Update Options -~~~~~~~~~~~~~~ - -When using the ``updateOne()`` and ``updateMany()`` methods, you can -include an ``UpdateOptions`` document to specify the ``upsert`` -option or the ``bypassDocumentationValidation`` option: - -.. code-block:: scala - - collection.updateOne( - equal("_id", 1), - combine(set("name", "Fresh Breads and Tulips"), currentDate("lastModified")), - UpdateOptions().upsert(true).bypassDocumentValidation(true)) - .printResults() - -Replace an Existing Document ----------------------------- - -To replace an existing document in a collection, you can use the -collection's ``replaceOne()`` method. - -.. important:: - - The ``_id`` field is immutable, so you cannot replace the ``_id`` - field in a document. - -Filters -~~~~~~~ - -You can pass in a filter document to the ``replaceOne()`` method to -specify which document to replace. The filter document specification is the same as -for read operations. To facilitate the creation of filter objects, the -driver provides the ``Filters`` helper class. - -To specify an empty filter and match all documents in a collection, -use an empty ``Document`` object as the filter. - -The ``replaceOne()`` method replaces at most a single document, even -if the filter condition matches multiple documents in the collection. - -Replace a Document -~~~~~~~~~~~~~~~~~~ - -To replace a document, pass a new document to the ``replaceOne()`` -method. - -.. important:: - - The replacement document can have different fields from the original - document. In the replacement document, you can omit the ``_id`` field - since the ``_id`` field is immutable. However, if you do include the - ``_id`` field, you cannot specify a different value for the ``_id`` field. - -The following operation on the ``restaurants`` collection replaces the -document in which the value of the ``_id`` field is -``BsonObjectId("57506d62f57802807471dd41")``: - -.. code-block:: scala - - collection.replaceOne( - equal("_id", BsonObjectId("57506d62f57802807471dd41")), - Document("name" -> "Green Salads Buffet", "contact" -> "TBD", - "categories" -> Seq("Salads", "Health Foods", "Buffet"))) - .printResults() - -Update Options -~~~~~~~~~~~~~~ - -When using the ``replaceOne()`` method, you can include an ``UpdateOptions`` -document to specify the ``upsert`` option or the -``bypassDocumentationValidation`` option: - -.. code-block:: scala - - collection.replaceOne( - equal("name", "Orange Patisserie and Gelateria"), - Document("stars" -> 5, "contact" -> "TBD", - "categories" -> Seq("Cafe", "Pastries", "Ice Cream")), - UpdateOptions().upsert(true).bypassDocumentValidation(true)) - .printResults() - -Delete Documents ----------------- - -To delete documents in a collection, you can use the ``deleteOne()`` -and ``deleteMany()`` methods. - -Filters -~~~~~~~ - -You can pass in a filter document to the methods to specify which -documents to delete. The filter document specification is the same as -for read operations. To facilitate the creation of filter objects, the -driver provides the ``Filters`` helper class. - -To specify an empty filter and match all documents in a collection, -use an empty ``Document`` object as the filter. - -Delete a Single Document -~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``deleteOne()`` method deletes at most a single document, even if -the filter condition matches multiple documents in the collection. - -The following operation on the ``restaurants`` collection deletes a -document in which the value of the ``_id`` field is -``ObjectId("57506d62f57802807471dd41")``: - -.. code-block:: scala - - collection.deleteOne(equal("_id", new ObjectId("57506d62f57802807471dd41"))).subscribe(new ObservableSubscriber()) - -Delete Multiple Documents -~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``deleteMany()`` method deletes all documents that match the -filter condition. - -The following operation on the ``restaurants`` collection deletes all -documents in which the value of the ``stars`` field is ``4``: - -.. code-block:: scala - - collection.deleteMany(equal("stars", 4)).printResults() - -Write Concern -------------- - -Write concern describes the level of acknowledgment requested from -MongoDB for write operations. - -You can configure a write concern at the following levels: - -- In a ``MongoClient`` in the following ways: - - - By creating a ``MongoClientSettings`` instance: - - .. code-block:: scala - - val mongoClient: MongoClient = MongoClient(MongoClientSettings.builder() - .applyConnectionString(ConnectionString("mongodb://host1,host2")) - .writeConcern(WriteConcern.MAJORITY) - .build()) - - - By creating a ``ConnectionString`` instance: - - .. code-block:: scala - - val mongoClientt = MongoClient("mongodb://host1:27017,host2:27017/?w=majority") - -- In a ``MongoDatabase`` by using the ``withWriteConcern()`` method: - - .. code-block:: scala - - val database = mongoClient.getDatabase("test").withWriteConcern(WriteConcern.MAJORITY) - - -- In a ``MongoCollection`` by using the ``withWriteConcern()`` method: - - .. code-block:: scala - - val collection = database.getCollection("restaurants").withWriteConcern(WriteConcern.MAJORITY) - -``MongoDatabase`` and ``MongoCollection`` instances are immutable. Calling -``withWriteConcern()`` on an existing ``MongoDatabase`` or -``MongoCollection`` instance returns a new instance and does not affect -the instance on which the method is called. - -In the following example, the ``collWithWriteConcern`` instance -has the write concern of ``majority`` whereas the read -preference of the ``collection`` is unaffected: - -.. code-block:: scala - - val collWithWriteConcern = collection.withWriteConcern(WriteConcern.MAJORITY) - -You can build ``MongoClientSettings``, ``MongoDatabase``, or -``MongoCollection`` instances to include combinations of read concerns, read -preferences, and write concerns. - -For example, the following code sets all three at the collection level: - -.. code-block:: scala - - val collection = database.getCollection("restaurants") - .withReadPreference(ReadPreference.primary()) - .withReadConcern(ReadConcern.MAJORITY) - .withWriteConcern(WriteConcern.MAJORITY) diff --git a/source/validate-signatures.txt b/source/validate-signatures.txt deleted file mode 100644 index 0594711..0000000 --- a/source/validate-signatures.txt +++ /dev/null @@ -1,3 +0,0 @@ -.. _scala-validate-signatures: - -.. sharedinclude:: dbx/jvm/validate-artifacts.rst From 4cc5e1c2410c38f0b2fe93a43a8953dd6ce58b77 Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Thu, 17 Oct 2024 09:13:09 -0400 Subject: [PATCH 05/44] DOCSP-42321: Insert landing page (#55) --- snooty.toml | 3 +- .../usage-examples/SampleWriteApp.scala | 26 +++ .../usage-examples/WriteCodeExamples.scala | 118 +++++++++++ .../usage-examples/sample-app-intro.rst | 9 + source/index.txt | 1 + source/write.txt | 188 ++++++++++++++++++ 6 files changed, 344 insertions(+), 1 deletion(-) create mode 100644 source/includes/usage-examples/SampleWriteApp.scala create mode 100644 source/includes/usage-examples/WriteCodeExamples.scala create mode 100644 source/includes/usage-examples/sample-app-intro.rst create mode 100644 source/write.txt diff --git a/snooty.toml b/snooty.toml index ea86361..cc671c9 100644 --- a/snooty.toml +++ b/snooty.toml @@ -14,7 +14,8 @@ toc_landing_pages = [ "/tutorials/connect", "/tutorials/write-ops", "/builders", - "/get-started" + "/get-started", + "/write" ] [constants] diff --git a/source/includes/usage-examples/SampleWriteApp.scala b/source/includes/usage-examples/SampleWriteApp.scala new file mode 100644 index 0000000..afa88d8 --- /dev/null +++ b/source/includes/usage-examples/SampleWriteApp.scala @@ -0,0 +1,26 @@ + +import org.mongodb.scala._ +import org.mongodb.scala.bson.Document +import org.mongodb.scala.model._ +import org.mongodb.scala.model.Filters._ +import org.mongodb.scala.result._ +import org.mongodb.scala.model.Updates._ + +object SampleWriteApp { + + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + val database: MongoDatabase = mongoClient.getDatabase("") + val collection: MongoCollection[Document] = database.getCollection("") + + // Start example code here + + // End example code here + + // Wait for the operations to complete before closing client + // Note: This example uses Thread.sleep() for brevity and does not guarantee all + // operations will be completed in time + Thread.sleep(1000) + mongoClient.close() + } +} diff --git a/source/includes/usage-examples/WriteCodeExamples.scala b/source/includes/usage-examples/WriteCodeExamples.scala new file mode 100644 index 0000000..94b2c24 --- /dev/null +++ b/source/includes/usage-examples/WriteCodeExamples.scala @@ -0,0 +1,118 @@ + +import org.mongodb.scala._ +import org.mongodb.scala.bson.Document +import org.mongodb.scala.model._ +import org.mongodb.scala.model.Filters._ +import org.mongodb.scala.result._ +import org.mongodb.scala.model.Updates._ + +object WriteCodeExamples { + + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + val database: MongoDatabase = mongoClient.getDatabase("") + val collection: MongoCollection[Document] = database.getCollection("") + + // start-insert-one + val doc: Document = Document("" -> "") + val observable: Observable[InsertOneResult] = collection.insertOne(doc) + + observable.subscribe(new Observer[InsertOneResult] { + override def onNext(result: InsertOneResult): Unit = println(result) + override def onError(e: Throwable): Unit = println("Failed: " + e.getMessage) + override def onComplete(): Unit = println("Completed") + }) + // end-insert-one + + // start-insert-many + val docs: Seq[Document] = Seq( + Document("" -> ""), + Document("" -> "") + ) + val observable: Observable[InsertManyResult] = collection.insertMany(docs) + + observable.subscribe(new Observer[InsertManyResult] { + override def onNext(result: InsertManyResult): Unit = println(result) + override def onError(e: Throwable): Unit = println("Failed: " + e.getMessage) + override def onComplete(): Unit = println("Completed") + }) + // end-insert-many + + // start-update-one + val filter = equal("", "") + val update = set("", "") + val observable: Observable[UpdateResult] = collection.updateOne(filter, update) + + observable.subscribe(new Observer[UpdateResult] { + override def onNext(result: UpdateResult): Unit = println(result) + override def onError(e: Throwable): Unit = println("Failed: " + e.getMessage) + override def onComplete(): Unit = println("Completed") + }) + // end-update-one + + // start-update-many + val filter = equal("", "value to match") + val update = set("", "") + val observable: Observable[UpdateResult] = collection.updateMany(filter, update) + + observable.subscribe(new Observer[UpdateResult] { + override def onNext(result: UpdateResult): Unit = println(result) + override def onError(e: Throwable): Unit = println("Failed: " + e.getMessage) + override def onComplete(): Unit = println("Completed") + }) + // end-update-many + + // start-replace-one + val filter = equal("", "") + val replacementDoc: Document = Document("" -> "") + val observable: Observable[UpdateResult] = collection.replaceOne(filter, replacementDoc) + + observable.subscribe(new Observer[UpdateResult] { + override def onNext(result: UpdateResult): Unit = println(result) + override def onError(e: Throwable): Unit = println("Failed: " + e.getMessage) + override def onComplete(): Unit = println("Completed") + }) + // end-replace-one + + // start-delete-one + val filter = equal("", "") + val observable: Observable[DeleteResult] = collection.deleteOne(filter) + + observable.subscribe(new Observer[DeleteResult] { + override def onNext(result: DeleteResult): Unit = println(result) + override def onError(e: Throwable): Unit = println("Failed: " + e.getMessage) + override def onComplete(): Unit = println("Completed") + }) + // end-delete-one + + // start-delete-many + val filter = equal("", "") + val observable: Observable[DeleteResult] = collection.deleteMany(filter) + + observable.subscribe(new Observer[DeleteResult] { + override def onNext(result: DeleteResult): Unit = println(result) + override def onError(e: Throwable): Unit = println("Failed: " + e.getMessage) + override def onComplete(): Unit = println("Completed") + }) + // end-delete-many + + // start-bulk-write + val operations = Seq( + InsertOneModel(Document("" -> "")), + UpdateManyModel(equal("", ""), set("", "")), + DeleteOneModel(equal("", "")) + ) + val observable: Observable[BulkWriteResult] = collection.bulkWrite(operations) + + observable.subscribe(new Observer[BulkWriteResult] { + override def onNext(result: BulkWriteResult): Unit = println(result) + override def onError(e: Throwable): Unit = println("Failed: " + e.getMessage) + override def onComplete(): Unit = println("Completed") + }) + // end-bulk-write + + // Wait for the operations to complete before closing client + Thread.sleep(1000) + mongoClient.close() + } +} diff --git a/source/includes/usage-examples/sample-app-intro.rst b/source/includes/usage-examples/sample-app-intro.rst new file mode 100644 index 0000000..8a10e85 --- /dev/null +++ b/source/includes/usage-examples/sample-app-intro.rst @@ -0,0 +1,9 @@ +Sample Application +~~~~~~~~~~~~~~~~~~ + +You can use the following sample application to test the code examples on this +page. To use the sample application, perform the following steps: + +1. Ensure you have the {+driver-short+} installed in your Maven or sbt project. +#. Copy the following code and paste it into a new ``.scala`` file. +#. Copy a code example from this page and paste it on the specified lines in the file. \ No newline at end of file diff --git a/source/index.txt b/source/index.txt index 6872ae8..7d3dc98 100644 --- a/source/index.txt +++ b/source/index.txt @@ -13,6 +13,7 @@ :maxdepth: 1 /get-started + /write /whats-new /compatibility View the Source diff --git a/source/write.txt b/source/write.txt new file mode 100644 index 0000000..da3788e --- /dev/null +++ b/source/write.txt @@ -0,0 +1,188 @@ +.. _scala-write: + +===================== +Write Data to MongoDB +===================== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :description: Learn how to use the Scala driver to write data to MongoDB. + :keywords: usage examples, save, crud, create, code example + +.. toctree:: + :titlesonly: + :maxdepth: 1 + + /write/insert + /write/replace + /write/update + /write/delete + /write/bulk-write + /write/transactions + +Overview +-------- + +On this page, you can see copyable code examples that show common methods you +can use to write data to MongoDB by using the {+driver-short+}. + +.. tip:: + + To learn more about any of the methods shown on this page, see the link + provided in each section. + +To use an example from this page, copy the code example into the :ref:`sample +application ` or your own application. Be sure to replace all +placeholders in the code examples, such as ````, with the +relevant values for your MongoDB deployment. + +.. _scala-write-sample: + +.. include:: /includes/usage-examples/sample-app-intro.rst + +.. literalinclude:: /includes/usage-examples/SampleWriteApp.scala + :language: scala + :copyable: + :linenos: + :emphasize-lines: 15-17 + +Insert One +---------- + +The following code shows how to insert a single document into a collection: + +.. literalinclude:: /includes/usage-examples/WriteCodeExamples.scala + :start-after: start-insert-one + :end-before: end-insert-one + :language: scala + :copyable: + :dedent: + +.. TODO +.. To learn more about the ``insertOne()`` method, see the +.. :ref:`Insert Documents ` guide. + +Insert Multiple +--------------- + +The following code shows how to insert multiple documents into a collection: + +.. literalinclude:: /includes/usage-examples/WriteCodeExamples.scala + :start-after: start-insert-many + :end-before: end-insert-many + :language: scala + :copyable: + :dedent: + +.. TODO +.. To learn more about the ``insertMany()`` method, see the +.. :ref:`Insert Documents ` guide. + +Update One +---------- + +The following code shows how to update a single document in a collection by +creating or editing a field: + +.. literalinclude:: /includes/usage-examples/WriteCodeExamples.scala + :start-after: start-update-one + :end-before: end-update-one + :language: scala + :copyable: + :dedent: + +.. TODO +.. To learn more about the ``updateOne()`` method, see the +.. :ref:`Update Documents ` guide. + +Update Multiple +--------------- + +The following code shows how to update multiple documents in a collection by +creating or editing a field: + +.. literalinclude:: /includes/usage-examples/WriteCodeExamples.scala + :start-after: start-update-many + :end-before: end-update-many + :language: scala + :copyable: + :dedent: + +.. TODO +.. To learn more about the ``updateMany()`` method, see the +.. :ref:`Update Documents ` guide. + +Replace One +----------- + +The following code shows how to replace a single document in a collection with a new +document: + +.. literalinclude:: /includes/usage-examples/WriteCodeExamples.scala + :start-after: start-replace-one + :end-before: end-replace-one + :language: scala + :copyable: + :dedent: + +.. TODO +.. To learn more about the ``replaceOne()`` method, see the +.. :ref:`Replace Documents ` guide. + +Delete One +---------- + +The following code shows how to delete a single document in a collection: + +.. literalinclude:: /includes/usage-examples/WriteCodeExamples.scala + :start-after: start-delete-one + :end-before: end-delete-one + :language: scala + :copyable: + :dedent: + +.. TODO: method name +.. To learn more about the ``deleteOne()`` method, see the +.. :ref:`Delete Documents ` guide. + +Delete Multiple +--------------- + +The following code shows how to delete multiple documents in a collection: + +.. literalinclude:: /includes/usage-examples/WriteCodeExamples.scala + :start-after: start-delete-many + :end-before: end-delete-many + :language: scala + :copyable: + :dedent: + +.. TODO +.. To learn more about the ``deleteMany()`` method, see the +.. :ref:`Delete Documents ` guide. + +Bulk Write +---------- + +The following code shows how to perform multiple write operations in a single +bulk operation: + +.. literalinclude:: /includes/usage-examples/WriteCodeExamples.scala + :start-after: start-bulk-write + :end-before: end-bulk-write + :language: scala + :copyable: + :dedent: + +.. TODO +.. To learn more about the ``bulkWrite()`` +.. method, see the :ref:`Bulk Write Operations ` guide. From 7850b1ff641f1aea98aaa3cf40a1ffca365e5c72 Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Thu, 17 Oct 2024 09:42:39 -0400 Subject: [PATCH 06/44] DOCSP-42329: Retrieve data (#54) * DOCSP-42329: Retrieve data * edits * MM feedback --- source/includes/read/retrieve.scala | 49 ++++++ source/index.txt | 1 + source/read.txt | 25 +++ source/read/retrieve.txt | 248 ++++++++++++++++++++++++++++ 4 files changed, 323 insertions(+) create mode 100644 source/includes/read/retrieve.scala create mode 100644 source/read.txt create mode 100644 source/read/retrieve.txt diff --git a/source/includes/read/retrieve.scala b/source/includes/read/retrieve.scala new file mode 100644 index 0000000..57a6267 --- /dev/null +++ b/source/includes/read/retrieve.scala @@ -0,0 +1,49 @@ +import org.mongodb.scala._ +import org.mongodb.scala.model.Filters._ +import org.mongodb.scala.model.Projections._ +import org.mongodb.scala.model.Sorts._ +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.util.{Failure, Success} +import scala.concurrent.ExecutionContext.Implicits.global + +object Retrieve { + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + + // start-db-coll + val database: MongoDatabase = mongoClient.getDatabase("sample_training") + val collection: MongoCollection[Document] = database.getCollection("companies") + // end-db-coll + + // Finds documents with a "founded_year" value of 1970 + // start-find-many + val filter = equal("founded_year", 1970) + + collection.find(filter).subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + //end-find-many + + // Finds one document with a "name" value of "LinkedIn" + // start-find-one + val filter = equal("name", "LinkedIn") + + collection.find(filter).first().subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-find-one + + // Finds and prints up to 5 documents with a "number_of_employees" value of 1000 + // start-modify + val filter = equal("number_of_employees", 1000) + + collection.find(filter).limit(5).subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-modify + + // Keep the main thread alive long enough for the asynchronous operations to complete + Thread.sleep(5000) + + // Close the MongoClient connection + mongoClient.close() + } +} \ No newline at end of file diff --git a/source/index.txt b/source/index.txt index 7d3dc98..f2bef56 100644 --- a/source/index.txt +++ b/source/index.txt @@ -13,6 +13,7 @@ :maxdepth: 1 /get-started + /read /write /whats-new /compatibility diff --git a/source/read.txt b/source/read.txt new file mode 100644 index 0000000..d876d46 --- /dev/null +++ b/source/read.txt @@ -0,0 +1,25 @@ +.. _scala-read: + +====================== +Read Data from MongoDB +====================== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :description: Learn how to use the Scala driver to read data from MongoDB. + :keywords: usage examples, save, crud, read, code example + +.. toctree:: + :titlesonly: + :maxdepth: 1 + + /read/retrieve \ No newline at end of file diff --git a/source/read/retrieve.txt b/source/read/retrieve.txt new file mode 100644 index 0000000..86eb89c --- /dev/null +++ b/source/read/retrieve.txt @@ -0,0 +1,248 @@ +.. _scala-retrieve: + +============= +Retrieve Data +============= + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: code examples, read, query, cursor + +Overview +-------- + +In this guide, you can learn how to use the {+driver-short+} to retrieve +data from a MongoDB collection by using **read operations**. You can call the +``find()`` method on a collection to retrieve documents that match a set of +criteria. + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``companies`` collection in the ``sample_training`` +database from the :atlas:`Atlas sample datasets `. To access this collection +from your Scala application, create a ``MongoClient`` that connects to an Atlas cluster +and assign the following value to your ``database`` and ``collection`` variables: + +.. literalinclude:: /includes/read/retrieve.scala + :language: scala + :dedent: + :start-after: start-db-coll + :end-before: end-db-coll + +To learn how to create a free MongoDB Atlas cluster and load the sample datasets, see the +:atlas:`Get Started with Atlas ` guide. + +.. _scala-retrieve-find: + +Find Documents +-------------- + +To retrieve documents from a collection, use the ``find()`` method. This method +takes a **query filter** parameter and returns an instance of the ``FindObservable`` class +from which you can access the query results. The ``FindObservable`` class also +provides additional methods that you can chain to a ``FindObservable`` instance to modify +its behavior, such as ``first()``. + +.. TODO: + .. tip:: + + .. To learn more about query filters, see the :ref:`scala-specify-query` guide. + +.. _scala-retrieve-find-multiple: + +Find Multiple Documents +~~~~~~~~~~~~~~~~~~~~~~~ + +To find multiple documents in a collection, pass a query filter to the +``find()`` method that specifies the criteria of the documents you want to +retrieve. + +The ``find()`` method returns an instance of ``FindObservable``, which you can +iterate to see the matching documents. Use the ``subscribe()`` method to iterate +through the ``FindObservable``. + +The following example uses the ``find()`` method to find all documents in which +the ``founded_year`` field has the value ``1970`` and prints the results: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/retrieve.scala + :start-after: start-find-many + :end-before: end-find-many + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id":{"$oid":"..."},"name":"Mitsubishi Motors","permalink":"mitsubishi-motors", + "crunchbase_url":"http://www.crunchbase.com/company/mitsubishi-motors", + ... } + + {"_id":{"$oid":"..."},"name":"Western Digital","permalink":"western-digital", + "crunchbase_url":"http://www.crunchbase.com/company/western-digital", + ... } + + {"_id":{"$oid":"..."},"name":"Celarayn","permalink":"celarayn","crunchbase_url": + "http://www.crunchbase.com/company/celarayn", + ... } + +.. note:: Find All Documents + + To find all documents in a collection, call the ``find()`` method + without passing any parameters: + + .. code-block:: scala + + collection.find() + +.. _scala-retrieve-find-one: + +Find One Document +~~~~~~~~~~~~~~~~~ + +To find a single document in a collection, call the ``find()`` method +and pass a query filter that specifies the criteria of the document you want +to find. Then, chain the ``first()`` method to ``find()``. + +The ``find()`` method returns a ``FindObservable`` instance, and the ``first()`` +method returns a ``SingleObserver`` instance that contains the first query result +stored by the ``FindObservable``. You can access the ``SingleObserver`` result +by calling the ``subscribe()`` method. + +The following example uses the ``find()`` and ``first()`` methods to find the +first document in which the ``name`` field has the value ``"LinkedIn"``: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/retrieve.scala + :start-after: start-find-one + :end-before: end-find-one + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id": {"$oid": "..."}, "name": "LinkedIn", "permalink": "linkedin", "crunchbase_url": + "http://www.crunchbase.com/company/linkedin", "homepage_url": "http://linkedin.com", + ...} + +.. tip:: Sort Order + + The ``first()`` method returns the first document in + :manual:`natural order ` + on disk if no sort criteria is specified. + +.. _scala-retrieve-modify: + +Modify Find Behavior +~~~~~~~~~~~~~~~~~~~~ + +You can modify the behavior of the ``find()`` method by chaining +methods provided by the ``FindObservable`` class. The following +table describes some of these methods: + +.. list-table:: + :widths: 30 70 + :header-rows: 1 + + * - Method + - Description + + * - ``explain()`` + - | Explains the execution plan for this operation with the specified verbosity level. + | **Parameter Type**: ``ExplainVerbosity`` + + * - ``collation()`` + - | Sets the collation to use for the operation. The default value is the collation + specified for the collection. + | **Parameter Type**: ``Collation`` + + * - ``comment()`` + - | Attaches a comment to the operation. + | **Parameter Type**: ``String`` + + * - ``first()`` + - | Returns an ``Observable`` that stores only the first query result. To view an example that + uses this method, see :ref:`scala-retrieve-find-one` on this page. + + * - ``limit()`` + - | Sets the maximum number of documents the operation can return. + | **Parameter Type**: ``Int`` + + * - ``skip()`` + - | Sets the number of documents to skip before returning results. + | **Parameter Type**: ``Int`` + + * - ``sort()`` + - | Sets the order in which the operation returns matching documents. + | **Parameter Type**: ``Bson`` + +The following example uses the ``find()`` method to find all documents in which +the ``number_of_employees`` field has the value ``1000``. The example uses the +``limit()`` method to return a maximum of ``5`` results: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/retrieve.scala + :start-after: start-modify + :end-before: end-modify + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id": {"$oid": "..."}, "name": "Akamai Technologies", "permalink": "akamai-technologies", + "crunchbase_url": "http://www.crunchbase.com/company/akamai-technologies", "homepage_url": + "http://www.akamai.com", ... } + + {"_id": {"$oid": "..."}, "name": "Yodle", "permalink": "yodle", "crunchbase_url": + "http://www.crunchbase.com/company/yodle", "homepage_url": "http://www.yodle.com", ... } + + {"_id": {"$oid": "..."}, "name": "Antal International", "permalink": "antal-international", + "crunchbase_url": "http://www.crunchbase.com/company/antal-international", "homepage_url": + "http://antal.com", ... } + + {"_id": {"$oid": "..."}, "name": "Yatra online", "permalink": "yatra-online", "crunchbase_url": + "http://www.crunchbase.com/company/yatra-online", "homepage_url": "http://www.Yatra.com", ... } + + {"_id": {"$oid": "..."}, "name": "Gumtree", "permalink": "gumtree", "crunchbase_url": + "http://www.crunchbase.com/company/gumtree", "homepage_url": "http://www.gumtree.co.za", ... } + +For a full list of ``FindObservable`` member methods, see the API documentation for the +`FindObservable <{+api+}/org/mongodb/scala/FindObservable.html>`__ class. + +.. _scala-retrieve-additional-information: + +Additional Information +---------------------- + +.. TODO: To learn more about query filters, see the :ref:`scala-specify-query` guide. + +.. TODO: To view code examples of retrieving documents with the {+driver-short+}, + see :ref:`scala-read`. + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about any of the methods discussed in this +guide, see the following API documentation: + +- `find() <{+api+}/org/mongodb/scala/MongoCollection.html#find[C](filter:org.mongodb.scala.bson.conversions.Bson)(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[C,TResult],implicitct:scala.reflect.ClassTag[C]):org.mongodb.scala.FindObservable[C]>`__ +- `first() <{+api+}/org/mongodb/scala/FindObservable.html#first():org.mongodb.scala.SingleObservable[TResult]>`__ +- `limit() <{+api+}/org/mongodb/scala/FindObservable.html#limit(limit:Int):org.mongodb.scala.FindObservable[TResult]>`__ \ No newline at end of file From bee1e78a935d67490e0ce7ed0a65a0b6d0a65f16 Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Fri, 18 Oct 2024 09:11:40 -0400 Subject: [PATCH 07/44] DOCSP-42321: Insert Documents (#61) --- snooty.toml | 1 + source/includes/write/insert.scala | 61 +++++++++ source/write.txt | 10 +- source/write/insert.txt | 191 +++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+), 6 deletions(-) create mode 100644 source/includes/write/insert.scala create mode 100644 source/write/insert.txt diff --git a/snooty.toml b/snooty.toml index cc671c9..5ccba57 100644 --- a/snooty.toml +++ b/snooty.toml @@ -30,3 +30,4 @@ driver-source-gh = "https://github.com/mongodb/mongo-java-driver" rs-docs = "https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/org/reactivestreams" core-api = "https://mongodb.github.io/mongo-java-driver/{+version+}/apidocs/mongodb-driver-core" mongocrypt-version = "{+full-version+}" +mdb-server = "MongoDB Server" \ No newline at end of file diff --git a/source/includes/write/insert.scala b/source/includes/write/insert.scala new file mode 100644 index 0000000..42d8409 --- /dev/null +++ b/source/includes/write/insert.scala @@ -0,0 +1,61 @@ + +import org.mongodb.scala._ +import org.mongodb.scala.bson.Document +import org.mongodb.scala.model._ +import org.mongodb.scala.result._ + +object Insert { + + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + + // start-db-coll + val database: MongoDatabase = mongoClient.getDatabase("sample_restaurants") + val collection: MongoCollection[Document] = database.getCollection("restaurants") + // end-db-coll + + // start-insert-one + val doc: Document = Document("name" -> "Neighborhood Bar & Grill", "borough" -> "Queens") + val observable: Observable[InsertOneResult] = collection.insertOne(doc) + + observable.subscribe(new Observer[InsertOneResult] { + override def onNext(result: InsertOneResult): Unit = println(result) + override def onError(e: Throwable): Unit = println("Failed: " + e.getMessage) + override def onComplete(): Unit = println("Completed") + }) + // end-insert-one + + // start-insert-many + val docs: Seq[Document] = Seq( + Document("name" -> "Metropolitan Cafe", "borough" -> "Queens"), + Document("name" -> "Yankee Bistro", "borough" -> "Bronx") + ) + val observable: Observable[InsertManyResult] = collection.insertMany(docs) + + observable.subscribe(new Observer[InsertManyResult] { + override def onNext(result: InsertManyResult): Unit = println(result) + override def onError(e: Throwable): Unit = println("Failed: " + e.getMessage) + override def onComplete(): Unit = println("Completed") + }) + // end-insert-many + + // start-insert-opts + val docs: Seq[Document] = Seq( + Document("name" -> "One Night's Delight", "borough" -> "Queens"), + Document("name" -> "Second Street Pub", "borough" -> "Manhattan"), + Document("name" -> "Triple Crown Diner", "borough" -> "Brooklyn") + ) + val opts: InsertManyOptions = InsertManyOptions().bypassDocumentValidation(true) + val observable: Observable[InsertManyResult] = collection.insertMany(docs, opts) + + observable.subscribe(new Observer[InsertManyResult] { + override def onNext(result: InsertManyResult): Unit = println(result) + override def onError(e: Throwable): Unit = println("Failed: " + e.getMessage) + override def onComplete(): Unit = println("Completed") + }) + // end-insert-opts + + Thread.sleep(1000) + mongoClient.close() + } +} diff --git a/source/write.txt b/source/write.txt index da3788e..5cb83a3 100644 --- a/source/write.txt +++ b/source/write.txt @@ -67,9 +67,8 @@ The following code shows how to insert a single document into a collection: :copyable: :dedent: -.. TODO -.. To learn more about the ``insertOne()`` method, see the -.. :ref:`Insert Documents ` guide. +To learn more about the ``insertOne()`` method, see the +:ref:`Insert Documents ` guide. Insert Multiple --------------- @@ -83,9 +82,8 @@ The following code shows how to insert multiple documents into a collection: :copyable: :dedent: -.. TODO -.. To learn more about the ``insertMany()`` method, see the -.. :ref:`Insert Documents ` guide. +To learn more about the ``insertMany()`` method, see the +:ref:`Insert Documents ` guide. Update One ---------- diff --git a/source/write/insert.txt b/source/write/insert.txt new file mode 100644 index 0000000..abf3b57 --- /dev/null +++ b/source/write/insert.txt @@ -0,0 +1,191 @@ +.. _scala-write-insert: + +================ +Insert Documents +================ + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: code example, write, save, create + +Overview +-------- + +In this guide, you can learn how to use the {+driver-short+} to add +documents to a MongoDB collection by performing **insert operations**. + +An insert operation inserts one or more documents into a MongoDB collection. +You can perform an insert operation by using the following methods: + +- ``insertOne()`` to insert a single document +- ``insertMany()`` to insert one or more documents + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``restaurants`` collection in the ``sample_restaurants`` +database from the :atlas:`Atlas sample datasets `. To access this collection +from your Scala application, create a ``MongoClient`` that connects to an Atlas cluster +and assign the following value to your ``database`` and ``collection`` variables: + +.. literalinclude:: /includes/write/insert.scala + :language: scala + :dedent: + :start-after: start-db-coll + :end-before: end-db-coll + +To learn how to create a free MongoDB Atlas cluster and load the sample datasets, see the +:atlas:`Get Started with Atlas ` guide. + +The _id Field +------------- + +In a MongoDB collection, each document *must* contain an ``_id`` field +with a unique field value. + +MongoDB allows you to manage this field in two ways: + +- Set the ``_id`` field for each document yourself, ensuring each + value is unique. +- Let the driver automatically generate unique ``BsonObjectId`` + values for each document ``_id`` field. + +Unless you can guarantee uniqueness, we recommend +letting the driver automatically generate ``_id`` values. + +.. note:: + + Duplicate ``_id`` values violate unique index constraints, which + causes the driver to return an error. + +To learn more about the ``_id`` field, see the +:manual:`Unique Indexes ` guide in the {+mdb-server+} manual. + +To learn more about document structure and rules, see the +:manual:`Documents ` guide in the {+mdb-server+} manual. + +Insert One Document +------------------- + +To add a single document to a MongoDB collection, call the ``insertOne()`` +method and pass the document you want to insert. + +The following example inserts a document into the ``restaurants`` collection: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/write/insert.scala + :language: scala + :start-after: start-insert-one + :end-before: end-insert-one + :dedent: + + .. output:: + :language: console + :visible: false + + AcknowledgedInsertOneResult{insertedId=BsonObjectId{value=...}} + Completed + +Insert Multiple Documents +------------------------- + +To add multiple documents to a MongoDB collection, call the ``insertMany()`` +function and pass a list of documents you want to insert. + +The following example inserts two documents into the ``restaurants`` collection: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/write/insert.scala + :language: scala + :start-after: start-insert-many + :end-before: end-insert-many + :dedent: + + .. output:: + :language: console + :visible: false + + AcknowledgedInsertManyResult{insertedIds={0=BsonObjectId{value=...}, 1=BsonObjectId{value=...}}} + Completed + +Modify Insert Behavior +---------------------- + +The ``insertOne()`` method optionally accepts an ``InsertOneOptions`` +parameter that sets options to configure the insert operation. +If you don't specify any options, the driver performs the insert +operation with default settings. Pass options as the last parameter to +the ``insertOne()`` method. + +The following table describes the setter methods that you can use to +configure an ``InsertOneOptions`` instance: + +.. list-table:: + :widths: 30 70 + :header-rows: 1 + + * - Method + - Description + + * - ``bypassDocumentValidation()`` + - | If set to ``true``, allows the driver to ignore + :manual:`document-level validation `. + | Defaults to ``false``. + + * - ``comment()`` + - | Sets a comment to attach to the operation. For more information, see the :manual:`insert command + fields ` guide in the + {+mdb-server+} manual for more information. + +You can set the preceding settings on the ``insertMany()`` method +by configuring an ``InsertManyOptions`` instance. You can also use the +``ordered()`` setter method to specify the order in which the driver +inserts documents into MongoDB. Pass options as the last parameter to the ``insertMany()`` +method. + +Example +~~~~~~~ + +The following code uses the ``insertMany()`` method to insert +three new documents into a collection. Because the ``bypassDocumentValidation`` +option is enabled, this insert operation bypasses document-level validation: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/write/insert.scala + :language: scala + :start-after: start-insert-opts + :end-before: end-insert-opts + :dedent: + + .. output:: + :language: console + :visible: false + + AcknowledgedInsertManyResult{insertedIds={0=BsonObjectId{value=...}, 1=BsonObjectId{value=...}, 2=BsonObjectId{value=...}}} + Completed + +Additional Information +---------------------- + +To learn more about any of the methods discussed in this +guide, see the following API documentation: + +- `insertOne() <{+api+}/org/mongodb/scala/MongoCollection.html#insertOne(clientSession:org.mongodb.scala.ClientSession,document:TResult,options:org.mongodb.scala.model.InsertOneOptions):org.mongodb.scala.SingleObservable[org.mongodb.scala.result.InsertOneResult]>`__ +- `insertMany() <{+api+}/org/mongodb/scala/MongoCollection.html#insertMany(clientSession:org.mongodb.scala.ClientSession,documents:Seq[_%3C:TResult],options:org.mongodb.scala.model.InsertManyOptions):org.mongodb.scala.SingleObservable[org.mongodb.scala.result.InsertManyResult]>`__ +- `InsertOneOptions <{+core-api+}/com/mongodb/client/model/InsertOneOptions.html>`__ +- `InsertManyOptions <{+core-api+}/com/mongodb/client/model/InsertManyOptions.html>`__ \ No newline at end of file From 58fdbbf9b614e7288665a040dc9b8738296f9ba7 Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Fri, 18 Oct 2024 10:34:41 -0400 Subject: [PATCH 08/44] DOCSP-42328: Specify a query (#57) * DOCSP-42328: Specify a query * link fix * edit * JS feedback * output * RL feedback --- snooty.toml | 2 +- source/includes/read/specify-queries.scala | 79 +++++ source/read.txt | 6 +- source/read/specify-a-query.txt | 369 +++++++++++++++++++++ 4 files changed, 453 insertions(+), 3 deletions(-) create mode 100644 source/includes/read/specify-queries.scala create mode 100644 source/read/specify-a-query.txt diff --git a/snooty.toml b/snooty.toml index 5ccba57..02b1f52 100644 --- a/snooty.toml +++ b/snooty.toml @@ -30,4 +30,4 @@ driver-source-gh = "https://github.com/mongodb/mongo-java-driver" rs-docs = "https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/org/reactivestreams" core-api = "https://mongodb.github.io/mongo-java-driver/{+version+}/apidocs/mongodb-driver-core" mongocrypt-version = "{+full-version+}" -mdb-server = "MongoDB Server" \ No newline at end of file +mdb-server = "MongoDB Server" diff --git a/source/includes/read/specify-queries.scala b/source/includes/read/specify-queries.scala new file mode 100644 index 0000000..ce26e87 --- /dev/null +++ b/source/includes/read/specify-queries.scala @@ -0,0 +1,79 @@ +import org.mongodb.scala._ +import org.mongodb.scala.model.Filters._ +import org.mongodb.scala.model.Projections._ +import org.mongodb.scala.model.Sorts._ +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.util.{Failure, Success} +import scala.concurrent.ExecutionContext.Implicits.global +import org.mongodb.scala.result.InsertManyResult + +object Retrieve { + def main(args: Array[String]): Unit = { + // start-setup + val uri: String = "" + val client: MongoClient = MongoClient(uri) + val database: MongoDatabase = client.getDatabase("db") + val collection: MongoCollection[Document] = database.getCollection("fruits") + + // Inserts documents representing fruits + val fruits: Seq[Document] = Seq( + Document("_id" -> 1, "name" -> "apples", "qty" -> 5, "rating" -> 3, "color" -> "red", "type" -> Seq("fuji", "honeycrisp")), + Document("_id" -> 2, "name" -> "bananas", "qty" -> 7, "rating" -> 4, "color" -> "yellow", "type" -> Seq("cavendish")), + Document("_id" -> 3, "name" -> "oranges", "qty" -> 6, "rating" -> 2, "type" -> Seq("naval", "mandarin")), + Document("_id" -> 4, "name" -> "pineapples", "qty" -> 3, "rating" -> 5, "color" -> "yellow") + ) + + val result = collection.insertMany(fruits) + .subscribe((result: InsertManyResult) => println(result)) + // end-setup + + // Retrieves documents in which the "color" value is "yellow" + // start-find-exact + val filter = equal("color", "yellow") + + collection.find(filter).subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-find-exact + + // Retrieves and prints documents in which the "rating" value is greater than 2 + // start-find-comparison + val filter = gt("rating", 2) + + collection.find(filter).subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-find-comparison + + // Retrieves and prints documents that match one or both query filters + // start-find-logical + val filter = or(gt("qty", 5), equal("color", "yellow")) + + collection.find(filter).subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-find-logical + + // Retrieves and prints documents in which the "type" array has 2 elements + // start-find-array + val filter = size("type", 2) + + collection.find(filter).subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-find-array + + // Retrieves and prints documents that have a "color" field + // start-find-element + val filter = exists("color") + + collection.find(filter).subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-find-element + + // Retrieves and prints documents in which the "name" value has at least two consecutive "p" characters + // start-find-evaluation + val filter = regex("name", "p{2,}") + + collection.find(filter).subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-find-evaluation + } +} \ No newline at end of file diff --git a/source/read.txt b/source/read.txt index d876d46..20897e2 100644 --- a/source/read.txt +++ b/source/read.txt @@ -13,7 +13,7 @@ Read Data from MongoDB .. facet:: :name: genre :values: reference - + .. meta:: :description: Learn how to use the Scala driver to read data from MongoDB. :keywords: usage examples, save, crud, read, code example @@ -22,4 +22,6 @@ Read Data from MongoDB :titlesonly: :maxdepth: 1 - /read/retrieve \ No newline at end of file + /read/retrieve + /read/specify-a-query + diff --git a/source/read/specify-a-query.txt b/source/read/specify-a-query.txt new file mode 100644 index 0000000..b5c7b06 --- /dev/null +++ b/source/read/specify-a-query.txt @@ -0,0 +1,369 @@ +.. _scala-specify-query: + +=============== +Specify a Query +=============== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: expressions, operations, read, write, filter + +Overview +-------- + +In this guide, you can learn how to specify a query by using the {+driver-short+}. + +You can refine the set of documents that a query returns by creating a +**query filter**. A query filter is an expression that specifies the search +criteria that MongoDB uses to match documents in a read or write operation. + +You can use **query operators** to express more complex matching criteria in +a query filter. The {+driver-short+} includes a ``Filters`` class that provides +helper methods for applying query operators. + +.. tip:: + + To view a full list of ``Filters`` helper methods, see the `Filters + <{+api+}/org/mongodb/scala/model/Filters$.html>`__ API documentation. + +Sample Data +~~~~~~~~~~~ + +The examples in this guide run operations on the ``fruits`` collection, +which contains documents representing fruits. The following +code example shows how to create a database and collection, then +insert the sample documents into your collection: + +.. literalinclude:: /includes/read/specify-queries.scala + :start-after: start-setup + :end-before: end-setup + :language: scala + :dedent: + :copyable: + +Exact Match +----------- + +Literal value queries return documents that have an exact match to your query filter. + +The following example specifies a query filter as a parameter to the ``find()`` +method. The code returns all documents in which the value of the ``color`` field +is ``"yellow"``: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/specify-queries.scala + :start-after: start-find-exact + :end-before: end-find-exact + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id": 2, "name": "bananas", "qty": 7, "rating": 4, "color": "yellow", "type": ["cavendish"]} + {"_id": 4, "name": "pineapples", "qty": 3, "rating": 5, "color": "yellow"} + +.. note:: Find All Documents + + To find all documents in a collection, call the ``find()`` method + without passing any parameters: + + .. code-block:: scala + + collection.find() + +Comparison Operators +-------------------- + +Comparison operators evaluate a document field value against a specified value +in your query filter. The following list defines common comparison operators and +their corresponding ``Filters`` helper methods: + +.. list-table:: + :widths: 20 20 60 + :header-rows: 1 + + * - Query Operator + - Helper Method + - Description + + * - ``$gt`` + - ``gt()`` + - | Matches documents in which the value of the given field is *greater than* + the specified value. + + * - ``$lte`` + - ``lte()`` + - | Matches documents in which the value of the given field is *less than or + equal to* the specified value. + + * - ``$ne`` + - ``ne()`` + - | Matches documents in which the value of the given field *does not equal* the + specified value. + +.. tip:: + + To view a full list of comparison operators, see the :manual:`Comparison Query Operators + ` guide in the {+mdb-server+} manual. + +The following example passes a query filter to the ``find()`` method and uses the +``gt()`` method to apply the ``$gt`` comparison operator. The code returns all documents +in which the value of the ``rating`` field is greater than ``2``: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/specify-queries.scala + :start-after: start-find-comparison + :end-before: end-find-comparison + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id": 1, "name": "apples", "qty": 5, "rating": 3, "color": "red", "type": ["fuji", "honeycrisp"]} + {"_id": 2, "name": "bananas", "qty": 7, "rating": 4, "color": "yellow", "type": ["cavendish"]} + {"_id": 4, "name": "pineapples", "qty": 3, "rating": 5, "color": "yellow"} + +Logical Operators +----------------- + +Logical operators match documents by using logic applied to the results of two or +more sets of expressions. The following table describes each logical operator and +its corresponding ``Filters`` helper method: + +.. list-table:: + :widths: 20 20 60 + :header-rows: 1 + + * - Query Operator + - Helper Method + - Description + + * - ``$and`` + - ``and()`` + - | Matches documents that satisfy the conditions of *all* clauses + + * - ``$or`` + - ``or()`` + - | Matches documents that satisfy the conditions of *one* clause + + * - ``$nor`` + - ``nor()`` + - | Matches documents that *do not* satisfy the conditions of any clause + + * - ``$not`` + - ``not()`` + - | Matches documents that *do not* match the expression + +.. tip:: + + To learn more about logical operators, see the :manual:`Logical Query Operators + ` guide in the {+mdb-server+} manual. + +The following example passes a query filter to the ``find()`` method and uses +the ``or()`` method to apply the ``$or`` logical operator. The code returns all documents +in which the ``qty`` field value is greater than ``5`` **or** the ``color`` field +value is ``"yellow"``: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/specify-queries.scala + :start-after: start-find-logical + :end-before: end-find-logical + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id": 2, "name": "bananas", "qty": 7, "rating": 4, "color": "yellow", "type": ["cavendish"]} + {"_id": 3, "name": "oranges", "qty": 6, "rating": 2, "type": ["naval", "mandarin"]} + {"_id": 4, "name": "pineapples", "qty": 3, "rating": 5, "color": "yellow"} + +Array Operators +--------------- + +Array operators match documents based on the value or quantity of elements in an +array field. The following table describes each array operator and its +corresponding ``Filters`` helper method: + +.. list-table:: + :widths: 20 20 60 + :header-rows: 1 + + * - Query Operator + - Helper Method + - Description + + * - ``$all`` + - ``all()`` + - | Matches documents that have arrays containing all elements in the query + + * - ``$elemMatch`` + - ``elemMatch()`` + - | Matches documents if an element in their array field satisfies all + conditions in the query + + * - ``$size`` + - ``size()`` + - | Matches documents that have arrays of a specified size + +.. tip:: + + To learn more about array operators, see the :manual:`Array Query Operators + ` guide in the {+mdb-server+} manual. + +The following example passes a query filter to the ``find()`` method and uses +the ``size()`` method to apply the ``$size`` array operator. The code returns all +documents in which the ``type`` array field contains ``2`` elements: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/specify-queries.scala + :start-after: start-find-array + :end-before: end-find-array + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id": 1, "name": "apples", "qty": 5, "rating": 3, "color": "red", "type": ["fuji", "honeycrisp"]} + {"_id": 3, "name": "oranges", "qty": 6, "rating": 2, "type": ["naval", "mandarin"]} + +Element Operators +----------------- + +Element operators query data based on the presence or type of a field. The +following table describes each element operator and its corresponding ``Filters`` +helper method: + +.. list-table:: + :widths: 20 20 60 + :header-rows: 1 + + * - Query Operator + - Helper Method + - Description + + * - ``$exists`` + - ``exists()`` + - | Matches documents that have the specified field + + * - ``$type`` + - ``type()`` + - | Matches documents if a field has the specified type + +.. tip:: + + To learn more about element operators, see the :manual:`Element Query Operators + ` guide in the {+mdb-server+} manual. + +The following example passes a query filter to the ``find()`` method and uses +the ``exists()`` method to apply the ``$exists`` element operator. The code returns all +documents that have a ``color`` field: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/specify-queries.scala + :start-after: start-find-element + :end-before: end-find-element + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id": 1, "name": "apples", "qty": 5, "rating": 3, "color": "red", "type": ["fuji", "honeycrisp"]} + {"_id": 2, "name": "bananas", "qty": 7, "rating": 4, "color": "yellow", "type": ["cavendish"]} + {"_id": 4, "name": "pineapples", "qty": 3, "rating": 5, "color": "yellow"} + +Evaluation Operators +-------------------- + +Evaluation operators return data based on evaluations of either individual +fields or the entire collection's documents. The following table describes +common element operators and their corresponding ``Filters`` helper methods: + +.. list-table:: + :widths: 20 20 60 + :header-rows: 1 + + * - Query Operator + - Helper Method + - Description + + * - ``$text`` + - ``text()`` + - | Performs a text search on documents + + * - ``$regex`` + - ``regex()`` + - | Matches documents that have values satisfying a specified + regular expression + + * - ``$mod`` + - ``mod()`` + - | Performs a modulo operation on the value of a field + and matches documents with a specified result + +.. tip:: + + To view a full list of evaluation operators, see the :manual:`Evaluation Query Operators + ` guide in the {+mdb-server+} manual. + +The following example passes a query filter to the ``find()`` method and uses +the ``regex()`` method to apply the ``$regex`` evaluation operator. The code uses a regular +expression to return all documents in which the ``name`` field value has at least +two consecutive ``'p'`` characters: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/specify-queries.scala + :start-after: start-find-evaluation + :end-before: end-find-evaluation + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id": 1, "name": "apples", "qty": 5, "rating": 3, "color": "red", "type": ["fuji", "honeycrisp"]} + {"_id": 4, "name": "pineapples", "qty": 3, "rating": 5, "color": "yellow"} + +Additional Information +---------------------- + +To learn more about querying documents, see the :manual:`Query Documents +` guide in the {+mdb-server+} manual. + +To learn more about retrieving documents with the {+driver-short+}, see the +:ref:`scala-retrieve` guide. + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about any of the methods or types discussed in this +guide, see the following API documentation: + +- `find() <{+api+}/org/mongodb/scala/MongoCollection.html#find[C](filter:org.mongodb.scala.bson.conversions.Bson)(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[C,TResult],implicitct:scala.reflect.ClassTag[C]):org.mongodb.scala.FindObservable[C]>`__ +- `Filters <{+api+}/org/mongodb/scala/model/Filters$.html>`__ +- `insertMany() <{+api+}/org/mongodb/scala/MongoCollection.html#insertMany(documents:Seq[_\<:TResult]):org.mongodb.scala.SingleObservable[org.mongodb.scala.result.InsertManyResult]>`__ \ No newline at end of file From 927603298282b3657b1d3903db48e9ea38574b4d Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Fri, 18 Oct 2024 11:19:08 -0400 Subject: [PATCH 09/44] DOCSP-42330: Projection guide (#58) * DOCSP-42330: Projection guide * edits * add constant * MM fix --- source/includes/read/project.scala | 48 +++++++++ source/read/project.txt | 160 +++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 source/includes/read/project.scala create mode 100644 source/read/project.txt diff --git a/source/includes/read/project.scala b/source/includes/read/project.scala new file mode 100644 index 0000000..f3d0165 --- /dev/null +++ b/source/includes/read/project.scala @@ -0,0 +1,48 @@ +import org.mongodb.scala._ +import org.mongodb.scala.model.Filters._ +import org.mongodb.scala.model.Projections._ +import org.mongodb.scala.model.Sorts._ +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.util.{Failure, Success} +import scala.concurrent.ExecutionContext.Implicits.global + +object Retrieve { + def main(args: Array[String]): Unit = { + val uri: String = "" + val client: MongoClient = MongoClient(uri) + + // start-db-coll + val database: MongoDatabase = client.getDatabase("sample_restaurants") + val collection: MongoCollection[Document] = database.getCollection("restaurants") + // end-db-coll + + // Retrieves documents matching the "name" field query and projects their "name", "cuisine", and "borough" values + // start-project-include + collection + .find(equal("name", "Emerald Pub")) + .projection(include("name", "cuisine", "borough")) + .subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-project-include + + // Retrieves documents matching the "name" field query + // and projects their "name", "cuisine", and "borough" values while excluding the "_id" values + // start-project-include-without-id + collection + .find(equal("name", "Emerald Pub")) + .projection(fields(include("name", "cuisine", "borough"), excludeId())) + .subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-project-include-without-id + + // Retrieves documents matching the "name" field query and excludes their "grades" and "address" values when printing + // start-project-exclude + collection + .find(equal("name", "Emerald Pub")) + .projection(exclude("name", "address")) + .subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-project-exclude + } +} \ No newline at end of file diff --git a/source/read/project.txt b/source/read/project.txt new file mode 100644 index 0000000..1f56672 --- /dev/null +++ b/source/read/project.txt @@ -0,0 +1,160 @@ +.. _scala-project: + +======================== +Specify Fields To Return +======================== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: read, filter, project, select + +Overview +-------- + +In this guide, you can learn how to use the {+driver-short+} to specify which fields +to return from a read operation by using a **projection**. A projection is a document +that specifies which fields MongoDB returns from a query. + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``restaurants`` collection in the ``sample_restaurants`` +database from the :atlas:`Atlas sample datasets `. To access this collection +from your Scala application, create a ``MongoClient`` that connects to an Atlas cluster +and assign the following values to your ``database`` and ``collection`` variables: + +.. literalinclude:: /includes/read/project.scala + :language: scala + :dedent: + :start-after: start-db-coll + :end-before: end-db-coll + +To learn how to create a free MongoDB Atlas cluster and load the sample datasets, see the +:atlas:`Get Started with Atlas ` guide. + +Projection Types +---------------- + +You can use a projection to specify which fields to include in a return +document, or to specify which fields to exclude. You cannot combine inclusion and +exclusion statements in a single projection, unless you are excluding the +``_id`` field. + +Specify Fields to Include +~~~~~~~~~~~~~~~~~~~~~~~~~ + +To specify the fields to include in the result, chain the ``projection()`` method +to the ``find()`` method. The ``Projections`` class provides the ``include()`` helper +method that you can use to set the fields to include. + +The following example uses the ``find()`` method to find all restaurants in which the ``name`` +field value is ``"Emerald Pub"``. Then, the code calls the ``projection()`` and ``include()`` +methods to instruct the find operation to return only the ``name``, ``cuisine``, and ``borough`` fields +of matching documents: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/project.scala + :start-after: start-project-include + :end-before: end-project-include + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id": {"$oid": "..."}, "borough": "Manhattan", "cuisine": "American", "name": "Emerald Pub"} + {"_id": {"$oid": "..."}, "borough": "Queens", "cuisine": "American", "name": "Emerald Pub"} + +When you use a projection to specify fields to include in the return +document, the ``_id`` field is also included by default. All other fields are +implicitly excluded. To remove the ``_id`` field from the return +document, you must :ref:`explicitly exclude it `. + +.. _scala-project-remove-id: + +Exclude the ``_id`` Field +~~~~~~~~~~~~~~~~~~~~~~~~~ + +When specifying fields to include, you can also exclude the ``_id`` field from +the returned document. The ``Projections`` class provides the ``excludeId()`` helper +method that you can use to omit this field. + +The following example performs the same query as the preceding example but +excludes the ``_id`` field from the projection: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/project.scala + :start-after: start-project-include-without-id + :end-before: end-project-include-without-id + :language: scala + :dedent: + + .. output:: + :visible: false + + {"borough": "Manhattan", "cuisine": "American", "name": "Emerald Pub"} + {"borough": "Queens", "cuisine": "American", "name": "Emerald Pub"} + +Specify Fields to Exclude +~~~~~~~~~~~~~~~~~~~~~~~~~ + +To specify the fields to exclude from the result, chain the ``projection()`` method +to the ``find()`` method. The ``Projections`` class provides the ``exclude()`` helper +method that you can use to set the fields to exclude. + +The following example uses the ``find()`` method to find all restaurants in which the ``name`` +field value is ``"Emerald Pub"``. Then, the code calls the ``projection()`` and ``exclude()`` +methods to instruct the find operation to omit the ``name`` and ``address`` fields +in the result: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/project.scala + :start-after: start-project-exclude + :end-before: end-project-exclude + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id": {"$oid": "..."}, "borough": "Manhattan", "cuisine": "American", + "grades": [...], "restaurant_id": "40367329"} + {"_id": {"$oid": "..."}, "borough": "Queens", "cuisine": "American", + "grades": [...], "restaurant_id": "40668598"} + +When you use a projection to specify which fields to exclude, +any unspecified fields are implicitly included in the return document. + +Additional Information +---------------------- + +To learn more about projections, see the :manual:`Project Fields +` guide in the {+mdb-server+} manual. + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about any of the methods or types discussed in this +guide, see the following API documentation: + +- `find() <{+api+}/org/mongodb/scala/MongoCollection.html#find[C](filter:org.mongodb.scala.bson.conversions.Bson)(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[C,TResult],implicitct:scala.reflect.ClassTag[C]):org.mongodb.scala.FindObservable[C]>`__ +- `projection() <{+api+}/org/mongodb/scala/FindObservable.html#projection(projection:org.mongodb.scala.bson.conversions.Bson):org.mongodb.scala.FindObservable[TResult]>`__ +- `Projections <{+api+}/org/mongodb/scala/model/Projections$.html>`__ +- `include() <{+api+}/org/mongodb/scala/model/Projections$.html#include(fieldNames:String*):org.mongodb.scala.bson.conversions.Bson>`__ +- `excludeId() <{+api+}/org/mongodb/scala/model/Projections$.html#excludeId():org.mongodb.scala.bson.conversions.Bson>`__ +- `exclude() <{+api+}/org/mongodb/scala/model/Projections$.html#exclude(fieldNames:String*):org.mongodb.scala.bson.conversions.Bson>`__ \ No newline at end of file From db684b25f0869ceadfd88ea5d3965f4310a4fbcd Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Mon, 21 Oct 2024 08:53:19 -0400 Subject: [PATCH 10/44] DOCSP-42323: Replace (#62) --- source/includes/write/replace.scala | 48 +++++++ source/write.txt | 5 +- source/write/replace.txt | 213 ++++++++++++++++++++++++++++ 3 files changed, 263 insertions(+), 3 deletions(-) create mode 100644 source/includes/write/replace.scala create mode 100644 source/write/replace.txt diff --git a/source/includes/write/replace.scala b/source/includes/write/replace.scala new file mode 100644 index 0000000..6d55565 --- /dev/null +++ b/source/includes/write/replace.scala @@ -0,0 +1,48 @@ +import org.mongodb.scala._ +import org.mongodb.scala.bson.Document +import org.mongodb.scala.model.Filters.equal +import org.mongodb.scala.model.ReplaceOptions +import org.mongodb.scala.result.UpdateResult + +object Replace { + + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + + // start-db-coll + val database: MongoDatabase = mongoClient.getDatabase("sample_restaurants") + val collection: MongoCollection[Document] = database.getCollection("restaurants") + // end-db-coll + + // start-replace-one + val filter = equal("name", "Primola Restaurant") + val replacement = Document( + "name" -> "Frutti Di Mare", + "borough" -> "Queens", + "cuisine" -> "Seafood", + "owner" -> "Sal Thomas" + ) + val observable: Observable[UpdateResult] = collection.replaceOne(filter, replacement) + + observable.subscribe(new Observer[UpdateResult] { + override def onNext(result: UpdateResult): Unit = println(s"Replaced document count: ${result.getModifiedCount}") + override def onError(e: Throwable): Unit = println(s"Failed: ${e.getMessage}") + override def onComplete(): Unit = println("Completed") + }) + // end-replace-one + + // start-replace-options + val options = ReplaceOptions().upsert(true) + val observable: Observable[UpdateResult] = collection.replaceOne(filter, replacement, options) + + observable.subscribe(new Observer[UpdateResult] { + override def onNext(result: UpdateResult): Unit = println(s"Replaced document count: ${result.getModifiedCount}") + override def onError(e: Throwable): Unit = println(s"Failed: ${e.getMessage}") + override def onComplete(): Unit = println("Completed") + }) + // end-replace-options + + Thread.sleep(1000) + mongoClient.close() + } +} diff --git a/source/write.txt b/source/write.txt index 5cb83a3..c9c8be4 100644 --- a/source/write.txt +++ b/source/write.txt @@ -132,9 +132,8 @@ document: :copyable: :dedent: -.. TODO -.. To learn more about the ``replaceOne()`` method, see the -.. :ref:`Replace Documents ` guide. +To learn more about the ``replaceOne()`` method, see the +:ref:`Replace Documents ` guide. Delete One ---------- diff --git a/source/write/replace.txt b/source/write/replace.txt new file mode 100644 index 0000000..96dbf54 --- /dev/null +++ b/source/write/replace.txt @@ -0,0 +1,213 @@ +.. _scala-write-replace: + +================= +Replace Documents +================= + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: modify, change, code example + +Overview +-------- + +In this guide, you can learn how to use the {+driver-short+} to perform a **replace +operation** on a document in a MongoDB collection. A replace operation +removes all fields and values from a specified document except the +``_id`` field, and adds new fields and values that you specify. This +operation differs from an update operation, which changes only +specified fields in one or more documents. + +To learn more about update operations, see the +:ref:`scala-write-update` guide. + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``restaurants`` collection in the ``sample_restaurants`` +database from the :atlas:`Atlas sample datasets `. To access this collection +from your Scala application, create a ``MongoClient`` object that connects to an Atlas cluster +and assign the following values to your ``database`` and ``collection`` variables: + +.. literalinclude:: /includes/write/replace.scala + :language: scala + :dedent: + :start-after: start-db-coll + :end-before: end-db-coll + +To learn how to create a free MongoDB Atlas cluster and load the sample datasets, see the +:atlas:`Get Started with Atlas ` guide. + +Replace Operation +----------------- + +You can perform a replace operation in MongoDB by using the +``replaceOne()`` method. This method removes all fields except the +``_id`` field from the first document that matches the specified query filter. It +then adds the fields and values you specify to the empty document. + +Required Parameters +~~~~~~~~~~~~~~~~~~~ + +You must pass the following parameters to the ``replaceOne()`` method: + +- **Query filter**, which matches which documents to update. To learn + more about query filters, see the :ref:`scala-specify-query` + guide. + +- **Replacement document**, which specifies the fields and values that + you want to replace the existing fields and values with. + +Replace One Document +-------------------- + +The following example uses the ``replaceOne()`` method to replace the +fields and values of a document in which the value of the ``name`` field +is ``"Primola Restaurant"``: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/write/replace.scala + :start-after: start-replace-one + :end-before: end-replace-one + :language: scala + :dedent: + + .. output:: + :language: console + :visible: false + + Replaced document count: 1 + Completed + +.. important:: + + The value of the ``_id`` field is immutable. If your replacement + document specifies a value for the ``_id`` field, it must be the same + as the ``_id`` value of the existing document or the driver raises a + ``WriteError``. + +Customize the Replace Operation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``replaceOne()`` method optionally accepts a parameter with the ``ReplaceOptions`` data +type. The ``ReplaceOptions`` class contains setter methods that you can use to configure +the replace option. If you don't specify any options, the driver performs the replace +operation with default settings. + +The following table describes the setter methods in the ``ReplaceOptions`` class: + +.. list-table:: + :widths: 30 70 + :header-rows: 1 + + * - Method + - Description + + * - ``upsert()`` + - | Specifies whether the replace operation performs an upsert operation if no + documents match the query filter. For more information, see :manual:`upsert + behavior ` + in the {+mdb-server+} manual. + | Defaults to ``false``. + + * - ``bypassDocumentValidation()`` + - | Specifies whether the update operation bypasses document validation. This lets you + update documents that don't meet the schema validation requirements, if any + exist. For more information about schema validation, see :manual:`Schema + Validation ` in the {+mdb-server+} manual. + | Defaults to ``false``. + + * - ``collation()`` + - | Specifies the kind of language collation to use when sorting + results. For more information, see :manual:`Collation ` + in the {+mdb-server+} manual. + + * - ``hint()`` + - | Sets the index to use when matching documents. + For more information, see the :manual:`hint statement + ` + in the {+mdb-server+} manual. + + * - ``let()`` + - | Provides a map of parameter names and values to set top-level + variables for the operation. Values must be constant or closed + expressions that don't reference document fields. + + * - ``comment()`` + - | Sets a comment to attach to the operation. + +The following code sets the ``upsert`` option to ``true``, which instructs the +driver to insert a new document that has the fields and values specified +in the replacement document if the query filter doesn't match any +existing documents: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/write/replace.scala + :start-after: start-replace-options + :end-before: end-replace-options + :language: scala + :dedent: + + .. output:: + :language: console + :visible: false + + Replaced document count: 1 + Completed + +Return Value +~~~~~~~~~~~~ + +The ``replaceOne()`` method returns an ``UpdateResult`` +object. You can use the following methods to access information from +an ``UpdateResult`` instance: + +.. list-table:: + :widths: 30 70 + :header-rows: 1 + + * - Method + - Description + + * - ``getMatchedCount()`` + - | Returns the number of documents that matched the query filter. + + * - ``getModifiedCount()`` + - | Returns the number of documents modified by the update operation. If an updated + document is identical to the original, it is not included in this + count. + + * - ``wasAcknowledged()`` + - | Returns ``true`` if the server acknowledged the result. + + * - ``getUpsertedId()`` + - | Returns the ``_id`` value of the document that the driver upserted + into the database, if any. + +Additional Information +---------------------- + +To view a runnable code example that demonstrates how to replace a +document, see :ref:`scala-write`. + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about any of the methods or types discussed in this +guide, see the following API documentation: + +- `replaceOne() <{+api+}/org/mongodb/scala/MongoCollection.html#replaceOne(clientSession:org.mongodb.scala.ClientSession,filter:org.mongodb.scala.bson.conversions.Bson,replacement:TResult,options:org.mongodb.scala.model.ReplaceOptions):org.mongodb.scala.SingleObservable[org.mongodb.scala.result.UpdateResult]>`__ +- `UpdateResult <{+core-api+}/com/mongodb/client/result/UpdateResult.html>`__ From 15cc939125c50170aef12c46428299a840cc0894 Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Mon, 21 Oct 2024 10:57:58 -0400 Subject: [PATCH 11/44] DOCSP-42331: Specify docs to return (#64) * DOCSP-42331: Specify docs to return * toc * MM feedback * code output --- source/includes/read/limit-skip-sort.scala | 62 ++++++ source/read.txt | 2 + source/read/specify-documents-to-return.txt | 209 ++++++++++++++++++++ 3 files changed, 273 insertions(+) create mode 100644 source/includes/read/limit-skip-sort.scala create mode 100644 source/read/specify-documents-to-return.txt diff --git a/source/includes/read/limit-skip-sort.scala b/source/includes/read/limit-skip-sort.scala new file mode 100644 index 0000000..7edc10e --- /dev/null +++ b/source/includes/read/limit-skip-sort.scala @@ -0,0 +1,62 @@ +import org.mongodb.scala._ +import org.mongodb.scala.model.Filters._ +import org.mongodb.scala.model.Projections._ +import org.mongodb.scala.model.Sorts._ +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.util.{Failure, Success} +import scala.concurrent.ExecutionContext.Implicits.global + +object LimitSkipSort { + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + + // start-db-coll + val database: MongoDatabase = mongoClient.getDatabase("sample_restaurants") + val collection: MongoCollection[Document] = database.getCollection("restaurants") + // end-db-coll + + // Retrieves 5 documents that have a "cuisine" value of "Italian" + // start-limit + val filter = equal("cuisine", "Italian") + + collection.find(filter).limit(5).subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + //end-limit + + // Retrieves documents with a "cuisine" value of "Italian" and sorts in ascending "name" order + // start-sort + val filter = equal("cuisine", "Italian") + + collection.find(filter).sort(ascending("name")).subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-sort + + // Retrieves documents with a "borough" value of "Manhattan" but skips the first 10 results + // start-skip + val filter = equal("borough", "Manhattan") + + collection.find(filter).skip(10).subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-skip + + // Retrieves 5 documents with a "cuisine" value of "Italian", skips the first 10 results, + // and sorts by ascending "name" order + // start-limit-sort-skip + val filter = equal("cuisine", "Italian") + + collection.find(filter) + .limit(5) + .skip(10) + .sort(ascending("name")) + .subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-limit-sort-skip + + // Keep the main thread alive long enough for the asynchronous operations to complete + Thread.sleep(5000) + + // Close the MongoClient connection + mongoClient.close() + } +} \ No newline at end of file diff --git a/source/read.txt b/source/read.txt index 20897e2..f51c1fe 100644 --- a/source/read.txt +++ b/source/read.txt @@ -24,4 +24,6 @@ Read Data from MongoDB /read/retrieve /read/specify-a-query + /read/specify-documents-to-return + diff --git a/source/read/specify-documents-to-return.txt b/source/read/specify-documents-to-return.txt new file mode 100644 index 0000000..1eff2f4 --- /dev/null +++ b/source/read/specify-documents-to-return.txt @@ -0,0 +1,209 @@ +.. _scala-specify-documents-to-return: + +=========================== +Specify Documents to Return +=========================== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: read, paginate, pagination, order, code example + +Overview +-------- + +In this guide, you can learn how to specify which documents to return +from a read operation by chaining the following methods to the ``find()`` +method: + +- :ref:`limit() `: Specifies the maximum number of documents + to return from a query +- :ref:`sort() `: Specifies the sort order for the returned documents +- :ref:`skip() `: Specifies the number of documents to skip before + returning query results + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``restaurants`` collection in the ``sample_restaurants`` +database from the :atlas:`Atlas sample datasets `. To access this collection +from your Scala application, create a ``MongoClient`` that connects to an Atlas cluster +and assign the following values to your ``database`` and ``collection`` variables: + +.. literalinclude:: /includes/read/limit-skip-sort.scala + :language: scala + :dedent: + :start-after: start-db-coll + :end-before: end-db-coll + +To learn how to create a free MongoDB Atlas cluster and load the sample datasets, see the +:atlas:`Get Started with Atlas ` guide. + +.. _scala-return-documents-limit: + +Limit +----- + +To specify the maximum number of documents returned from a read operation, use +the ``limit()`` method provided by the ``FindObservable`` class. After calling +the ``find()`` method, chain the ``limit()`` method to modify the behavior of the +operation. + +The following example finds all restaurants that have a ``cuisine`` field value +of ``"Italian"`` and limits the results to ``5`` documents: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/limit-skip-sort.scala + :start-after: start-limit + :end-before: end-limit + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id": {"$oid": "..."}, ... , "name": "Isle Of Capri Resturant", "restaurant_id": "40364373"} + {"_id": {"$oid": "..."}, ... , "name": "Marchis Restaurant", "restaurant_id": "40364668"} + {"_id": {"$oid": "..."}, ... , "name": "Crystal Room", "restaurant_id": "40365013"} + {"_id": {"$oid": "..."}, ... , "name": "Forlinis Restaurant", "restaurant_id": "40365098"} + {"_id": {"$oid": "..."}, ... , "name": "Angelo Of Mulberry St.", "restaurant_id": "40365293"} + +.. tip:: + + The preceding example returns the first five documents matched by the query + according to their :manual:`natural order ` + in the database. The following section describes how to return the documents + in a specified order. + +.. _scala-return-documents-sort: + +Sort +---- + +To return documents in a specified order, use the ``sort()`` method provided by +the ``FindObservable`` class. After calling the ``find()`` method, chain the ``sort()`` +method to modify the behavior of the operation. + +When calling ``sort()``, pass the field to sort the results by and the +sort direction. You can use the ``ascending()`` method to sort values from +lowest to highest, or the ``descending()`` method to sort them from highest +to lowest. + +The following example returns all documents that have a ``cuisine`` field value +of ``"Italian"``, sorted in ascending order of ``name`` field values: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/limit-skip-sort.scala + :start-after: start-sort + :end-before: end-sort + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id": {"$oid": "..."}, ... , "name": "44 Sw Ristorante & Bar", "restaurant_id": "40698807"} + {"_id": {"$oid": "..."}, ... , "name": "900 Park", "restaurant_id": "41707964"} + {"_id": {"$oid": "..."}, ... , "name": "A Voce", "restaurant_id": "41434084"} + ... + {"_id": {"$oid": "..."}, ... , "name": "Zucchero E Pomodori", "restaurant_id": "41189590"} + +.. _scala-return-documents-skip: + +Skip +---- + +To skip a specified number of documents before returning your query results, use +the ``skip()`` method provided by the ``FindObservable`` class. After calling +the ``find()`` method, chain the ``skip()`` method to modify the behavior of the +operation. + +The following example returns all documents that have a ``borough`` field value +of ``"Manhattan"`` and skips the first ``10`` documents: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/limit-skip-sort.scala + :start-after: start-skip + :end-before: end-skip + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id": {"$oid": "..."}, ... , "name": "Cafe Metro", "restaurant_id": "40363298"} + {"_id": {"$oid": "..."}, ... , "name": "Lexler Deli", "restaurant_id": "40363426"} + {"_id": {"$oid": "..."}, ... , "name": "Domino'S Pizza", "restaurant_id": "40363644"} + ... + +.. _scala-return-documents-combine: + +Combine Limit, Sort, and Skip +----------------------------- + +You can chain the ``limit()``, ``sort()``, and ``skip()`` methods to a single +``find()`` method call. This allows you to set a maximum number of sorted documents +to return from the read operation, skipping a specified number of documents before +returning. + +The following example returns ``5`` documents that have a ``cuisine`` value of +``"Italian"``. The results are sorted in ascending order by the ``name`` field value, +skipping the first ``10`` documents: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/limit-skip-sort.scala + :start-after: start-limit-sort-skip + :end-before: end-limit-sort-skip + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id": {"$oid": "..."}, ... , "name": "Acqua", "restaurant_id": "40871070"} + {"_id": {"$oid": "..."}, ... , "name": "Acqua Restaurant", "restaurant_id": "41591488"} + {"_id": {"$oid": "..."}, ... , "name": "Acqua Santa", "restaurant_id": "40735858"} + {"_id": {"$oid": "..."}, ... , "name": "Acquista Trattoria", "restaurant_id": "40813992"} + {"_id": {"$oid": "..."}, ... , "name": "Acquolina Catering", "restaurant_id": "41381423"} + +.. note:: + + The order in which you call these methods doesn't change the documents + that are returned. The {+driver-short+} automatically reorders the calls to + perform the sort operation first, the skip operation next, and then the limit + operation. + +Additional Information +---------------------- + +For more information about retrieving documents, see the :ref:`scala-retrieve` guide. + +For more information about specifying a query, see the :ref:`scala-specify-query` guide. + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about any of the methods or types discussed in this +guide, see the following API documentation: + +- `find() <{+api+}/org/mongodb/scala/MongoCollection.html#find[C](filter:org.mongodb.scala.bson.conversions.Bson)(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[C,TResult],implicitct:scala.reflect.ClassTag[C]):org.mongodb.scala.FindObservable[C]>`__ +- `FindObservable <{+api+}/org/mongodb/scala/FindObservable.html>`__ +- `limit() <{+api+}/org/mongodb/scala/FindObservable.html#limit(limit:Int):org.mongodb.scala.FindObservable[TResult]>`__ +- `skip() <{+api+}/org/mongodb/scala/FindObservable.html#skip(skip:Int):org.mongodb.scala.FindObservable[TResult]>`__ +- `sort() <{+api+}/org/mongodb/scala/FindObservable.html#sort(sort:org.mongodb.scala.bson.conversions.Bson):org.mongodb.scala.FindObservable[TResult]>`__ \ No newline at end of file From e130df4993ef2dda8c24135fdcaa50de827c1143 Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Tue, 22 Oct 2024 08:52:46 -0400 Subject: [PATCH 12/44] DOCSP-42322: Update (#63) --- snooty.toml | 1 + source/includes/write/update.scala | 64 +++++++ source/write.txt | 10 +- source/write/update.txt | 258 +++++++++++++++++++++++++++++ 4 files changed, 327 insertions(+), 6 deletions(-) create mode 100644 source/includes/write/update.scala create mode 100644 source/write/update.txt diff --git a/snooty.toml b/snooty.toml index 02b1f52..30d84fb 100644 --- a/snooty.toml +++ b/snooty.toml @@ -31,3 +31,4 @@ rs-docs = "https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/org/r core-api = "https://mongodb.github.io/mongo-java-driver/{+version+}/apidocs/mongodb-driver-core" mongocrypt-version = "{+full-version+}" mdb-server = "MongoDB Server" +language = "Scala" diff --git a/source/includes/write/update.scala b/source/includes/write/update.scala new file mode 100644 index 0000000..39da7d3 --- /dev/null +++ b/source/includes/write/update.scala @@ -0,0 +1,64 @@ +import org.mongodb.scala._ +import org.mongodb.scala.bson.Document +import org.mongodb.scala.model.Filters.equal +import org.mongodb.scala.model.UpdateOptions +import org.mongodb.scala.model.Updates.{combine, rename, set} +import org.mongodb.scala.result.UpdateResult + +object Update { + + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + + // start-db-coll + val database: MongoDatabase = mongoClient.getDatabase("sample_restaurants") + val collection: MongoCollection[Document] = database.getCollection("restaurants") + // end-db-coll + + // start-update-one + val filter = equal("name", "Happy Garden") + val update = set("name", "Mountain House") + val observable: Observable[UpdateResult] = collection.updateOne(filter, update) + + observable.subscribe(new Observer[UpdateResult] { + override def onNext(result: UpdateResult): Unit = + println(s"Updated document count: ${result.getModifiedCount}") + override def onError(e: Throwable): Unit = println(s"Failed: ${e.getMessage}") + override def onComplete(): Unit = println("Completed") + }) + // end-update-one + + // start-update-many + val filter = equal("name", "Starbucks") + val update = rename("address", "location") + val observable: Observable[UpdateResult] = collection.updateMany(filter, update) + + observable.subscribe(new Observer[UpdateResult] { + override def onNext(result: UpdateResult): Unit = + println(s"Updated document count: ${result.getModifiedCount}") + override def onError(e: Throwable): Unit = println(s"Failed: ${e.getMessage}") + override def onComplete(): Unit = println("Completed") + }) + // end-update-many + + // start-update-options + val filter = equal("name", "Sunrise Pizzeria") + val opts = UpdateOptions().upsert(true) + val update = combine( + set("borough", "Queens"), + set("cuisine", "Italian") + ) + val observable: Observable[UpdateResult] = collection.updateOne(filter, update, opts) + + observable.subscribe(new Observer[UpdateResult] { + override def onNext(result: UpdateResult): Unit = + println(s"Updated document count: ${result.getModifiedCount}") + override def onError(e: Throwable): Unit = println(s"Failed: ${e.getMessage}") + override def onComplete(): Unit = println("Completed") + }) + // end-update-options + + Thread.sleep(1000) + mongoClient.close() + } +} diff --git a/source/write.txt b/source/write.txt index c9c8be4..0ba3c07 100644 --- a/source/write.txt +++ b/source/write.txt @@ -98,9 +98,8 @@ creating or editing a field: :copyable: :dedent: -.. TODO -.. To learn more about the ``updateOne()`` method, see the -.. :ref:`Update Documents ` guide. +To learn more about the ``updateOne()`` method, see the +:ref:`scala-write-update` guide. Update Multiple --------------- @@ -115,9 +114,8 @@ creating or editing a field: :copyable: :dedent: -.. TODO -.. To learn more about the ``updateMany()`` method, see the -.. :ref:`Update Documents ` guide. +To learn more about the ``updateMany()`` method, see the +:ref:`scala-write-update` guide. Replace One ----------- diff --git a/source/write/update.txt b/source/write/update.txt new file mode 100644 index 0000000..34ee448 --- /dev/null +++ b/source/write/update.txt @@ -0,0 +1,258 @@ +.. _scala-write-update: + +================ +Update Documents +================ + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: modify, change, operator, code example + +Overview +-------- + +In this guide, you can learn how to use the {+driver-short+} to update +documents in a MongoDB collection by using the ``updateOne()`` and +``updateMany()`` methods. + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``restaurants`` collection in the ``sample_restaurants`` +database from the :atlas:`Atlas sample datasets `. To access this collection +from your {+language+} application, create a ``MongoClient`` that connects to an Atlas cluster +and assign the following values to your ``database`` and ``collection`` variables: + +.. literalinclude:: /includes/write/update.scala + :language: scala + :dedent: + :start-after: start-db-coll + :end-before: end-db-coll + +To learn how to create a free MongoDB Atlas cluster and load the sample datasets, see the +:atlas:`Get Started with Atlas ` guide. + +Update Operations +----------------- + +You can update documents in MongoDB by using the following methods: + +- ``updateOne()``, which updates *the first document* that matches the search criteria +- ``updateMany()``, which updates *all documents* that match the search criteria + +Each update method requires the following parameters: + +- **Query filter**, which matches the documents you want to update. To learn + more about query filters, see the :ref:`scala-specify-query` + guide. + +- **Update document**, which specifies the update operator and the fields and values to be + updated. The update operator specifies the type of update to perform. To view a list of + update operators and learn about their usages, see the + :manual:`Field Update Operators guide page` in the + {+mdb-server+} manual. + +Update One Document Example +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following example uses the ``updateOne()`` method to update the +``name`` field value of a document from ``"Happy Garden"`` to ``"Mountain +House"``. The update document uses the ``set()`` method to update the ``name`` field +value: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/write/update.scala + :start-after: start-update-one + :end-before: end-update-one + :emphasize-lines: 3 + :language: scala + :dedent: + + .. output:: + :language: console + :visible: false + + Updated document count: 1 + Completed + +Update Many Documents Example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following example uses the ``updateMany()`` method to update all documents +in which the ``name`` field value is ``"Starbucks"``. The update document uses the +``rename()`` method to change the name of the ``address`` field to ``location``: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/write/update.scala + :start-after: start-update-many + :end-before: end-update-many + :emphasize-lines: 3 + :language: scala + :dedent: + + .. output:: + :language: console + :visible: false + + Updated document count: 11 + Completed + +Customize the Update Operation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``updateOne()`` and ``updateMany()`` methods optionally accept +a parameter that sets options to configure the update operation. +If you don't specify any options, the driver performs update +operations with default settings. + +The following table describes the setter methods that you can use to +configure an ``UpdateOptions`` instance: + +.. list-table:: + :widths: 30 70 + :header-rows: 1 + + * - Method + - Description + + * - ``upsert()`` + - | Specifies whether the update operation performs an upsert operation if no + documents match the query filter. For more information, see the :manual:`upsert + statement ` + in the {+mdb-server+} manual. + | Defaults to ``false`` + + * - ``bypassDocumentValidation()`` + - | Specifies whether the update operation bypasses document validation. This lets you + update documents that don't meet the schema validation requirements, if any + exist. For more information about schema validation, see :manual:`Schema + Validation ` in the MongoDB + Server manual. + | Defaults to ``false``. + + * - ``collation()`` + - | Specifies the kind of language collation to use when sorting + results. For more information, see :manual:`Collation ` + in the {+mdb-server+} manual. + + * - ``arrayFilters()`` + - | Provides a list of filters that you specify to select which + array elements the update applies to. + + * - ``hint()`` + - | Sets the index to use when matching documents. + For more information, see the :manual:`hint statement ` + in the {+mdb-server+} manual. + + * - ``let()`` + - | Provides a map of parameter names and values to set top-level + variables for the operation. Values must be constant or closed + expressions that don't reference document fields. For more information, + see the :manual:`let statement + ` in the + {+mdb-server+} manual. + + * - ``comment()`` + - | Sets a comment to attach to the operation. For more + information, see the :manual:`update command + fields ` guide in the + {+mdb-server+} manual for more information. + +Modify Update Example +````````````````````` + +This example creates and passes options to the ``updateOne()`` method. +The example uses the ``equal()`` helper method to match documents +in which the ``name`` field value is ``"Sunrise Pizzeria"``. It then uses the ``set()`` +method to set the ``borough`` field value in the first matching document to +``"Queens"`` and the ``cuisine`` field value to ``"Italian"``. The code uses the +``combine()`` method to specify multiple updates in one update document. + +Because the ``upsert`` option is set to ``true`` in the ``UpdateOptions`` instance, +the driver inserts a new document that has the fields and values specified in the filter +and update document if the query filter doesn't match any existing documents. + +.. io-code-block:: + :copyable: true + + .. input:: /includes/write/update.scala + :start-after: start-update-options + :end-before: end-update-options + :language: scala + :emphasize-lines: 2 + :dedent: + + .. output:: + :language: console + :visible: false + + Updated document count: 1 + Completed + +Return Value +~~~~~~~~~~~~ + +The ``updateOne()`` and ``updateMany()`` methods each return an ``UpdateResult`` +object. You can use the following methods to access information from +an ``UpdateResult`` instance: + +.. list-table:: + :widths: 30 70 + :header-rows: 1 + + * - Method + - Description + + * - ``getMatchedCount()`` + - | Returns the number of documents that matched the query filter, regardless of + how many updates were performed. + + * - ``getModifiedCount()`` + - | Returns the number of documents modified by the update operation. If an updated + document is identical to the original, it is not included in this + count. + + * - ``wasAcknowledged()`` + - | Returns ``true`` if the server acknowledged the result. + + * - ``getUpsertedId()`` + - | Returns the ``_id`` value of the document that was upserted + in the database, if the driver performed an upsert. + +.. note:: + + If the ``wasAcknowledged()`` method returns ``false``, trying to + access other information from the ``UpdateResult`` instance results in an + ``InvalidOperation`` exception. The driver cannot + determine these values if the server does not acknowledge the write + operation. + +Additional Information +---------------------- + +To view runnable code examples that demonstrate how to update documents by +using the {+driver-short+}, see :ref:`scala-write`. + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about any of the methods or types discussed in this +guide, see the following API documentation: + +- `updateOne() <{+api+}/org/mongodb/scala/MongoCollection.html#updateOne(clientSession:org.mongodb.scala.ClientSession,filter:org.mongodb.scala.bson.conversions.Bson,update:Seq[org.mongodb.scala.bson.conversions.Bson],options:org.mongodb.scala.model.UpdateOptions):org.mongodb.scala.SingleObservable[org.mongodb.scala.result.UpdateResult]>`__ +- `updateMany() <{+api+}/org/mongodb/scala/MongoCollection.html#updateMany(clientSession:org.mongodb.scala.ClientSession,filter:org.mongodb.scala.bson.conversions.Bson,update:Seq[org.mongodb.scala.bson.conversions.Bson],options:org.mongodb.scala.model.UpdateOptions):org.mongodb.scala.SingleObservable[org.mongodb.scala.result.UpdateResult]>`__ +- `UpdateOptions <{+core-api+}/com/mongodb/client/model/UpdateOptions.html>`__ +- `UpdateResult <{+core-api+}/com/mongodb/client/result/UpdateResult.html>`__ From 6f8885c0d981c2aad2919949def46ded5cdcbf32 Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Tue, 22 Oct 2024 10:00:07 -0400 Subject: [PATCH 13/44] DOCSP-42332: Count documents (#66) * DOCSP-42332: Count documents * edits * build errors * RR feedback * scroll * build error --- snooty.toml | 1 - source/get-started/connect-to-mongodb.txt | 2 +- source/includes/read/count.scala | 56 ++++++ source/read.txt | 3 + source/read/count.txt | 222 ++++++++++++++++++++++ 5 files changed, 282 insertions(+), 2 deletions(-) create mode 100644 source/includes/read/count.scala create mode 100644 source/read/count.txt diff --git a/snooty.toml b/snooty.toml index 30d84fb..02b1f52 100644 --- a/snooty.toml +++ b/snooty.toml @@ -31,4 +31,3 @@ rs-docs = "https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/org/r core-api = "https://mongodb.github.io/mongo-java-driver/{+version+}/apidocs/mongodb-driver-core" mongocrypt-version = "{+full-version+}" mdb-server = "MongoDB Server" -language = "Scala" diff --git a/source/get-started/connect-to-mongodb.txt b/source/get-started/connect-to-mongodb.txt index 4a8d0a5..ece827d 100644 --- a/source/get-started/connect-to-mongodb.txt +++ b/source/get-started/connect-to-mongodb.txt @@ -67,7 +67,7 @@ the Atlas sample datasets. .. step:: Assign the connection string Replace the ```` placeholder with the - connection string that you copied from the :ref:`scala-quick-start-connection-string` + connection string that you copied from the :ref:`scala-connection-string` step of this guide. .. step:: Run your Scala application diff --git a/source/includes/read/count.scala b/source/includes/read/count.scala new file mode 100644 index 0000000..2973c0d --- /dev/null +++ b/source/includes/read/count.scala @@ -0,0 +1,56 @@ +import org.mongodb.scala._ +import org.mongodb.scala.model.Filters._ +import org.mongodb.scala.model.CountOptions +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.util.{Failure, Success} +import scala.concurrent.ExecutionContext.Implicits.global + +object Count { + def main(args: Array[String]): Unit = { + val uri: String = "" + val client: MongoClient = MongoClient(uri) + + // start-db-coll + val database: MongoDatabase = mongoClient.getDatabase("sample_training") + val collection: MongoCollection[Document] = database.getCollection("companies") + // end-db-coll + + // Counts all documents in the collection + // start-count-all + collection.countDocuments() + .subscribe((count: Long) => println(s"Number of documents: $count"), + (e: Throwable) => println(s"There was an error: $e")) + // end-count-all + + // Counts documents that have a "founded_year" value of 2010 + // start-count-accurate + collection.countDocuments(equal("founded_year", 2010)) + .subscribe((count: Long) => println(s"Companies founded in 2010: $count"), + (e: Throwable) => println(s"There was an error: $e")) + // end-count-accurate + + // Counts a maximum of 100 documents that have a "number_of_employees" value of 50 + // start-modify-accurate + val countOptions = CountOptions().limit(100) + collection.countDocuments(equal("number_of_employees", 50), countOptions) + .subscribe((count: Long) => println(s"Companies with 50 employees: $count"), + (e: Throwable) => println(s"There was an error: $e")) + // end-modify-accurate + + // Estimates the number of documents in the collection + // start-count-estimate + collection.estimatedDocumentCount() + .subscribe((count: Long) => println(s"Estimated number of documents: $count"), + (e: Throwable) => println(s"There was an error: $e")) + // end-count-estimate + + // Estimates the number of documents in the collection and sets a time limit on the operation + // start-modify-estimate + val estimatedOptions = EstimatedDocumentCountOptions().maxTime(3, SECONDS) + collection.estimatedDocumentCount(estimatedOptions) + .subscribe((count: Long) => println(s"Estimated number of documents: $count"), + (e: Throwable) => println(s"There was an error: $e")) + // end-modify-estimate + } +} \ No newline at end of file diff --git a/source/read.txt b/source/read.txt index f51c1fe..4af4198 100644 --- a/source/read.txt +++ b/source/read.txt @@ -25,5 +25,8 @@ Read Data from MongoDB /read/retrieve /read/specify-a-query /read/specify-documents-to-return + /read/count + /read/project + diff --git a/source/read/count.txt b/source/read/count.txt new file mode 100644 index 0000000..24055f7 --- /dev/null +++ b/source/read/count.txt @@ -0,0 +1,222 @@ +.. _scala-count: + +=============== +Count Documents +=============== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: number, amount, estimation, code example + +Overview +-------- + +In this guide, you can learn how to use the {+driver-short+} to retrieve an accurate +and estimated count of the number of documents in a collection. The following methods +count documents in a collection: + +- ``countDocuments()``: Returns the exact number of documents that match a + query filter or that exist in a collection + +- ``estimatedDocumentCount()``: Returns the estimated number of documents + in a collection + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``companies`` collection in the ``sample_training`` +database from the :atlas:`Atlas sample datasets `. To access this collection +from your Scala application, create a ``MongoClient`` that connects to an Atlas cluster +and assign the following value to your ``database`` and ``collection`` variables: + +.. literalinclude:: /includes/read/count.scala + :language: scala + :dedent: + :start-after: start-db-coll + :end-before: end-db-coll + +To learn how to create a free MongoDB Atlas cluster and load the sample datasets, see the +:atlas:`Get Started with Atlas ` guide. + +.. _scala-accurate-count: + +Retrieve an Accurate Count +-------------------------- + +Use the ``countDocuments()`` method to count the number of documents in a +collection. To count the number of documents that match specific search criteria, +pass a query filter to the ``countDocuments()`` method. + +To learn more about specifying a query, see the :ref:`scala-specify-query` guide. + +.. _scala-count-all: + +Count All Documents +~~~~~~~~~~~~~~~~~~~ + +To return a count of all documents in the collection, call the ``countDocuments()`` +method without passing any parameters, as shown in the following example: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/count.scala + :start-after: start-count-all + :end-before: end-count-all + :language: scala + :dedent: + + .. output:: + :visible: false + + Number of documents: 9500 + +.. _scala-count-specific: + +Count Specific Documents +~~~~~~~~~~~~~~~~~~~~~~~~ + +To return a count of documents that match specific search criteria, pass a query +filter to the ``countDocuments()`` method. + +The following example counts the number of documents in which the value of the +``founded_year`` field is ``2010``: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/count.scala + :start-after: start-count-accurate + :end-before: end-count-accurate + :language: scala + :dedent: + + .. output:: + :visible: false + + Number of companies founded in 2010: 33 + +Customize Count Behavior +~~~~~~~~~~~~~~~~~~~~~~~~ + +You can modify the behavior of the ``countDocuments()`` method by +passing a ``CountOptions`` instance as a parameter. The following table +describes some member functions of the ``CountOptions`` class, which you +can use to set options for the count operation: + +.. list-table:: + :widths: 30 70 + :header-rows: 1 + + * - Method + - Description + + * - ``collation()`` + - | Sets the collation to use for the operation. + | **Parameter Type**: ``Collation`` + + * - ``hint()`` + - | Sets the index to use for the operation. + | **ParameterType**: ``Bson`` + + * - ``limit()`` + - | Sets the maximum number of documents to count. This value must be a positive integer. + | **Parameter Type**: ``int`` + + * - ``maxTime()`` + - | Sets the maximum amount of time that the operation can run. + | **Parameter Types**: ``long`` and ``TimeUnit`` + + * - ``skip()`` + - | Sets the number of documents to skip before counting documents. + | **Parameter Type**: ``int`` + +The following example uses the ``countDocuments()`` method to count the number of +documents in which the ``number_of_employees`` field has the value ``50`` and instructs the +operation to count a maximum of ``100`` results: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/count.scala + :start-after: start-modify-accurate + :end-before: end-modify-accurate + :language: scala + :dedent: + + .. output:: + :visible: false + + Number of companies with 50 employees: 100 + +.. _scala-estimated-count: + +Retrieve an Estimated Count +--------------------------- + +You can retrieve an estimate of the number of documents in a collection by calling +the ``estimatedDocumentCount()`` method. The method estimates the amount of documents +based on collection metadata, which might be faster than performing an accurate count. + +The following example estimates the number of documents in a collection: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/count.scala + :start-after: start-count-estimate + :end-before: end-count-estimate + :language: scala + :dedent: + + .. output:: + :visible: false + + Estimated number of documents: 9500 + +Customize Estimated Count Behavior +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can modify the behavior of the ``estimatedDocumentCount()`` method by +passing an ``EstimatedDocumentCountOptions`` instance as a parameter. The +``EstimatedDocumentCountOptions`` class includes the ``maxTime()`` member function, +which you can use to configure your ``EstimatedDocumentCountOptions`` object and +set the maximum time that the count operation can run. + +The following example uses the ``estimatedDocumentCount()`` method to return an +estimate of the number of documents in the collection and sets a timeout of +``3`` seconds on the operation: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/count.scala + :start-after: start-modify-estimate + :end-before: end-modify-estimate + :language: scala + :dedent: + + .. output:: + :visible: false + + Estimated number of documents: 9500 + +API Documentation +----------------- + +To learn more about any of the methods or types discussed in this +guide, see the following API documentation: + +- `countDocuments() <{+api+}/org/mongodb/scala/MongoCollection.html#countDocuments(filter:org.mongodb.scala.bson.conversions.Bson):org.mongodb.scala.SingleObservable[Long]>`__ +- `CountOptions <{+api+}/org/mongodb/scala/model/index.html#CountOptions=com.mongodb.client.model.CountOptions>`__ +- `estimatedDocumentCount() <{+api+}/org/mongodb/scala/MongoCollection.html#estimatedDocumentCount():org.mongodb.scala.SingleObservable[Long]>`__ +- `EstimatedDocumentCountOptions <{+api+}/org/mongodb/scala/model/index.html#EstimatedDocumentCountOptions=com.mongodb.client.model.EstimatedDocumentCountOptions>`__ \ No newline at end of file From 0ac13581b18004c884c526d38bfe100638578550 Mon Sep 17 00:00:00 2001 From: lindseymoore <71525840+lindseymoore@users.noreply.github.com> Date: Fri, 1 Nov 2024 14:50:21 -0400 Subject: [PATCH 14/44] DOCSP-42312 Create a MongoDB Client (#59) * DOCSP-42312 Create a MongoDB Client * on TOC * retry TOC * build * api doc * fix links * code fix * review comments * test * test * fix snooty * merge error --- snooty.toml | 6 +- source/connect.txt | 32 +++++ source/connect/connection-options.txt | 23 ++++ source/connect/mongoclient.txt | 132 ++++++++++++++++++++ source/connect/stable-api.txt | 24 ++++ source/includes/connect/ClientBasic.scala | 28 +++++ source/includes/connect/ClientExample.scala | 37 ++++++ source/index.txt | 1 + 8 files changed, 281 insertions(+), 2 deletions(-) create mode 100644 source/connect.txt create mode 100644 source/connect/connection-options.txt create mode 100644 source/connect/mongoclient.txt create mode 100644 source/connect/stable-api.txt create mode 100644 source/includes/connect/ClientBasic.scala create mode 100644 source/includes/connect/ClientExample.scala diff --git a/snooty.toml b/snooty.toml index 02b1f52..8ddd6c0 100644 --- a/snooty.toml +++ b/snooty.toml @@ -10,13 +10,15 @@ intersphinx = [ sharedinclude_root = "https://raw.githubusercontent.com/10gen/docs-shared/main/" toc_landing_pages = [ + "/get-started", + "/connect", "/bson", "/tutorials/connect", "/tutorials/write-ops", "/builders", "/get-started", "/write" - ] +] [constants] driver-short = "Scala driver" @@ -25,9 +27,9 @@ version = "5.2" full-version = "{+version+}.0" language = "Scala" language-version = "2.13.15" +mdb-server = "MongoDB Server" api = "https://mongodb.github.io/mongo-java-driver/{+version+}/apidocs/mongo-scala-driver" driver-source-gh = "https://github.com/mongodb/mongo-java-driver" rs-docs = "https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/org/reactivestreams" core-api = "https://mongodb.github.io/mongo-java-driver/{+version+}/apidocs/mongodb-driver-core" mongocrypt-version = "{+full-version+}" -mdb-server = "MongoDB Server" diff --git a/source/connect.txt b/source/connect.txt new file mode 100644 index 0000000..ac6166e --- /dev/null +++ b/source/connect.txt @@ -0,0 +1,32 @@ +.. _scala-connect: + +================== +Connect to MongoDB +================== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :description: Learn how to use the Scala driver to connect to MongoDB. + :keywords: client, ssl, tls, localhost + +.. toctree:: + + /connect/mongoclient + /connect/stable-api +.. /connect/connection-targets +.. /connect/connection-options +.. /connect/tls + +Overview +-------- + +.. TODO \ No newline at end of file diff --git a/source/connect/connection-options.txt b/source/connect/connection-options.txt new file mode 100644 index 0000000..2541353 --- /dev/null +++ b/source/connect/connection-options.txt @@ -0,0 +1,23 @@ +.. _scala-connection-options: + +========================== +Specify Connection Options +========================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: connection string, URI, server, Atlas, settings, client + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +.. TODO \ No newline at end of file diff --git a/source/connect/mongoclient.txt b/source/connect/mongoclient.txt new file mode 100644 index 0000000..293f592 --- /dev/null +++ b/source/connect/mongoclient.txt @@ -0,0 +1,132 @@ +.. _scala-mongoclient: + +==================== +Create a MongoClient +==================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: connection string, URI, server, Atlas, settings, client + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +To connect to a MongoDB deployment, you need two things: + +- A **connection URI**, also known as a *connection string*, which tells the {+driver-short+} + which MongoDB deployment to connect to. +- A **MongoClient** object, which creates the connection to and allows you to perform operations + on the MongoDB deployment. + +You can also use ``MongoClientSettings`` to customize the way the {+driver-short+} behaves +while connected to MongoDB. + +This guide shows you how to create a connection string and use a ``MongoClient`` object +to connect to MongoDB. + +.. _scala-connection-uri: + +Connection URI +-------------- + +A standard connection string includes the following components: + +.. TODO, add this as last sentence for ``username:password`` description once a scala auth page is made: +.. For more information about the ``authSource`` connection option, see :ref:`scala-sync-auth`. + +.. list-table:: + :widths: 20 80 + :header-rows: 1 + + * - Component + - Description + + * - ``mongodb://`` + + - Required. A prefix that identifies this as a string in the + standard connection format. + + * - ``username:password`` + + - Optional. Authentication credentials. If you include these, the client + authenticates the user against the database specified in ``authSource``. + + * - ``host[:port]`` + + - Required. The host and optional port number where MongoDB is running. If you don't + include the port number, the driver uses the default port, ``27017``. + + * - ``/defaultauthdb`` + + - Optional. The authentication database to use if the + connection string includes ``username:password@`` + authentication credentials but not the ``authSource`` option. If you don't include + this component, the client authenticates the user against the ``admin`` database. + + * - ``?`` + + - Optional. A query string that specifies connection-specific + options as ``=`` pairs. See + :ref:`scala-connection-options` for a full description of + these options. + +For more information about creating a connection string, see +:manual:`Connection Strings ` in the +{+mdb-server+} manual. + +Atlas Connection Example +------------------------ + +To connect to a MongoDB deployment on Atlas, you must first create a client. + +You can pass a connection URI as a string to the ``MongoClient.create()`` method +to connect to a MongoDB instance: + +.. literalinclude:: /includes/connect/ClientBasic.scala + :start-after: start-create-a-client + :end-before: end-create-a-client + :language: scala + :copyable: + :dedent: + +You can also create a client with your desired configurations by passing a +``MongoClientSettings`` object to a ``MongoClient`` object. + +To instantiate a ``MongoClientSettings`` object, call the ``builder()`` method, +then chain the ``applyConnectionString()`` method and pass in your connection +string. You can also use the ``builder()`` method to specify any other client +options. Once you have your desired configuration, call the ``build()`` method. + +You can set the Stable API version client option to avoid breaking changes when +you upgrade to a new server version. To learn more about the Stable API feature, +see the :ref:`Stable API page `. + +The following code shows how you can specify the connection string and the +Stable API client option when connecting to a MongoDB deployment on Atlas +and verify that the connection is successful: + +.. literalinclude:: /includes/connect/ClientExample.scala + :start-after: start-client-example + :end-before: end-client-example + :language: scala + :copyable: + :dedent: + +API Documentation +----------------- + +For more information about creating a ``MongoClient`` object with the +{+driver-short+}, see the following API documentation: + +- `MongoClient <{+api+}/org/mongodb/scala/MongoClient.html>`__ + +- `MongoClientSettings <{+api+}/org/mongodb/scala/MongoClientSettings$.html>`__ diff --git a/source/connect/stable-api.txt b/source/connect/stable-api.txt new file mode 100644 index 0000000..f0533be --- /dev/null +++ b/source/connect/stable-api.txt @@ -0,0 +1,24 @@ +.. _scala-stable-api: + +=========== +Stable API +=========== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: compatible, backwards, upgrade + +.. note:: + + The Stable API feature requires {+mdb-server+} 5.0 or later. + +.. TODO \ No newline at end of file diff --git a/source/includes/connect/ClientBasic.scala b/source/includes/connect/ClientBasic.scala new file mode 100644 index 0000000..8def940 --- /dev/null +++ b/source/includes/connect/ClientBasic.scala @@ -0,0 +1,28 @@ +import com.mongodb.{ServerApi, ServerApiVersion} +import org.mongodb.scala.{ConnectionString, MongoClient, MongoClientSettings} +import org.mongodb.scala.bson.Document + +import scala.concurrent.Await +import scala.concurrent.duration.DurationInt +import scala.util.Using + +object ClientBasic { + + def main(args: Array[String]): Unit = { + + // start-create-a-client + // Replace the placeholder with your Atlas connection string + val connectionString = "" + + // Create a new client and connect to the server + val mongoClient = MongoClient(connectionString) + val database = mongoClient.getDatabase("sample_mflix") + // end-create-a-client + + // Send a ping to confirm a successful connection + val ping = database.runCommand(Document("ping" -> 1)).head() + + Await.result(ping, 10.seconds) + System.out.println("Pinged your deployment. You successfully connected to MongoDB!") + } +} diff --git a/source/includes/connect/ClientExample.scala b/source/includes/connect/ClientExample.scala new file mode 100644 index 0000000..3e5c6c1 --- /dev/null +++ b/source/includes/connect/ClientExample.scala @@ -0,0 +1,37 @@ +// start-client-example +import com.mongodb.{ServerApi, ServerApiVersion} +import org.mongodb.scala.{ConnectionString, MongoClient, MongoClientSettings} +import org.mongodb.scala.bson.Document + +import scala.concurrent.Await +import scala.concurrent.duration.DurationInt +import scala.util.Using + +object MongoClientConnectionExample { + + def main(args: Array[String]): Unit = { + + // Replace the placeholder with your Atlas connection string + val connectionString = "" + + // Construct a ServerApi instance using the ServerApi.builder() method + val serverApi = ServerApi.builder.version(ServerApiVersion.V1).build() + + val settings = MongoClientSettings + .builder() + .applyConnectionString(ConnectionString(connectionString)) + .serverApi(serverApi) + .build() + + // Create a new client and connect to the server + Using(MongoClient(settings)) { mongoClient => + // Send a ping to confirm a successful connection + val database = mongoClient.getDatabase("admin") + val ping = database.runCommand(Document("ping" -> 1)).head() + + Await.result(ping, 10.seconds) + System.out.println("Pinged your deployment. You successfully connected to MongoDB!") + } + } +} +// end-client-example diff --git a/source/index.txt b/source/index.txt index f2bef56..cfd6ce1 100644 --- a/source/index.txt +++ b/source/index.txt @@ -13,6 +13,7 @@ :maxdepth: 1 /get-started + /connect /read /write /whats-new From 3237882ab0501b83c21a90606c99c108d856738a Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Mon, 4 Nov 2024 15:09:15 -0500 Subject: [PATCH 15/44] DOCSP-42324: Delete (#65) --- snooty.toml | 1 + source/includes/write/delete.scala | 57 ++++++++ source/write.txt | 10 +- source/write/delete.txt | 212 +++++++++++++++++++++++++++++ 4 files changed, 274 insertions(+), 6 deletions(-) create mode 100644 source/includes/write/delete.scala create mode 100644 source/write/delete.txt diff --git a/snooty.toml b/snooty.toml index 8ddd6c0..15dbb19 100644 --- a/snooty.toml +++ b/snooty.toml @@ -33,3 +33,4 @@ driver-source-gh = "https://github.com/mongodb/mongo-java-driver" rs-docs = "https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/org/reactivestreams" core-api = "https://mongodb.github.io/mongo-java-driver/{+version+}/apidocs/mongodb-driver-core" mongocrypt-version = "{+full-version+}" +mongodb-server = "MongoDB Server" \ No newline at end of file diff --git a/source/includes/write/delete.scala b/source/includes/write/delete.scala new file mode 100644 index 0000000..a193906 --- /dev/null +++ b/source/includes/write/delete.scala @@ -0,0 +1,57 @@ +import org.mongodb.scala._ +import org.mongodb.scala.bson.Document +import org.mongodb.scala.model.Filters.{and, equal, regex} +import org.mongodb.scala.model.{DeleteOptions} +import org.mongodb.scala.result.{DeleteResult} + +object Delete { + + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + + // start-db-coll + val database: MongoDatabase = mongoClient.getDatabase("sample_restaurants") + val collection: MongoCollection[Document] = database.getCollection("restaurants") + // end-db-coll + + // start-delete-one + val filter = equal("name", "Happy Garden") + val observable: Observable[DeleteResult] = collection.deleteOne(filter) + + observable.subscribe(new Observer[DeleteResult] { + override def onNext(result: DeleteResult): Unit = println(s"Deleted document count: ${result.getDeletedCount}") + override def onError(e: Throwable): Unit = println(s"Error: $e") + override def onComplete(): Unit = println("Completed") +// }) + // end-delete-one + + // start-delete-many + val filter = and( + equal("borough", "Brooklyn"), + equal("name", "Starbucks") + ) + val observable: Observable[DeleteResult] = collection.deleteMany(filter) + + observable.subscribe(new Observer[DeleteResult] { + override def onNext(result: DeleteResult): Unit = println(s"Deleted document count: ${result.getDeletedCount}") + override def onError(e: Throwable): Unit = println(s"Error: $e") + override def onComplete(): Unit = println("Completed") + }) + // end-delete-many + + // start-delete-options + val filter = regex("name", "Red") + val opts = DeleteOptions().comment("sample comment") + val observable = collection.deleteMany(filter, opts) + + observable.subscribe(new Observer[DeleteResult] { + override def onNext(result: DeleteResult): Unit = println(s"Deleted document count: ${result.getDeletedCount}") + override def onError(e: Throwable): Unit = println(s"Error: $e") + override def onComplete(): Unit = println("Completed") + }) + // end-delete-options + + Thread.sleep(1000) + mongoClient.close() + } +} diff --git a/source/write.txt b/source/write.txt index 0ba3c07..82bbcb2 100644 --- a/source/write.txt +++ b/source/write.txt @@ -145,9 +145,8 @@ The following code shows how to delete a single document in a collection: :copyable: :dedent: -.. TODO: method name -.. To learn more about the ``deleteOne()`` method, see the -.. :ref:`Delete Documents ` guide. +To learn more about the ``deleteOne()`` method, see the +:ref:`Delete Documents ` guide. Delete Multiple --------------- @@ -161,9 +160,8 @@ The following code shows how to delete multiple documents in a collection: :copyable: :dedent: -.. TODO -.. To learn more about the ``deleteMany()`` method, see the -.. :ref:`Delete Documents ` guide. +To learn more about the ``deleteMany()`` method, see the +:ref:`Delete Documents ` guide. Bulk Write ---------- diff --git a/source/write/delete.txt b/source/write/delete.txt new file mode 100644 index 0000000..20a920b --- /dev/null +++ b/source/write/delete.txt @@ -0,0 +1,212 @@ +.. _scala-write-delete: + +================ +Delete Documents +================ + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: remove, drop, code example + +Overview +-------- + +In this guide, you can learn how to use the {+driver-short+} to remove +documents from a MongoDB collection by performing **delete operations**. + +A delete operation removes one or more documents from a MongoDB collection. +You can perform a delete operation by using the ``deleteOne()`` or +``deleteMany()`` methods. + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``restaurants`` collection in the ``sample_restaurants`` +database from the :atlas:`Atlas sample datasets `. To access this collection +from your Scala application, create a ``MongoClient`` that connects to an Atlas cluster +and assign the following values to your ``database`` and ``collection`` variables: + +.. literalinclude:: /includes/write/delete.scala + :language: scala + :dedent: + :start-after: start-db-coll + :end-before: end-db-coll + +To learn how to create a free MongoDB Atlas cluster and load the sample datasets, see the +:atlas:`Get Started with Atlas ` guide. + +Delete Operations +----------------- + +You can perform delete operations in MongoDB by using the following methods: + +- ``deleteOne()``, which deletes *the first document* that matches the search criteria +- ``deleteMany()``, which deletes *all documents* that match the search criteria + +Each delete method requires a **query filter** document, which specifies the +search criteria that determine which documents to select for removal. +To learn more about query filters, see the :ref:`scala-specify-query` guide. + +Delete One Document +~~~~~~~~~~~~~~~~~~~ + +The following example uses the ``deleteOne()`` method to remove a +document in which the value of the ``name`` field is ``"Happy Garden"``: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/write/delete.scala + :start-after: start-delete-many + :end-before: end-delete-many + :language: scala + :dedent: + + .. output:: + :language: console + :visible: false + + Deleted document count: 1 + Completed + +Delete Multiple Documents +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following example uses the ``deleteMany()`` method to remove all documents +in which the value of the ``borough`` field is ``"Brooklyn"`` and the +value of the ``name`` field is ``"Starbucks"``: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/write/delete.scala + :start-after: start-delete-many + :end-before: end-delete-many + :language: scala + :dedent: + + .. output:: + :language: console + :visible: false + + Deleted document count: 3 + Completed + +Customize the Delete Operation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``deleteOne()`` and ``deleteMany()`` methods optionally accept a +``DeleteOptions`` parameter, which represents options you can use to +configure the delete operation. If you don't specify any +options, the driver performs the delete operation with default settings. + +The following table describes the setter methods that you can use to +configure a ``DeleteOptions`` instance: + +.. list-table:: + :widths: 30 70 + :header-rows: 1 + + * - Method + - Description + + * - ``collation()`` + - | Specifies the kind of language collation to use when sorting + results. For more information, see :manual:`Collation ` + in the {+mdb-server+} manual. + + * - ``hint()`` + - | Specifies the index to use when matching documents. + For more information, see the :manual:`hint ` + option in the ``delete`` reference page of the {+mdb-server+} manual. + + * - ``hintString()`` + - | Specifies the index as a string to use when matching documents. + For more information, see the :manual:`hint ` + option in the `` delete`` reference page of the {+mdb-server+} manual. + + * - ``let()`` + - | Provides a map of parameter names and values to set top-level + variables for the operation. Values must be constant or closed + expressions that don't reference document fields. For more information, + see the :manual:`let + ` option in the ``delete`` + reference page of the {+mdb-server+} manual. + + * - ``comment()`` + - | Sets a comment to attach to the operation. For more + information, see the :manual:`Command + Fields ` section in the ``delete`` + reference page of the {+mdb-server+} manual. + +Modify Delete Example +````````````````````` + +The following code creates options and uses the ``comment()`` method to +add a comment to the delete operation. Then, the example uses the +``deleteMany()`` method to delete all documents in the ``restaurants`` +collection in which the value of the ``name`` field includes the string +``"Red"``. + +.. io-code-block:: + :copyable: true + + .. input:: /includes/write/delete.scala + :start-after: start-delete-options + :end-before: end-delete-options + :language: scala + :dedent: + + .. output:: + :language: console + :visible: false + + Deleted document count: 124 + Completed + +.. tip:: + + If you use the the ``deleteOne()`` method in the preceding example + instead of the ``deleteMany()`` method, the driver deletes only the + first document that matches the query filter. + +Return Value +~~~~~~~~~~~~ + +The ``deleteOne()`` and ``deleteMany()`` methods each return a +``DeleteResult`` instance. You can access the following information from +a ``DeleteResult`` instance: + +- ``deletedCount``, which indicates the number of documents deleted +- ``wasAcknowledged()``, which returns ``true`` if the server + acknowledges the result + +If the query filter does not match any documents, the driver doesn't delete any +documents, and the value of ``deletedCount`` is ``0``. + +.. note:: + + If the ``wasAcknowledged()`` method returns ``false``, trying to + access the ``deletedCount`` property results in an + ``InvalidOperation`` exception. The driver cannot + determine these values if the server does not acknowledge the write + operation. + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about any of the methods or types discussed in this +guide, see the following API documentation: + +- `deleteOne() <{+api+}/org/mongodb/scala/MongoCollection.html#deleteOne(clientSession:org.mongodb.scala.ClientSession,filter:org.mongodb.scala.bson.conversions.Bson,options:org.mongodb.scala.model.DeleteOptions):org.mongodb.scala.SingleObservable[org.mongodb.scala.result.DeleteResult]>`__ +- `deleteMany() <{+api+}/org/mongodb/scala/MongoCollection.html#deleteMany(clientSession:org.mongodb.scala.ClientSession,filter:org.mongodb.scala.bson.conversions.Bson,options:org.mongodb.scala.model.DeleteOptions):org.mongodb.scala.SingleObservable[org.mongodb.scala.result.DeleteResult]>`__ +- `DeleteResult <{+core-api+}/com/mongodb/client/result/DeleteResult.html>`__ From f1594a3dd6a9e8aaa7e2fe1ee60d5dee2ee12b7a Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Mon, 4 Nov 2024 15:12:28 -0500 Subject: [PATCH 16/44] DOCSP-42325: Bulk Write (#68) --- source/includes/write/bulk-write.scala | 91 +++++++ source/write.txt | 5 +- source/write/bulk-write.txt | 348 +++++++++++++++++++++++++ source/write/insert.txt | 2 +- 4 files changed, 442 insertions(+), 4 deletions(-) create mode 100644 source/includes/write/bulk-write.scala create mode 100644 source/write/bulk-write.txt diff --git a/source/includes/write/bulk-write.scala b/source/includes/write/bulk-write.scala new file mode 100644 index 0000000..663ead7 --- /dev/null +++ b/source/includes/write/bulk-write.scala @@ -0,0 +1,91 @@ +import org.mongodb.scala._ +import org.mongodb.scala.bson.Document +import org.mongodb.scala.model.Filters.equal +import org.mongodb.scala.model.Updates.set +import org.mongodb.scala.model._ + +object BulkWrite { + + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + + // start-db-coll + val database: MongoDatabase = mongoClient.getDatabase("sample_restaurants") + val collection: MongoCollection[Document] = database.getCollection("restaurants") + // end-db-coll + + { + // start-bulk-insert-one + val insertOneModel = InsertOneModel( + Document("name" -> "Blue Moon Grill", + "borough" -> "Brooklyn", + "cuisine" -> "American") + ) + // end-bulk-insert-one + + // start-bulk-update-one + val updateOneFilter = equal("name", "White Horse Tavern") + val updateOneDoc = set("borough", "Queens") + val updateOneModel = UpdateOneModel(updateOneFilter, updateOneDoc) + // end-bulk-update-one + + // start-bulk-update-many + val updateManyFilter = equal("name", "Wendy's") + val updateManyDoc = set("cuisine", "Fast food") + val updateManyModel = UpdateOneModel(updateManyFilter, updateManyDoc) + // end-bulk-update-many + + // start-bulk-replace-one + val replaceFilter = equal("name", "Cooper Town Diner") + val replaceDoc = Document("name" -> "Smith Town Diner", + "borough" -> "Brooklyn", + "cuisine" -> "American") + val replaceOneModel = ReplaceOneModel(replaceFilter, replaceDoc) + // end-bulk-replace-one + + // start-bulk-delete-one + val deleteOneModel = DeleteOneModel(equal("name", "Morris Park Bake Shop")) + // end-bulk-delete-one + + // start-bulk-delete-many + val deleteManyModel = DeleteManyModel(equal("cuisine", "Experimental")) + // end-bulk-delete-many + } + { + // start-bulk-write-mixed + val insertOneModel = InsertOneModel( + Document("name" -> "Red's Pizza", + "borough" -> "Brooklyn", + "cuisine" -> "Pizzeria") + ) + val updateOneModel = UpdateOneModel(equal("name", "Moonlit Tavern"), set("borough", "Queens")) + val deleteManyModel = DeleteManyModel(equal("name", "Crepe")) + + val writes = Seq(insertOneModel, updateOneModel, deleteManyModel) + val observable = collection.bulkWrite(writes) + + observable.subscribe( + (result: BulkWriteResult) => println(s"Success: $result"), + (error: Throwable) => println(s"Error: ${error.getMessage}"), + () => println("Completed") + ) + // end-bulk-write-mixed + } + + { + val insertOneModel = InsertOneModel(Document("name" -> "Red's Pizza", "borough" -> "Brooklyn", "cuisine" -> "Pizzeria")) + val updateOneModel = UpdateOneModel(equal("name", "Moonlit Tavern"), set("borough", "Queens")) + val deleteManyModel = DeleteManyModel(equal("name", "Crepe")) + + val writes = Seq(insertOneModel, updateOneModel, deleteManyModel) + + // start-bulk-write-unordered + val options = BulkWriteOptions().ordered(false) + val observable = collection.bulkWrite(writes, options) + // end-bulk-write-unordered + } + + Thread.sleep(1000) + mongoClient.close() + } +} diff --git a/source/write.txt b/source/write.txt index 82bbcb2..c598b77 100644 --- a/source/write.txt +++ b/source/write.txt @@ -176,6 +176,5 @@ bulk operation: :copyable: :dedent: -.. TODO -.. To learn more about the ``bulkWrite()`` -.. method, see the :ref:`Bulk Write Operations ` guide. +To learn more about the ``bulkWrite()`` +method, see the :ref:`Bulk Write Operations ` guide. diff --git a/source/write/bulk-write.txt b/source/write/bulk-write.txt new file mode 100644 index 0000000..2f6d9a2 --- /dev/null +++ b/source/write/bulk-write.txt @@ -0,0 +1,348 @@ +.. _scala-bulk-write: + +===================== +Bulk Write Operations +===================== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: insert, update, replace, code example, multiple changes + +Overview +-------- + +This guide shows you how to use the {+driver-short+} to perform a bulk +write operation that makes multiple changes to your data in a single +database call. + +Consider a situation that requires you to insert documents, update +documents, and delete documents for the same task. If you use +the individual write methods to perform each type of operation, each write +accesses the database separately. You can use a bulk write operation to +optimize the number of calls your application makes to the server. + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``restaurants`` collection in the ``sample_restaurants`` +database from the :atlas:`Atlas sample datasets `. To access this collection +from your Scala application, create a ``MongoClient`` that connects to an Atlas cluster +and assign the following values to your ``database`` and ``collection`` variables: + +.. literalinclude:: /includes/write/bulk-write.scala + :language: scala + :dedent: + :start-after: start-db-coll + :end-before: end-db-coll + +To learn how to create a free MongoDB Atlas cluster and load the sample datasets, see the +:atlas:`Get Started with Atlas ` guide. + +Define the Write Operations +--------------------------- + +For each write operation you want to perform, create a corresponding +instance of one of the following operation classes that inherit from the +generic ``WriteModel`` class: + +- ``InsertOneModel`` +- ``UpdateOneModel`` +- ``UpdateManyModel`` +- ``ReplaceOneModel`` +- ``DeleteOneModel`` +- ``DeleteManyModel`` + +Then, pass a list of these instances to the ``bulkWrite()`` method. + +The following sections show how to create and use instances of the +preceding classes. The :ref:`scala-bulkwrite-method` section +demonstrates how to pass a list of models to the ``bulkWrite()`` method +to perform the bulk operation. + +Insert Operations +~~~~~~~~~~~~~~~~~ + +To perform an insert operation, create an ``InsertOneModel`` instance and specify +the document you want to insert. + +The following example creates an instance of ``InsertOneModel``: + +.. literalinclude:: /includes/write/bulk-write.scala + :start-after: start-bulk-insert-one + :end-before: end-bulk-insert-one + :language: scala + :copyable: + :dedent: + +To insert multiple documents, create an instance of ``InsertOneModel`` +for each document. + +.. important:: + + When performing a bulk operation, the ``InsertOneModel`` cannot + insert a document with an ``_id`` that already exists in the + collection. In this situation, the driver throws a + ``MongoBulkWriteException``. + +Update Operations +~~~~~~~~~~~~~~~~~ + +To update a document, create an instance of ``UpdateOneModel`` and pass +the following arguments: + +- **Query filter** that specifies the criteria used to match documents in your collection. +- Update operation you want to perform. For more information about update + operations, see the :manual:`Field Update Operators + ` guide in the {+mdb-server+} manual. + +An ``UpdateOneModel`` instance specifies an update for *the first* +document that matches your query filter. + +The following example creates an instance of ``UpdateOneModel``: + +.. literalinclude:: /includes/write/bulk-write.scala + :start-after: start-bulk-update-one + :end-before: end-bulk-update-one + :language: scala + :copyable: + :dedent: + +To update multiple documents, create an instance of ``UpdateManyModel`` and pass +the same arguments as for ``UpdateOneModel``. The ``UpdateManyModel`` +class specifies updates for *all* documents that match your query +filter. + +The following example creates an instance of ``UpdateManyModel``: + +.. literalinclude:: /includes/write/bulk-write.scala + :start-after: start-bulk-update-many + :end-before: end-bulk-update-many + :language: scala + :copyable: + :dedent: + +Replace Operations +~~~~~~~~~~~~~~~~~~ + +A replace operation removes all fields and values of a specified document and +replaces them with new fields and values that you specify. To perform a +replace operation, create an instance of ``ReplaceOneModel`` and pass the following +arguments: + +- Query filter that specifies the criteria used to match documents in your collection +- Replacement document that specifies the new fields and values to insert + +The following example creates an instance of ``ReplaceOneModel``: + +.. literalinclude:: /includes/write/bulk-write.scala + :start-after: start-bulk-replace-one + :end-before: end-bulk-replace-one + :language: scala + :copyable: + :dedent: + +To replace multiple documents, you must create an instance of +``ReplaceOneModel`` for each document. + +Delete Operations +~~~~~~~~~~~~~~~~~ + +To delete a document, create an instance of ``DeleteOneModel`` and pass a +query filter specifying the document you want to delete. A +``DeleteOneModel`` instance provides instructions to delete +only *the first* document that matches your query filter. + +The following example creates an instance of ``DeleteOneModel``: + +.. literalinclude:: /includes/write/bulk-write.scala + :start-after: start-bulk-delete-one + :end-before: end-bulk-delete-one + :language: scala + :copyable: + :dedent: + +To delete multiple documents, create an instance of ``DeleteManyModel`` and pass a +query filter specifying the document you want to delete. An instance of +``DeleteManyModel`` provides instructions to remove *all* documents that +match your query filter. + +The following example creates an instance of ``DeleteManyModel``: + +.. literalinclude:: /includes/write/bulk-write.scala + :start-after: start-bulk-delete-many + :end-before: end-bulk-delete-many + :language: scala + :copyable: + :dedent: + +.. _scala-bulkwrite-method: + +Perform the Bulk Operation +-------------------------- + +After you define a model instance for each operation you want to perform, +pass a list of these instances to the ``bulkWrite()`` method. +By default, the method runs the operations in the order +specified by the list of models. + +The following example performs multiple write operations by using the +``bulkWrite()`` method: + +.. io-code-block:: + :copyable: + + .. input:: /includes/write/bulk-write.scala + :start-after: start-bulk-write-mixed + :end-before: end-bulk-write-mixed + :language: scala + :dedent: + + .. output:: + :visible: false + + Success: AcknowledgedBulkWriteResult{insertedCount=1, matchedCount=1, removedCount=1, + modifiedCount=1, upserts=[], inserts=[BulkWriteInsert{index=0, id=BsonObjectId{value=...}}]} + Completed + +If any of the write operations fail, the {+driver-short+} raises a +``BulkWriteError`` and does not perform any further operations. +``BulkWriteError`` provides a ``details`` item that includes the +operation that failed and details about the exception. + +.. note:: + + When the driver runs a bulk operation, it uses the write concern of the + target collection. The driver reports all write concern errors after + attempting all operations, regardless of execution order. + +Customize Bulk Write Operation +------------------------------ + +The ``bulkWrite()`` method optionally accepts a ``BulkWriteOptions`` parameter, which +specifies options you can use to configure the bulk write +operation. If you don't specify any options, the driver performs the +bulk operation with default settings. + +The following table describes the setter methods that you can use to +configure a ``BulkWriteOptions`` instance: + +.. list-table:: + :widths: 30 70 + :header-rows: 1 + + * - Method + - Description + + * - ``ordered()`` + - | If ``true``, the driver performs the write operations in the order + provided. If an error occurs, the remaining operations are not + attempted. + | + | If ``false``, the driver performs the operations in an + arbitrary order and attempts to perform all operations. + | Defaults to ``true``. + + * - ``bypassDocumentValidation()`` + - | Specifies whether the update operation bypasses document validation. This lets you + update documents that don't meet the schema validation requirements, if any + exist. For more information about schema validation, see :manual:`Schema + Validation ` in the MongoDB + Server manual. + | Defaults to ``false``. + + * - ``comment()`` + - | Sets a comment to attach to the operation. + + * - ``let()`` + - | Provides a map of parameter names and values to set top-level + variables for the operation. Values must be constant or closed + expressions that don't reference document fields. + +The following code creates options and sets the ``ordered`` option to ``false`` to +specify an unordered bulk write. Then, the example uses the +``bulkWrite()`` method to perform a bulk operation: + +.. literalinclude:: /includes/write/bulk-write.scala + :start-after: start-bulk-write-unordered + :end-before: end-bulk-write-unordered + :language: scala + :copyable: + :dedent: + +If any of the write operations in an unordered bulk write fail, the {+driver-short+} +reports the errors only after attempting all operations. + +.. note:: + + Unordered bulk operations do not guarantee an order of execution. The + order can differ from the way you list them to optimize the runtime. + +Return Value +------------ + +The ``bulkWrite()`` method returns a ``SingleObservable`` object that contains a +``BulkWriteResult``. You can access information from the ``BulkWriteResult`` instance by +subscribing to the observable and using the following methods: + +.. list-table:: + :widths: 30 70 + :header-rows: 1 + + * - Method + - Description + + * - ``wasAcknowledged()`` + - | Indicates if the server acknowledged the write operation. + + * - ``getDeletedCount()`` + - | The number of documents deleted, if any. + + * - ``getInsertedCount()`` + - | The number of documents inserted, if any. + + * - ``getInserts()`` + - | The list of inserted documents, if any. + + * - ``getMatchedCount()`` + - | The number of documents matched for an update, if applicable. + + * - ``getModifiedCount()`` + - | The number of documents modified, if any. + + * - ``getUpserts()`` + - | The list of upserted documents, if any. + +Additional Information +---------------------- + +To learn how to perform individual write operations, see the following guides: + +- :ref:`scala-write-insert` +- :ref:`scala-write-update` +- :ref:`scala-write-delete` +- :ref:`scala-write-replace` + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about any of the methods or types discussed in this +guide, see the following API documentation: + +- `bulkWrite() <{+api+}/org/mongodb/scala/MongoCollection.html#bulkWrite(clientSession:org.mongodb.scala.ClientSession,requests:Seq[_%3C:org.mongodb.scala.model.WriteModel[_%3C:TResult]],options:org.mongodb.scala.model.BulkWriteOptions):org.mongodb.scala.SingleObservable[org.mongodb.scala.BulkWriteResult]>`__ +- `InsertOneModel <{+core-api+}/com/mongodb/client/model/InsertOneModel.html>`__ +- `UpdateOneModel <{+core-api+}/com/mongodb/client/model/UpdateOneModel.html>`__ +- `UpdateManyModel <{+core-api+}/com/mongodb/client/model/UpdateManyModel.html>`__ +- `ReplaceOneModel <{+core-api+}/com/mongodb/client/model/ReplaceOneModel.html>`__ +- `DeleteOneModel <{+core-api+}/com/mongodb/client/model/DeleteOneModel.html>`__ +- `DeleteManyModel <{+core-api+}/com/mongodb/client/model/DeleteManyModel.html>`__ +- `BulkWriteOptions <{+core-api+}/com/mongodb/client/model/BulkWriteOptions.html>`__ +- `BulkWriteResult <{+core-api+}/com/mongodb/bulk/BulkWriteResult.html>`__ diff --git a/source/write/insert.txt b/source/write/insert.txt index abf3b57..7c6e453 100644 --- a/source/write/insert.txt +++ b/source/write/insert.txt @@ -35,7 +35,7 @@ Sample Data The examples in this guide use the ``restaurants`` collection in the ``sample_restaurants`` database from the :atlas:`Atlas sample datasets `. To access this collection from your Scala application, create a ``MongoClient`` that connects to an Atlas cluster -and assign the following value to your ``database`` and ``collection`` variables: +and assign the following values to your ``database`` and ``collection`` variables: .. literalinclude:: /includes/write/insert.scala :language: scala From 0836eeecfab09eb33d39ad17d97ed2b43e53e382 Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Tue, 5 Nov 2024 10:20:48 -0500 Subject: [PATCH 17/44] DOCSP-42317: Databases and Collections (#77) --- snooty.toml | 1 + source/databases-collections.txt | 273 ++++++++++++++++++ .../databases-collections.scala | 99 +++++++ source/index.txt | 11 +- 4 files changed, 379 insertions(+), 5 deletions(-) create mode 100644 source/databases-collections.txt create mode 100644 source/includes/databases-collections/databases-collections.scala diff --git a/snooty.toml b/snooty.toml index 15dbb19..dfb82df 100644 --- a/snooty.toml +++ b/snooty.toml @@ -17,6 +17,7 @@ toc_landing_pages = [ "/tutorials/write-ops", "/builders", "/get-started", + "/databases-collections", "/write" ] diff --git a/source/databases-collections.txt b/source/databases-collections.txt new file mode 100644 index 0000000..14ee480 --- /dev/null +++ b/source/databases-collections.txt @@ -0,0 +1,273 @@ +.. _scala-databases-collections: + +========================= +Databases and Collections +========================= + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: table, row, organize, storage + +.. toctree:: + :titlesonly: + :maxdepth: 1 + + /databases-collections/time-series + +Overview +-------- + +In this guide, you can learn how to interact with MongoDB databases and +collections by using the {+driver-short+}. + +MongoDB organizes data into a hierarchy of the following levels: + +- **Databases**: Top-level data structures in a MongoDB deployment that store collections. +- **Collections**: Groups of MongoDB documents. They are analogous to tables in relational databases. +- **Documents**: Units that store literal data such as string, numbers, dates, and other embedded documents. + For more information about document field types and structure, see the + :manual:`Documents ` guide in the {+mdb-server+} manual. + +Access a Database +----------------- + +Access a database by calling the ``getDatabase()`` method on a ``MongoClient`` instance. + +The following example accesses a database named ``"test_database"``: + +.. literalinclude:: /includes/databases-collections/databases-collections.scala + :start-after: start-access-database + :end-before: end-access-database + :language: scala + :copyable: + :dedent: + + +Access a Collection +------------------- + +Access a collection by calling the ``getCollection()`` method on a ``MongoDatabase`` +instance. + +The following example accesses a collection named ``"test_collection"``: + +.. literalinclude:: /includes/databases-collections/databases-collections.scala + :start-after: start-access-collection + :end-before: end-access-collection + :language: scala + :copyable: + :dedent: + +.. tip:: + + If the provided collection name does not already exist in the database, + MongoDB implicitly creates the collection when you first insert data + into it. + +Create a Collection +------------------- + +Use the ``createCollection()`` method on a ``MongoDatabase`` instance to explicitly create +a collection in a database. + +The following example creates a collection named ``"example_collection"``: + +.. literalinclude:: /includes/databases-collections/databases-collections.scala + :start-after: start-create-collection + :end-before: end-create-collection + :language: scala + :copyable: + :dedent: + +You can specify collection options, such as maximum size and document +validation rules, by passing a ``CreateCollectionOptions`` instance to the +``createCollection()`` method. For a full list of +optional parameters, see the :manual:`create command ` +documentation in the {+mdb-server+} manual. + +Get a List of Collections +------------------------- + +You can query for a list of collections in a database by calling the +``listCollections()`` method of a ``MongoDatabase`` instance. + +The following example lists all collections in a database: + +.. io-code-block:: + :copyable: + + .. input:: /includes/databases-collections/databases-collections.scala + :language: scala + :start-after: start-find-collections + :end-before: end-find-collections + :dedent: + + .. output:: + :language: console + :visible: false + + Iterable((name,BsonString{value='test_collection'}), (type,BsonString{value='collection'}), ... ) + Iterable((name,BsonString{value='example_collection'}), (type,BsonString{value='collection'}), ... ) + +To query for only the names of the collections in the database, call the +``listCollectionNames()`` method as follows: + +.. io-code-block:: + :copyable: + + .. input:: /includes/databases-collections/databases-collections.scala + :language: scala + :start-after: start-find-collection-names + :end-before: end-find-collection-names + :dedent: + + .. output:: + :language: console + :visible: false + + test_collection + example_collection + +.. TODO +.. For more information about iterating over a cursor, see :ref:`scala-cursors`. + +Delete a Collection +------------------- + +You can delete a collection by calling the ``drop()`` method on a +``MongoCollection`` instance. + +The following example deletes the ``"test_collection"`` collection: + +.. literalinclude:: /includes/databases-collections/databases-collections.scala + :start-after: start-delete-collection + :end-before: end-delete-collection + :language: scala + :copyable: + :dedent: + +.. warning:: Dropping a Collection Deletes All Data in the Collection + + Dropping a collection from your database permanently deletes all + documents and all indexes within that collection. + + Drop a collection only if the data in it is no longer needed. + +Configure Read and Write Operations +----------------------------------- + +You can control how the driver routes read operations by setting a **read preference**. +You can also control options for how the driver waits for acknowledgment of +read and write operations on a replica set by setting a **read concern** and a +**write concern**. + +By default, databases inherit these settings from the ``MongoClient`` instance, +and collections inherit them from the database. However, you can change these +settings on your database by using the ``withReadPreference()`` method. + +The following example accesses a database while specifying the read preference of the +database as ``secondary``: + +.. literalinclude:: /includes/databases-collections/databases-collections.scala + :start-after: start-database-read-prefs + :end-before: end-database-read-prefs + :language: scala + :copyable: + :dedent: + +You can also change the read and write settings on your collections by using the ``withReadPreference()`` +method. The following example shows how to access a collection while specifying the read +preference of the collection as ``secondary``: + +.. literalinclude:: /includes/databases-collections/databases-collections.scala + :start-after: start-collection-read-prefs + :end-before: end-collection-read-prefs + :language: scala + :copyable: + :dedent: + +.. tip:: + + To see the types of available read preferences, see the + `API documentation <{+api+}/org/mongodb/scala/ReadPreference$.html>`__. + +To learn more about the read and write settings, see the following guides in the +{+mdb-server+} manual: + +- :manual:`Read Preference ` +- :manual:`Read Concern ` +- :manual:`Write Concern ` + +Tag Sets +~~~~~~~~ + +In the {+mdb-server+}, you can apply key-value :manual:`tags +` to replica-set +members according to any criteria you choose. You can then use +those tags to target one or more members for a read operation. + +By default, the {+driver-short+} ignores tags when choosing a member to read from. To +instruct the {+driver-short+} to prefer certain tags, pass a ``TagSet`` instance to the +``ReadPreference`` constructor, then pass the ``ReadPreference`` instance to the +``MongoClientSettings`` you use to instantiate a ``MongoClient``. + +In the following code example, the tag set passed to the ``ReadPreference`` +constructor instructs the {+driver-short+} to prefer reads from the +New York data center (``'dc': 'ny'``) and to fall back to the San Francisco data +center (``'dc': 'sf'``): + +.. literalinclude:: /includes/databases-collections/databases-collections.scala + :start-after: start-tags + :end-before: end-tags + :language: scala + :copyable: + :dedent: + +Local Threshold +~~~~~~~~~~~~~~~ + +If multiple replica-set members match the read preference and tag sets you specify, +the {+driver-short+} reads from the nearest replica-set members, chosen according to +their ping time. + +By default, the driver uses only those members whose ping times are within 15 milliseconds +of the nearest member for queries. To distribute reads between members with +higher latencies, use the ``localThreshold()`` method within the ``ClusterSettings.Builder`` +block provided by the ``applyToClusterSettings()`` method of the ``MongoClientSettings.Builder`` +class. Alternatively, include the ``localThresholdMS`` parameter in your connection string +URI. + +The following example connects to a MongoDB deployment running on ``localhost:27017`` +and specifies a local threshold of 35 milliseconds: + +.. literalinclude:: /includes/databases-collections/databases-collections.scala + :start-after: start-local-threshold + :end-before: end-local-threshold + :language: scala + :copyable: + :dedent: + +In the preceding example, the {+driver-short+} distributes reads between matching members +within 35 milliseconds of the closest member's ping time. + +API Documentation +----------------- + +To learn more about any of the types or methods discussed in this +guide, see the following API documentation: + +- `MongoClient <{+api+}/org/mongodb/scala/MongoClient.html>`__ +- `MongoDatabase <{+api+}/org/mongodb/scala/MongoDatabase.html>`__ +- `MongoCollection <{+api+}/org/mongodb/scala/MongoCollection.html>`__ +- `CreateCollectionOptions <{+api+}/org/mongodb/scala/model/package$$CreateCollectionOptions$.html>`__ +- `ClusterSettings <{+api+}/org/mongodb/scala/connection/ClusterSettings$.html>`__ +- `MongoClientSettings <{+api+}/org/mongodb/scala/MongoClientSettings$.html>`__ \ No newline at end of file diff --git a/source/includes/databases-collections/databases-collections.scala b/source/includes/databases-collections/databases-collections.scala new file mode 100644 index 0000000..456b469 --- /dev/null +++ b/source/includes/databases-collections/databases-collections.scala @@ -0,0 +1,99 @@ +import org.mongodb.scala._ +import org.mongodb.scala.bson.{Document, ObjectId} +import org.mongodb.scala.gridfs.GridFSBucket +import org.mongodb.scala.model._ + +import java.nio.ByteBuffer +import java.nio.charset.StandardCharsets +import java.util.concurrent.TimeUnit +import scala.concurrent.Await +import scala.concurrent.duration.Duration +import scala.jdk.CollectionConverters.SeqHasAsJava + +object DatabasesCollections { + + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + + // start-access-database + val database = mongoClient.getDatabase("test_database") + // end-access-database + + { + // start-access-collection + val collection = database.getCollection("test_collection") + // end-access-collection + } + + // start-create-collection + val createObservable = database.createCollection("example_collection") + Await.result(createObservable.toFuture(), Duration(10, TimeUnit.SECONDS)) + // end-create-collection + + { + // start-find-collections + val results = Await.result(database.listCollections().toFuture(), Duration(10, TimeUnit.SECONDS)) + results.foreach(println) + // end-find-collections + } + + { + // start-find-collection-names + val results = Await.result(database.listCollectionNames().toFuture(), Duration(10, TimeUnit.SECONDS)) + results.foreach(println) + // end-find-collection-names + } + + { + // start-delete-collection + val deleteObservable = database.getCollection("test_collection").drop() + Await.result(deleteObservable.toFuture(), Duration(10, TimeUnit.SECONDS)) + // end-delete-collection + } + + // start-database-read-prefs + val databaseWithReadPrefs = + mongoClient.getDatabase("test_database").withReadPreference(ReadPreference.secondary()) + // end-database-read-prefs + + // start-collection-read-prefs + val collectionWithReadPrefs = + database.getCollection("test_collection").withReadPreference(ReadPreference.secondary()) + // end-collection-read-prefs + + + { + // start-tags + val tag1 = new Tag("dc", "ny") + val tag2 = new Tag("dc", "sf") + val tagSet = new TagSet(List(tag1, tag2).asJava) + + val connectionString = ConnectionString("") + val readPreference = ReadPreference.primaryPreferred(tagSet) + + val mongoClientSettings = MongoClientSettings.builder() + .applyConnectionString(connectionString) + .readPreference(readPreference) + .build() + + val clientWithTags = MongoClient(mongoClientSettings) + // end-tags + } + + { + // start-local-threshold + val connectionString = ConnectionString("mongodb://localhost:27017") + + val mongoClientSettings = MongoClientSettings.builder() + .applyConnectionString(connectionString) + .applyToClusterSettings(builder => builder.localThreshold(35, TimeUnit.MILLISECONDS)) + .build() + + val client = MongoClient(mongoClientSettings) + // end-local-threshold + } + + Thread.sleep(1000) + mongoClient.close() + } +} diff --git a/source/index.txt b/source/index.txt index cfd6ce1..2c901f5 100644 --- a/source/index.txt +++ b/source/index.txt @@ -14,8 +14,10 @@ /get-started /connect + /databases-collections /read /write + /read /whats-new /compatibility View the Source @@ -41,11 +43,10 @@ working with data in the :ref:`scala-get-started` tutorial. .. Learn how to create and configure a connection to a MongoDB deployment .. in the :ref:`scala-connect` section. -.. TODO -.. Databases and Collections -.. ------------------------- -.. Learn how to use the {+driver-short+} to work with MongoDB databases and collections in the -.. :ref:`scala-databases-collections` section. +Databases and Collections +------------------------- +Learn how to use the {+driver-short+} to work with MongoDB databases and collections in the +:ref:`scala-databases-collections` section. What's New ---------- From 51f96b82ae6b4cae67ca14a8105cb62023461376 Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Tue, 5 Nov 2024 11:00:38 -0500 Subject: [PATCH 18/44] DOCSP-42333: Distinct values (#67) * DOCSP-42333: Distinct values * edits * build error * RR feedback --- source/includes/read/distinct.scala | 52 ++++++++ source/read.txt | 3 +- source/read/distinct.txt | 177 ++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 source/includes/read/distinct.scala create mode 100644 source/read/distinct.txt diff --git a/source/includes/read/distinct.scala b/source/includes/read/distinct.scala new file mode 100644 index 0000000..402ec0a --- /dev/null +++ b/source/includes/read/distinct.scala @@ -0,0 +1,52 @@ +import org.mongodb.scala._ +import org.mongodb.scala.model.Filters._ +import org.mongodb.scala.model.Projections._ +import org.mongodb.scala.model.Sorts._ +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.util.{Failure, Success} +import scala.concurrent.ExecutionContext.Implicits.global + +object Retrieve { + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + + // start-db-coll + val database: MongoDatabase = client.getDatabase("sample_restaurants") + val collection: MongoCollection[Document] = database.getCollection("restaurants") + // end-db-coll + + // Retrieves distinct values of the "borough" field + // start-distinct + collection.distinct("borough") + .subscribe((value: String) => println(value), + (e: Throwable) => println(s"There was an error: $e")) + //end-distinct + + // Retrieves distinct "borough" field values for documents with a "cuisine" value of "Italian" + // start-distinct-with-query + val filter = equal("cuisine", "Italian") + + collection.distinct("borough", filter) + .subscribe((value: String) => println(value), + (e: Throwable) => println(s"There was an error: $e")) + // end-distinct-with-query + + // Retrieves distinct "name" field values for documents matching the "borough" and "cuisine" fields query + // and attaches a comment to the operation + // start-distinct-with-comment + val filter = and(equal("borough", "Bronx"), equal("cuisine", "Pizza")) + + collection.distinct("name", filter) + .comment("Bronx Pizza restaurants") + .subscribe((value: String) => println(value), + (e: Throwable) => println(s"There was an error: $e")) + // end-distinct-with-comment + + // Keep the main thread alive long enough for the asynchronous operations to complete + Thread.sleep(5000) + + // Close the MongoClient connection + mongoClient.close() + } +} \ No newline at end of file diff --git a/source/read.txt b/source/read.txt index 4af4198..555d29b 100644 --- a/source/read.txt +++ b/source/read.txt @@ -25,8 +25,9 @@ Read Data from MongoDB /read/retrieve /read/specify-a-query /read/specify-documents-to-return - /read/count /read/project + /read/distinct + /read/count diff --git a/source/read/distinct.txt b/source/read/distinct.txt new file mode 100644 index 0000000..225f297 --- /dev/null +++ b/source/read/distinct.txt @@ -0,0 +1,177 @@ +.. _scala-distinct: + +============================== +Retrieve Distinct Field Values +============================== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: read, unique, code example + +Overview +-------- + +In this guide, you can learn how to use the {+driver-short+} to retrieve the +distinct values of a specified field across a collection. + +Within a collection, different documents might contain different values for a +single field. For example, one document in a ``restaurants`` collection has a +``borough`` value of ``"Manhattan"``, and another has a ``borough`` value of +``"Queens"``. By using the {+driver-short+}, you can retrieve all the unique values +that a field contains across multiple documents in a collection. + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``restaurants`` collection in the ``sample_restaurants`` +database from the :atlas:`Atlas sample datasets `. To access this collection +from your Scala application, create a ``MongoClient`` that connects to an Atlas cluster +and assign the following values to your ``database`` and ``collection`` variables: + +.. literalinclude:: /includes/read/distinct.scala + :language: scala + :dedent: + :start-after: start-db-coll + :end-before: end-db-coll + +To learn how to create a free MongoDB Atlas cluster and load the sample datasets, see the +:atlas:`Get Started with Atlas ` guide. + +Retrieve Distinct Values +------------------------ + +To retrieve the distinct values for a specified field, call the ``distinct()`` +method and pass the name of the field you want to find distinct values for. + +Retrieve Values Across a Collection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following example retrieves the distinct values of the ``borough`` field in +the ``restaurants`` collection: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/distinct.scala + :start-after: start-distinct + :end-before: end-distinct + :language: scala + :dedent: + + .. output:: + :visible: false + + Bronx + Brooklyn + Manhattan + Missing + Queens + Staten Island + +The operation returns an instance of the ``DistinctObservable`` class, which +you can iterate through to access each distinct ``borough`` field value. Although +several documents have the same value in the ``borough`` field, each value appears +in the results only once. + +Retrieve Values Across Specified Documents +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can provide a **query filter** to the ``distinct()`` method to find distinct +field values within a subset of documents in a collection. A query filter is an expression +that specifies search criteria used to match documents in an operation. For more information +about creating a query filter, see the :ref:`scala-specify-query` guide. + +The following example retrieves the distinct values of the ``borough`` field for +all documents that have a ``cuisine`` field value of ``"Italian"``: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/distinct.scala + :start-after: start-distinct-with-query + :end-before: end-distinct-with-query + :language: scala + :dedent: + + .. output:: + :visible: false + + Bronx + Brooklyn + Manhattan + Queens + Staten Island + +Modify Distinct Behavior +~~~~~~~~~~~~~~~~~~~~~~~~ + +You can modify the behavior of the ``distinct()`` method by chaining +methods provided by the ``DistinctObservable`` class. The following +table describes some of these methods: + +.. list-table:: + :widths: 30 70 + :header-rows: 1 + + * - Method + - Description + + * - ``collation()`` + - | Sets the collation to use for the operation. + | **Parameter Type**: ``Collation`` + + * - ``maxTime()`` + - | Sets the maximum amount of time that the operation can run. + | **Parameter Type**: ``Duration`` + + * - ``comment()`` + - | Attaches a comment to the operation. + | **Parameter Type**: ``BsonValue`` or ``String`` + + * - ``first()`` + - | Retrieves only the first distinct field value. + +The following example retrieves the distinct values of the ``name`` field for +all documents that have a ``borough`` field value of ``"Bronx"`` and a +``cuisine`` field value of ``"Pizza"``. Then, it chains the ``comment()`` +method to ``distinct()`` to add a comment to the operation: + +.. io-code-block:: + :copyable: + + .. input:: /includes/read/distinct.scala + :start-after: start-distinct-with-comment + :end-before: end-distinct-with-comment + :language: scala + :dedent: + + .. output:: + :visible: false + + $1.25 Pizza + 18 East Gunhill Pizza + 2 Bros + Aenos Pizza + Alitalia Pizza Restaurant + Amici Pizza And Pasta + Angie'S Cafe Pizza + ... + +API Documentation +----------------- + +To learn more about any of the methods or types discussed in this +guide, see the following API documentation: + +- `distinct() <{+api+}/org/mongodb/scala/MongoCollection.html#distinct[C](fieldName:String)(implicitct:scala.reflect.ClassTag[C]):org.mongodb.scala.DistinctObservable[C]>`__ +- `DistinctObservable <{+api+}/org/mongodb/scala/DistinctObservable.html>`__ +- `comment() <{+api+}/org/mongodb/scala/DistinctObservable.html#comment(comment:org.mongodb.scala.bson.BsonValue):org.mongodb.scala.DistinctObservable[TResult]>`__ \ No newline at end of file From 08826ba69204e4a4c325db6bde278ec9347443f1 Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Tue, 5 Nov 2024 11:17:00 -0500 Subject: [PATCH 19/44] DOCSP-42335: Change streams (#69) * DOCSP-42335: Change streams * code * edits * fixes * toc * MW feedback * MW feedback * fix --- source/includes/read/change-streams.scala | 73 ++++++ source/read.txt | 1 + source/read/change-streams.txt | 285 ++++++++++++++++++++++ 3 files changed, 359 insertions(+) create mode 100644 source/includes/read/change-streams.scala create mode 100644 source/read/change-streams.txt diff --git a/source/includes/read/change-streams.scala b/source/includes/read/change-streams.scala new file mode 100644 index 0000000..0ff21d5 --- /dev/null +++ b/source/includes/read/change-streams.scala @@ -0,0 +1,73 @@ +import org.mongodb.scala._ +import org.mongodb.scala.model.{ Aggregates, Filters, Updates } +import org.mongodb.scala.model.Projections._ +import org.mongodb.scala.model.Sorts._ +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.util.{Failure, Success} +import scala.concurrent.ExecutionContext.Implicits.global +import org.mongodb.scala.result.UpdateResult + +object ChangeStreams { + def main(args: Array[String]): Unit = { + val uri: String = "" + val client: MongoClient = MongoClient(uri) + + // start-db-coll + val database: MongoDatabase = client.getDatabase("sample_restaurants") + val collection: MongoCollection[Document] = database.getCollection("restaurants") + // end-db-coll + + // start-latched-observer + case class LatchedObserver() extends Observer[ChangeStreamDocument[Document]] { + val latch = new CountDownLatch(1) + + override def onSubscribe(subscription: Subscription): Unit = subscription.request(Long.MaxValue) // Request data + + override def onNext(changeDocument: ChangeStreamDocument[Document]): Unit = println(changeDocument) + + override def onError(throwable: Throwable): Unit = { + println(s"Error: '$throwable") + latch.countDown() + } + + override def onComplete(): Unit = latch.countDown() + + def await(): Unit = latch.await() + } + // end-latched-observer + + // Monitors and prints changes to the "restaurants" collection + // start-open-change-stream + val observer = LatchedObserver() + collection.watch().subscribe(observer) + observer.await() + // end-open-change-stream + + // Updates a document that has a "name" value of "Blarney Castle" + // start-update-for-change-stream + val filter = equal("name", "Blarney Castle") + val update = set("cuisine", "American") + + collection.updateOne(filter, update) + .subscribe((res: UpdateResult) => println(res), + (e: Throwable) => println(s"There was an error: $e")) + // end-update-for-change-stream + + // Passes a pipeline argument to watch() to monitor only update operations + // start-change-stream-pipeline + val observer = LatchedObserver() + collection.watch(Seq(Aggregates.filter(Filters.in("operationType", "update")))) + observer.await() + // end-change-stream-pipeline + + // Passes an options argument to watch() to include the post-image of updated documents + // start-change-stream-post-image + val observer = LatchedObserver() + collection.watch() + .fullDocument(FullDocument.UPDATE_LOOKUP) + .subscribe(observer) + observer.await() + // end-change-stream-post-image + } +} \ No newline at end of file diff --git a/source/read.txt b/source/read.txt index 555d29b..b6d3e7a 100644 --- a/source/read.txt +++ b/source/read.txt @@ -28,6 +28,7 @@ Read Data from MongoDB /read/project /read/distinct /read/count + /read/change-streams diff --git a/source/read/change-streams.txt b/source/read/change-streams.txt new file mode 100644 index 0000000..6f530fd --- /dev/null +++ b/source/read/change-streams.txt @@ -0,0 +1,285 @@ +.. _scala-change-streams: + +==================== +Monitor Data Changes +==================== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: watch, code example + +Overview +-------- + +In this guide, you can learn how to use a **change stream** to monitor real-time +changes to your data. A change stream is a {+mdb-server+} feature that +allows your application to subscribe to data changes on a collection, database, +or deployment. + +When using the {+driver-short+}, you can call the ``watch()`` method to return an +instance of ``ChangeStreamObservable``. Then, you can subscribe to the +``ChangeStreamObservable`` instance to see new data changes, such as updates, +insertions, and deletions. + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``restaurants`` collection in the ``sample_restaurants`` +database from the :atlas:`Atlas sample datasets `. To access this collection +from your Scala application, create a ``MongoClient`` that connects to an Atlas cluster +and assign the following values to your ``database`` and ``collection`` variables: + +.. literalinclude:: /includes/read/change-streams.scala + :language: scala + :dedent: + :start-after: start-db-coll + :end-before: end-db-coll + +.. tip:: + + To learn how to create a free MongoDB Atlas cluster and load the sample datasets, + see the :atlas:`Get Started with Atlas ` guide. + +Some examples use instances of the ``LatchedObserver`` class to handle change +stream events. This class is a custom observer that prints change stream events +and continues monitoring data changes until the stream completes or generates +an error. To use the ``LatchedObserver`` class, paste the following code into +your application file: + +.. literalinclude:: /includes/read/change-streams.scala + :language: scala + :dedent: + :start-after: start-latched-observer + :end-before: end-latched-observer + +Open a Change Stream +-------------------- + +To open a change stream, call the ``watch()`` method. The instance on which you +call the ``watch()`` method determines the scope of events that the change +stream monitors. You can call the ``watch()`` method on instances of the following +classes: + +- ``MongoClient``: Monitors changes to all collections across all databases + in a deployment, excluding :manual:`system collections ` + or collections in the ``admin``, ``local``, and ``config`` databases +- ``MongoDatabase``: Monitors changes to all collections in one database +- ``MongoCollection``: Monitors changes to one collection + +The following example calls the ``watch()`` method to open a change stream on the +``restaurants`` collection. The code creates a ``LatchedObserver`` instance to +receive and output changes as they occur: + +.. literalinclude:: /includes/read/change-streams.scala + :start-after: start-open-change-stream + :end-before: end-open-change-stream + :language: scala + :dedent: + +To begin watching for changes, run the preceding code. Then, in a separate +shell, run the following code to update a document that has a ``name`` field +value of ``"Blarney Castle"``: + +.. _scala-change-stream-update: + +.. literalinclude:: /includes/read/change-streams.scala + :start-after: start-update-for-change-stream + :end-before: end-update-for-change-stream + :language: scala + :dedent: + +When you run the preceding code to update the collection, the change stream +application prints the change as it occurs. The printed change event resembles +the following output: + +.. code-block:: none + :copyable: false + + ChangeStreamDocument{ operationType=update, resumeToken={"_data": "..."}, + namespace=sample_restaurants.restaurants, destinationNamespace=null, + fullDocument=null, fullDocumentBeforeChange=null, documentKey={"_id": {...}}, + clusterTime=Timestamp{...}, updateDescription=UpdateDescription{removedFields=[], + updatedFields={"cuisine": "Irish"}, truncatedArrays=[], disambiguatedPaths=null}, + txnNumber=null, lsid=null, splitEvent=null, wallTime=BsonDateTime{...}} + +Modify the Change Stream Output +------------------------------- + +To modify the change stream output, you can pass a list of pipeline stages as a +parameter to the ``watch()`` method. You can include the following stages in the +list: + +- ``$addFields`` or ``$set``: Adds new fields to documents +- ``$match``: Filters the documents +- ``$project``: Projects a subset of the document fields +- ``$replaceWith`` or ``$replaceRoot``: Replaces the input document with the + specified document +- ``$redact``: Restricts the contents of the documents +- ``$unset``: Removes fields from documents + +The {+driver-short+} provides the ``Aggregates`` class, which includes helper methods +for building the preceding pipeline stages. + +.. tip:: + + To learn more about pipeline stages and their corresponding ``Aggregates`` + helper methods, see the following resources: + + - :manual:`Aggregation Stages ` in the + {+mdb-server+} manual + - `Aggregates <{+api+}/org/mongodb/scala/model/Aggregates$.html>`__ in the API + documentation + +The following example creates a pipeline that uses the ``Aggregates.filter()`` method +to build the ``$match`` stage. Then, the code passes this pipeline to the ``watch()`` +method and instructs ``watch()`` to output events only when update operations occur: + +.. literalinclude:: /includes/read/change-streams.scala + :start-after: start-change-stream-pipeline + :end-before: end-change-stream-pipeline + :language: scala + :dedent: + +Modify watch() Behavior +----------------------- + +You can modify the behavior of the ``watch()`` method by chaining +methods provided by the ``ChangeStreamObservable`` class. The following +table describes some of these methods: + +.. list-table:: + :widths: 30 70 + :header-rows: 1 + + * - Method + - Description + + * - ``fullDocument()`` + - | Specifies whether to show the full document after the change, rather + than showing only the changes made to the document. To learn more about + this option, see the :ref:`scala-change-stream-pre-post-image` section of this + guide. + + * - ``fullDocumentBeforeChange()`` + - | Specifies whether to show the full document as it was before the change, rather + than showing only the changes made to the document. To learn more about + this option, see :ref:`scala-change-stream-pre-post-image`. + + * - ``comment()`` + - | Attaches a comment to the operation. + + * - ``startAtOperationTime()`` + - | Instructs the change stream to provide only changes that occurred at or after + the specified timestamp. + + * - ``collation()`` + - | Sets the collation to use for the change stream cursor. + +For a full list of ``watch()`` options, see `ChangeStreamObservable +<{+api+}/org/mongodb/scala/ChangeStreamObservable.html>`__ in the API +documentation. + +.. _scala-change-stream-pre-post-image: + +Include Pre-Images and Post-Images +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. important:: + + You can enable pre-images and post-images on collections only if your + deployment uses {+mdb-server+} v6.0 or later. + +By default, when you perform an operation on a collection, the +corresponding change event includes only the modified fields and +their values before and after the operation. + +You can instruct the ``watch()`` method to return the document's **pre-image**, the +full version of the document *before* changes, in addition to the modified fields. To +include the pre-image in the change stream event, chain the ``fullDocumentBeforeChange()`` +method to ``watch()``. Pass one of the following values to the ``fullDocumentBeforeChange()`` +method: + +- ``FullDocumentBeforeChange.WHEN_AVAILABLE``: The change event includes a pre-image + of the modified document for change events. If the pre-image is not available, this + change event field has a ``null`` value. +- ``FullDocumentBeforeChange.REQUIRED``: The change event includes a pre-image + of the modified document for change events. If the pre-image is not available, the + server raises an error. + +You can also instruct the ``watch()`` method to return the document's **post-image**, +the full version of the document *after* changes, in addition to the modified fields. +To include the post-image in the change stream event, chain the ``fullDocument()`` +method to ``watch()``. Pass one of the following values to the ``fullDocument()`` method: + +- ``FullDocument.UPDATE_LOOKUP``: The change event includes a + copy of the entire changed document from some time after the change. +- ``FullDocument.WHEN_AVAILABLE``: The change event includes + a post-image of the modified document for change events. If the post-image is not + available, this change event field has a ``null`` value. +- ``FullDocument.REQUIRED``: The change event includes a post-image + of the modified document for change events. If the post-image is not available, the + server raises an error. + +The following example calls the ``watch()`` method on a collection and includes the post-image +of updated documents by chaining the ``fullDocument()`` method: + +.. literalinclude:: /includes/read/change-streams.scala + :start-after: start-change-stream-post-image + :end-before: end-change-stream-post-image + :language: scala + :dedent: + +With the change stream application running in a separate shell, updating a +document in the ``restaurants`` collection by using the :ref:`preceding update +example ` prints a change event that resembles the following +output: + +.. code-block:: none + :copyable: false + :emphasize-lines: 3-8 + + ChangeStreamDocument{ operationType=update, resumeToken={"_data": "..."}, + namespace=sample_restaurants.restaurants, destinationNamespace=null, + fullDocument=Iterable((_id,BsonObjectId{...}), (address,{"building": "202-24", + "coord": [-73.9250442, 40.5595462], "street": "Rockaway Point Boulevard", + "zipcode": "11697"}), (borough,BsonString{value='Queens'}), + (cuisine,BsonString{value='Irish'}), (grades,BsonArray{values=[...]}), + (name,BsonString{value='Blarney Castle'}), (restaurant_id,BsonString{...}), + (blank,BsonString{value='Irish'})), fullDocumentBeforeChange=null, + documentKey={"_id": {"$oid": "..."}}, clusterTime=Timestamp{...}, updateDescription= + UpdateDescription{removedFields=[], updatedFields={"cuisine": "Irish"}, + truncatedArrays=[], disambiguatedPaths=null}, txnNumber=null, lsid=null, + splitEvent=null, wallTime=BsonDateTime{...}} + +.. tip:: + + To learn more about pre-images and post-images, see + :manual:`Change Streams with Document Pre- and Post-Images + ` + in the {+mdb-server+} manual. + +Additional Information +---------------------- + +To learn more about change streams, see :manual:`Change Streams +` in the {+mdb-server+} manual. + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about any of the methods or types discussed in this +guide, see the following API documentation: + +- `watch() <{+api+}/org/mongodb/scala/MongoCollection.html#watch[C]()(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[C,TResult],implicitct:scala.reflect.ClassTag[C]):org.mongodb.scala.ChangeStreamObservable[C]>`__ +- `ChangeStreamObservable <{+api+}/org/mongodb/scala/ChangeStreamObservable.html>`__ +- `fullDocument() <{+api+}/org/mongodb/scala/ChangeStreamObservable.html#fullDocument(fullDocument:org.mongodb.scala.model.changestream.FullDocument):org.mongodb.scala.ChangeStreamObservable[TResult]>`__ +- `fullDocumentBeforeChange() <{+api+}/org/mongodb/scala/ChangeStreamObservable.html#fullDocumentBeforeChange(fullDocumentBeforeChange:org.mongodb.scala.model.changestream.FullDocumentBeforeChange):org.mongodb.scala.ChangeStreamObservable[TResult]>`__ From 32d3ffadcbb773713a7f1ff1c15ed824095a9dfb Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Tue, 5 Nov 2024 11:21:04 -0500 Subject: [PATCH 20/44] DOCSP-42311: Run a Command (#75) * DOCSP-42311: Run a Command * edits * code edits * MM feedback * JY feedback --- source/databases-collections.txt | 5 +- source/databases-collections/run-command.txt | 197 +++++++++++++++++++ source/includes/run-command.scala | 36 ++++ source/index.txt | 1 - 4 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 source/databases-collections/run-command.txt create mode 100644 source/includes/run-command.scala diff --git a/source/databases-collections.txt b/source/databases-collections.txt index 14ee480..523fbba 100644 --- a/source/databases-collections.txt +++ b/source/databases-collections.txt @@ -15,12 +15,13 @@ Databases and Collections :values: reference .. meta:: - :keywords: table, row, organize, storage + :keywords: table, row, organize, storage, code example .. toctree:: :titlesonly: :maxdepth: 1 + /databases-collections/run-command /databases-collections/time-series Overview @@ -270,4 +271,4 @@ guide, see the following API documentation: - `MongoCollection <{+api+}/org/mongodb/scala/MongoCollection.html>`__ - `CreateCollectionOptions <{+api+}/org/mongodb/scala/model/package$$CreateCollectionOptions$.html>`__ - `ClusterSettings <{+api+}/org/mongodb/scala/connection/ClusterSettings$.html>`__ -- `MongoClientSettings <{+api+}/org/mongodb/scala/MongoClientSettings$.html>`__ \ No newline at end of file +- `MongoClientSettings <{+api+}/org/mongodb/scala/MongoClientSettings$.html>`__ diff --git a/source/databases-collections/run-command.txt b/source/databases-collections/run-command.txt new file mode 100644 index 0000000..61cdc16 --- /dev/null +++ b/source/databases-collections/run-command.txt @@ -0,0 +1,197 @@ +.. _scala-run-command: + +====================== +Run a Database Command +====================== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: administration, code example + +Overview +-------- + +In this guide, you can learn how to use the {+driver-short+} to +run a database command. You can use database commands to perform a +variety of administrative and diagnostic tasks, such as fetching server +statistics, initializing a replica set, or running an aggregation pipeline. + +.. important:: Prefer Driver Methods to Database Commands + + The driver provides wrapper methods for many database commands. + If possible, we recommend using these methods instead of executing + database commands. + + To perform administrative tasks, use the :mongosh:`MongoDB Shell ` + instead of the {+driver-short+}. The shell provides helper methods + that might not be available in the driver. + + If there are no available helpers in the driver or the shell, you + can use the ``db.runCommand()`` shell method or the driver's + ``runCommand()`` method, which is described in this + guide. + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``sample_restaurants`` database +from the :atlas:`Atlas sample datasets `. To access this database +from your Scala application, create a ``MongoClient`` that connects to an Atlas cluster +and assign the following value to your ``database`` variable: + +.. literalinclude:: /includes/run-command.scala + :language: scala + :dedent: + :start-after: start-db + :end-before: end-db + +To learn how to create a free MongoDB Atlas cluster and load the sample datasets, see the +:atlas:`Get Started with Atlas ` guide. + +.. _scala-execute-command: + +Execute a Command +----------------- + +To run a database command, create a ``Document`` object that specifies +the command and pass it as a parameter to the ``runCommand()`` method. +This method returns a ``SingleObservable`` instance, and you can subscribe +to this observable to execute the command and access the command result. + +The following example calls the ``runCommand()`` method on a database +to run the ``hello`` command, which returns information about the server: + +.. literalinclude:: /includes/run-command.scala + :language: scala + :dedent: + :start-after: start-hello + :end-before: end-hello + +.. tip:: + + To view a full list of database commands and their corresponding + parameters, see :manual:`Database Commands ` in + the {+mdb-server+} manual. + +.. _scala-command-read-pref: + +Set a Read Preference +---------------------- + +The ``runCommand()`` method does not inherit the read preference you might +have set on your ``MongoDatabase`` instance. By default, ``runCommand()`` +uses the ``primary`` read preference. + +You can set a read preference for the command execution by passing a +``ReadPreference`` instance as a parameter to ``runCommand()``, as +shown in the following code: + + .. literalinclude:: /includes/run-command.scala + :language: scala + :dedent: + :start-after: start-readpref + :end-before: end-readpref + +.. tip:: + + To learn more about read preference options, see :manual:`Read + Preference ` in the {+mdb-server+} manual. + +.. _scala-command-response: + +Response +-------- + +The ``runCommand()`` method returns a ``SingleObservable`` that contains +the response from the database for the given command. You can call the +``subscribe()`` method on the observable to run the command and access +the response as a document. + +The raw command response document contains the following fields: + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Field + - Description + + * - ```` + - Fields specific to the database command. For example, + the ``hello`` command returns the ``topologyVersion`` field. + + * - ``ok`` + - Indicates whether the command has succeeded (``1.0``) or failed (``0.0``). The + driver raises a ``MongoCommandException`` if the ``ok`` + value is ``0.0``. + + * - ``$clusterTime`` + - A document that contains the signed cluster time. Cluster time is a + logical time used for the ordering of operations. This field only + applies to commands run on replica sets or sharded cluster. + + * - ``operationTime`` + - The logical time of the operation execution. This field only + applies to commands run on replica sets or sharded cluster. + +.. tip:: + + To learn more about logical time, see the Wikipedia entry on + the :wikipedia:`logical clock `. + +Example +~~~~~~~ + +The following example runs the ``dbStats`` command to retrieve +storage statistics for the ``sample_restaurants`` database, then prints the +command results: + +.. literalinclude:: /includes/run-command.scala + :language: scala + :dedent: + :start-after: start-print-command + :end-before: end-print-command + +The output of this command includes information about the data stored in +the database, as shown in the following code: + +.. code-block:: none + :copyable: false + + {"db": "sample_restaurants", "collections": 4, "views": 0, "objects": + 18767, "avgObjSize": 596.1911866574306, "dataSize": 11188720, + "storageSize": 7528448, "totalFreeStorageSize": 0, "numExtents": 0, + "indexes": 6, "indexSize": 1519616, "indexFreeStorageSize": 0, + "fileSize": 0, "nsSizeMB": 0, "ok": 1} + +.. _scala-addtl-info-runcommand: + +Additional Information +---------------------- + +For more information about the concepts in this guide, see the following +documentation in the {+mdb-server+} manual: + +- :manual:`db.runCommand() ` +- :manual:`Database Commands ` +- :manual:`hello Command ` +- :manual:`dbStats Command ` + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about any of the methods or types discussed in this +guide, see the following API documentation: + +- `runCommand() <{+api+}/org/mongodb/scala/MongoDatabase.html#runCommand[TResult](command:org.mongodb.scala.bson.conversions.Bson)(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[TResult,org.mongodb.scala.Document],implicitct:scala.reflect.ClassTag[TResult]):org.mongodb.scala.SingleObservable[TResult]>`__ +- `SingleObservable <{+api+}/org/mongodb/scala/SingleObservable.html>`__ +- `ReadPreference <{+api+}/org/mongodb/scala/ReadPreference$.html>`__ diff --git a/source/includes/run-command.scala b/source/includes/run-command.scala new file mode 100644 index 0000000..37143ee --- /dev/null +++ b/source/includes/run-command.scala @@ -0,0 +1,36 @@ +import org.mongodb.scala._ + +object RunCommand { + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + + // start-db + val database: MongoDatabase = mongoClient.getDatabase("sample_restaurants") + // end-db + + // Runs a command that returns information about the deployment + // start-hello + database.runCommand(Document("hello" -> 1)) + .subscribe((doc: Document) => ()) + // end-hello + + // Runs a command that returns deployment information and sets a read preference + // start-readpref + database.runCommand(Document("hello" -> 1), ReadPreference.secondary()) + .subscribe((doc: Document) => ()) + // end-readpref + + // Runs a command to retrieve database statistics and prints the result + // start-print-command + database.runCommand(Document("dbStats" -> 1)) + .subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-print-command + + // Keep the main thread alive long enough for the asynchronous operations to complete + Thread.sleep(5000) + + // Close the MongoClient connection + mongoClient.close() + } +} \ No newline at end of file diff --git a/source/index.txt b/source/index.txt index 2c901f5..8929517 100644 --- a/source/index.txt +++ b/source/index.txt @@ -17,7 +17,6 @@ /databases-collections /read /write - /read /whats-new /compatibility View the Source From 31d98848f9b7fcafe70de39dc4dc7046494077ac Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Tue, 5 Nov 2024 11:23:52 -0500 Subject: [PATCH 21/44] DOCSP-42318: Time Series (#78) --- source/databases-collections/time-series.txt | 183 ++++++++++++++++++ .../databases-collections/time-series.scala | 52 +++++ 2 files changed, 235 insertions(+) create mode 100644 source/databases-collections/time-series.txt create mode 100644 source/includes/databases-collections/time-series.scala diff --git a/source/databases-collections/time-series.txt b/source/databases-collections/time-series.txt new file mode 100644 index 0000000..31a532a --- /dev/null +++ b/source/databases-collections/time-series.txt @@ -0,0 +1,183 @@ +.. _scala-time-series: + +================ +Time Series Data +================ + +.. contents:: On this page + :local: + :backlinks: none + :depth: 1 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to use the {+driver-short+} to store +and interact with **time series data**. + +Time series data is composed of the following components: + +- Measured quantity +- Timestamp for the measurement +- Metadata that describes the measurement + +The following table describes sample situations for which you could store time +series data: + +.. list-table:: + :widths: 33, 33, 33 + :header-rows: 1 + :stub-columns: 1 + + * - Situation + - Measured Quantity + - Metadata + + * - Recording monthly sales by industry + - Revenue in USD + - Company, country + + * - Tracking weather changes + - Precipitation level + - Location, sensor type + + * - Recording fluctuations in housing prices + - Monthly rent price + - Location, currency + +.. _scala-time-series-create: + +Create a Time Series Collection +------------------------------- + +.. important:: Server Version for Time Series Collections + + To create and interact with time series collections, you must be + connected to a deployment running {+mdb-server+} 5.0 or later. + +You can create a time series collection to store time series data. +To create a time series collection, pass the following parameters to the +``createCollection()`` method: + +- Name of the new collection to create + +- `CreateCollectionOptions <{+api+}/org/mongodb/scala/model/package$$CreateCollectionOptions$.html>`__ + object with the `TimeSeriesOptions <{+api+}/org/mongodb/scala/model/package$$TimeSeriesOptions$.html>`__ set + using the ``timeSeriesOptions()`` method + +.. _scala-time-series-create-example: + +Example +~~~~~~~ + +This example creates the ``october2024`` time series collection in the +``fall_weather`` database with the ``timeField`` option set to the ``"timestamp"`` field +and the ``metaField`` option set to the ``"location"`` field: + +.. literalinclude:: /includes/databases-collections/time-series.scala + :language: scala + :start-after: start-create-time-series + :end-before: end-create-time-series + :dedent: + +To verify that you successfully created the time series collection, run +the ``listCollections()`` method on the database and print the results: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/databases-collections/time-series.scala + :language: scala + :start-after: start-print-time-series + :end-before: end-print-time-series + :dedent: + + .. output:: + :visible: false + + { + "name": "october2024", + "type": "timeseries", + "options": { + "timeseries": { + "timeField": "temperature", + "granularity": "seconds", + "bucketMaxSpanSeconds": 3600 + } + }, + "info": { + "readOnly": false + } + } + ... + +.. _scala-time-series-store: + +Store Time Series Data +---------------------- + +You can insert data into a time series collection by using the ``insertOne()`` +or ``insertMany()`` methods and specifying the measurement, timestamp, and metadata +in each inserted document. + +.. tip:: + + To learn more about inserting documents into a collection, see the :ref:`scala-write-insert` + guide. + +Example +~~~~~~~ + +This example inserts New York City temperature data into the ``october2024`` +time series collection created in the :ref:`Create a Time Series Collection example +`. Each document contains the following fields: + +- ``temperature``, which stores temperature measurements in degrees Fahrenheit +- ``location``, which stores location metadata +- ``timestamp``, which stores the time of the measurement collection + +.. literalinclude:: /includes/databases-collections/time-series.scala + :language: scala + :start-after: start-insert-time-series-data + :end-before: end-insert-time-series-data + :dedent: + +.. _scala-time-series-query: + +Query Time Series Data +---------------------- + +You can use the same syntax and conventions to query data stored in a time +series collection as you use when performing read or aggregation operations on +other collections. To learn more about these operations, see +the :ref:`Additional Information ` section. + +.. _scala-time-series-addtl-info: + +Additional Information +---------------------- + +To learn more about the concepts mentioned in this guide, see the +following {+mdb-server+} manual entries: + +- :manual:`Time Series ` +- :manual:`Create and Query a Time Series Collection ` +- :manual:`Set Granularity for Time Series Data ` + +To learn more about performing read operations, see :ref:`scala-read`. + +.. TODO +.. To learn more about performing aggregation operations, see the :ref:`scala-aggregation` +.. guide. + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about the methods mentioned in this guide, see the following +API documentation: + +- `createCollection() <{+api+}/org/mongodb/scala/MongoDatabase.html#createCollection(clientSession:org.mongodb.scala.ClientSession,collectionName:String,options:com.mongodb.client.model.CreateCollectionOptions):org.mongodb.scala.SingleObservable[Unit]>`__ +- `listCollections() <{+api+}/org/mongodb/scala/MongoDatabase.html#listCollections[TResult](clientSession:org.mongodb.scala.ClientSession)(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[TResult,org.mongodb.scala.Document],implicitct:scala.reflect.ClassTag[TResult]):org.mongodb.scala.ListCollectionsObservable[TResult]>`__ +- `insertOne() <{+api+}/org/mongodb/scala/MongoCollection.html#insertOne(clientSession:org.mongodb.scala.ClientSession,document:TResult,options:org.mongodb.scala.model.InsertOneOptions):org.mongodb.scala.SingleObservable[org.mongodb.scala.result.InsertOneResult]>`__ +- `insertMany() <{+api+}/org/mongodb/scala/MongoCollection.html#insertMany(clientSession:org.mongodb.scala.ClientSession,documents:Seq[_%3C:TResult],options:org.mongodb.scala.model.InsertManyOptions):org.mongodb.scala.SingleObservable[org.mongodb.scala.result.InsertManyResult]>`__ \ No newline at end of file diff --git a/source/includes/databases-collections/time-series.scala b/source/includes/databases-collections/time-series.scala new file mode 100644 index 0000000..48c1d26 --- /dev/null +++ b/source/includes/databases-collections/time-series.scala @@ -0,0 +1,52 @@ +import org.bson.json.JsonWriterSettings +import org.mongodb.scala._ +import org.mongodb.scala.bson.{BsonDateTime, Document} +import org.mongodb.scala.model._ + +import java.util.concurrent.TimeUnit +import scala.concurrent.Await +import scala.concurrent.duration.Duration + +object TimeSeries { + + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + + // Create a time series collection + // start-create-time-series + val database = mongoClient.getDatabase("fall_weather") + + val tsOptions = TimeSeriesOptions("timestamp").metaField("location") + val collectionOptions = CreateCollectionOptions().timeSeriesOptions(tsOptions) + + val createObservable = database.createCollection("october2024", collectionOptions) + Await.result(createObservable.toFuture(), Duration(10, TimeUnit.SECONDS)) + // end-create-time-series + + // Print the details of the collections in the database, including the time series collection + // start-print-time-series + val listObservable = database.listCollections() + val list = Await.result(listObservable.toFuture(), Duration(10, TimeUnit.SECONDS)) + val jsonSettings = JsonWriterSettings.builder().indent(true).build() + + list.foreach(collection => { + println(collection.toJson(jsonSettings)) + }) + // end-print-time-series + + // Insert data into the time series collection + // start-insert-time-series-data + val collection = database.getCollection("october2024") + + val temperatures = Seq( + Document("timestamp" -> BsonDateTime(1727755200000L), "temperature" -> 54, "location" -> "New York City"), + Document("timestamp" -> BsonDateTime(1727841600000L), "temperature" -> 55, "location" -> "New York City"), + ) + + val insertObservable = collection.insertMany(temperatures) + Await.result(insertObservable.toFuture(), Duration(10, TimeUnit.SECONDS)) + // end-insert-time-series-data + + mongoClient.close() + } +} From db65164bbb97371571e5469ca167eef2d3b6d834 Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Tue, 5 Nov 2024 13:50:32 -0500 Subject: [PATCH 22/44] DOCSP-42326: GridFS (#72) --- source/includes/figures/GridFS-upload.png | Bin 0 -> 145136 bytes source/includes/write/gridfs.scala | 95 +++++++++ source/write.txt | 1 + source/write/gridfs.txt | 225 ++++++++++++++++++++++ 4 files changed, 321 insertions(+) create mode 100644 source/includes/figures/GridFS-upload.png create mode 100644 source/includes/write/gridfs.scala create mode 100644 source/write/gridfs.txt diff --git a/source/includes/figures/GridFS-upload.png b/source/includes/figures/GridFS-upload.png new file mode 100644 index 0000000000000000000000000000000000000000..eff67c5f4989417e6a9b2cac581c02b17bebb480 GIT binary patch literal 145136 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 z

0FF6Ct?GDZIfpIbNMp{L10DdC5t_7G#Fm7J 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 Z5pM|EHPNrJHxYoJijpR%QNi-l{{hHFmA3!@ literal 0 HcmV?d00001 diff --git a/source/includes/write/gridfs.scala b/source/includes/write/gridfs.scala new file mode 100644 index 0000000..22a0ebb --- /dev/null +++ b/source/includes/write/gridfs.scala @@ -0,0 +1,95 @@ +import org.mongodb.scala._ +import org.mongodb.scala.bson.{Document, ObjectId} +import org.mongodb.scala.gridfs.GridFSBucket +import org.mongodb.scala.model._ + +import java.nio.ByteBuffer +import java.nio.charset.StandardCharsets +import java.util.concurrent.TimeUnit +import scala.concurrent.Await +import scala.concurrent.duration.Duration + +object GridFS { + + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + + // start-db-coll + val database: MongoDatabase = mongoClient.getDatabase("sample_gridfs") + // end-db-coll + + // start-create-bucket + val bucket = GridFSBucket(database) + // end-create-bucket + + // start-create-custom-bucket + val filesBucket = GridFSBucket(database, "files") + // end-create-custom-bucket + + { + // start-upload-files + // Get the input stream + val observableToUploadFrom = Observable( + Seq(ByteBuffer.wrap("MongoDB Tutorial".getBytes(StandardCharsets.UTF_8))) + ) + + // Create some custom options + val options = new GridFSUploadOptions() + .chunkSizeBytes(358400) + .metadata(Document("type" -> "presentation")) + + // Upload the file + val fileIdObservable = filesBucket.uploadFromObservable("mongodb-tutorial", observableToUploadFrom, options) + val fileId = Await.result(fileIdObservable.toFuture(), Duration(10, TimeUnit.SECONDS)) + println(s"File uploaded with id: ${fileId.toHexString}") + // end-upload-files + } + + { + // start-retrieve-file-info + val filesObservable = filesBucket.find() + val results = Await.result(filesObservable.toFuture(), Duration(10, TimeUnit.SECONDS)) + results.foreach(file => println(s" - ${file.getFilename}")) + // end-retrieve-file-info + } + + { + // start-retrieve-file-info-filter + val filesObservable = filesBucket.find(Filters.equal("metadata.contentType", "image/png")) + val results = Await.result(filesObservable.toFuture(), Duration(10, TimeUnit.SECONDS)) + results.foreach(file => println(s" - ${file.getFilename}")) + // end-retrieve-file-info-filter + } + + { + // start-download-files-id + val downloadObservable = filesBucket.downloadToObservable("") + val downloadById = Await.result(downloadObservable.toFuture(), Duration(10, TimeUnit.SECONDS)) + // end-download-files-id + } + + { + // start-download-files-name + val downloadObservable = filesBucket.downloadToObservable("mongodb-tutorial") + val downloadById = Await.result(downloadObservable.toFuture(), Duration(10, TimeUnit.SECONDS)) + // end-download-files-name + } + + { + // start-rename-files + val renameObservable = filesBucket.rename("", "mongodbTutorial") + Await.result(renameObservable.toFuture(), Duration(10, TimeUnit.SECONDS)) + // end-rename-files + } + + { + // start-delete-files + val deleteObservable = filesBucket.delete("") + Await.result(deleteObservable.toFuture(), Duration(10, TimeUnit.SECONDS)) + // end-delete-files + } + + Thread.sleep(1000) + mongoClient.close() + } +} diff --git a/source/write.txt b/source/write.txt index c598b77..4a0d9cf 100644 --- a/source/write.txt +++ b/source/write.txt @@ -28,6 +28,7 @@ Write Data to MongoDB /write/delete /write/bulk-write /write/transactions + /write/gridfs Overview -------- diff --git a/source/write/gridfs.txt b/source/write/gridfs.txt new file mode 100644 index 0000000..2e753ec --- /dev/null +++ b/source/write/gridfs.txt @@ -0,0 +1,225 @@ +.. _scala-gridfs: + +================================= +Store Large Files by Using GridFS +================================= + +.. contents:: On this page + :local: + :backlinks: none + :depth: 1 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: binary large object, blob, storage + +Overview +-------- + +In this guide, you can learn how to store and retrieve large files in +MongoDB by using **GridFS**. GridFS is a specification that describes how to split files +into chunks when storing them and reassemble them when retrieving them. The {+driver-short+}'s +implementation of GridFS is an abstraction that manages the operations and organization of +the file storage. + +Use GridFS if the size of your files exceeds the BSON document +size limit of 16MB. For more detailed information on whether GridFS is +suitable for your use case, see :manual:`GridFS ` in the +{+mdb-server+} manual. + +The following sections describe GridFS operations and how to +perform them. + +How GridFS Works +---------------- + +GridFS organizes files in a **bucket**, a group of MongoDB collections +that contain the chunks of files and information describing them. The +bucket contains the following collections, named using the convention +defined in the GridFS specification: + +- The ``chunks`` collection stores the binary file chunks. +- The ``files`` collection stores the file metadata. + +When you create a new GridFS bucket, the driver creates the ``fs.chunks`` and ``fs.files`` +collections, unless you specify a different name in the ``GridFSBucket()`` constructor. The +driver also creates an index on each collection to ensure efficient retrieval of the files and related +metadata. The driver creates the GridFS bucket, if it doesn't exist, only when the first write +operation is performed. The driver creates indexes only if they don't exist and when the +bucket is empty. For more information about +GridFS indexes, see :manual:`GridFS Indexes ` +in the {+mdb-server+} manual. + +When storing files with GridFS, the driver splits the files into smaller +chunks, each represented by a separate document in the ``chunks`` collection. +It also creates a document in the ``files`` collection that contains +a file ID, file name, and other file metadata. You can upload the file from +memory or from a stream. See the following diagram to see how GridFS splits +the files when uploaded to a bucket. + +.. figure:: /includes/figures/GridFS-upload.png + :alt: A diagram that shows how GridFS uploads a file to a bucket + +When retrieving files, GridFS fetches the metadata from the ``files`` +collection in the specified bucket and uses the information to reconstruct +the file from documents in the ``chunks`` collection. You can read the file +into memory or output it to a stream. + +Create a GridFS Bucket +---------------------- + +To store or retrieve files from GridFS, create a GridFS bucket by calling the +``GridFSBucket()`` constructor and passing in a ``MongoDatabase`` instance. +You can use the ``GridFSBucket`` instance to +call read and write operations on the files in your bucket. + +.. literalinclude:: /includes/write/gridfs.scala + :language: scala + :dedent: + :start-after: start-create-bucket + :end-before: end-create-bucket + +To create or reference a bucket with a custom name other than the default name +``fs``, pass your bucket name as the second parameter to the ``GridFSBucket()`` +constructor, as shown in the following example: + +.. literalinclude:: /includes/write/gridfs.scala + :language: scala + :dedent: + :start-after: start-create-custom-bucket + :end-before: end-create-custom-bucket + +Upload Files +------------ + +The ``GridFSBucket.uploadFromObservable()`` method reads the contents of an +``Observable[ByteBuffer]`` and saves it to the ``GridFSBucket`` instance. + +You can use the ``GridFSUploadOptions`` type to configure the chunk size or include +additional metadata. + +The following example uploads the contents of an ``Observable[ByteBuffer]`` into +``GridFSBucket``: + +.. literalinclude:: /includes/write/gridfs.scala + :language: scala + :dedent: + :start-after: start-upload-files + :end-before: end-upload-files + +Retrieve File Information +------------------------- + +In this section, you can learn how to retrieve file metadata stored in the +``files`` collection of the GridFS bucket. The metadata contains information +about the file it refers to, including: + +- The ``_id`` of the file +- The name of the file +- The length/size of the file +- The upload date and time +- A ``metadata`` document in which you can store any other information + +To learn more about fields you can retrieve from the ``files`` collection, see the +:manual:`GridFS Files Collection ` documentation in the +{+mdb-server+} manual. + +To retrieve files from a GridFS bucket, call the ``find()`` method on the ``GridFSBucket`` +instance. The following code example retrieves and prints file metadata from all files in +a GridFS bucket: + +.. literalinclude:: /includes/write/gridfs.scala + :language: scala + :dedent: + :start-after: start-retrieve-file-info + :end-before: end-retrieve-file-info + +To learn more about querying MongoDB, see :ref:``. + +Download Files +-------------- + +The ``downloadToObservable()`` method returns an ``Observable[ByteBuffer]`` +that reads the contents from MongoDB. + +To download a file by its file ``_id``, pass the ``_id`` to the method. +The following example downloads a file by its file ``_id``: + +.. literalinclude:: /includes/write/gridfs.scala + :language: scala + :dedent: + :start-after: start-download-files-id + :end-before: end-download-files-id + +If you don't know the ``_id`` of the file but know the filename, then you +can pass the filename to the ``downloadToObservable()`` method. The following example +downloads a file named ``mongodb-tutorial``: + +.. literalinclude:: /includes/write/gridfs.scala + :language: scala + :dedent: + :start-after: start-download-files-name + :end-before: end-download-files-name + +.. note:: + + If there are multiple documents with the same ``filename`` value, + GridFS will fetch the most recent file with the given name (as + determined by the ``uploadDate`` field). + +Rename Files +------------ + +Use the ``rename()`` method to update the name of a GridFS file in your +bucket. You must specify the file to rename by its ``_id`` field +rather than its file name. + +The following example renames a file to ``mongodbTutorial``: + +.. literalinclude:: /includes/write/gridfs.scala + :language: scala + :dedent: + :start-after: start-rename-files + :end-before: end-rename-files + +.. note:: + + The ``rename()`` method supports updating the name of only one file at + a time. To rename multiple files, retrieve a list of files matching the + file name from the bucket, extract the ``_id`` field from the files you + want to rename, and pass each value in separate calls to the ``rename()`` + method. + +Delete Files +------------ + +Use the ``delete()`` method to remove a file's collection document and associated +chunks from your bucket. You must specify the file by its ``_id`` field rather than its +file name. + +The following example deletes a file by its ``_id``: + +.. literalinclude:: /includes/write/gridfs.scala + :language: scala + :dedent: + :start-after: start-delete-files + :end-before: end-delete-files + +.. note:: + + The ``delete()`` method supports deleting only one file at a time. To + delete multiple files, retrieve the files from the bucket, extract + the ``_id`` field from the files you want to delete, and pass each value + in separate calls to the ``delete()`` method. + +API Documentation +----------------- + +To learn more about using GridFS to store and retrieve large files, +see the following API documentation: + +- `GridFSBucket <{+api+}/org/mongodb/scala/gridfs/GridFSBucket$.html>`__ \ No newline at end of file From 9d5e341164fb8d393e5d96b4d0ebc21361836bb5 Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Tue, 5 Nov 2024 13:56:47 -0500 Subject: [PATCH 23/44] DOCSP-42342: Aggregation (#71) * DOCSP-42342: Aggregation * edits * code imports * RR feedback * import fixes * JY feedback --- source/aggregation.txt | 232 ++++++++++++++++++++++++++++++ source/includes/aggregation.scala | 41 ++++++ source/index.txt | 11 +- 3 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 source/aggregation.txt create mode 100644 source/includes/aggregation.scala diff --git a/source/aggregation.txt b/source/aggregation.txt new file mode 100644 index 0000000..bb58e27 --- /dev/null +++ b/source/aggregation.txt @@ -0,0 +1,232 @@ +.. _scala-aggregation: + +==================================== +Transform Your Data with Aggregation +==================================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: code example, transform, computed, pipeline + :description: Learn how to use the Scala driver to perform aggregation operations. + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. TODO: + .. toctree:: + :titlesonly: + :maxdepth: 1 + + /aggregation/aggregation-tutorials + +Overview +-------- + +In this guide, you can learn how to use the {+driver-short+} to perform +**aggregation operations**. + +Aggregation operations process data in your MongoDB collections and +return computed results. The MongoDB Aggregation framework, which is +part of the Query API, is modeled on the concept of data processing +pipelines. Documents enter a pipeline that contains one or more stages, +and this pipeline transforms the documents into an aggregated result. + +An aggregation operation is similar to a car factory. A car factory has +an assembly line, which contains assembly stations with specialized +tools to do specific jobs, like drills and welders. Raw parts enter the +factory, and then the assembly line transforms and assembles them into a +finished product. + +The **aggregation pipeline** is the assembly line, **aggregation stages** are the +assembly stations, and **operator expressions** are the +specialized tools. + +Compare Aggregation and Find Operations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following table lists the different tasks that find +operations can perform and compares them to what aggregation +operations can perform. The aggregation framework provides +expanded functionality that allows you to transform and manipulate +your data. + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Find Operations + - Aggregation Operations + + * - | Select *certain* documents to return + | Select *which* fields to return + | Sort the results + | Limit the results + | Count the results + - | Select *certain* documents to return + | Select *which* fields to return + | Sort the results + | Limit the results + | Count the results + | Rename fields + | Compute new fields + | Summarize data + | Connect and merge data sets + +Limitations +~~~~~~~~~~~ + +Consider the following limitations when performing aggregation operations: + +- Returned documents cannot violate the + :manual:`BSON document size limit ` + of 16 megabytes. +- Pipeline stages have a memory limit of 100 megabytes by default. You can exceed this + limit by passing a value of ``true`` to the ``allowDiskUse()`` method and chaining the + method to ``aggregate()``. +- The :manual:`$graphLookup ` + operator has a strict memory limit of 100 megabytes and ignores the + value passed to the ``allowDiskUse()`` method. + +.. _scala-aggregation-example: + +Aggregation Example +------------------- + +.. note:: Sample Data + + The examples in this guide use the ``restaurants`` collection in the ``sample_restaurants`` + database from the :atlas:`Atlas sample datasets `. To learn how to create a + free MongoDB Atlas cluster and load the sample datasets, see the :atlas:`Get Started with Atlas + ` guide. + +To perform an aggregation, pass a list containing the pipeline stages to +the ``aggregate()`` method. The {+driver-short+} provides the ``Aggregates`` class, +which includes helper methods for building pipeline stages. + +.. tip:: + + To learn more about pipeline stages and their corresponding ``Aggregates`` helper + methods, see the following resources: + + - :manual:`Aggregation Stages ` in the + {+mdb-server+} manual + - `Aggregates <{+api+}/org/mongodb/scala/model/Aggregates$.html>`__ in the API documentation + +This code example produces a count of the number of bakeries in each borough +of New York. To do so, it calls the ``aggregate()`` method and passes an aggregation +pipeline as a list of stages. The code builds these stages by using the following +``Aggregates`` helper methods: + +- ``filter()``: Builds the :manual:`$match ` stage + to filter for documents that have a ``cuisine`` value of ``"Bakery"`` + +- ``group()``: Builds the :manual:`$group ` stage to + group the matching documents by the ``borough`` field, accumulating a count of documents for each + distinct value + +.. io-code-block:: + :copyable: + + .. input:: /includes/aggregation.scala + :start-after: start-match-group + :end-before: end-match-group + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id": "Brooklyn", "count": 173} + {"_id": "Queens", "count": 204} + {"_id": "Bronx", "count": 71} + {"_id": "Staten Island", "count": 20} + {"_id": "Missing", "count": 2} + {"_id": "Manhattan", "count": 221} + +Explain an Aggregation +~~~~~~~~~~~~~~~~~~~~~~ + +To view information about how MongoDB executes your operation, you can +instruct the MongoDB query planner to **explain** it. When MongoDB explains +an operation, it returns **execution plans** and performance statistics. +An execution plan is a potential way in which MongoDB can complete an operation. +When you instruct MongoDB to explain an operation, it returns both the +plan MongoDB executed and any rejected execution plans by default. + +To explain an aggregation operation, chain the ``explain()`` method to the +``aggregate()`` method. You can pass a verbosity level to ``explain()``, +which modifies the type and amount of information that the method returns. For more +information about verbosity, see :manual:`Verbosity Modes ` +in the {+mdb-server+} manual. + +The following example instructs MongoDB to explain the aggregation operation +from the preceding :ref:`scala-aggregation-example`. The code passes a verbosity +value of ``ExplainVerbosity.EXECUTION_STATS`` to the ``explain()`` method, which +configures the method to return statistics describing the execution of the winning +plan: + +.. io-code-block:: + :copyable: + + .. input:: /includes/aggregation.scala + :start-after: start-explain + :end-before: end-explain + :language: scala + :dedent: + + .. output:: + :visible: false + + {"explainVersion": "2", "queryPlanner": {"namespace": "sample_restaurants.restaurants", + "indexFilterSet": false, "parsedQuery": {"cuisine": {"$eq": "Bakery"}}, "queryHash": "865F14C3", + "planCacheKey": "0FC225DA", "optimizedPipeline": true, "maxIndexedOrSolutionsReached": false, + "maxIndexedAndSolutionsReached": false, "maxScansToExplodeReached": false, "winningPlan": + {"queryPlan": {"stage": "GROUP", "planNodeId": 3, "inputStage": {"stage": "COLLSCAN", + "planNodeId": 1, "filter": {"cuisine": {"$eq": "Bakery"}}, "direction": "forward"}}, + ...} + +Additional Information +---------------------- + +MongoDB Server Manual +~~~~~~~~~~~~~~~~~~~~~ + +To learn more about the topics discussed in this guide, see the following +pages in the {+mdb-server+} manual: + +- To view a full list of expression operators, see :manual:`Aggregation + Operators `. + +- To learn about assembling an aggregation pipeline and to view examples, see + :manual:`Aggregation Pipeline `. + +- To learn more about creating pipeline stages, see :manual:`Aggregation + Stages `. + +- To learn more about explaining MongoDB operations, see + :manual:`Explain Output ` and + :manual:`Query Plans `. + +.. TODO: + Aggregation Tutorials + ~~~~~~~~~~~~~~~~~~~~~ + +.. To view step-by-step explanations of common aggregation tasks, see +.. :ref:`scala-aggregation-tutorials-landing`. + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about the methods and types discussed in this guide, see the +following API documentation: + +- `aggregate() <{+api+}/org/mongodb/scala/MongoCollection.html#aggregate[C](pipeline:Seq[org.mongodb.scala.bson.conversions.Bson])(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[C,TResult],implicitct:scala.reflect.ClassTag[C]):org.mongodb.scala.AggregateObservable[C]>`__ +- `Aggregates <{+api+}/org/mongodb/scala/model/Aggregates$.html>`__ +- `explain() <{+api+}/org/mongodb/scala/AggregateObservable.html#explain[ExplainResult](verbosity:com.mongodb.ExplainVerbosity)(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[ExplainResult,org.mongodb.scala.Document],implicitct:scala.reflect.ClassTag[ExplainResult]):org.mongodb.scala.SingleObservable[ExplainResult]>`__ + diff --git a/source/includes/aggregation.scala b/source/includes/aggregation.scala new file mode 100644 index 0000000..945bbbc --- /dev/null +++ b/source/includes/aggregation.scala @@ -0,0 +1,41 @@ +import org.mongodb.scala._ +import org.mongodb.scala.model.{ Aggregates, Filters, Accumulators } +import org.mongodb.scala.bson.Document +import com.mongodb.ExplainVerbosity + +object Aggregation { + + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + + val database: MongoDatabase = mongoClient.getDatabase("sample_restaurants") + val collection: MongoCollection[Document] = database.getCollection("restaurants") + + // Retrieves documents with a cuisine value of "Bakery", groups them by "borough", and + // counts each borough's matching documents + // start-match-group + val pipeline = Seq(Aggregates.filter(Filters.equal("cuisine", "Bakery")), + Aggregates.group("$borough", Accumulators.sum("count", 1)) + ) + + collection.aggregate(pipeline) + .subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-match-group + + // Performs the same aggregation operation as above but asks MongoDB to explain it + // start-explain + val pipelineToExplain = Seq(Aggregates.filter(Filters.equal("cuisine", "Bakery")), + Aggregates.group("$borough", Accumulators.sum("count", 1)) + ) + + collection.aggregate(pipelineToExplain) + .explain(ExplainVerbosity.EXECUTION_STATS) + .subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-explain + + Thread.sleep(1000) + mongoClient.close() + } +} diff --git a/source/index.txt b/source/index.txt index 8929517..77f3886 100644 --- a/source/index.txt +++ b/source/index.txt @@ -17,6 +17,7 @@ /databases-collections /read /write + /aggregation /whats-new /compatibility View the Source @@ -72,12 +73,12 @@ section. .. Learn how to work with common types of indexes in the :ref:`scala-indexes` .. section. -.. TODO -.. Transform Your Data with Aggregation -.. ------------------------------------ -.. Learn how to use the {+driver-short+} to perform aggregation operations in the -.. :ref:`scala-aggregation` section. +Transform Your Data with Aggregation +------------------------------------ + +Learn how to use the {+driver-short+} to perform aggregation operations in the +:ref:`scala-aggregation` section. .. TODO .. Configure Operations on Replica Sets From 7fe74bf0d73c40ead462f74330fcfb2feff36df9 Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Wed, 6 Nov 2024 09:53:53 -0500 Subject: [PATCH 24/44] DOCSP-42316: Stable API (#81) --- snooty.toml | 3 +- source/connect/stable-api.txt | 109 ++++++++++++++++++++++- source/includes/connect/stable-api.scala | 38 ++++++++ 3 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 source/includes/connect/stable-api.scala diff --git a/snooty.toml b/snooty.toml index dfb82df..8d307aa 100644 --- a/snooty.toml +++ b/snooty.toml @@ -34,4 +34,5 @@ driver-source-gh = "https://github.com/mongodb/mongo-java-driver" rs-docs = "https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/org/reactivestreams" core-api = "https://mongodb.github.io/mongo-java-driver/{+version+}/apidocs/mongodb-driver-core" mongocrypt-version = "{+full-version+}" -mongodb-server = "MongoDB Server" \ No newline at end of file +mongodb-server = "MongoDB Server" +stable-api = "Stable API" \ No newline at end of file diff --git a/source/connect/stable-api.txt b/source/connect/stable-api.txt index f0533be..18e9f9b 100644 --- a/source/connect/stable-api.txt +++ b/source/connect/stable-api.txt @@ -21,4 +21,111 @@ Stable API The Stable API feature requires {+mdb-server+} 5.0 or later. -.. TODO \ No newline at end of file +Overview +-------- + +In this guide, you can learn how to specify **{+stable-api+}** compatibility when +connecting to a MongoDB deployment. + +The {+stable-api+} feature forces the server to run operations with behaviors compatible +with the API version you specify. Using the {+stable-api+} ensures consistent responses +from the server and provides long-term API stability for your application. + +The following sections describe how you can enable and customize {+stable-api+} for +your MongoDB client. For more information about the {+stable-api+}, including a list of +the commands it supports, see :manual:`Stable API ` in the +{+mdb-server+} manual. + +Enable the {+stable-api+} +------------------------- + +To enable the {+stable-api+}, perform the following steps: + +1. Construct a ``ServerApi`` object and specify a {+stable-api+} version. You must use + a {+stable-api+} version defined in the ``ServerApiVersion`` enum. +#. Construct a ``MongoClientSettings`` object by using the ``MongoClientSettings.Builder`` class. +#. Instantiate a ``MongoClient`` by using the constructor and + pass your ``MongoClientSettings`` instance as a parameter. + +The following code example shows how to specify {+stable-api+} version 1: + +.. literalinclude:: /includes/connect/stable-api.scala + :start-after: start-enable-stable-api + :end-before: end-enable-stable-api + :language: scala + :copyable: + :dedent: + +Once you create a ``MongoClient`` instance with +a specified API version, all commands that you run with the client use the specified +version. If you must run commands using more than one version of the +{+stable-api+}, create a new ``MongoClient``. + +Configure the {+stable-api+} +---------------------------- + +The following table describes {+stable-api+} options that you can set by calling methods +from the ``ServerApi`` class. You can use these options to customize the behavior of the +{+stable-api+}. + +.. list-table:: + :header-rows: 1 + :stub-columns: 1 + :widths: 25,75 + + * - Option Name + - Description + + * - strict + - | **Optional**. When ``true``, if you call a command that isn't part of + the declared API version, the driver raises an exception. + | + | Default: **false** + + * - deprecationErrors + - | **Optional**. When ``true``, if you call a command that is deprecated in the + declared API version, the driver raises an exception. + | + | Default: **false** + +The following code example shows how you can set the two options on an instance of ``ServerApi`` +by chaining methods on the ``ServerApi.Builder``: + +.. literalinclude:: /includes/connect/stable-api.scala + :start-after: start-stable-api-options + :end-before: end-stable-api-options + :language: scala + :copyable: + :dedent: + +Troubleshooting +--------------- + +Unrecognized field 'apiVersion' on server +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The {+driver-short+} raises this exception if you specify an API version and connect to a +MongoDB server that doesn't support the {+stable-api+}. Ensure you're connecting to a +deployment running {+mdb-server+} v5.0 or later. + +Provided apiStrict:true, but the command count is not in API Version +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The {+driver-short+} raises this exception if your ``MongoClient`` runs an operation that +isn't in the {+stable-api+} version you specified. To avoid this error, use an alternative +operation supported by the specified {+stable-api+} version, or set the ``strict`` +option to ``false`` when constructing your ``ServerApi`` object. + +API Documentation +----------------- + +For more information about using the {+stable-api+} with the {+driver-short+}, see the +following API documentation: + +- `ServerApi <{+core-api+}/com/mongodb/ServerApi.html>`__ +- `ServerApi.Builder <{+core-api+}/com/mongodb/ServerApi.Builder.html>`__ +- `ServerApiVersion <{+core-api+}/com/mongodb/ServerApiVersion.html>`__ +- `ServerAddress <{+api+}/org/mongodb/scala/ServerAddress$.html>`__ +- `MongoClientSettings <{+api+}/org/mongodb/scala/MongoClientSettings$.html>`__ +- `MongoClientSettings.Builder <{+core-api+}/com/mongodb/MongoClientSettings.Builder.html>`__ +- `MongoClient <{+api+}/org/mongodb/scala/MongoClient$.html>`__ diff --git a/source/includes/connect/stable-api.scala b/source/includes/connect/stable-api.scala new file mode 100644 index 0000000..d4836c8 --- /dev/null +++ b/source/includes/connect/stable-api.scala @@ -0,0 +1,38 @@ +import com.mongodb.{ServerApi, ServerApiVersion} +import org.mongodb.scala._ + +object StableAPI { + + def main(args: Array[String]): Unit = { + + { + // start-enable-stable-api + val serverApi = ServerApi.builder() + .version(ServerApiVersion.V1) + .build() + + // Replace the uri string placeholder with your MongoDB deployment's connection string + val uri = "" + + val settings = MongoClientSettings.builder() + .applyConnectionString(ConnectionString(uri)) + .serverApi(serverApi) + .build() + + val mongoClient = MongoClient(settings) + // end-enable-stable-api + } + + { + // start-stable-api-options + val serverApi = ServerApi.builder() + .version(ServerApiVersion.V1) + .strict(true) + .deprecationErrors(true) + .build() + // end-stable-api-options + } + + mongoClient.close() + } +} From 060c5c2c2304ecec2312cc15d8b3b2138dee31bc Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Wed, 6 Nov 2024 10:31:15 -0500 Subject: [PATCH 25/44] DOCSP-42336: Cluster monitoring (#73) * DOCSP-42336: Cluster monitoring * edits * fixes, code edit * MM feedback * remove unused imports * fix indent --- source/includes/monitoring/sdam.scala | 57 ++++++++ source/index.txt | 1 + source/monitoring.txt | 10 ++ source/monitoring/cluster-monitoring.txt | 158 +++++++++++++++++++++++ 4 files changed, 226 insertions(+) create mode 100644 source/includes/monitoring/sdam.scala create mode 100644 source/monitoring.txt create mode 100644 source/monitoring/cluster-monitoring.txt diff --git a/source/includes/monitoring/sdam.scala b/source/includes/monitoring/sdam.scala new file mode 100644 index 0000000..b329829 --- /dev/null +++ b/source/includes/monitoring/sdam.scala @@ -0,0 +1,57 @@ +import org.mongodb.scala._ +import org.mongodb.scala.connection._ +import com.mongodb.event._ + +object Sdam { + // start-listener-class + case class TestClusterListener(readPreference: ReadPreference) extends ClusterListener { + var isWritable: Boolean = false + var isReadable: Boolean = false + + override def clusterOpening(event: ClusterOpeningEvent): Unit = + println(s"Cluster with ID ${event.getClusterId} opening") + + override def clusterClosed(event: ClusterClosedEvent): Unit = + println(s"Cluster with ID ${event.getClusterId} closed") + + override def clusterDescriptionChanged(event: ClusterDescriptionChangedEvent): Unit = { + if (!isWritable) { + if (event.getNewDescription.hasWritableServer) { + isWritable = true + println("Writable server available") + } + } else { + if (!event.getNewDescription.hasWritableServer) { + isWritable = false + println("No writable server available") + } + } + + if (!isReadable) { + if (event.getNewDescription.hasReadableServer(readPreference)) { + isReadable = true + println("Readable server available") + } + } else { + if (!event.getNewDescription.hasReadableServer(readPreference)) { + isReadable = false + println("No readable server available") + } + } + } + } + // end-listener-class + + def main(args: Array[String]): Unit = { + // start-configure-client + val uri: ConnectionString = ConnectionString("") + val settings: MongoClientSettings = MongoClientSettings + .builder() + .applyToClusterSettings((builder: ClusterSettings.Builder) => + builder.addClusterListener(TestClusterListener(ReadPreference.secondary()))) + .applyConnectionString(uri) + .build() + val client: MongoClient = MongoClient(settings) + // end-configure-client + } +} diff --git a/source/index.txt b/source/index.txt index 77f3886..2a0a511 100644 --- a/source/index.txt +++ b/source/index.txt @@ -17,6 +17,7 @@ /databases-collections /read /write + /monitoring /aggregation /whats-new /compatibility diff --git a/source/monitoring.txt b/source/monitoring.txt new file mode 100644 index 0000000..dbd1d17 --- /dev/null +++ b/source/monitoring.txt @@ -0,0 +1,10 @@ +.. _scala-monitoring: + +======================== +Monitor Your Application +======================== + +.. toctree:: + :caption: Monitoring categories + + /monitoring/cluster-monitoring \ No newline at end of file diff --git a/source/monitoring/cluster-monitoring.txt b/source/monitoring/cluster-monitoring.txt new file mode 100644 index 0000000..841683f --- /dev/null +++ b/source/monitoring/cluster-monitoring.txt @@ -0,0 +1,158 @@ +.. _scala-cluster-monitoring: + +================== +Cluster Monitoring +================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: code example, server, topology + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecols + +Overview +-------- + +This guide shows you how to use the {+driver-short+} to monitor server +discovery and monitoring (SDAM) events in a MongoDB instance, replica +set, or sharded cluster. These events occur when there are any changes +in the state of the MongoDB instance or cluster that you are connected +to. + +The {+driver-short+} defines nine SDAM events and provides the following +listener interfaces, which listen for three SDAM events each: + +- ``ClusterListener``: Listens for events related to topology changes, + or changes in the state and structure of the cluster +- ``ServerListener``: Listens for events related to individual server + changes +- ``ServerMonitorListener``: Listens for heartbeat-related events, or + reports on the status of communication between replica set members + +You can use information about SDAM events in your application to +understand cluster changes, assess cluster health, or perform capacity +planning. + +.. _scala-subscribe-sdam: + +Subscribe to Events +------------------- + +You can access details about SDAM events by subscribing to them +in your application. To subscribe to an event, create a class that +implements the ``ClusterListener``, ``ServerListener``, or +``ServerMonitorListener`` interface. Then, add the listener to your +client by configuring an instance of ``MongoClientSettings`` and passing +it to the ``MongoClient`` constructor. + +The following code creates the ``TestClusterListener`` class, which implements +the ``ClusterListener`` interface. The class contains the following methods +to handle topology-related events: + +- ``clusterOpening()``: Prints a message when the driver first connects to a cluster +- ``clusterClosed()``: Prints a message when the driver disconnects from a cluster +- ``clusterDescriptionChanged()``: Prints a message about changes to the read + and write availability of the cluster + +.. literalinclude:: /includes/monitoring/sdam.scala + :start-after: start-listener-class + :end-before: end-listener-class + :language: scala + :copyable: + :dedent: + +Then, subscribe to the ``TestClusterListener`` class by configuring settings +for your ``MongoClient`` instance, as shown in the following code: + +.. literalinclude:: /includes/monitoring/sdam.scala + :start-after: start-configure-client + :end-before: end-configure-client + :language: scala + :copyable: + :dedent: + +When you run the application, your subscriber records the SDAM event and +outputs messages such as the following: + +.. code-block:: none + :copyable: false + + Cluster with ID ClusterId{value='...', description='...'} opening + Writable server available + Readable server available + Cluster with ID ClusterId{value='...', description='...'} closed + +Event Descriptions +------------------ + +You can subscribe to SDAM events by defining a class that implements +the event's corresponding listener interface and includes a method +to process the event. The following table provides the name of each SDAM event, +the listener interface that handles the event, and a description of when +the event is published: + +.. list-table:: + :widths: 20 20 60 + :header-rows: 1 + + * - Event Type + - Listener Interface + - Description + + * - `ClusterDescriptionChangedEvent <{+core-api+}/com/mongodb/event/ClusterDescriptionChangedEvent.html>`__ + - `ClusterListener <{+core-api+}/com/mongodb/event/ClusterListener.html>`__ + - Created when the topology description changes, such as when there + is an election of a new primary. + + * - `ClusterOpeningEvent <{+core-api+}/com/mongodb/event/ClusterOpeningEvent.html>`__ + - `ClusterListener <{+core-api+}/com/mongodb/event/ClusterListener.html>`__ + - Created when the driver first connects to the cluster. + + * - `ClusterClosedEvent <{+core-api+}/com/mongodb/event/ClusterClosedEvent.html>`__ + - `ClusterListener <{+core-api+}/com/mongodb/event/ClusterListener.html>`__ + - Created when the driver disconnects from the cluster. + + * - `ServerDescriptionChangedEvent <{+core-api+}/com/mongodb/event/ServerDescriptionChangedEvent.html>`__ + - `ServerListener <{+core-api+}/com/mongodb/event/ServerListener.html>`__ + - Created when the server description changes. + + * - `ServerOpeningEvent <{+core-api+}/com/mongodb/event/ServerOpeningEvent.html>`__ + - `ServerListener <{+core-api+}/com/mongodb/event/ServerListener.html>`__ + - Created when a new server is added to the topology. + + * - `ServerClosedEvent <{+core-api+}/com/mongodb/event/ServerClosedEvent.html>`__ + - `ServerListener <{+core-api+}/com/mongodb/event/ServerListener.html>`__ + - Created when an existing server is removed from the topology. + + * - `ServerHeartbeatStartedEvent <{+core-api+}/com/mongodb/event/ServerHeartbeatStartedEvent.html>`__ + - `ServerMonitorListener <{+core-api+}/com/mongodb/event/ServerMonitorListener.html>`__ + - Created when the server monitor sends a ``hello`` command to the server. + This action is called a heartbeat. + + * - `ServerHeartbeatSucceededEvent <{+core-api+}/com/mongodb/event/ServerHeartbeatSucceededEvent.html>`__ + - `ServerMonitorListener <{+core-api+}/com/mongodb/event/ServerMonitorListener.html>`__ + - Created when the heartbeat succeeds. + + * - `ServerHeartbeatFailedEvent <{+core-api+}/com/mongodb/event/ServerHeartbeatFailedEvent.html>`__ + - `ServerMonitorListener <{+core-api+}/com/mongodb/event/ServerMonitorListener.html>`__ + - Created when the heartbeat fails. + +To see a full list of event monitoring classes, see the `event <{+core-api+}/com/mongodb/event/package-summary.html>`__ +package in the Java API documentation. + +API Documentation +----------------- + +To learn more about any of the methods discussed in this guide, see the +following API documentation: + +- `clusterOpening() <{+core-api+}/com/mongodb/event/ClusterListener.html#clusterOpening(com.mongodb.event.ClusterOpeningEvent)>`__ +- `clusterClosed() <{+core-api+}/com/mongodb/event/ClusterListener.html#clusterClosed(com.mongodb.event.ClusterClosedEvent)>`__ +- `clusterDescriptionChanged() <{+core-api+}/com/mongodb/event/ClusterListener.html#clusterDescriptionChanged(com.mongodb.event.ClusterDescriptionChangedEvent)>`__ From e8d8ee079c2cdbf82180a5c433da82c44e569141 Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Fri, 8 Nov 2024 09:20:18 -0500 Subject: [PATCH 26/44] DOCSP-42341: Atlas Search Indexes (#82) --- .../includes/indexes/atlas-search-index.scala | 60 +++++++ source/indexes/atlas-search-index.txt | 160 ++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 source/includes/indexes/atlas-search-index.scala create mode 100644 source/indexes/atlas-search-index.txt diff --git a/source/includes/indexes/atlas-search-index.scala b/source/includes/indexes/atlas-search-index.scala new file mode 100644 index 0000000..88f38d8 --- /dev/null +++ b/source/includes/indexes/atlas-search-index.scala @@ -0,0 +1,60 @@ +import org.mongodb.scala._ +import org.mongodb.scala.model.SearchIndexModel + +import java.util.concurrent.TimeUnit +import scala.concurrent.Await +import scala.concurrent.duration.Duration + +object AtlasSearchIndexes { + + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + + val database = mongoClient.getDatabase("sample_mflix") + val collection = database.getCollection("movies") + + { + // start-create-search-index + val index = Document("mappings" -> Document("dynamic" -> true)) + val observable = collection.createSearchIndex("", index) + Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) + // end-create-search-index + } + + { + // start-create-search-indexes + val indexOne = SearchIndexModel("", Document("mappings" -> Document("dynamic" -> true, "fields" -> Document("field1" -> Document("type" -> "string"))))) + val indexTwo = SearchIndexModel("", Document("mappings" -> Document("dynamic" -> false, "fields" -> Document("field2" -> Document("type" -> "string"))))) + val observable = collection.createSearchIndexes(List(indexOne, indexTwo)) + Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) + // end-create-search-indexes + } + + { + // start-list-search-indexes + val observable = collection.listSearchIndexes() + observable.subscribe(new Observer[Document] { + override def onNext(index: Document): Unit = println(index.toJson()) + override def onError(e: Throwable): Unit = println("Error: " + e.getMessage) + override def onComplete(): Unit = println("Completed") + }) + // end-list-search-indexes + } + + { + // start-update-search-indexes + val updateIndex = Document("mappings" -> Document("dynamic" -> false)) + val observable = collection.updateSearchIndex("", updateIndex) + Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) + // end-update-search-indexes + } + + // start-drop-search-index + val observable = collection.dropSearchIndex("") + Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) + // end-drop-search-index + + Thread.sleep(1000) + mongoClient.close() + } +} diff --git a/source/indexes/atlas-search-index.txt b/source/indexes/atlas-search-index.txt new file mode 100644 index 0000000..2678f3e --- /dev/null +++ b/source/indexes/atlas-search-index.txt @@ -0,0 +1,160 @@ +.. _scala-atlas-search-index: + +==================== +Atlas Search Indexes +==================== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 1 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: index, query, optimization, efficiency + +Overview +-------- + +:atlas:`Atlas Search ` enables you to perform full-text searches on +collections hosted on MongoDB Atlas. Atlas Search indexes specify the behavior of +the search and which fields to index. + +You can call the following methods on a collection to manage your Atlas Search +indexes: + +- ``createSearchIndex()`` +- ``createSearchIndexes()`` +- ``listSearchIndexes()`` +- ``updateSearchIndex()`` +- ``dropSearchIndex()`` + +.. note:: + + The Atlas Search index management methods run asynchronously and might return before + confirming that they ran successfully. To determine the current status of the indexes, + call the ``listSearchIndexes()`` method. + +The following sections provide code examples that demonstrate how to use +each of the preceding methods. + +.. _scala-atlas-search-index-create: + +Create a Search Index +--------------------- + +You can use the ``createSearchIndex()`` and the ``createSearchIndexes()`` methods to +create one or more Atlas Search indexes. + +You can also use these methods to create Atlas Vector Search Indexes. +Atlas Vector Search enables you to perform semantic searches on vector +embeddings stored in MongoDB Atlas. To learn more about this feature, +see the :atlas:`Atlas Vector Search Overview +`. + +The following code example shows how to create an Atlas Search index: + +.. literalinclude:: /includes/indexes/atlas-search-index.scala + :language: scala + :start-after: start-create-search-index + :end-before: end-create-search-index + :dedent: + +The following code example shows how to create multiple indexes. Unlike the +``createSearchIndex()`` method, which assigns a default name to the created index, +you must provide index names for each index when using the ``createSearchIndexes()`` method. + +.. literalinclude:: /includes/indexes/atlas-search-index.scala + :language: scala + :start-after: start-create-search-indexes + :end-before: end-create-search-indexes + :dedent: + +To learn more about the syntax used to define Atlas Search indexes, see the +:atlas:`Review Atlas Search Index Syntax ` guide +in the Atlas manual. + +.. _scala-atlas-search-index-list: + +List Search Indexes +------------------- + +You can use the ``listSearchIndexes()`` method to return all Atlas Search indexes in a +collection. + +The following code example shows how to print a list of the search indexes in +a collection by using the ``Observable`` returned by the ``listSearchIndexes()``: + +.. .. literalinclude:: /includes/indexes/atlas-search-index.scala +.. :language: scala +.. :start-after: start-list-search-indexes +.. :end-before: end-list-search-indexes +.. :dedent: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/indexes/atlas-search-index.scala + :language: scala + :start-after: start-list-search-indexes + :end-before: end-list-search-indexes + :dedent: + + .. output:: + :visible: false + + {"id": "...", "name": "", "type": "search", "status": "READY", "queryable": true, ... } + {"id": "...", "name": "", "type": "search", "status": "READY", "queryable": true, ... } + Completed + +.. _scala-atlas-search-index-update: + +Update a Search Index +--------------------- + +You can use the ``updateSearchIndex()`` method to update an Atlas Search index. + +The following code shows how to update a search index: + +.. literalinclude:: /includes/indexes/atlas-search-index.scala + :language: scala + :start-after: start-update-search-indexes + :end-before: end-update-search-indexes + :dedent: + +.. _scala-atlas-search-index-drop: + +Delete a Search Index +--------------------- + +You can use the ``dropSearchIndex()`` method to delete an Atlas Search index. + +The following code shows how to delete a search index from a collection: + +.. literalinclude:: /includes/indexes/atlas-search-index.scala + :language: scala + :start-after: start-drop-search-index + :end-before: end-drop-search-index + :dedent: + +Additional Information +---------------------- + +To learn more about MongoDB Atlas Search, see the :atlas:`Atlas Search ` +documentation. + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about the methods and types mentioned in this guide, see the following API documentation: + +- `SearchIndexModel <{+api+}/org/mongodb/scala/model/package$$SearchIndexModel$.html>`_ +- `createSearchIndex() <{+api+}/org/mongodb/scala/MongoCollection.html#createSearchIndex(definition:org.mongodb.scala.bson.conversions.Bson):org.mongodb.scala.SingleObservable[String]>`__ +- `createSearchIndexes() <{+api+}/org/mongodb/scala/MongoCollection.html#createSearchIndexes(searchIndexModels:List[org.mongodb.scala.model.SearchIndexModel]):org.mongodb.scala.Observable[String]>`__ +- `listSearchIndexes() <{+api+}/org/mongodb/scala/MongoCollection.html#listSearchIndexes[C]()(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[C,org.mongodb.scala.Document],implicitct:scala.reflect.ClassTag[C]):org.mongodb.scala.ListSearchIndexesObservable[C]>`__ +- `updateSearchIndex() <{+api+}/org/mongodb/scala/MongoCollection.html#updateSearchIndex(indexName:String,definition:org.mongodb.scala.bson.conversions.Bson):org.mongodb.scala.SingleObservable[Unit]>`__ +- `dropSearchIndex() <{+api+}/org/mongodb/scala/MongoCollection.html#dropSearchIndex(indexName:String):org.mongodb.scala.SingleObservable[Unit]>`__ \ No newline at end of file From 734c3e040b85bd7379e33d7a9e460ed81d802610 Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Mon, 11 Nov 2024 09:26:39 -0500 Subject: [PATCH 27/44] Uncomment link to compat tables on landing page (#84) --- source/index.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/source/index.txt b/source/index.txt index 2a0a511..c55c965 100644 --- a/source/index.txt +++ b/source/index.txt @@ -95,12 +95,11 @@ Learn how to use the {+driver-short+} to perform aggregation operations in the .. Learn how to report bugs, contribute to the driver, and find more resources for .. asking questions and receiving help in the :ref:`Issues & Help ` section. -.. TODO -.. Compatibility -.. ------------ +Compatibility +------------- -.. To learn about the versions of the {+mdb-server+} and the C language that are compatible with -.. each version of the {+driver-short+}, see the :ref:`Compatibility ` section. +To learn about the versions of the {+mdb-server+} and the C language that are compatible with +each version of the {+driver-short+}, see the :ref:`Compatibility ` section. Developer Hub ------------- From 052dbf58931cfacc2af7602b47998bbca1d005be Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Mon, 11 Nov 2024 13:18:40 -0500 Subject: [PATCH 28/44] DOCSP-40287: Read landing page (#85) --- snooty.toml | 1 + .../usage-examples/ReadCodeExamples.scala | 83 +++++++++++ .../usage-examples/SampleReadApp.scala | 25 ++++ source/index.txt | 14 +- source/read.txt | 130 ++++++++++++++++++ 5 files changed, 245 insertions(+), 8 deletions(-) create mode 100644 source/includes/usage-examples/ReadCodeExamples.scala create mode 100644 source/includes/usage-examples/SampleReadApp.scala diff --git a/snooty.toml b/snooty.toml index 8d307aa..78e593a 100644 --- a/snooty.toml +++ b/snooty.toml @@ -18,6 +18,7 @@ toc_landing_pages = [ "/builders", "/get-started", "/databases-collections", + "/read", "/write" ] diff --git a/source/includes/usage-examples/ReadCodeExamples.scala b/source/includes/usage-examples/ReadCodeExamples.scala new file mode 100644 index 0000000..67613ff --- /dev/null +++ b/source/includes/usage-examples/ReadCodeExamples.scala @@ -0,0 +1,83 @@ +import org.mongodb.scala._ +import org.mongodb.scala.bson.Document +import org.mongodb.scala.model.Filters._ +import org.mongodb.scala.model.changestream._ + +object ReadCodeExamples { + + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + val database: MongoDatabase = mongoClient.getDatabase("") + val collection: MongoCollection[Document] = database.getCollection("") + + { + // start-find-one + val filter = equal("", "") + + collection.find(filter).first().subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-find-one + } + + { + // start-find-multiple + val filter = equal("", "") + + collection.find(filter).subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-find-multiple + } + + { + // start-count-collection + collection.countDocuments() + .subscribe((count: Long) => println(s"Number of documents: $count"), + (e: Throwable) => println(s"There was an error: $e")) + // end-count-collection + } + + { + // start-count-documents + collection.countDocuments(equal("", "")) + .subscribe((count: Long) => println(s"Number of documents: $count"), + (e: Throwable) => println(s"There was an error: $e")) + // end-count-documents + } + + { + // start-estimate-count + collection.estimatedDocumentCount() + .subscribe((count: Long) => println(s"Estimated number of documents: $count"), + (e: Throwable) => println(s"There was an error: $e")) + // end-estimate-count + } + + { + // start-retrieve-distinct + collection.distinct("") + .subscribe((value: String) => println(value), + (e: Throwable) => println(s"There was an error: $e")) + // end-retrieve-distinct + } + + { + // start-monitor-changes + val changeStreamObservable = collection.watch() + + changeStreamObservable.subscribe( + (changeEvent: ChangeStreamDocument[Document]) => { + println(s"Received a change to the collection: ${changeEvent}") + }, + (e: Throwable) => { + println(s"Encountered an error: ${e.getMessage}") + }, + () => println("Completed") + ) + // end-monitor-changes + } + + // Wait for the operations to complete before closing client + Thread.sleep(1000) + mongoClient.close() + } +} diff --git a/source/includes/usage-examples/SampleReadApp.scala b/source/includes/usage-examples/SampleReadApp.scala new file mode 100644 index 0000000..9f35c5f --- /dev/null +++ b/source/includes/usage-examples/SampleReadApp.scala @@ -0,0 +1,25 @@ + +import org.mongodb.scala._ +import org.mongodb.scala.bson.Document +import org.mongodb.scala.model.Filters._ +import org.mongodb.scala.model.changestream._ + +object SampleReadApp { + + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + val database: MongoDatabase = mongoClient.getDatabase("") + val collection: MongoCollection[Document] = database.getCollection("") + + + // Start example code here + + // End example code here + + // Wait for the operations to complete before closing client + // Note: This example uses Thread.sleep() for brevity and does not guarantee all + // operations will be completed in time + Thread.sleep(1000) + mongoClient.close() + } +} diff --git a/source/index.txt b/source/index.txt index c55c965..d6e3e59 100644 --- a/source/index.txt +++ b/source/index.txt @@ -55,17 +55,15 @@ What's New For a list of new features and changes in each version, see the :ref:`What's New ` section. -.. TODO -.. Write Data to MongoDB -.. --------------------- +Write Data to MongoDB +--------------------- -.. Learn how you can write data to MongoDB in the :ref:`scala-write` section. +Learn how you can write data to MongoDB in the :ref:`scala-write` section. -.. TODO -.. Read Data from MongoDB -.. ---------------------- +Read Data from MongoDB +---------------------- -.. Learn how you can retrieve data from MongoDB in the :ref:`scala-read` section. +Learn how you can retrieve data from MongoDB in the :ref:`scala-read` section. .. TODO .. Optimize Queries by Using Indexes diff --git a/source/read.txt b/source/read.txt index b6d3e7a..1366a6c 100644 --- a/source/read.txt +++ b/source/read.txt @@ -30,5 +30,135 @@ Read Data from MongoDB /read/count /read/change-streams +Overview +-------- +On this page, you can see copyable code examples that show common {+driver-short+} methods you +can use to read data from MongoDB. +.. tip:: + + To learn more about any of the methods shown on this page, see the link + provided in each section. + +To use an example from this page, copy the code example into the +:ref:`sample application ` or your own application. +Be sure to replace all placeholders in the code examples, such as ````, with +the relevant values for your MongoDB deployment. + +.. _scala-read-sample: + +Sample Application +~~~~~~~~~~~~~~~~~~ + +.. include:: /includes/usage-examples/sample-app-intro.rst + +.. literalinclude:: /includes/usage-examples/SampleReadApp.scala + :language: scala + :copyable: + :linenos: + :emphasize-lines: 14-16 + +Find One +-------- + +The following example retrieves a document that matches the criteria specified by the +given filter: + +.. literalinclude:: /includes/usage-examples/ReadCodeExamples.scala + :start-after: start-find-one + :end-before: end-find-one + :language: scala + :copyable: + :dedent: + +To learn more about the ``first()`` method, see the :ref:`Retrieve Data ` guide. + +Find Multiple +------------- + +The following example retrieves all documents that match the criteria specified by the +given filter: + +.. literalinclude:: /includes/usage-examples/ReadCodeExamples.scala + :start-after: start-find-multiple + :end-before: end-find-multiple + :language: scala + :copyable: + :dedent: + +To learn more about the ``find()`` method, see the :ref:`Retrieve Data ` guide. + +Count Documents in a Collection +------------------------------- + +The following example returns the number of documents in the specified collection: + +.. literalinclude:: /includes/usage-examples/ReadCodeExamples.scala + :start-after: start-count-collection + :end-before: end-count-collection + :language: scala + :copyable: + :dedent: + +To learn more about the ``countDocuments()`` method, see the :ref:`Count Documents ` guide. + +Count Documents Returned from a Query +------------------------------------- + +The following example returns the number of documents in the specified +collection that match query criteria: + +.. literalinclude:: /includes/usage-examples/ReadCodeExamples.scala + :start-after: start-count-documents + :end-before: end-count-documents + :language: scala + :copyable: + :dedent: + +To learn more about the ``countDocuments()`` method, see the :ref:`Count Documents ` guide. + +Estimated Document Count +------------------------ + +The following example returns an approximate number of documents in the specified +collection based on collection metadata: + +.. literalinclude:: /includes/usage-examples/ReadCodeExamples.scala + :start-after: start-estimate-count + :end-before: end-estimate-count + :language: scala + :copyable: + :dedent: + +To learn more about the ``estimatedDocumentCount()`` method, see the :ref:`Count Documents ` guide. + +Retrieve Distinct Values +------------------------ + +The following example returns all distinct values of the specified field name in a given +collection: + +.. literalinclude:: /includes/usage-examples/ReadCodeExamples.scala + :start-after: start-retrieve-distinct + :end-before: end-retrieve-distinct + :language: scala + :copyable: + :dedent: + +To learn more about the ``distinct()`` method, see the :ref:`` guide. + +Monitor Data Changes +-------------------- + +The following example creates a change stream for a given collection and prints out +subsequent change events in that collection: + +.. literalinclude:: /includes/usage-examples/ReadCodeExamples.scala + :start-after: start-monitor-changes + :end-before: end-monitor-changes + :language: scala + :copyable: + :dedent: + +To learn more about the ``watch()`` method, see the :ref:`Monitor Data Changes ` guide. From 3dc6ae63f4e481a5a9045743515e07ffe785d2ee Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Tue, 12 Nov 2024 10:48:23 -0500 Subject: [PATCH 29/44] DOCSP-42343: Secure Your Data + Authentication Mechanisms (#83) --- snooty.toml | 3 +- source/includes/security/auth.scala | 93 ++++++++++++++++ source/index.txt | 7 ++ source/security.txt | 35 ++++++ source/security/auth.txt | 159 ++++++++++++++++++++++++++++ 5 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 source/includes/security/auth.scala create mode 100644 source/security.txt create mode 100644 source/security/auth.txt diff --git a/snooty.toml b/snooty.toml index 78e593a..e9803f3 100644 --- a/snooty.toml +++ b/snooty.toml @@ -19,7 +19,8 @@ toc_landing_pages = [ "/get-started", "/databases-collections", "/read", - "/write" + "/write", + "/security" ] [constants] diff --git a/source/includes/security/auth.scala b/source/includes/security/auth.scala new file mode 100644 index 0000000..9d2e8e5 --- /dev/null +++ b/source/includes/security/auth.scala @@ -0,0 +1,93 @@ +import org.mongodb.scala._ + +import java.util.Collections + +object StableAPI { + + def main(args: Array[String]): Unit = { + + { + // start-default + val user = "" // the username + val source = "" // the source where the user is defined + val password = ... // the password as a character array + + val credential = MongoCredential.createCredential(user, source, password) + val mongoClient = MongoClient(MongoClientSettings + .builder() + .applyToClusterSettings(builder => + builder.hosts(Collections.singletonList(ServerAddress("localhost", 27017)))) + .credential(credential) + .build()) + // end-default + } + + { + // start-default-connection-string + val mongoClient = MongoClient("mongodb://user1:pwd1@host1/?authSource=db1") + // end-default-connection-string + } + + { + // start-scram-sha-256 + val user = "" // the username + val source = "" // the source where the user is defined + val password = ... // the password as a character array + + val credential = MongoCredential.createScramSha256Credential(user, source, password) + val mongoClient = MongoClient(MongoClientSettings + .builder() + .applyToClusterSettings(builder => + builder.hosts(Collections.singletonList(ServerAddress("localhost", 27017)))) + .credential(credential) + .build()) + // end-scram-sha-256 + } + + { + // start-scram-sha-256-connection-string + val mongoClient = MongoClient("mongodb://user1:pwd1@host1/?authSource=db1&authMechanism=SCRAM-SHA-256") + // end-scram-sha-256-connection-string + } + + { + // start-scram-sha-1 + val user = "" // the username + val source = "" // the source where the user is defined + val password = ... // the password as a character array + + val credential = MongoCredential.createScramSha1Credential(user, source, password) + val mongoClient = MongoClient(MongoClientSettings + .builder() + .applyToClusterSettings(builder => + builder.hosts(Collections.singletonList(ServerAddress("localhost", 27017)))) + .credential(credential) + .build()) + // end-scram-sha-1 + } + + { + // start-scram-sha-1-connection-string + val mongoClient = MongoClient("mongodb://user1:pwd1@host1/?authSource=db1&authMechanism=SCRAM-SHA-1") + // end-scram-sha-1-connection-string + } + + { + // start-mongodb-x509 + val credential = MongoCredential.createMongoX509Credential() + val mongoClient = MongoClient(MongoClientSettings + .builder() + .applyToClusterSettings(builder => + builder.hosts(Collections.singletonList(ServerAddress("localhost", 27017)))) + .credential(credential) + .build()) + // end-mongodb-x509 + } + + { + // start-mongodb-x509-connection-string + val mongoClient = MongoClient("mongodb://subjectName@host1/?authMechanism=MONGODB-X509&ssl=true") + // end-mongodb-x509-connection-string + } + } +} diff --git a/source/index.txt b/source/index.txt index d6e3e59..6bb3f41 100644 --- a/source/index.txt +++ b/source/index.txt @@ -19,6 +19,7 @@ /write /monitoring /aggregation + /security /whats-new /compatibility View the Source @@ -79,6 +80,12 @@ Transform Your Data with Aggregation Learn how to use the {+driver-short+} to perform aggregation operations in the :ref:`scala-aggregation` section. +Secure Your Data +---------------- + +Learn how to authenticate your application and encrypt your data in the +:ref:`scala-security` section. + .. TODO .. Configure Operations on Replica Sets .. ------------------------------------ diff --git a/source/security.txt b/source/security.txt new file mode 100644 index 0000000..bbd41f1 --- /dev/null +++ b/source/security.txt @@ -0,0 +1,35 @@ +.. _scala-security: + +================ +Secure Your Data +================ + +.. contents:: On this page + :local: + :backlinks: none + :depth: 1 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ldap, encryption, principal, tls, authorize, ecs, aws + :description: Learn how to use the Scala driver to secure your data. + +.. toctree:: + :titlesonly: + :maxdepth: 1 + + /security/auth + +Overview +-------- + +MongoDB supports multiple mechanisms that you can use to secure your +application. To learn more about how to secure your application, see the +following sections: + +- Learn how to use different authentication mechanisms to secure your data in + the :ref:`Authentication Mechanisms ` guide. diff --git a/source/security/auth.txt b/source/security/auth.txt new file mode 100644 index 0000000..f3ce4c7 --- /dev/null +++ b/source/security/auth.txt @@ -0,0 +1,159 @@ +.. _scala-auth: + +========================= +Authentication Mechanisms +========================= + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: code example, verify, AWS, Kerberos + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +The {+driver-short+} supports all MongoDB authentication mechanisms, +including those available only in the MongoDB Enterprise Edition. + +MongoCredential +--------------- + +An authentication credential is represented as an instance of the +``MongoCredential`` class. The ``MongoCredential`` class includes +static factory methods for each of the supported authentication +mechanisms. + +Default Authentication Mechanism +-------------------------------- + +In MongoDB 3.0, MongoDB changed the default authentication mechanism +from ``MONGODB-CR`` to ``SCRAM-SHA-1``. In MongoDB 4.0, support for +the deprecated ``MONGODB-CR`` mechanism was removed and ``SCRAM-SHA-256`` support was +added. + +To create a credential that authenticates by using the default +authentication mechanism, regardless of server version, create a +credential by using the ``createCredential()`` static factory method: + +.. literalinclude:: /includes/security/auth.scala + :start-after: start-default + :end-before: end-default + :language: scala + :copyable: + :dedent: + +Or, you can use a connection string without explicitly specifying the +authentication mechanism: + +.. literalinclude:: /includes/security/auth.scala + :start-after: start-default-connection-string + :end-before: end-default-connection-string + :language: scala + :copyable: + :dedent: + +For challenge and response mechanisms, we recommend using the default +authentication mechanism. This approach simplifies upgrading from MongoDB 2.6 to MongoDB +3.0, even after upgrading the authentication schema. For MongoDB 4.0 users, we also recommend +the default authentication mechanism because it checks the mechanisms and uses the correct +hashing algorithm. + +SCRAM-Based Mechanisms +---------------------- + +Salted Challenge-Response Authentication Mechanism (``SCRAM``) has been +the default authentication mechanism for MongoDB since 3.0. ``SCRAM`` is +based on the `IETF RFC 5802 +`__ standard that defines +best practices for implementation of challenge-response mechanisms for authenticating +users with passwords. + +MongoDB 3.0 introduced support for ``SCRAM-SHA-1``, which uses the +``SHA-1`` hashing function. MongoDB 4.0 introduced support for ``SCRAM-SHA-256`` which +uses the ``SHA-256`` hashing function. + +SCRAM-SHA-256 +~~~~~~~~~~~~~ + +Using this mechanism requires MongoDB 4.0 and +``featureCompatibilityVersion`` to be set to 4.0. + +To explicitly create a credential of type ``SCRAM-SHA-256``, use +the ``createScramSha256Credential()`` method: + +.. literalinclude:: /includes/security/auth.scala + :start-after: start-scram-sha-256 + :end-before: end-scram-sha-256 + :language: scala + :copyable: + :dedent: + +Or, you can use a connection string that explicitly specifies +``authMechanism=SCRAM-SHA-256``: + +.. literalinclude:: /includes/security/auth.scala + :start-after: start-scram-sha-256-connection-string + :end-before: end-scram-sha-256-connection-string + :language: scala + :copyable: + :dedent: + +SCRAM-SHA-1 +~~~~~~~~~~~ + +To explicitly create a credential of type ``SCRAM-SHA-1``, use the +``createScramSha1Credential()`` method: + +.. literalinclude:: /includes/security/auth.scala + :start-after: start-scram-sha-1 + :end-before: end-scram-sha-1 + :language: scala + :copyable: + :dedent: + +Or, you can use a connection string that explicitly specifies +``authMechanism=SCRAM-SHA-1``: + +.. literalinclude:: /includes/security/auth.scala + :start-after: start-scram-sha-1-connection-string + :end-before: end-scram-sha-1-connection-string + :language: scala + :copyable: + :dedent: + +x.509 +----- + +With the x.509 mechanism, MongoDB uses the x.509 certificate presented +during SSL negotiation to authenticate a user whose name is derived +from the distinguished name of the x.509 certificate. + +x.509 authentication requires the use of SSL connections with +certificate validation. To create a credential of this type use the +``createMongoX509Credential()`` static factory method: + +.. literalinclude:: /includes/security/auth.scala + :start-after: start-mongodb-x509 + :end-before: end-mongodb-x509 + :language: scala + :copyable: + :dedent: + +Or, you can use a connection string that explicitly specifies +``authMechanism=MONGODB-X509``: + +.. literalinclude:: /includes/security/auth.scala + :start-after: start-mongodb-x509-connection-string + :end-before: end-mongodb-x509-connection-string + :language: scala + :copyable: + :dedent: + +See the :manual:`Use x.509 Certificates to Authenticate Clients ` +tutorial in the Server manual to learn more about using x.509 certificates in your +application. From 2a10a119b8b56082f94944868e7e4ab584de9f4c Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Thu, 14 Nov 2024 14:39:15 -0500 Subject: [PATCH 30/44] DOCSP-42319: Read and write settings (#79) * DOCSP-42319: Read and write settings * edits * code comments * MW feedback * code edits * JY feedback * imports * edits * JY feedback 2 * build --- source/includes/read-write-pref.scala | 96 +++++++++ source/index.txt | 10 +- source/read-write-pref.txt | 278 ++++++++++++++++++++++++++ 3 files changed, 379 insertions(+), 5 deletions(-) create mode 100644 source/includes/read-write-pref.scala create mode 100644 source/read-write-pref.txt diff --git a/source/includes/read-write-pref.scala b/source/includes/read-write-pref.scala new file mode 100644 index 0000000..5df82ee --- /dev/null +++ b/source/includes/read-write-pref.scala @@ -0,0 +1,96 @@ +import org.mongodb.scala._ +import com.mongodb.{ TransactionOptions, ReadConcern, ReadPreference, WriteConcern } +import java.util.concurrent.TimeUnit +import scala.concurrent.Await +import scala.concurrent.duration.Duration +import scala.jdk.CollectionConverters._ + +object ReadWritePref { + + def main(args: Array[String]): Unit = { + + // Uses the settings builder methods to set read and write settings for the client + // start-client-settings + val mongoClient = MongoClient(MongoClientSettings.builder() + .applyConnectionString(ConnectionString("mongodb://localhost:27017/")) + .readPreference(ReadPreference.secondary()) + .readConcern(ReadConcern.LOCAL) + .writeConcern(WriteConcern.W2) + .build()) + // end-client-settings + + // Uses connection URI parameters to read and write settings for the client + // start-client-settings-uri + val uriClient = MongoClient("mongodb://localhost:27017/?readPreference=secondary&w=2&readConcernLevel=local") + // end-client-settings-uri + + // Sets read and write settings for the transaction + // start-transaction-settings + val clientSessionFuture = mongoClient.startSession().toFuture() + val clientSession = Await.result(clientSessionFuture, Duration(10, TimeUnit.SECONDS)) + + val tOptions: TransactionOptions = TransactionOptions.builder() + .readPreference(ReadPreference.primary()) + .readConcern(ReadConcern.MAJORITY) + .writeConcern(WriteConcern.W1) + .build() + clientSession.startTransaction(tOptions) + // end-transaction-settings + + // Sets read and write settings for the "test_database" database + // start-database-settings + val database = mongoClient.getDatabase("test_database") + .withReadPreference(ReadPreference.primaryPreferred()) + .withReadConcern(ReadConcern.AVAILABLE) + .withWriteConcern(WriteConcern.MAJORITY) + // end-database-settings + + // Sets read and write settings for the "test_collection" collection + // start-collection-settings + val collection = database.getCollection("test_collection") + .withReadPreference(ReadPreference.secondaryPreferred()) + .withReadConcern(ReadConcern.AVAILABLE) + .withWriteConcern(WriteConcern.UNACKNOWLEDGED) + // end-collection-settings + + // Instructs the driver to prefer reads from secondary replica set members + // located in New York, followed by a secondary in San Francisco, and + // lastly fall back to any secondary. + // start-tag-set + val tag1 = new TagSet(new Tag("dc", "ny")) + val tag2 = new TagSet(new Tag("dc", "sf")) + val tag3 = new TagSet() + + val readPreference = ReadPreference.secondary(List(tag1, tag2, tag3).asJava) + + val database = mongoClient.getDatabase("test_database") + .withReadPreference(readPreference) + // end-tag-set + + // Instructs the library to distribute reads between members within 35 milliseconds + // of the closest member's ping time using client settings + // start-local-threshold-uri + val connectionString = "mongodb://localhost:27017/?replicaSet=repl0&localThresholdMS=35" + val client = MongoClient(connectionString) + // end-local-threshold-uri + + // Instructs the library to distribute reads between members within 35 milliseconds + // of the closest member's ping time using a URI option + // start-local-threshold-settings + val client = MongoClient(MongoClientSettings.builder() + .applyConnectionString(ConnectionString("mongodb://localhost:27017/")) + .applyToClusterSettings(builder => builder + .localThreshold(35, TimeUnit.MILLISECONDS) + ) + .build()) + // end-local-threshold-settings + + // Keep the main thread alive long enough for the asynchronous operations to complete + Thread.sleep(5000) + + // Close the MongoClient connection + mongoClient.close() + + } + +} \ No newline at end of file diff --git a/source/index.txt b/source/index.txt index 6bb3f41..1c3ae62 100644 --- a/source/index.txt +++ b/source/index.txt @@ -21,6 +21,7 @@ /aggregation /security /whats-new + /read-write-pref /compatibility View the Source API Documentation <{+api+}/index.html> @@ -86,12 +87,11 @@ Secure Your Data Learn how to authenticate your application and encrypt your data in the :ref:`scala-security` section. -.. TODO -.. Configure Operations on Replica Sets -.. ------------------------------------ +Configure Operations on Replica Sets +------------------------------------ -.. Learn how to configure read and write operations on a replica set in the -.. :ref:`scala-read-write-config` section. +Learn how to configure read and write operations on a replica set in the +:ref:`scala-read-write-pref` section. .. TODO .. Issues & Help diff --git a/source/read-write-pref.txt b/source/read-write-pref.txt new file mode 100644 index 0000000..8810c8c --- /dev/null +++ b/source/read-write-pref.txt @@ -0,0 +1,278 @@ +.. _scala-read-write-pref: + +==================================== +Configure Operations on Replica Sets +==================================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: customize, preferences, replica set, consistency + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to configure **write concern**, **read concern**, +and **read preference** options to modify the way that the {+driver-short+} runs create, +read, update, and delete (CRUD) operations on replica sets. + +Read and Write Settings Precedence +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can set write concern, read concern, and read preference options at the following +levels: + +- Client, which sets the *default for all operation executions* unless overridden +- Transaction +- Database +- Collection + +This list also indicates the increasing order of precedence of the option settings. For +example, if you set a read concern level for a transaction, it will override a read +concern level inherited from the client. + +Write concern, read concern, and read preference options allow you to customize the +causal consistency and availability of the data in your replica sets. To see a full +list of these options, see the following guides in the {+mdb-server+} manual: + +- :manual:`Read Preference ` +- :manual:`Read Concern ` +- :manual:`Write Concern ` + +.. _scala-read-write-config: + +Configure Read and Write Operations +----------------------------------- + +You can control how the driver routes read operations among replica set members +by setting a read preference. You can also control how the driver waits for +acknowledgment of read and write operations on a replica set by setting read and +write concerns. + +The following sections show how to configure these read and write settings +at various levels. + +.. _scala-read-write-client: + +Client Configuration +~~~~~~~~~~~~~~~~~~~~ + +This example shows how to set the read preference, read concern, and +write concern of a ``MongoClient`` instance by passing a ``MongoClientSettings`` +instance to the constructor. The code configures the following settings: + +- ``secondary`` read preference: Read operations retrieve data from + secondary replica set members. +- ``LOCAL`` read concern: Read operations return the instance's most recent data + without guaranteeing that the data has been written to a majority of the replica + set members. +- ``W2`` write concern: The primary replica set member and one secondary member + must acknowledge the write operation. + +.. literalinclude:: /includes/read-write-pref.scala + :language: scala + :dedent: + :start-after: start-client-settings + :end-before: end-client-settings + +Alternatively, you can specify the read and write settings in the connection +URI, which is passed as a parameter to the ``MongoClient`` constructor: + +.. literalinclude:: /includes/read-write-pref.scala + :language: scala + :dedent: + :start-after: start-client-settings-uri + :end-before: end-client-settings-uri + +.. _scala-read-write-transaction: + +Transaction Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~ + +This example shows how to set the read preference, read concern, and +write concern of a transaction by passing a ``TransactionOptions`` +instance to the ``startTransaction()`` method. Transactions run within +*sessions*, which are groupings of related read or write operations that you +intend to run sequentially. Before configuring transaction options, create a +``ClientSession`` instance to start a session. + +.. tip:: + + To learn more about sessions, see :manual:`Server Sessions ` + in the {+mdb-server+} manual. + +The example configures the following settings: + +- ``primary`` read preference: Read operations retrieve data from + the primary replica set member. +- ``MAJORITY`` read concern: Read operations return the instance's most recent data + that has been written to a majority of replica set members. +- ``W1`` write concern: The primary replica set member must acknowledge the + write operation. + +.. literalinclude:: /includes/read-write-pref.scala + :language: scala + :dedent: + :start-after: start-transaction-settings + :end-before: end-transaction-settings + +.. _scala-read-write-database: + +Database Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +This example shows how to set the read preference, read concern, and +write concern of a database called ``test_database`` by chaining setter +methods to the ``getDatabase()`` method. The code configures the following +settings: + +- ``primaryPreferred`` read preference: Read operations retrieve data from + the primary replica set member, or secondary members if the primary is unavailable. +- ``AVAILABLE`` read concern: Read operations return the instance's most recent data + without guaranteeing that the data has been written to a majority of the replica + set members. +- ``MAJORITY`` write concern: The majority of all replica set members + must acknowledge the write operation. + +.. literalinclude:: /includes/read-write-pref.scala + :language: scala + :dedent: + :start-after: start-database-settings + :end-before: end-database-settings + +.. _scala-read-write-collection: + +Collection Configuration +~~~~~~~~~~~~~~~~~~~~~~~~ + +This example shows how to set the read preference, read concern, and +write concern of a collection called ``test_collection`` by chaining setter +methods to the ``getCollection()`` method. The code configures the following +settings: + +- ``secondaryPreferred`` read preference: Read operations retrieve data from + secondary replica set members, or the primary members if no secondary members are + available. +- ``AVAILABLE`` read concern: Read operations return the instance's most recent data + without guaranteeing that the data has been written to a majority of the replica + set members. +- ``UNACKNOWLEDGED`` write concern: Replica set members do not need to acknowledge + the write operation. + +.. literalinclude:: /includes/read-write-pref.scala + :language: scala + :dedent: + :start-after: start-collection-settings + :end-before: end-collection-settings + +.. _scala-read-write-advanced: + +Advanced Read Configurations +---------------------------- + +The following sections describe ways to further customize how the {+driver-short+} +reads from replica set members. + +.. _scala-tag-sets: + +Tag Sets +~~~~~~~~ + +In {+mdb-server+}, you can apply key-value :manual:`tags +` to replica set members +according to any criteria you choose. You can then use those +tags to target one or more members for a read operation. + +By default, the {+driver-short+} ignores tags when choosing a member +to read from. To instruct the {+driver-short+} to prefer certain tags, +pass the tags as a list to your read preference setter method. + +Suppose you are connected to a replica set that contains members hosted +at multiple data centers across the United States. You want the driver to +prefer reads from secondary replica set members in the following order: + +1. Members from the New York data center, tagged with ``("dc", "ny")`` +#. Members from the San Francisco data center, tagged with ``("dc", "sf")`` +#. Any secondary members + +This code example passes a list of tags representing the preceding replica +set members to the ``ReadPreference.secondary()`` setter method. Then, the code +passes the read preference information to the ``withReadPreference()`` method +to set the read order on the database: + +.. literalinclude:: /includes/read-write-pref.scala + :language: scala + :dedent: + :start-after: start-tag-set + :end-before: end-tag-set + +.. _scala-local-threshold: + +Local Threshold +~~~~~~~~~~~~~~~ + +If multiple replica set members match the read preference and tag sets that you specify, +the {+driver-short+} reads from the nearest replica set members, chosen according to +their ping time. + +By default, the driver uses only members whose ping times are within 15 milliseconds +of the nearest member for queries. To distribute reads among members with +higher latencies, set the ``localThreshold`` option in a ``MongoClientSettings`` +instance or the ``localThresholdMS`` option in your connection URI. + +The following example specifies a local threshold of 35 milliseconds. Select +the :guilabel:`MongoClientSettings` or :guilabel:`Connection URI` tab to see +corresponding code for each approach: + +.. tabs:: + + .. tab:: MongoClientSettings + :tabid: settings + + .. literalinclude:: /includes/read-write-pref.scala + :language: rust + :dedent: + :start-after: start-local-threshold-settings + :end-before: end-local-threshold-settings + + + .. tab:: Connection URI + :tabid: uri + + .. literalinclude:: /includes/read-write-pref.scala + :language: rust + :dedent: + :start-after: start-local-threshold-uri + :end-before: end-local-threshold-uri + +In the preceding example, the {+driver-short+} distributes reads among matching members +within 35 milliseconds of the closest member's ping time. + +.. note:: + + The {+driver-short+} ignores the ``localThresholdMS`` option when communicating with a + replica set through a ``mongos`` instance. In this case, use the + :manual:`localThreshold ` + command-line option. + +API Documentation +----------------- + +To learn more about any of the methods or types discussed in this +guide, see the following API documentation: + +- `MongoClient <{+api+}/org/mongodb/scala/MongoClient.html>`__ +- `MongoClientSettings <{+api+}/org/mongodb/scala/MongoClientSettings$.html>`__ +- `TransactionOptions <{+api+}/org/mongodb/scala/TransactionOptions$.html>`__ +- `startTransaction() <{+core-api+}/com/mongodb/client/ClientSession.html#startTransaction()>`__ +- `MongoDatabase <{+api+}/org/mongodb/scala/MongoDatabase.html>`__ +- `MongoCollection <{+api+}/org/mongodb/scala/MongoCollection.html>`__ +- `TagSet <{+api+}/org/mongodb/scala/TagSet$.html>`__ \ No newline at end of file From c388e76f8cf9b6a72b4f7620072e76c22e297717 Mon Sep 17 00:00:00 2001 From: lindseymoore <71525840+lindseymoore@users.noreply.github.com> Date: Thu, 14 Nov 2024 18:24:53 -0500 Subject: [PATCH 31/44] DOCSP-42338 Single Field Indexes (#70) * DOCSP-42338 Single Field Indexes * new content * first draft * fix toc * try section heading * fixes * fix links * ref fix * review comments * add helper method import * helper method explanation * edit helpers section * edicts * edit * edit out helpers * edict copy * fix database * unnecessary imports --- snooty.toml | 1 + source/includes/indexes/single-field.scala | 48 ++++++++ source/index.txt | 1 + source/indexes.txt | 34 ++++++ source/indexes/single-field-index.txt | 128 +++++++++++++++++++++ 5 files changed, 212 insertions(+) create mode 100644 source/includes/indexes/single-field.scala create mode 100644 source/indexes.txt create mode 100644 source/indexes/single-field-index.txt diff --git a/snooty.toml b/snooty.toml index e9803f3..d2aeb05 100644 --- a/snooty.toml +++ b/snooty.toml @@ -20,6 +20,7 @@ toc_landing_pages = [ "/databases-collections", "/read", "/write", + "/indexes", "/security" ] diff --git a/source/includes/indexes/single-field.scala b/source/includes/indexes/single-field.scala new file mode 100644 index 0000000..0839585 --- /dev/null +++ b/source/includes/indexes/single-field.scala @@ -0,0 +1,48 @@ +import org.mongodb.scala.{ConnectionString, MongoClient, MongoClientSettings} + +// start-single-index-imports +import org.mongodb.scala._ +import org.mongodb.scala.model.Indexes +import org.mongodb.scala.model.IndexOptions._ +import org.mongodb.scala.model.Filters._ + +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.util.{Failure, Success} +import java.util.concurrent.TimeUnit +// end-single-index-imports + +object SingleFieldIndex { + + def main(args: Array[String]): Unit = { + + // Replace the placeholder with your Atlas connection string + val connectionString = ""; + + // Create a new client and connect to the server + val mongoClient = MongoClient(connectionString) + val database = mongoClient.getDatabase("sample_mflix") + val collection = database.getCollection("movies") + + // start-index-single + val index = Indexes.ascending("title") + val observable = collection.createIndex(index) + Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) + // end-index-single + + // start-index-single-query + val filter = equal("title", "Sweethearts") + + collection.find(filter).first().subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-index-single-query + + // start-check-single-index + collection.listIndexes().subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-check-single-index + + Thread.sleep(1000) + mongoClient.close() + } +} diff --git a/source/index.txt b/source/index.txt index 1c3ae62..d2670fe 100644 --- a/source/index.txt +++ b/source/index.txt @@ -17,6 +17,7 @@ /databases-collections /read /write + /indexes /monitoring /aggregation /security diff --git a/source/indexes.txt b/source/indexes.txt new file mode 100644 index 0000000..f452b76 --- /dev/null +++ b/source/indexes.txt @@ -0,0 +1,34 @@ +.. _scala-indexes: + +================================= +Optimize Queries by Using Indexes +================================= + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :description: Learn how to use indexes by using the MongoDB Scala Driver. + :keywords: query, optimization, efficiency, usage example, code example + +.. toctree:: + :titlesonly: + :maxdepth: 1 + + /indexes/single-field-index + +Overview +-------- + +On this page, you can see copyable code examples that show how to manage +different types of indexes by using the {+driver-long+}. + +.. TODO + diff --git a/source/indexes/single-field-index.txt b/source/indexes/single-field-index.txt new file mode 100644 index 0000000..5b5f653 --- /dev/null +++ b/source/indexes/single-field-index.txt @@ -0,0 +1,128 @@ +.. _scala-single-field-index: + +==================== +Single Field Indexes +==================== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: index, query, optimization, efficiency + +Overview +-------- + +Single field indexes are indexes with a reference to a single field of a +document in a collection. These indexes improve single field query and +sort performance. They also support :manual:`TTL Indexes ` +that automatically remove documents from a collection after a certain +amount of time or at a specified clock time. + +When creating a single-field index, you must specify the following +details: + +- The field on which to create the index +- The sort order for the indexed values as either ascending or + descending + +.. note:: + + The default ``_id_`` index is an example of a single-field index. + This index is automatically created on the ``_id`` field when a new + collection is created. + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``movies`` collection in the +``sample_mflix`` database from the :atlas:`Atlas sample datasets +`. To learn how to create a free MongoDB Atlas cluster and +load the sample datasets, see the :atlas:`Get Started with Atlas +` guide. + +Create Single-Field Index +------------------------- + +To run the examples in this guide, you must include the following import +statements in your file: + +.. literalinclude:: /includes/indexes/single-field.scala + :start-after: start-single-index-imports + :end-before: end-single-index-imports + :language: scala + :copyable: + :dedent: + +Use the ``createIndex()`` method to create a single +field index. The following example creates an index in ascending order on the +``title`` field: + +.. literalinclude:: /includes/indexes/single-field.scala + :start-after: start-index-single + :end-before: end-index-single + :language: scala + :copyable: + :dedent: + +You can verify that the index was created by using the ``listIndexes()`` method. +You should see an index for ``title`` in the list, as shown in the following output: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/indexes/single-field.scala + :start-after: start-check-single-index + :end-before: end-check-single-index + :language: scala + :dedent: + + .. output:: + :visible: true + + {"v": 2, "key": {"title": 1}, "name": "title_1"} + +The following is an example of a query that is covered by the index +created on the ``title`` field: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/indexes/single-field.scala + :start-after: start-index-single-query + :end-before: end-index-single-query + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id":...,"plot":"A musical comedy duo...", + "genres":["Musical"],...,"title":"Sweethearts",...} + +Additional Information +---------------------- + +To view runnable examples that demonstrate how to manage indexes, see +:ref:`scala-indexes`. + +To learn more about single field indexes, see :manual:`Single Field +Indexes ` in the {+mdb-server+} manual. + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about any of the methods discussed in this guide, see the +following API documentation: + +- `createIndex() <{+api+}/org/mongodb/scala/MongoCollection.html#createIndex(key:org.mongodb.scala.bson.conversions.Bson,options:org.mongodb.scala.model.IndexOptions):org.mongodb.scala.SingleObservable[String]>`__ +- `find() <{+api+}/org/mongodb/scala/MongoCollection.html#find[C](filter:org.mongodb.scala.bson.conversions.Bson)(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[C,TResult],implicitct:scala.reflect.ClassTag[C]):org.mongodb.scala.FindObservable[C]>`__ +- `listIndexes() <{+api+}/org/mongodb/scala/MongoCollection.html#listIndexes[C]()(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[C,org.mongodb.scala.Document],implicitct:scala.reflect.ClassTag[C]):org.mongodb.scala.ListIndexesObservable[C]>`__ + From 6ce37a81a975b863a3df71a5738e3a5841472fec Mon Sep 17 00:00:00 2001 From: lindseymoore <71525840+lindseymoore@users.noreply.github.com> Date: Thu, 14 Nov 2024 18:52:50 -0500 Subject: [PATCH 32/44] DOCSP-42339 Compound Indexes (#74) * DOCSP-42339 Compound Indexes * toc * fix examples * 1 review comment * api comment * spacing * remove helpers * fix scroll * edits * edit output spacing --- source/includes/indexes/compound-field.scala | 50 ++++++++ source/indexes.txt | 1 + source/indexes/compound-index.txt | 126 +++++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 source/includes/indexes/compound-field.scala create mode 100644 source/indexes/compound-index.txt diff --git a/source/includes/indexes/compound-field.scala b/source/includes/indexes/compound-field.scala new file mode 100644 index 0000000..efdbd1b --- /dev/null +++ b/source/includes/indexes/compound-field.scala @@ -0,0 +1,50 @@ +import com.mongodb.{ServerApi, ServerApiVersion} +import org.mongodb.scala.{ConnectionString, MongoClient, MongoClientSettings} + +// start-compound-index-imports +import org.mongodb.scala._ +import org.mongodb.scala.model.Indexes +import org.mongodb.scala.model.IndexOptions._ +import org.mongodb.scala.model.Filters._ + +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.util.{Failure, Success} +import java.util.concurrent.TimeUnit +// end-compound-index-imports + +object CompoundFieldIndex { + + def main(args: Array[String]): Unit = { + + // Replace the placeholder with your Atlas connection string + val connectionString = ""; + + // Create a new client and connect to the server + val mongoClient = MongoClient(connectionString) + + // start-db-coll + val database: MongoDatabase = mongoClient.getDatabase("sample_mflix") + val collection: MongoCollection[Document] = database.getCollection("movies") + // end-db-coll + + // start-index-compound + val index = Indexes.compoundIndex(Indexes.descending("runtime"), + Indexes.ascending("year")) + val observable = collection.createIndex(index) + Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) + // end-index-compound + + // start-index-compound-query + val filter = and(gt("runtime", 80), gt("year", 1999)) + + collection.find(filter).first().subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-index-compound-query + + // start-check-compound-index + collection.listIndexes().subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-check-compound-index + } +} diff --git a/source/indexes.txt b/source/indexes.txt index f452b76..09ad246 100644 --- a/source/indexes.txt +++ b/source/indexes.txt @@ -23,6 +23,7 @@ Optimize Queries by Using Indexes :maxdepth: 1 /indexes/single-field-index + /indexes/compound-index Overview -------- diff --git a/source/indexes/compound-index.txt b/source/indexes/compound-index.txt new file mode 100644 index 0000000..82a2d9e --- /dev/null +++ b/source/indexes/compound-index.txt @@ -0,0 +1,126 @@ +.. _scala-compound-index: + +================ +Compound Indexes +================ + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: index, query, optimization, efficiency + +Overview +-------- + +**Compound indexes** hold references to multiple +fields within a collection's documents, improving query and sort +performance. + +When creating a compound index, you must specify the following details: + +- The fields on which to create the index + +- The sort order for each field (ascending or descending) + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``movies`` collection in the ``sample_mflix`` +database from the :atlas:`Atlas sample datasets `. To access this collection +from your Scala application, create a ``MongoClient`` that connects to an Atlas cluster +and assign the following values to your ``database`` and ``collection`` variables: + +.. literalinclude:: /includes/indexes/compound-field.scala + :language: scala + :dedent: + :start-after: start-db-coll + :end-before: end-db-coll + +To learn how to create a free MongoDB Atlas cluster and load the sample datasets, see the +:atlas:`Get Started with Atlas ` guide. + +Create a Compound Index +----------------------- + +To run the examples in this guide, you must include the following import +statements in your file: + +.. literalinclude:: /includes/indexes/compound-field.scala + :start-after: start-compound-index-imports + :end-before: end-compound-index-imports + :language: scala + :copyable: + :dedent: + +Use the ``createIndex()`` method to create a +compound index. The following example creates an index in descending +order on the ``runtime`` field and in ascending order on the ``year`` field: + +.. literalinclude:: /includes/indexes/compound-field.scala + :start-after: start-index-compound + :end-before: end-index-compound + :language: scala + :copyable: + :dedent: + +You can verify that the index was created by using the ``listIndexes()`` method. +You should see an index for ``runtime`` and ``year`` in the list, as shown in the following output: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/indexes/compound-field.scala + :start-after: start-check-compound-index + :end-before: end-check-compound-index + :language: scala + :dedent: + + .. output:: + :visible: true + + {"v": 2, "key": {"runtime": -1, "year": 1}, "name": "runtime_-1_year_1"} + +The following is an example of a query that is covered by the index +created on the ``runtime`` and ``year`` fields: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/indexes/compound-field.scala + :start-after: start-index-compound-query + :end-before: end-index-compound-query + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id":...,"runtime": 98,...,"title": "In the Mood for Love",...,"year": 2000,...} + +Additional Information +---------------------- + +To learn more about compound indexes, see :manual:`Compound +Indexes ` in the {+mdb-server+} manual. + +To view runnable examples that demonstrate how to manage indexes, see +:ref:`scala-indexes`. + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about any of the methods discussed in this guide, see the following API +documentation: + +- `compoundIndex() <{+api+}/org/mongodb/scala/model/Indexes$.html#compoundIndex(indexes:org.mongodb.scala.bson.conversions.Bson*):org.mongodb.scala.bson.conversions.Bson>`__ +- `createIndex() <{+api+}/org/mongodb/scala/MongoCollection.html#createIndex(key:org.mongodb.scala.bson.conversions.Bson,options:org.mongodb.scala.model.IndexOptions):org.mongodb.scala.SingleObservable[String]>`__ +- `find() <{+api+}/org/mongodb/scala/MongoCollection.html#find[C](filter:org.mongodb.scala.bson.conversions.Bson)(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[C,TResult],implicitct:scala.reflect.ClassTag[C]):org.mongodb.scala.FindObservable[C]>`__ +- `listIndexes() <{+api+}/org/mongodb/scala/MongoCollection.html#listIndexes[C]()(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[C,org.mongodb.scala.Document],implicitct:scala.reflect.ClassTag[C]):org.mongodb.scala.ListIndexesObservable[C]>`__ From 230f68e95ade2c18f1e567696476b796e50465c5 Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Fri, 15 Nov 2024 15:07:10 -0500 Subject: [PATCH 33/44] DOCSP-42314: Enable TLS (#88) * DOCSP-42314: Enable TLS * edits * code comments * intro edit --- snooty.toml | 1 + source/connect.txt | 2 +- source/connect/tls.txt | 343 ++++++++++++++++++++++++++++++ source/includes/connect/tls.scala | 65 ++++++ 4 files changed, 410 insertions(+), 1 deletion(-) create mode 100644 source/connect/tls.txt create mode 100644 source/includes/connect/tls.scala diff --git a/snooty.toml b/snooty.toml index d2aeb05..95586e4 100644 --- a/snooty.toml +++ b/snooty.toml @@ -37,5 +37,6 @@ driver-source-gh = "https://github.com/mongodb/mongo-java-driver" rs-docs = "https://www.reactive-streams.org/reactive-streams-1.0.4-javadoc/org/reactivestreams" core-api = "https://mongodb.github.io/mongo-java-driver/{+version+}/apidocs/mongodb-driver-core" mongocrypt-version = "{+full-version+}" +java-version = "23" mongodb-server = "MongoDB Server" stable-api = "Stable API" \ No newline at end of file diff --git a/source/connect.txt b/source/connect.txt index ac6166e..f5ab087 100644 --- a/source/connect.txt +++ b/source/connect.txt @@ -22,9 +22,9 @@ Connect to MongoDB /connect/mongoclient /connect/stable-api + /connect/tls .. /connect/connection-targets .. /connect/connection-options -.. /connect/tls Overview -------- diff --git a/source/connect/tls.txt b/source/connect/tls.txt new file mode 100644 index 0000000..dc72518 --- /dev/null +++ b/source/connect/tls.txt @@ -0,0 +1,343 @@ +.. _scala-tls: + +======================================== +Configure Transport Layer Security (TLS) +======================================== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: security, authentication, transport layer security, encrypt + +Overview +-------- + +In this guide, you can learn how to use the Transport Layer Security (TLS) +protocol to secure your connection to a MongoDB deployment. + +When you enable TLS for a connection, the {+driver-short+} performs the following actions: + +- Uses TLS to connect to the MongoDB deployment +- Verifies the deployment's certificate + +To learn how to configure your MongoDB deployment for TLS, see the +:manual:`TLS configuration guide ` in the +{+mdb-server+} manual. + +.. note:: + + This page assumes prior knowledge of TLS/SSL and access to valid certificates. + A full description of TLS/SSL, PKI (Public Key Infrastructure) certificates, and + Certificate Authorities (CAs) is beyond the scope of this documentation. To + learn more about TLS, see the Wikipedia entry for :wikipedia:`Transport Layer Security `. + +.. _scala-enable-tls: + +Enable TLS +---------- + +You can enable TLS for the connection to your MongoDB deployment in the following +ways: + +- Use the ``enabled()`` method from the ``SslSettings.Builder`` class when creating + a ``MongoClientSettings`` instance +- Set the ``tls`` parameter in your connection URI + +Select the :guilabel:`MongoClientSettings` or :guilabel:`Connection URI` tab to see +corresponding code that enables TLS: + +.. tabs:: + + .. tab:: MongoClientSettings + :tabid: settings + + .. literalinclude:: /includes/connect/tls.scala + :language: scala + :dedent: + :start-after: start-enable-tls-settings + :end-before: end-enable-tls-settings + + + .. tab:: Connection URI + :tabid: uri + + .. literalinclude:: /includes/connect/tls.scala + :language: scala + :dedent: + :start-after: start-enable-tls-uri + :end-before: end-enable-tls-uri + +.. tip:: + + If your connection string includes the ``+srv`` modification, which specifies the + SRV connection format, TLS is enabled on your connection by default. + + To learn more about the SRV connection format, see + :manual:`SRV Connection Format ` + in the {+mdb-server+} documentation. + +.. _scala-tls-certificates: + +Configure Certificates +---------------------- + +.. note:: + + The instructions in these sections are based on the documentation for + Oracle JDK. They might not apply to your JDK or to your custom TLS/SSL + implementation. + +{+language+} applications that initiate TLS requests require access to +cryptographic certificates that prove the application's identity and verify +other applications with which the {+language+} application interacts. You can configure +access to these certificates in your application in the following ways: + +- Use a JVM trust store and JVM key store +- Use a client-specific trust store and key store + +.. _scala-tls-configure-jvm-truststore: + +Configure the JVM Trust Store +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The JVM trust store saves certificates that securely identify other +applications with which your {+language+} application interacts. By using these +certificates, your application can prove that the connection to another +application is genuine and secure from tampering by third parties. + +The Java Runtime Environment (JRE) provides a default certificate store, which +includes commonly used public certificates from signing authorities. If your MongoDB +deployment uses a certificate signed by an authority that is not present +in the JRE's default certificate store, your application must configure the following system +properties to initiate TLS requests: + +- ``javax.net.ssl.trustStore``: The path to a trust store containing the certificate of + the signing authority +- ``javax.net.ssl.trustStorePassword``: The password to access the trust store defined by + the ``javax.net.ssl.trustStore`` property + +You can use the `keytool `__ +command line tool to define the preceding properties. The following example runs the ``keytool`` +command to specify the certificate authority file path, the trust store path, and the trust store password: + +.. code-block:: none + + keytool -importcert -trustcacerts -file + -keystore -storepass + +Configure the JVM Key Store +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. note:: + + By default, MongoDB instances do not perform client certificate + validation. You must configure the key store if you configured your MongoDB + instance to validate client certificates. + +An application that initiates TLS requests must set the following JVM system +properties to ensure that the client presents a TLS certificate to +the MongoDB server: + +- ``javax.net.ssl.keyStore``: The path to a key store containing the client's + TLS/SSL certificates + +- ``javax.net.ssl.keyStorePassword``: The password to access the key store + defined in ``javax.net.ssl.keyStore`` + +You can create a key store by using the `keytool +`__ +or `openssl `__ +command line tool. + +To learn more about configuring a {+language+} application to use TLS, +see the `JSSE Reference Guide `__ +in the Java language documentation. + +Configure a Client-Specific Trust Store and Key Store +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can configure a client-specific trust store and key store by using the +``init()`` method of the ``SSLContext`` class. + +To view an example that configures a client to use an ``SSLContext`` +instance, see the :ref:`` section of this guide. + +.. _scala-tls-disable-hostname: + +Disable Hostname Verification +----------------------------- + +By default, the driver ensures that the hostname included in the server's +TLS certificates matches the hostnames provided when constructing +a ``MongoClient``. You can disable hostname verification in the following +ways: + +- Use the ``invalidHostNameAllowed()`` method from the ``SslSettings.Builder`` + class when creating a ``MongoClientSettings`` instance +- Set the ``tlsAllowInvalidHostnames`` parameter in your connection URI + +Select the :guilabel:`MongoClientSettings` or :guilabel:`Connection URI` tab to see +corresponding code that disables hostname verification: + +.. tabs:: + + .. tab:: MongoClientSettings + :tabid: settings + + .. literalinclude:: /includes/connect/tls.scala + :language: scala + :dedent: + :start-after: start-disable-host-validation-settings + :end-before: end-disable-host-validation-settings + + + .. tab:: Connection URI + :tabid: uri + + .. literalinclude:: /includes/connect/tls.scala + :language: scala + :dedent: + :start-after: start-disable-host-validation-uri + :end-before: end-disable-host-validation-uri + +.. warning:: + + Disabling hostname verification makes your application insecure and potentially + vulnerable to expired certificates and foreign processes posing as valid client + instances. + +.. _scala-tls-restrict-tls-1.2: + +Restrict Connections to TLS 1.2 +------------------------------- + +To restrict your application to use only the TLS 1.2 protocol, set the +``jdk.tls.client.protocols`` system property to ``"TLSv1.2"``. + +.. note:: + + Java Runtime Environments (JREs) before Java 8 enabled + only the TLS 1.2 protocol in update releases. If your JRE has not enabled + the TLS 1.2 protocol, upgrade to a later release to use + TLS 1.2. + +.. _scala-tls-custom-sslContext: + +Customize Configuration with SSLContext +--------------------------------------- + +If your TLS configuration requires customization, you can +set the ``sslContext`` property of your ``MongoClient`` object. Pass +an ``SSLContext`` object to the ``context()`` method builder in the +``applyToSslSettings()`` block, as shown in the following code: + +.. literalinclude:: /includes/connect/tls.scala + :start-after: start-ssl-context + :end-before: end-ssl-context + :language: scala + :copyable: + :dedent: + +For more information on the ``SSLContext`` class, see the API +documentation for `SSL Context `__. + +Online Certificate Status Protocol (OCSP) +----------------------------------------- + +OCSP is a standard used to check whether X.509 certificates have been +revoked. A certificate authority (CA) can add an X.509 certificate to the +Certificate Revocation List (CRL) before the expiry time to invalidate +the certificate. When a client sends an X.509 certificate during the TLS +handshake, the CA's revocation server checks the CRL and returns a status +of ``good``, ``revoked``, or ``unknown``. + +The driver supports the following variations of OCSP: + +- Client-Driven OCSP +- OCSP Stapling + +The following sections describe these variations and show how to enable +them for your application. + +.. note:: + + The {+driver-short+} uses the JVM arguments configured for the application, + which cannot be overridden for a specific ``MongoClient`` instance. + +Client-Driven OCSP +~~~~~~~~~~~~~~~~~~ + +In client-driven OCSP, the client receives the certificate from the server +and sends this certificate in an OCSP request to an OCSP responder. The OCSP +responder checks the status of the certificate with a CA and sends a +report about its validity to the client. + +To enable client-driven OCSP for your application, set the following JVM +system properties: + +.. list-table:: + :header-rows: 1 + :widths: 35 65 + + * - Property + - Description + + * - ``com.sun.net.ssl.checkRevocation`` + - Set this property to ``true`` to enable revocation checking. + + * - ``ocsp.enable`` + - Set this property to ``true`` to enable client-driven OCSP. + +.. warning:: + + If the OCSP responder is unavailable, the TLS support provided by the + JDK reports a "hard fail". This differs from the "soft fail" behavior of + the MongoDB Shell and some other drivers. + +OCSP Stapling +~~~~~~~~~~~~~ + +OCSP stapling is a mechanism in which the server must obtain the signed +certificate from the CA and include it in a time-stamped OCSP response +to the client. + +To enable OCSP stapling for your application, set the following JVM system +properties: + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Property + - Description + + * - ``com.sun.net.ssl.checkRevocation`` + - Set this property to ``true`` to enable revocation checking. + + * - ``jdk.tls.client.enableStatusRequestExtension`` + - | Set this property to ``true`` to enable OCSP stapling. + | + | If unset or set to ``false``, the connection can proceed regardless of the presence or status of the certificate revocation response. + +To learn more about OCSP, view the following resources: + +- `Client-Driven OCSP and OCSP Stapling `__ + in the Oracle JDK 8 documentation +- :rfc:`Official IETF specification for OCSP (RFC 6960) <6960>` + +API Documentation +----------------- + +For more information about any of the types discussed in this guide, +see the following API documentation: + +- `ConnectionString <{+core-api+}/com/mongodb/ConnectionString.html>`__ +- `MongoClientSettings <{+core-api+}/com/mongodb/MongoClientSettings.html>`__ \ No newline at end of file diff --git a/source/includes/connect/tls.scala b/source/includes/connect/tls.scala new file mode 100644 index 0000000..9605126 --- /dev/null +++ b/source/includes/connect/tls.scala @@ -0,0 +1,65 @@ +import org.mongodb.scala._ +import javax.net.ssl.SSLContext + +object Tls { + + def main(args: Array[String]): Unit = { + + // Enables TLS by using client settings + // start-enable-tls-settings + val tlsUri = "mongodb://localhost:27017/" + val tlsSettings = MongoClientSettings.builder() + .applyConnectionString(ConnectionString(tlsUri)) + .applyToSslSettings(builder => builder.enabled(true)) + .build() + val tlsClient1 = MongoClient(tlsSettings) + // end-enable-tls-settings + + // Enables TLS by using a connection URI parameter + // start-enable-tls-uri + val tlsClient2 = MongoClient("mongodb://localhost:27017/?tls=true") + // end-enable-tls-uri + + // Disables host validation by using client settings + // start-disable-host-validation-settings + val invalidHostUri = "mongodb://localhost:27017/" + val invalidHostSettings = MongoClientSettings.builder() + .applyConnectionString(ConnectionString(invalidHostUri)) + .applyToSslSettings(builder => builder + .enabled(true) + .invalidHostNameAllowed(true) + ) + .build() + val invalidHostClient1 = MongoClient(invalidHostSettings) + // end-disable-host-validation-settings + + // Disables host validation by using a connection URI parameter + // start-disable-host-validation-uri + val invalidHostClient2 = MongoClient("mongodb://localhost:27017/?tls=true&tlsAllowInvalidHostnames=true") + // end-disable-host-validation-uri + + // Creates an SSLContext object to further customize the TLS configuration + // start-ssl-context + val sslContext = SSLContext.getDefault() + + val contextSettings = MongoClientSettings.builder() + .applyToSslSettings(builder => builder + .enabled(true) + .context(sslContext) + ) + .build() + val mongoClient = MongoClient(contextSettings); + // end-ssl-context + + // Keep the main thread alive long enough for the asynchronous operations to complete + Thread.sleep(5000) + + // Close the MongoClient connections + tlsClient1.close() + tlsClient2.close() + invalidHostClient1.close() + invalidHostClient2.close() + mongoClient.close() + } + +} \ No newline at end of file From 5d491dbea04e17257a818d1ded2adbd8e220dfad Mon Sep 17 00:00:00 2001 From: Maya Raman Date: Tue, 19 Nov 2024 14:13:41 -0500 Subject: [PATCH 34/44] DOCSP-42337: Optimize Queries with Indexes (#92) --- .../indexes/index-code-examples.scala | 57 +++++++ .../includes/indexes/index-starter-code.scala | 28 ++++ source/indexes.txt | 157 +++++++++++++++++- 3 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 source/includes/indexes/index-code-examples.scala create mode 100644 source/includes/indexes/index-starter-code.scala diff --git a/source/includes/indexes/index-code-examples.scala b/source/includes/indexes/index-code-examples.scala new file mode 100644 index 0000000..c2e916c --- /dev/null +++ b/source/includes/indexes/index-code-examples.scala @@ -0,0 +1,57 @@ +// start-index-single +val index = Indexes.ascending("") +val observable = collection.createIndex(index) +Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) +// end-index-single + +// start-index-compound +val index = Indexes.compoundIndex( + Indexes.descending(""), + Indexes.ascending("") +) +val observable = collection.createIndex(index) +Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) +// end-index-compound + +// start-create-multikey-index +val index = Indexes.ascending("") +val observable = collection.createIndex(index) +Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) +// end-create-multikey-index + +// start-create-geospatial-index +val observable = collection.createIndex(Indexes.geo2dsphere("<2d index>")) +Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) +// end-create-geospatial-index + +// start-drop-single-index +val observable = collection.dropIndex("") +Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) +// end-drop-single-index + +// start-create-search-index +val index = Document("mappings" -> Document("dynamic" -> )) +val observable = collection.createSearchIndex("", index) +Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) +// end-create-search-index + + +// start-list-search-indexes +val observable = collection.listSearchIndexes() +observable.subscribe(new Observer[Document] { + override def onNext(index: Document): Unit = println(index.toJson()) + override def onError(e: Throwable): Unit = println("Error: " + e.getMessage) + override def onComplete(): Unit = println("Completed") +}) +// end-list-search-indexes + +// start-update-search-indexes +val updateIndex = Document("mappings" -> Document("dynamic" -> false)) +val observable = collection.updateSearchIndex("", updateIndex) +Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) +// end-update-search-indexes + +// start-drop-search-index +val observable = collection.dropSearchIndex("") +Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) +// end-drop-search-index diff --git a/source/includes/indexes/index-starter-code.scala b/source/includes/indexes/index-starter-code.scala new file mode 100644 index 0000000..142d8f8 --- /dev/null +++ b/source/includes/indexes/index-starter-code.scala @@ -0,0 +1,28 @@ +import org.mongodb.scala._ +import org.mongodb.scala.model.SearchIndexModel + +import java.util.concurrent.TimeUnit +import scala.concurrent.Await +import scala.concurrent.duration.Duration + +import org.mongodb.scala.model.Indexes + +object SearchIndexes { + + def main(args: Array[String]): Unit = { + + // Create a new client and connect to the server + val mongoClient = MongoClient("") + + val database = mongoClient.getDatabase("") + val collection = database.getCollection("") + + // Start example code here + + + // End example code here + + Thread.sleep(1000) + mongoClient.close() + } +} diff --git a/source/indexes.txt b/source/indexes.txt index 09ad246..48f622b 100644 --- a/source/indexes.txt +++ b/source/indexes.txt @@ -24,6 +24,7 @@ Optimize Queries by Using Indexes /indexes/single-field-index /indexes/compound-index + /indexes/atlas-search-index Overview -------- @@ -31,5 +32,159 @@ Overview On this page, you can see copyable code examples that show how to manage different types of indexes by using the {+driver-long+}. -.. TODO +To use an example from this page, copy the code example into the sample +application or your own application. Be sure to replace all placeholders in the +code examples, such as ````, with the relevant values for +your MongoDB deployment. +Sample Application +~~~~~~~~~~~~~~~~~~ + +You can use the following sample application to test the code on this page. To +use the sample application, perform the following steps: + +1. Ensure you have the {+driver-short+} installed in your project. See the + :ref:`scala-quick-start-download-and-install` guide to learn more. + +#. Copy the following code and paste it into a new ``.scala`` file. + +#. Copy a code example from this page and paste it on the specified lines in the + file. + +.. literalinclude:: /includes/indexes/index-starter-code.scala + :language: scala + :emphasize-lines: 20-23 + +Single Field Index +------------------ + +The following example creates an ascending index on the specified field: + +.. literalinclude:: /includes/indexes/index-code-examples.scala + :language: scala + :start-after: start-index-single + :end-before: end-index-single + +To learn more about single field indexes, see the :ref:`scala-single-field-index` +guide. + +Compound Index +-------------- + +The following example creates a compound index on the two specified fields. + +.. literalinclude:: /includes/indexes/index-code-examples.scala + :language: scala + :start-after: start-index-compound + :end-before: end-index-compound + +To learn more about compound indexes, see the :ref:`scala-compound-index` guide. + +Multikey Index +-------------- + +The following example creates a multikey index on the specified array-valued field: + +.. literalinclude:: /includes/indexes/index-code-examples.scala + :language: scala + :start-after: start-create-multikey-index + :end-before: end-create-multikey-index + +To learn more about multikey indexes, see the :ref:`scala-multikey-index` guide. + +Geospatial Index +---------------- + +The following example creates a 2dsphere index on the specified field that +contains GeoJSON objects: + +.. literalinclude:: /includes/indexes/index-code-examples.scala + :language: scala + :start-after: start-create-geospatial-index + :end-before: end-create-geospatial-index + +For more information on 2dsphere indexes, see the +:manual:`2dsphere Indexes ` +guide in the {+mdb-server+} manual. + +For more information about the GeoJSON type, see the +:manual:`GeoJSON Objects ` guide in +the {+mdb-server+} manual. + +Drop Index +---------- + +The following example deletes an index with the specified name: + +.. literalinclude:: /includes/indexes/index-code-examples.scala + :language: scala + :start-after: start-drop-single-index + :end-before: end-drop-single-index + +Atlas Search Index Management +----------------------------- + +The following sections contain code examples that describe how to manage +Atlas Search indexes. + +To learn more about search indexes, see the :ref:`scala-atlas-search-index` guide. + +Create Search Index +~~~~~~~~~~~~~~~~~~~ + +The following example creates an Atlas Search index on the specified field: + +.. literalinclude:: /includes/indexes/index-code-examples.scala + :language: scala + :start-after: start-create-search-index + :end-before: end-create-search-index + :dedent: + +List Search Indexes +~~~~~~~~~~~~~~~~~~~ + +The following example prints a list of Atlas Search indexes in the specified +collection: + +.. literalinclude:: /includes/indexes/index-code-examples.scala + :language: scala + :start-after: start-list-search-indexes + :end-before: end-list-search-indexes + :dedent: + +Update Search Indexes +~~~~~~~~~~~~~~~~~~~~~ + +The following example updates an existing Atlas Search index with the specified +new index definition: + +.. literalinclude:: /includes/indexes/index-code-examples.scala + :language: scala + :start-after: start-update-search-indexes + :end-before: end-update-search-indexes + :dedent: + +Delete Search Indexes +~~~~~~~~~~~~~~~~~~~~~ + +The following example deletes an Atlas Search index with the specified name: + +.. literalinclude:: /includes/indexes/index-code-examples.scala + :language: scala + :start-after: start-drop-search-index + :end-before: end-drop-search-index + :dedent: + +API Documentation +----------------- + +To learn more about the methods or objects used in this guide, see the following +API documentation: + +- `createIndex() <{+api+}/org/mongodb/scala/MongoCollection.html#createIndex(key:org.mongodb.scala.bson.conversions.Bson,options:org.mongodb.scala.model.IndexOptions):org.mongodb.scala.SingleObservable[String]>`__ +- `Indexes object <{+api+}/org/mongodb/scala/model/Indexes$.html>`__ +- `dropIndex() <{+api+}/org/mongodb/scala/MongoCollection.html#dropIndex(keys:org.mongodb.scala.bson.conversions.Bson,dropIndexOptions:org.mongodb.scala.model.DropIndexOptions):org.mongodb.scala.SingleObservable[Unit]>`__ +- `createSearchIndex() <{+api+}/org/mongodb/scala/MongoCollection.html#createSearchIndex(indexName:String,definition:org.mongodb.scala.bson.conversions.Bson):org.mongodb.scala.SingleObservable[String]>`__ +- `listSearchIndexes() <{+api+}/org/mongodb/scala/MongoCollection.html#listSearchIndexes[C]()(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[C,org.mongodb.scala.Document],implicitct:scala.reflect.ClassTag[C]):org.mongodb.scala.ListSearchIndexesObservable[C]>`__ +- `updateSearchIndex() <{+api+}/org/mongodb/scala/MongoCollection.html#updateSearchIndex(indexName:String,definition:org.mongodb.scala.bson.conversions.Bson):org.mongodb.scala.SingleObservable[Unit]>`__ +- `dropSearchIndex() <{+api+}/org/mongodb/scala/MongoCollection.html#dropSearchIndex(indexName:String):org.mongodb.scala.SingleObservable[Unit]>`__ From 60014eab311376fe3f15a8b782b0a61c2cc60a2b Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Tue, 19 Nov 2024 14:51:08 -0500 Subject: [PATCH 35/44] DOCSP-42349: Transactions (#89) * DOCSP-42349: Transactions * edits * JS feedback * JY feedback --- source/includes/write/transaction.scala | 70 ++++++++++ source/write/transactions.txt | 164 ++++++++++++++++++++++++ 2 files changed, 234 insertions(+) create mode 100644 source/includes/write/transaction.scala create mode 100644 source/write/transactions.txt diff --git a/source/includes/write/transaction.scala b/source/includes/write/transaction.scala new file mode 100644 index 0000000..15783fd --- /dev/null +++ b/source/includes/write/transaction.scala @@ -0,0 +1,70 @@ +import org.mongodb.scala._ +import java.util.concurrent.TimeUnit +import scala.concurrent.Await +import org.mongodb.scala.model.Updates._ +import org.mongodb.scala.model.Filters._ +import scala.concurrent.duration.Duration +import org.mongodb.scala.result._ + +object Transaction { +// begin-transaction-method + def runTransaction( + database: MongoDatabase, + observable: SingleObservable[ClientSession] + ): SingleObservable[ClientSession] = { + observable.map(clientSession => { + val moviesCollection = database.getCollection("movies") + val usersCollection = database.getCollection("users") + + val transactionOptions = TransactionOptions + .builder() + .readConcern(ReadConcern.SNAPSHOT) + .writeConcern(WriteConcern.MAJORITY) + .build() + + // Starts the transaction with specified options + clientSession.startTransaction(transactionOptions) + + // Inserts a document into the "movies" collection + val insertObservable = moviesCollection.insertOne( + clientSession, + Document("name" -> "The Menu", "runtime" -> 107) + ) + val insertResult = Await.result(insertObservable.toFuture(), Duration(10, TimeUnit.SECONDS)) + println(s"Insert completed: $insertResult") + + // Updates a document in the "users" collection + val updateObservable = usersCollection.updateOne( + clientSession, + equal("name", "Amy Phillips"), set("name", "Amy Ryan") + ) + val updateResult = Await.result(updateObservable.toFuture(), Duration(10, TimeUnit.SECONDS)) + println(s"Update completed: $updateResult") + + clientSession + }) + } +// end-transaction-method + + def main(args: Array[String]): Unit = { + + // start-perform-transaction + val client = MongoClient("") + val database = client.getDatabase("sample_mflix") + val session = client.startSession(); + + val transactionObservable: SingleObservable[ClientSession] = + runTransaction(database, session) + + val commitTransactionObservable: SingleObservable[Unit] = + transactionObservable.flatMap(clientSession => clientSession.commitTransaction()) + + Await.result(commitTransactionObservable.toFuture(), Duration(10, TimeUnit.SECONDS)) + // end-perform-transaction + + // Keep the main thread alive long enough for the asynchronous operations to complete + Thread.sleep(1000) + client.close() + } + +} \ No newline at end of file diff --git a/source/write/transactions.txt b/source/write/transactions.txt new file mode 100644 index 0000000..d00b805 --- /dev/null +++ b/source/write/transactions.txt @@ -0,0 +1,164 @@ +.. _scala-transactions: + +===================== +Perform a Transaction +===================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: code example, ACID compliance, multi-document + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to use the {+driver-short+} to perform +**transactions**. Transactions allow you to perform a series of operations +that change data only if the entire transaction is committed. +If any operation in the transaction does not succeed, the driver stops the +transaction and discards all data changes before they ever become +visible. This feature is called **atomicity**. + +In MongoDB, transactions run within logical **sessions**. A +session is a grouping of related read or write operations that you +want to run sequentially. Sessions enable causal consistency for a group +of operations and allow you to run operations in an **ACID-compliant** +transaction, which is a transaction that meets an expectation of +atomicity, consistency, isolation, and durability. MongoDB guarantees +that the data involved in your transaction operations remains +consistent, even if the operations encounter unexpected errors. + +When using the {+driver-short+}, you can start a ``ClientSession`` by +calling the ``startSession()`` method on your client. Then, you can +perform transactions within the session. + +.. warning:: + + Use a ``ClientSession`` only in operations running on the + ``MongoClient`` that created it. Using a ``ClientSession`` with a + different ``MongoClient`` results in operation errors. + +Methods +------- + +After calling the ``startSession()`` method to start a session, you can +use methods from the ``ClientSession`` class to modify the session state. +The following table describes the methods you can use to manage a +transaction: + +.. list-table:: + :widths: 25 75 + :stub-columns: 1 + :header-rows: 1 + + * - Method + - Description + + * - ``startTransaction()`` + - | Starts a new transaction on this session. You cannot start a + transaction if there's already an active transaction running in + the session. + | + | You can set transaction options by passing a ``TransactionOptions`` + instance as a parameter. + + * - ``commitTransaction()`` + - | Commits the active transaction for this session. This method returns an + error if there is no active transaction for the session, the + transaction was previously ended, or if there is a write conflict. + + * - ``abortTransaction()`` + - | Ends the active transaction for this session. This method returns an + error if there is no active transaction for the session or if the + transaction was committed or ended. + +.. _scala-txn-example: + +Transaction Example +------------------- + +This example defines a ``runTransaction()`` method that +modifies data in the collections of the ``sample_mflix`` database. +The code performs the following actions: + +- Creates ``MongoCollection`` instances to access the ``movies`` + and ``users`` collections +- Specifies the read and write concern for the transaction +- Starts the transaction +- Inserts a document into the ``movies`` collection and prints the results +- Updates a document in the ``users`` collection and prints the results + +.. literalinclude:: /includes/write/transaction.scala + :copyable: + :language: scala + :dedent: + :start-after: begin-transaction-method + :end-before: end-transaction-method + +.. note:: + + Within a transaction, operations must run in sequence. The preceding + code awaits the result of each write operation to ensure that the operations + do not run concurrently. + +Then, run the following code to perform the transaction. This code +completes the following actions: + +- Creates a session from the client by using the ``startSession()`` method +- Calls the ``runTransaction()`` method defined in the preceding example, passing + the database and the session as parameters +- Commits the transaction by calling the ``commitTransaction()`` method and waits + for the operations to complete + +.. io-code-block:: + :copyable: + + .. input:: /includes/write/transaction.scala + :language: scala + :dedent: + :start-after: start-perform-transaction + :end-before: end-perform-transaction + + .. output:: + :language: console + :visible: false + + Insert completed: AcknowledgedInsertOneResult{insertedId=BsonObjectId{value=...}} + Update completed: AcknowledgedUpdateResult{matchedCount=1, modifiedCount=1, upsertedId=null} + +Additional Information +---------------------- + +To learn more about the concepts mentioned in this guide, see the +following pages in the {+mdb-server+} manual: + +- :manual:`Transactions ` +- :manual:`Server Sessions ` +- :manual:`Read Isolation, Consistency, and Recency + ` + +To learn more about ACID compliance, see the :website:`A Guide to ACID Properties in Database Management Systems +` article on the MongoDB website. + +To learn more about insert operations, see the +:ref:`scala-write-insert` guide. + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about the methods and types mentioned in this +guide, see the following API documentation: + +- `MongoClient <{+api+}/org/mongodb/scala/MongoClient.html>`__ +- `startSession() <{+api+}/org/mongodb/scala/MongoCluster.html#startSession():org.mongodb.scala.SingleObservable[org.mongodb.scala.ClientSession]>`__ +- `startTransaction() <{+core-api+}/com/mongodb/client/ClientSession.html#startTransaction()>`__ +- `commitTransaction() <{+api+}/org/mongodb/scala/ClientSessionImplicits$ScalaClientSession.html#commitTransaction():org.mongodb.scala.SingleObservable[Unit]>`__ +- `abortTransaction() <{+api+}/org/mongodb/scala/ClientSessionImplicits$ScalaClientSession.html#abortTransaction():org.mongodb.scala.SingleObservable[Unit]>`__ \ No newline at end of file From f78020cbccd102fc9d2444de6712f7ba67d8f08e Mon Sep 17 00:00:00 2001 From: lindseymoore <71525840+lindseymoore@users.noreply.github.com> Date: Tue, 19 Nov 2024 15:35:23 -0500 Subject: [PATCH 36/44] DOCSP-42340 Multikey Index (#76) * DOCSP-42340 Multikey Index * fixes * multikey index link * text fix * internal review comments * remover helpers * edits * uri --- source/includes/indexes/multikey-field.scala | 51 ++++++++ source/indexes.txt | 1 + source/indexes/multikey-index.txt | 126 +++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 source/includes/indexes/multikey-field.scala create mode 100644 source/indexes/multikey-index.txt diff --git a/source/includes/indexes/multikey-field.scala b/source/includes/indexes/multikey-field.scala new file mode 100644 index 0000000..1ba2ffb --- /dev/null +++ b/source/includes/indexes/multikey-field.scala @@ -0,0 +1,51 @@ +import com.mongodb.{ServerApi, ServerApiVersion} +import org.mongodb.scala.{ConnectionString, MongoClient, MongoClientSettings} +import org.mongodb.scala.bson.Document + +// start-multikey-index-imports +import org.mongodb.scala._ +import org.mongodb.scala.model.Indexes +import org.mongodb.scala.model.IndexOptions._ +import org.mongodb.scala.model.Filters._ + +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.util.{Failure, Success} +import java.util.concurrent.TimeUnit +// end-multikey-index-imports + +object MulitkeyFieldIndex { + + def main(args: Array[String]): Unit = { + + // Replace the placeholder with your Atlas connection string + val connectionString = ""; + + // Create a new client and connect to the server + val mongoClient = MongoClient(connectionString) + + // start-db-coll + val database: MongoDatabase = mongoClient.getDatabase("sample_mflix") + val collection: MongoCollection[Document] = database.getCollection("movies") + // end-db-coll + + // start-index-multikey + val index = Indexes.ascending("cast") + val observable = collection.createIndex(index) + Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) + + // end-index-multikey + + // start-index-multikey-query + val filter = and(equal("cast", "Aamir Khan"), equal("cast", "Kajol")) + + collection.find(filter).first().subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-index-multikey-query + + // start-check-multikey-index + collection.listIndexes().subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-check-multikey-index + } +} diff --git a/source/indexes.txt b/source/indexes.txt index 48f622b..28accc7 100644 --- a/source/indexes.txt +++ b/source/indexes.txt @@ -24,6 +24,7 @@ Optimize Queries by Using Indexes /indexes/single-field-index /indexes/compound-index + /indexes/multikey-index /indexes/atlas-search-index Overview diff --git a/source/indexes/multikey-index.txt b/source/indexes/multikey-index.txt new file mode 100644 index 0000000..23fecd5 --- /dev/null +++ b/source/indexes/multikey-index.txt @@ -0,0 +1,126 @@ +.. _scala-multikey-index: + +================ +Multikey Indexes +================ + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: index, query, optimization, efficiency + +Overview +-------- + +**Multikey indexes** are indexes that improve the performance of queries +on array-valued fields. You can create a multikey index on a collection +by using the ``createIndex()`` method and the same syntax that you use to create +a :ref:`single field index `. + +When creating a multikey index, you must specify the following details: + +- The fields on which to create the index + +- The sort order for each field (ascending or descending) + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``movies`` collection in the ``sample_mflix`` +database from the :atlas:`Atlas sample datasets `. To access this collection +from your Scala application, create a ``MongoClient`` that connects to an Atlas cluster +and assign the following values to your ``database`` and ``collection`` variables: + +.. literalinclude:: /includes/indexes/multikey-field.scala + :language: scala + :dedent: + :start-after: start-db-coll + :end-before: end-db-coll + +To learn how to create a free MongoDB Atlas cluster and load the sample datasets, see the +:atlas:`Get Started with Atlas ` guide. + +Create a Multikey Index +----------------------- + +To run the examples in this guide, you must include the following import +statements in your file: + +.. literalinclude:: /includes/indexes/multikey-field.scala + :start-after: start-multikey-index-imports + :end-before: end-multikey-index-imports + :language: scala + :copyable: + :dedent: + +Use the ``createIndex()`` method to create a +multikey index. The following example creates an index in ascending order +on the ``cast`` field: + +.. literalinclude:: /includes/indexes/multikey-field.scala + :start-after: start-index-multikey + :end-before: end-index-multikey + :language: scala + :copyable: + :dedent: + +You can verify that the index was created by calling the ``listIndexes()`` method. +You should see an index for ``cast`` in the list, as shown in the following output: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/indexes/multikey-field.scala + :start-after: start-check-multikey-index + :end-before: end-check-multikey-index + :language: scala + :dedent: + + .. output:: + :visible: true + + {"v": 2, "key": {"cast": 1}, "name": "cast_1"} + +The following is an example of a query that is covered by the index +created on the ``cast`` field: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/indexes/multikey-field.scala + :start-after: start-index-multikey-query + :end-before: end-index-multikey-query + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id":...,"title":"Fanaa",...,"cast": ["Aamir Khan", "Kajol", "Rishi Kapoor", "Tabu"],...} + +Additional Information +---------------------- + +To learn more about multikey indexes, see :manual:`Multikey +Indexes ` in the {+mdb-server+} manual. + +To view runnable examples that demonstrate how to manage indexes, see +:ref:`scala-indexes`. + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about any of the methods discussed in this guide, see the following API +documentation: + +- `createIndex() <{+api+}/org/mongodb/scala/MongoCollection.html#createIndex(key:org.mongodb.scala.bson.conversions.Bson,options:org.mongodb.scala.model.IndexOptions):org.mongodb.scala.SingleObservable[String]>`__ +- `find() <{+api+}/org/mongodb/scala/MongoCollection.html#find[C](filter:org.mongodb.scala.bson.conversions.Bson)(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[C,TResult],implicitct:scala.reflect.ClassTag[C]):org.mongodb.scala.FindObservable[C]>`__ +- `listIndexes() <{+api+}/org/mongodb/scala/MongoCollection.html#listIndexes[C]()(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[C,org.mongodb.scala.Document],implicitct:scala.reflect.ClassTag[C]):org.mongodb.scala.ListIndexesObservable[C]>`__ From cd0af93b26606a8c2aaaa1158100ae437fd8ac2b Mon Sep 17 00:00:00 2001 From: lindseymoore <71525840+lindseymoore@users.noreply.github.com> Date: Tue, 19 Nov 2024 17:03:01 -0500 Subject: [PATCH 37/44] DOCSP-42313 Connection Targets (#80) * DOCSP-42313 Connection Targets * toc * fixes * code fix * small change * sode file endline spacing * review edits * comment out connection option page * Correct connect section TOC --- snooty.toml | 2 +- source/connect.txt | 8 +- source/connect/connection-options.txt | 3 +- source/connect/connection-targets.txt | 150 ++++++++++++++++++ source/connect/stable-api.txt | 2 +- .../includes/connect/connection-target.scala | 37 +++++ .../connection-targets-local-replica.scala | 41 +++++ 7 files changed, 236 insertions(+), 7 deletions(-) create mode 100644 source/connect/connection-targets.txt create mode 100644 source/includes/connect/connection-target.scala create mode 100644 source/includes/connect/connection-targets-local-replica.scala diff --git a/snooty.toml b/snooty.toml index 95586e4..1977579 100644 --- a/snooty.toml +++ b/snooty.toml @@ -39,4 +39,4 @@ core-api = "https://mongodb.github.io/mongo-java-driver/{+version+}/apidocs/mong mongocrypt-version = "{+full-version+}" java-version = "23" mongodb-server = "MongoDB Server" -stable-api = "Stable API" \ No newline at end of file +stable-api = "Stable API" diff --git a/source/connect.txt b/source/connect.txt index f5ab087..3208a4b 100644 --- a/source/connect.txt +++ b/source/connect.txt @@ -13,20 +13,20 @@ Connect to MongoDB .. facet:: :name: genre :values: reference - + .. meta:: :description: Learn how to use the Scala driver to connect to MongoDB. :keywords: client, ssl, tls, localhost .. toctree:: - + /connect/mongoclient /connect/stable-api + /connect/connection-targets /connect/tls -.. /connect/connection-targets .. /connect/connection-options Overview -------- -.. TODO \ No newline at end of file +.. TODO diff --git a/source/connect/connection-options.txt b/source/connect/connection-options.txt index 2541353..80f1f8a 100644 --- a/source/connect/connection-options.txt +++ b/source/connect/connection-options.txt @@ -20,4 +20,5 @@ Specify Connection Options Overview -------- -.. TODO \ No newline at end of file +.. TODO + diff --git a/source/connect/connection-targets.txt b/source/connect/connection-targets.txt new file mode 100644 index 0000000..a5fa356 --- /dev/null +++ b/source/connect/connection-targets.txt @@ -0,0 +1,150 @@ +.. _scala-connection-targets: + +========================== +Choose a Connection Target +========================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: connection string, URI, server, settings, client + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to use a connection string and a ``MongoClient`` object +to connect to different types of MongoDB deployments. + +Atlas +----- + +To connect to a MongoDB deployment on Atlas, include the following elements +in your connection string: + +- The URL of your Atlas cluster +- Your MongoDB username +- Your MongoDB password + +Then, pass your connection string to the ``MongoClient`` constructor. + +.. tip:: + + Follow the :atlas:`Atlas driver connection guide ` + to retrieve your connection string. + +When you connect to Atlas, we recommend using the {+stable-api+} client option to avoid +breaking changes when Atlas upgrades to a new version of {+mdb-server+}. +To learn more about the {+stable-api+} feature, see the :ref:`` +guide. + +The following code shows how to use the {+driver-short+} to connect to an Atlas cluster. The +code also uses the ``serverApi()`` method to specify a {+stable-api+} version. + +.. literalinclude:: /includes/connect/connection-target.scala + :language: scala + :start-after: start-connect + :end-before: end-connect + :dedent: + +Local Deployments +----------------- + +You can connect to a local MongoDB deployment in the following ways: + +- Instantiate a ``MongoClient`` object without any parameters to + connect to a MongoDB server running on ``localhost`` on port ``27017``: + + .. literalinclude:: /includes/connect/connection-targets-local-replica.scala + :language: scala + :start-after: start-connect-local + :end-before: end-connect-local + :dedent: + +- Explicitly specify the ``hostname`` to connect to a MongoDB + instance running on the specified host on port ``27017``: + + .. literalinclude:: /includes/connect/connection-targets-local-replica.scala + :language: scala + :start-after: start-connect-local-host + :end-before: end-connect-local-host + :dedent: + +- Explicitly specify the ``hostname`` and the ``port``: + + .. literalinclude:: /includes/connect/connection-targets-local-replica.scala + :language: scala + :start-after: start-connect-local-host-port + :end-before: end-connect-local-host-port + :dedent: + +Replica Sets +------------ + +To connect to a replica set, specify the hostnames (or IP addresses) and +port numbers of the replica set members. + +If you aren't able to provide a full list of hosts in the replica set, you can +specify one or more of the hosts in the replica set and instruct the {+driver-short+} to +perform automatic discovery to find the others. To instruct the driver to perform +automatic discovery, perform one of the following actions: + +- Specify the name of the replica set as the value of the ``replicaSet`` parameter. +- Specify ``false`` as the value of the ``directConnection`` parameter. +- Specify more than one host in the replica set. + +You can connect to a MongoDB replica set by specifying the members in a +``ConnectionString``. The following example shows how to specify three members +of the replica set: + +.. literalinclude:: /includes/connect/connection-targets-local-replica.scala + :language: scala + :start-after: start-replica-set + :end-before: end-replica-set + :dedent: + +The following example shows how to specify members of the replica set and the +``replicaSet`` option with the replica set name: + +.. literalinclude:: /includes/connect/connection-targets-local-replica.scala + :language: scala + :start-after: start-replica-set-option + :end-before: end-replica-set-option + :dedent: + +The following example shows how to specify a list of ``ServerAddress`` instances +corresponding to all of the replica set members: + +.. literalinclude:: /includes/connect/connection-targets-local-replica.scala + :language: scala + :start-after: start-replica-set-server-address + :end-before: end-replica-set-server-address + :dedent: + +.. note:: + + The ``MongoClient`` constructor is *non-blocking*. + When you connect to a replica set, the constructor returns immediately while the + client uses background threads to connect to the replica set. + + If you construct a ``MongoClient`` and immediately print the string representation + of its ``nodes`` attribute, the list might be empty while the client connects to + the replica-set members. + +API Documentation +----------------- + +To learn more about any of the methods or types discussed in this guide, see the +following API documentation: + +- `MongoClient <{+api+}/org/mongodb/scala/MongoClient$.html>`__ +- `MongoClientSettings <{+api+}/org/mongodb/scala/MongoClientSettings$.html>`__ +- `MongoClientSettings.Builder <{+core-api+}/com/mongodb/MongoClientSettings.Builder.html>`__ +- `ServerApi <{+core-api+}/com/mongodb/ServerApi.html>`__ diff --git a/source/connect/stable-api.txt b/source/connect/stable-api.txt index 18e9f9b..361934c 100644 --- a/source/connect/stable-api.txt +++ b/source/connect/stable-api.txt @@ -13,7 +13,7 @@ Stable API .. facet:: :name: genre :values: reference - + .. meta:: :keywords: compatible, backwards, upgrade diff --git a/source/includes/connect/connection-target.scala b/source/includes/connect/connection-target.scala new file mode 100644 index 0000000..2889da8 --- /dev/null +++ b/source/includes/connect/connection-target.scala @@ -0,0 +1,37 @@ +import com.mongodb.{ServerApi, ServerApiVersion} +import org.mongodb.scala.{ConnectionString, MongoClient, MongoClientSettings} +import org.mongodb.scala.bson.Document + +import scala.concurrent.Await +import scala.concurrent.duration.DurationInt +import scala.util.Using + +// start-connect +object MongoClientConnectToAtlas { + + def main(args: Array[String]): Unit = { + + // Replace the placeholder with your Atlas connection string + val connectionString = ""; + + // Constructs a ServerApi instance using the ServerApi.builder() method + val serverApi = ServerApi.builder.version(ServerApiVersion.V1).build() + + val settings = MongoClientSettings + .builder() + .applyConnectionString(ConnectionString(connectionString)) + .serverApi(serverApi) + .build() + + // Creates a new client and connects to the server + Using(MongoClient(settings)) { mongoClient => + // Sends a ping to confirm a successful connection + val database = mongoClient.getDatabase("admin") + val ping = database.runCommand(Document("ping" -> 1)).head() + + Await.result(ping, 10.seconds) + System.out.println("Pinged your deployment. You successfully connected to MongoDB!") + } + } +} +// end-connect diff --git a/source/includes/connect/connection-targets-local-replica.scala b/source/includes/connect/connection-targets-local-replica.scala new file mode 100644 index 0000000..a63dc47 --- /dev/null +++ b/source/includes/connect/connection-targets-local-replica.scala @@ -0,0 +1,41 @@ +import com.mongodb.{ServerApi, ServerApiVersion} +import org.mongodb.scala.{ConnectionString, MongoClient, MongoClientSettings} +import org.mongodb.scala.bson.Document + +import scala.concurrent.Await +import scala.concurrent.duration.DurationInt +import scala.util.Using + +// start-connect +object MongoClientConnectionTargets { + + // start-connect-local + val mongoClient = MongoClient() + // end-connect-local + + // start-connect-local-host + val mongoClient = MongoClient("mongodb://host1") + // end-connect-local-host + + // start-connect-local-host-port + val mongoClient = MongoClient("mongodb://host1:27017") + // end-connect-local-host-port + + // start-replica-set + val mongoClient = MongoClient("mongodb://host1:27017,host2:27017,host3:27017") + // end-replica-set + + // start-replica-set-option + val mongoClient = MongoClient("mongodb://host1:27017,host2:27017,host3:27017/?replicaSet=myReplicaSet") + // end-replica-set-option + + // start-replica-set-server-address + val mongoClient = MongoClient( + MongoClientSettings.builder() + .applyToClusterSettings((builder: ClusterSettings.Builder) => builder.hosts(List( + new ServerAddress("host1", 27017), + new ServerAddress("host2", 27017), + new ServerAddress("host3", 27017)).asJava)) + .build()) + // end-replica-set-server-address +} From bd4ee2e9a0211d8bc8df21cffac70d82e49f3762 Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Wed, 20 Nov 2024 10:20:28 -0500 Subject: [PATCH 38/44] DOCSP-42334: Observables (#86) * DOCSP-42334: Observables * edits * fix error * MM feedback * edits * word * MM feedback 2 * edit * build * JY feedback * JY feedback 2 * edit --- source/includes/observables.scala | 58 ++++++ source/index.txt | 7 + source/observables.txt | 299 ++++++++++++++++++++++++++++++ 3 files changed, 364 insertions(+) create mode 100644 source/includes/observables.scala create mode 100644 source/observables.txt diff --git a/source/includes/observables.scala b/source/includes/observables.scala new file mode 100644 index 0000000..738f0e1 --- /dev/null +++ b/source/includes/observables.scala @@ -0,0 +1,58 @@ +import org.mongodb.scala._ +import org.mongodb.scala.bson.Document +import org.mongodb.scala.model.Filters._ +import org.mongodb.scala.result._ + +object Observables { + + def main(args: Array[String]): Unit = { + val mongoClient = MongoClient("") + + // start-db-coll + val database: MongoDatabase = mongoClient.getDatabase("sample_restaurants") + val collection: MongoCollection[Document] = database.getCollection("restaurants") + // end-db-coll + + // start-read-observable + val filter = equal("cuisine", "Czech") + val findObservable: Observable[Document] = collection.find(filter) + + findObservable.subscribe(new Observer[Document] { + override def onNext(result: Document): Unit = println(result) + override def onError(e: Throwable): Unit = println("Failed: " + e.getMessage) + override def onComplete(): Unit = println("Processed all results") + }) + // end-read-observable + + // start-write-observable + val docs: Seq[Document] = Seq( + Document("name" -> "Cafe Himalaya", "cuisine" -> "Nepalese"), + Document("name" -> "Taste From Everest", "cuisine" -> "Nepalese") + ) + val insertObservable: Observable[InsertManyResult] = collection.insertMany(docs) + + insertObservable.subscribe(new Observer[InsertManyResult] { + override def onNext(result: InsertManyResult): Unit = println(result) + override def onError(e: Throwable): Unit = println("Failed: " + e.getMessage) + override def onComplete(): Unit = println("Processed all results") + }) + // end-write-observable + + // start-lambda + collection.distinct("borough") + .subscribe((value: String) => println(value), + (e: Throwable) => println(s"Failed: $e"), + () => println("Processed all results")) + // end-lambda + + // start-to-future + val observable = collection.find(equal("name", "The Halal Guys")) + val results = Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) + results.foreach(println) + // end-to-future + + // Wait for the operations to complete before closing client + Thread.sleep(1000) + mongoClient.close() + } +} diff --git a/source/index.txt b/source/index.txt index d2670fe..b1d15ea 100644 --- a/source/index.txt +++ b/source/index.txt @@ -20,6 +20,7 @@ /indexes /monitoring /aggregation + /observables /security /whats-new /read-write-pref @@ -82,6 +83,12 @@ Transform Your Data with Aggregation Learn how to use the {+driver-short+} to perform aggregation operations in the :ref:`scala-aggregation` section. +Access Data From an Observable +------------------------------ + +Learn how to access results from a read or write operation in the +:ref:`scala-observables` section. + Secure Your Data ---------------- diff --git a/source/observables.txt b/source/observables.txt new file mode 100644 index 0000000..e44da3c --- /dev/null +++ b/source/observables.txt @@ -0,0 +1,299 @@ +.. _scala-observables: + +============================== +Access Data From an Observable +============================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: code example, subscribe, non-blocking + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to access the results of MongoDB operations +from an ``Observable`` instance. + +An ``Observable`` represents a stream of data emitted by an operation +over time. To access this data, you can create an ``Observer`` instance +that subscribes to the ``Observable``. + +.. note:: + + The {+driver-short+} is built upon the MongoDB Java Reactive Streams driver. + The ``Observable`` class extends the ``Publisher`` class from Java Reactive + Streams and includes additional convenience methods to help process results. + +How to Process an Observable +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To run a MongoDB operation and process its data, you must request +the operation results from an ``Observable``. The driver provides the +``Observable`` interface for operations that return any number of +results. Operations that produce no results or one result, such as the +``findOne()`` method, return a ``SingleObservable[T]``. The ``[T]`` +parameterization corresponds to the data type that the ``SingleObservable`` +handles. + +Operations that can produce an unbounded number of results return an instance of the +``Observable[T]`` type. Some operations return specific ``Observable`` types that +provide additional methods to filter and process results before subscribing to them. +The following list describes some types that allow you to chain operation-specific +methods to the ``Observable``: + +- ``FindObservable[T]``: Returned by the ``find()`` method +- ``DistinctObservable[T]``: Returned by the ``distinct()`` method +- ``AggregateObservable[T]``: Returned by the ``aggregate()`` method + +You can request operation results by calling the ``subscribe()`` method +on the operation's ``Observable``. Pass an instance of the ``Observer`` +class as a parameter to the ``subscribe()`` method. This ``Observer`` receives +the operation results from the ``Observable``. Then, you can use methods +provided by the ``Observer`` class to print results, handle errors, and +perform additional data processing. + +To learn more about processing results, see the following API documentation: + +- `Observable <{+api+}/org/mongodb/scala/Observable.html>`__ +- `Subscription <{+api+}/org/mongodb/scala/Subscription.html>`__ +- `Observer <{+api+}/org/mongodb/scala/Observer.html>`__ + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``restaurants`` collection in the ``sample_restaurants`` +database from the :atlas:`Atlas sample datasets `. To access this collection +from your Scala application, create a ``MongoClient`` that connects to an Atlas cluster +and assign the following values to your ``database`` and ``collection`` variables: + +.. literalinclude:: /includes/observables.scala + :language: scala + :dedent: + :start-after: start-db-coll + :end-before: end-db-coll + +To learn how to create a free MongoDB Atlas cluster and load the sample datasets, see the +:atlas:`Get Started with Atlas ` guide. + +.. _scala-observables-callbacks: + +Use Callbacks to Process Results +-------------------------------- + +After subscribing to an ``Observable[T]``, you can use the following +callback methods provided by the ``Observer`` class to access operation results +or handle errors: + +- ``onNext(result: TResult)``: Called when the ``Observer`` receives new results. You + can define logic for processing results by overriding this method. + +- ``onError(e: Throwable)``: Called when the operation generates an error and prevents + the ``Observer`` from receiving more data from the ``Observable``. You can define + error handling logic by overriding this method. + +- ``onComplete()``: Called when the ``Observer`` has consumed all results from the + ``Observable``. You can perform any final data processing by overriding this method. + +The following sections show how to customize these methods to process read and +write operation results from an ``Observable``. + +Access Read Operation Results +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To access data retrieved by a read operation, create an ``Observable[T]`` +to store the operation results. Then, subscribe to the observable and +override the ``Observer`` class callback methods to process the results. + +This example queries the ``restaurants`` collection for documents +in which the ``cuisine`` value is ``"Czech"``. To retrieve +and process results, the example assigns a ``Observable[Document]`` +to the operation and performs the following actions: + +- Calls the ``subscribe()`` method to subscribe to the ``Observable`` + and passes an ``Observer`` as a parameter +- Overrides the ``onNext()`` method to print each retrieved document, + which are ``Document`` instances +- Overrides the ``onError()`` method to print any errors +- Overrides the ``onComplete()`` methods to print a message after all + the results from the ``Observable`` are processed + +.. io-code-block:: + :copyable: + + .. input:: /includes/observables.scala + :language: scala + :start-after: start-read-observable + :end-before: end-read-observable + :dedent: + + .. output:: + :language: console + :visible: false + + Iterable((_id, ...), ..., (name,BsonString{value='Koliba Restaurant'}), + (restaurant_id,BsonString{value='40812870'})) + Iterable((_id, ...), ..., (name,BsonString{value='Bohemian Beer Garden'}), + (restaurant_id,BsonString{value='41485121'})) + Iterable((_id,...), ..., (name,BsonString{value='Hospoda'}), + (restaurant_id,BsonString{value='41569184'})) + Iterable((_id,...), ..., (name,BsonString{value='Olde Prague Tavern'}), + (restaurant_id,BsonString{value='41711983'})) + Processed all results + +Access Write Operation Results +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To access data retrieved by a write operation, create an ``Observable[T]`` +to store the operation results. Then, subscribe to the observable and +override the ``Observer`` class callback methods to process the results. + +This example inserts documents into the ``restaurants`` collection +in which the ``cuisine`` value is ``"Nepalese"``. To retrieve +and process results, the example assigns an ``Observable[InsertManyResult]`` +to the operation and performs the following actions: + +- Calls the ``subscribe()`` method to subscribe to the ``Observable`` + and passes an ``Observer`` as a parameter +- Overrides the ``onNext()`` method to print the result of the insert + operation, returned as an ``InsertManyResult`` +- Overrides the ``onError()`` method to print any errors +- Overrides the ``onComplete()`` methods to print a message after all + the results from the ``Observable`` are processed + +.. io-code-block:: + :copyable: + + .. input:: /includes/observables.scala + :language: scala + :start-after: start-write-observable + :end-before: end-write-observable + :dedent: + + .. output:: + :language: console + :visible: false + + AcknowledgedInsertManyResult{insertedIds={0=BsonObjectId{value=...}, + 1=BsonObjectId{value=...}}} + Processed all results + +.. _scala-observables-lambda: + +Use Lambda Functions to Process Results +--------------------------------------- + +Instead of explicitly overriding the callback functions from the +``Observer`` class, you can use lambda functions to concisely +process operation results. These functions allow you to use the +``=>`` arrow notation to customize the implementation of ``onNext()``, +``onError()``, and ``onComplete()``. + +.. tip:: + + To learn more about lambda functions, also known as anonymous + functions, see the :wikipedia:`Anonymous function ` + Wikipedia entry. + +Example +~~~~~~~ + +This example queries the ``restaurants`` collection for each +distinct value of the ``borough`` field. The code subscribes +to the ``Observable`` returned by the ``distinct()`` method, then +uses lambda functions to print results and handle errors: + +.. io-code-block:: + :copyable: + + .. input:: /includes/observables.scala + :language: scala + :start-after: start-lambda + :end-before: end-lambda + :dedent: + + .. output:: + :language: console + :visible: false + + Bronx + Brooklyn + Manhattan + Missing + Queens + Staten Island + Processed all results + +.. _scala-observables-futures: + +Use Futures to Retrieve All Results +----------------------------------- + +You can subscribe to an ``Observable`` implicitly and aggregate its results +by calling the ``toFuture()`` method. When you call ``toFuture()`` on +an ``Observable``, the driver performs the following actions: + +- Subscribes to the ``Observable`` +- Collects the items emitted by the ``Observable`` and stores them in a + ``Future`` instance + +Then, you can iterate through the ``Future`` to retrieve the operation +results. + +.. important:: + + If your ``Observable`` contains a large number of documents, calling + the ``toFuture()`` method will consume significant memory. If you expect + a large result set, consider using :ref:`callback ` + or :ref:`lambda ` functions to access results. + +Example +~~~~~~~ + +This example queries the ``restaurants`` collection for documents +in which the value of the ``name`` field is ``"The Halal Guys"``. +To access the operation results, the code converts the ``Observable`` +to a ``Future``, waits until the ``Future`` collects each result, and +iterates through the results: + +.. io-code-block:: + :copyable: + + .. input:: /includes/observables.scala + :language: scala + :start-after: start-to-future + :end-before: end-to-future + :dedent: + + .. output:: + :language: console + :visible: false + + Iterable((_id,...), ..., (name,BsonString{value='The Halal Guys'}), + (restaurant_id,BsonString{value='50012258'})) + Iterable((_id,...), ..., (name,BsonString{value='The Halal Guys'}), + (restaurant_id,BsonString{value='50017823'})) + + +API Documentation +----------------- + +To learn more about any of the methods or types discussed in this +guide, see the following API documentation: + +- `Observable <{+api+}/org/mongodb/scala/Observable.html>`__ +- `Observer <{+api+}/org/mongodb/scala/Observer.html>`__ +- `onNext() <{+api+}/org/mongodb/scala/Observer.html#onNext(result:T):Unit>`__ +- `onError() <{+api+}/org/mongodb/scala/Observer.html#onError(e:Throwable):Unit>`__ +- `onComplete() <{+api+}/org/mongodb/scala/Observer.html#onComplete():Unit>`__ +- `find() <{+api+}/org/mongodb/scala/MongoCollection.html#find[C](filter:org.mongodb.scala.bson.conversions.Bson)(implicite:org.mongodb.scala.bson.DefaultHelper.DefaultsTo[C,TResult],implicitct:scala.reflect.ClassTag[C]):org.mongodb.scala.FindObservable[C]>`__ +- `distinct() <{+api+}/org/mongodb/scala/MongoCollection.html#distinct[C](fieldName:String)(implicitct:scala.reflect.ClassTag[C]):org.mongodb.scala.DistinctObservable[C]>`__ +- `toFuture() <{+api+}/org/mongodb/scala/ObservableImplicits$ObservableFuture.html#toFuture():scala.concurrent.Future[Seq[T]]>`__ \ No newline at end of file From 5acbda0f1609a5f06d1fc09fbf42ecfc5565bdd8 Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Wed, 20 Nov 2024 13:17:02 -0500 Subject: [PATCH 39/44] DOCSP-24494: Atlas search section (#90) * DOCSP-24494: Atlas search section * edits * imports * admonitions * code output * rename headers * MR feedback * edits * JY feedback --- source/aggregation.txt | 73 ++++++++++++++++++++++++++----- source/includes/aggregation.scala | 15 +++++++ 2 files changed, 78 insertions(+), 10 deletions(-) diff --git a/source/aggregation.txt b/source/aggregation.txt index bb58e27..d216947 100644 --- a/source/aggregation.txt +++ b/source/aggregation.txt @@ -93,10 +93,10 @@ Consider the following limitations when performing aggregation operations: operator has a strict memory limit of 100 megabytes and ignores the value passed to the ``allowDiskUse()`` method. -.. _scala-aggregation-example: +.. _scala-run-aggregation: -Aggregation Example -------------------- +Run Aggregation Operations +-------------------------- .. note:: Sample Data @@ -109,14 +109,17 @@ To perform an aggregation, pass a list containing the pipeline stages to the ``aggregate()`` method. The {+driver-short+} provides the ``Aggregates`` class, which includes helper methods for building pipeline stages. -.. tip:: +To learn more about pipeline stages and their corresponding ``Aggregates`` helper +methods, see the following resources: + +- :manual:`Aggregation Stages ` in the + {+mdb-server+} manual +- `Aggregates <{+api+}/org/mongodb/scala/model/Aggregates$.html>`__ in the API documentation - To learn more about pipeline stages and their corresponding ``Aggregates`` helper - methods, see the following resources: +.. _scala-aggregation-example: - - :manual:`Aggregation Stages ` in the - {+mdb-server+} manual - - `Aggregates <{+api+}/org/mongodb/scala/model/Aggregates$.html>`__ in the API documentation +Filter, Group, and Count Documents +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This code example produces a count of the number of bakeries in each borough of New York. To do so, it calls the ``aggregate()`` method and passes an aggregation @@ -166,7 +169,7 @@ information about verbosity, see :manual:`Verbosity Modes ` clusters running v4.2 or later that are + covered by an :atlas:`Atlas Search index `. + +To specify a full-text search of one or more fields, you can create +a ``$search`` pipeline stage. The {+driver-short+} provides the +``Aggregates.search()`` helper method to create this stage. The ``search()`` +method requires the following arguments: + +- ``SearchOperator`` instance: Specifies the field and text to search for. +- ``SearchOptions`` instance: Specifies options to customize the full-text + search. You must set the ``index`` option to the name of the Atlas Search + index to use. + +This example creates pipeline stages to perform the following actions: + +- Search the ``name`` field for text that contains the word ``"Salt"`` +- Project only the ``_id`` and ``name`` values of matching documents + +.. io-code-block:: + :copyable: + + .. input:: /includes/aggregation.scala + :start-after: start-atlas-search + :end-before: end-atlas-search + :language: scala + :dedent: + + .. output:: + :visible: false + + {"_id": {"$oid": "..."}, "name": "Fresh Salt"} + {"_id": {"$oid": "..."}, "name": "Salt & Pepper"} + {"_id": {"$oid": "..."}, "name": "Salt + Charcoal"} + {"_id": {"$oid": "..."}, "name": "A Salt & Battery"} + {"_id": {"$oid": "..."}, "name": "Salt And Fat"} + {"_id": {"$oid": "..."}, "name": "Salt And Pepper Diner"} + +.. important:: + + To run the preceding example, you must create an Atlas Search index on the ``restaurants`` + collection that covers the ``name`` field. Then, replace the ``""`` + placeholder with the name of the index. To learn more about Atlas Search indexes, see + the :ref:`scala-atlas-search-index` guide. + Additional Information ---------------------- diff --git a/source/includes/aggregation.scala b/source/includes/aggregation.scala index 945bbbc..4abbea4 100644 --- a/source/includes/aggregation.scala +++ b/source/includes/aggregation.scala @@ -2,6 +2,9 @@ import org.mongodb.scala._ import org.mongodb.scala.model.{ Aggregates, Filters, Accumulators } import org.mongodb.scala.bson.Document import com.mongodb.ExplainVerbosity +import org.mongodb.scala.model.Projections +import org.mongodb.scala.model.search._ +import org.mongodb.scala.model.search.SearchOptions.searchOptions object Aggregation { @@ -35,6 +38,18 @@ object Aggregation { (e: Throwable) => println(s"There was an error: $e")) // end-explain + // start-atlas-search + val operator = SearchOperator.text(SearchPath.fieldPath("name"), "Salt") + val options = searchOptions().index("") + + val pipeline = Seq(Aggregates.search(operator, options), + Aggregates.project(Projections.include("name"))) + + collection.aggregate(pipeline) + .subscribe((doc: Document) => println(doc.toJson()), + (e: Throwable) => println(s"There was an error: $e")) + // end-atlas-search + Thread.sleep(1000) mongoClient.close() } From d47e9dc9add8b1332e417b62ae43460e516a58b4 Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Wed, 20 Nov 2024 13:28:59 -0500 Subject: [PATCH 40/44] DOCSP-42346: Upgrade driver (#91) * DOCSP-42346: Upgrade driver * edits * MW feedback * edits, link fixes * MW final feedback * JY feedback --- .../mongodb-compatibility-table-scala.rst | 2 +- source/index.txt | 19 +- source/upgrade.txt | 507 ++++++++++++++++++ 3 files changed, 521 insertions(+), 7 deletions(-) create mode 100644 source/upgrade.txt diff --git a/source/includes/mongodb-compatibility-table-scala.rst b/source/includes/mongodb-compatibility-table-scala.rst index 9a5781f..d602029 100644 --- a/source/includes/mongodb-compatibility-table-scala.rst +++ b/source/includes/mongodb-compatibility-table-scala.rst @@ -23,7 +23,7 @@ - ✓ - ✓ - ✓ - - ✓ + - * - 4.10 to 5.1 - ⊛ diff --git a/source/index.txt b/source/index.txt index b1d15ea..ff4504d 100644 --- a/source/index.txt +++ b/source/index.txt @@ -23,6 +23,7 @@ /observables /security /whats-new + /upgrade /read-write-pref /compatibility View the Source @@ -53,12 +54,6 @@ Databases and Collections Learn how to use the {+driver-short+} to work with MongoDB databases and collections in the :ref:`scala-databases-collections` section. -What's New ----------- - -For a list of new features and changes in each version, see the :ref:`What's New ` -section. - Write Data to MongoDB --------------------- @@ -108,6 +103,18 @@ Learn how to configure read and write operations on a replica set in the .. Learn how to report bugs, contribute to the driver, and find more resources for .. asking questions and receiving help in the :ref:`Issues & Help ` section. +What's New +---------- + +For a list of new features and changes in each version, see the :ref:`What's New ` +section. + +Upgrade Library Versions +------------------------ + +Learn what changes you must make to your application to upgrade driver +versions in the :ref:`scala-upgrade` section. + Compatibility ------------- diff --git a/source/upgrade.txt b/source/upgrade.txt new file mode 100644 index 0000000..17aba72 --- /dev/null +++ b/source/upgrade.txt @@ -0,0 +1,507 @@ +.. _scala-upgrade: + +======================= +Upgrade Driver Versions +======================= + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: backwards compatibility, update + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this section, you can identify the changes you must +make to your application to upgrade your driver to a new version. + +Before you upgrade, perform the following actions: + +- Ensure the new driver version is compatible with the {+mdb-server+} versions + your application connects to and the Java Runtime Environment (JRE) your + application runs on. To view compatibility information, see the + :ref:`Compatibility ` page. +- Address any breaking changes between the current version of the driver + your application is using and your planned upgrade version in the + :ref:`Breaking Changes ` section. To learn + more about {+mdb-server+} release compatibility changes, see the + :ref:`` section. + +.. tip:: + + To minimize the number of changes your application might require when + you upgrade driver versions in the future, use the + :ref:`{+stable-api+}. ` + +.. _scala-breaking-changes: + +Breaking Changes +---------------- + +A breaking change is a modification in a convention or behavior in +a specific version of the driver that might prevent your application from +working properly if not addressed before upgrading. + +The breaking changes in this section are categorized by the driver version that +introduced them. When upgrading driver versions, address all the breaking +changes between the current and upgrade versions. For example, if you +are upgrading the driver from v4.0 to v4.7, address all breaking changes from +the version after v4.0 including any listed for v4.7. + +.. _scala-breaking-changes-v5.2: + +Version 5.2 Breaking Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This driver version introduces the following breaking changes: + +- Drops support for {+mdb-server+} v3.6. To learn more about this change, + see the :ref:`` section. + +- Revises the `mongodb-crypt `__ + dependency versioning to match the versioning for the JVM drivers. Future versions of + ``mongodb-crypt`` will be released alongside the driver and will share the same version number. + You must upgrade your ``mongodb-crypt`` dependency to v5.2.0 when upgrading your driver for + this release. + +.. _scala-breaking-changes-v5.1: + +Version 5.1 Breaking Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This driver version introduces the following breaking changes: + +- When using the ``MONGODB-OIDC`` authentication mechanism, you must + not include comma characters in the ``authMechanismProperties`` connection + string value. + +.. _scala-breaking-changes-v5.0: + +Version 5.0 Breaking Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This driver version introduces the following breaking changes: + +- Introduces the following changes to the ``ConnectionId`` class: + + - The ``ConnectionId`` constructor now accepts a value of type ``long`` as its second + parameter instead of type ``int``. Similarly, the constructor now accepts a value of + type ``Long`` as its third parameter instead of type ``Integer``. Because this change breaks + binary compatibility, recompile any existing code that calls the + ``ConnectionId`` constructor. + + - The ``withServerValue()`` method now accepts a parameter of type ``long`` rather than + type ``int``. Because this change breaks binary compatibility, you must recompile any code + that calls the ``withServerValue()`` method. + + - The ``getServerValue()`` method now returns a value of type ``Long`` instead of type + ``Integer``. Similarly, the ``getLocalValue()`` method returns a value of type + ``long`` instead of type ``int``. Because this change breaks both binary and source + compatibility, update any source code that uses these methods and rebuild your binary. + +- Replaces the following record annotations from the + ``org.bson.codecs.record.annotations`` package with + annotations of the same name from the ``org.bson.codecs.pojo.annotations`` package: + + - ``BsonId`` + - ``BsonProperty`` + - ``BsonRepresentation`` + +- Changes the data type of the ``connectTimeout`` timeout duration parameter for the + ``SocketSettings.Builder.connectTimeout()`` and + ``SocketSettings.Builder.readTimeout()`` methods. The data type of this + parameter is now ``long`` instead of ``int``. + + In earlier versions, this parameter is of type ``int`` for both methods. This + change breaks binary compatibility and requires recompiling, but does not + require code changes. + +- Removes the ``Filters.eqFull()`` method, released + exclusively in ``Beta``, which allowed you + to construct an equality filter when performing a vector search. + Instead, you can use the ``Filters.eq()`` method when instantiating a + ``VectorSearchOptions`` type, as shown in the following code: + + .. code-block:: java + + VectorSearchOptions opts = vectorSearchOptions().filter(eq("x", 8)); + +.. _scala-breaking-changes-v5.0-observables: + +- Removes the ``org.mongodb.scala.ObservableImplicits.ToSingleObservableVoid`` + implicit class. The ``org.reactivestreams.Publisher[Void]`` type no longer + converts automatically to ``org.mongodb.scala.SingleObservable[Void]``. The + API also exposes ``org.mongodb.scala.Observable[Unit]`` instead of + ``org.mongodb.scala.Observable[Void]``. + + For more information, see the `Observable + `__ + trait in the Scala API documentation. + +- Changes how ``ClusterSettings`` computes the ``ClusterConnectionMode`` + setting, making it more consistent by using the specified + replica set name, regardless of how it is configured. Previously, the driver + considered the replica set name only if it was set in the connection string. + + For example, the following two code samples both return the value + ``ClusterConnectionMode.MULTIPLE``. Previously, the second example returned + ``ClusterConnectionMode.SINGLE``. + + .. code-block:: java + + ClusterSettings.builder() + .applyConnectionString(new ConnectionString("mongodb://127.0.0.1:27017/?replicaSet=replset")) + .build() + .getMode() + + .. code-block:: java + + ClusterSettings.builder() + .hosts(Collections.singletonList( + new ServerAddress("127.0.0.1", 27017) + )) + .requiredReplicaSetName("replset") + .build() + .getMode() + +- ``BsonDecimal128`` values respond to method calls in the same way as + ``Decimal128`` values. ``BsonDecimal128.isNumber()`` now returns ``true`` and + ``BsonDecimal128.asNumber()`` returns the equivalent ``BsonNumber``. + +- Removes the `ServerAddress <{+core-api+}/com/mongodb/ServerAddress.html>`__ + methods ``getSocketAddress()`` and ``getSocketAddresses()``. + + Instead of ``getSocketAddress()``, use the ``getByName()`` instance + method of ``java.net.InetAddress``. + + Instead of ``getSocketAddresses()``, use the ``getAllByName()`` instance + method of ``java.net.InetAddress``. + +- Removes the `UnixServerAddress <{+core-api+}/com/mongodb/UnixServerAddress.html>`__ + methods ``getSocketAddress()`` and ``getUnixSocketAddress()``. + + Instead of ``getSocketAddress()``, use the ``getByName()`` instance + method of ``java.net.InetAddress``. + + Instead of ``getUnixSocketAddress()``, construct an instance of + ``jnr.unixsocket.UnixSocketAddress``. Pass the full path of the UNIX + socket file to the constructor. By default, MongoDB creates a UNIX + socket file located at ``"/tmp/mongodb-27017.sock"``. To learn more + about the ``UnixSocketAddress`` class, see the `UnixSocketAddress `__ API documentation. + +- Removes the ``Parameterizable`` interface. Instead of + implementing this interface on a custom ``Codec`` type, + override the ``CodecProvider.get()`` method on the + codec's ``CodecProvider`` if the codec is intended for a parameterized + type. + +- Removes the ``isSlaveOk()`` method from the + ``ReadPreference`` and ``TaggableReadPreference`` classes. To check whether a read preference allows + reading from a secondary member of a replica set, use the ``isSecondaryOk()`` methods from + these classes instead. + +- Removes the ``DBCollection.getStats()`` and ``DBCollection.isCapped()`` + helper methods for the ``collStats`` command. Instead of these methods, you can use the + ``$collStats`` aggregation pipeline stage. + +- Removes the ``MapCodec`` and ``IterableCodec`` classes. + Instead of ``MapCodec``, use ``MapCodecProvider``. Instead of ``IterableCodec``, + use ``CollectionCodecProvider``, or ``IterableCodecProvider`` for ``Iterable`` + types that aren't ``Collection`` types. + +- Removes the ``sharded()`` and ``nonAtomic()`` methods from the + ``MapReducePublisher`` and ``MapReduceIterable`` classes. + +- Removes the following methods for use with ``geoHaystack`` indexes: + + - ``Indexes.geoHaystack()`` + - ``IndexOptions.getBucketSize()`` + - ``IndexOptions.bucketSize()`` + + Instead, you can use the ``$geoNear`` aggregation pipeline stage or a geospatial + query operator on a 2d index. For more information, see the + :manual:`Geospatial Queries ` page in the {+mdb-server+} manual. + +- Removes the ``oplogReplay`` option from find operations. The + following ``oplogReplay`` methods are no longer available: + + - ``DBCursor.oplogReplay()`` + - ``DBCollectionFindOptions.isOplogReplay()`` + - ``DBCollectionFindOptions.oplogReplay()`` + - ``FindPublisher.oplogReplay()`` + - ``FindIterable.oplogReplay()`` + +- Removes the following ``Exception`` constructors: + + - ``MongoBulkWriteException(BulkWriteResult, List, WriteConcernError, ServerAddress)`` + - ``MongoCursorNotFoundException(long, ServerAddress)`` + - ``MongoQueryException(ServerAddress, int, String)`` + - ``MongoQueryException(ServerAddress, int, String, String)`` + - ``MongoQueryException(MongoCommandException)`` + +- Removes the following overloads for the ``BulkWriteResult.acknowledged()`` method: + + - ``acknowledged(Type, int, List)`` + - ``acknowledged(Type, int, Integer, List)`` + - ``acknowledged(int, int, int, Integer, List)`` + +- Removes the following ``ChangeStreamDocument`` constructors: + + - ``ChangeStreamDocument(String, BsonDocument, BsonDocument, BsonDocument, + TDocument, TDocument, BsonDocument, ...)`` + - ``ChangeStreamDocument(String, BsonDocument, BsonDocument, BsonDocument, + TDocument, BsonDocument, BsonTimestamp, ...)`` + - ``ChangeStreamDocument(OperationType, BsonDocument, BsonDocument, BsonDocument, + TDocument, BsonDocument, BsonTimestamp, ...)`` + +- Removes the following constructors for events: + + - ``CommandEvent(RequestContext, int, ConnectionDescription, String)`` + - ``CommandEvent(int, ConnectionDescription, String)`` + - ``CommandEvent(RequestContext, long, int, ConnectionDescription, String)`` + - ``CommandFailedEvent(RequestContext, int, ConnectionDescription, String, + long, Throwable)`` + - ``CommandFailedEvent(int, ConnectionDescription, String, long, Throwable)`` + - ``CommandStartedEvent(RequestContext, int, ConnectionDescription, String, + String, BsonDocument)`` + - ``CommandStartedEvent(int, ConnectionDescription, String, String, BsonDocument)`` + - ``CommandSucceededEvent(RequestContext, int, ConnectionDescription, String, + BsonDocument, long)`` + - ``CommandSucceededEvent(int, ConnectionDescription, String, BsonDocument, long)`` + - ``ConnectionCheckedInEvent(ConnectionId)`` + - ``ConnectionCheckedOutEvent(ConnectionId, long)`` + - ``ConnectionCheckedOutEvent(ConnectionId)`` + - ``ConnectionCheckOutFailedEvent(ServerId, long, Reason)`` + - ``ConnectionCheckOutFailedEvent(ServerId, Reason)`` + - ``ConnectionCheckOutStartedEvent(ServerId)`` + - ``ConnectionReadyEvent(ConnectionId)`` + - ``ServerHeartbeatFailedEvent(ConnectionId, long, Throwable)`` + - ``ServerHeartbeatSucceededEvent(ConnectionId, BsonDocument, long)`` + +- Removes the ``errorLabels`` option from the ``WriteConcernError`` + class. This includes the ``addLabel()`` and ``getErrorLabels()`` methods and the + constructor that includes an ``errorLabels`` parameter. Instead, you can use + the error labels included in the ``MongoException`` object that contains the + ``WriteConcernError``. + +- Removes the following classes from the + ``com.mongodb.event`` package: + + - ``ConnectionAddedEvent`` + - ``ConnectionPoolOpenedEvent`` + - ``ConnectionRemovedEvent`` + - ``ClusterListenerAdapter`` + - ``ConnectionPoolListenerAdapter`` + - ``ServerListenerAdapter`` + - ``ServerMonitorListenerAdapter`` + + The driver also removes the following related methods from the + ``ConnectionPoolListener`` interface: + + - ``connectionAdded()`` + - ``connectionPoolOpened()`` + - ``connectionRemoved()`` + + For more information about the ``com.mongodb.event`` package, see the + `API documentation. <{+core-api+}/com/mongodb/event/package-summary.html>`__ + +.. _scala-breaking-changes-v5.0-list-collections: + +- Adds the ``authorizedCollection`` option for the ``listCollections`` command. + This introduces a breaking binary change in the ``MongoDatabase.listCollectionNames()`` + method. This change does not require any changes to source code, but you must + recompile any code that uses this method. + +- Removes the following methods and types related to the + `Stream + `__ + interface: + + - ``MongoClientSettings.Builder.streamFactoryFactory()`` method. + Use the ``MongoClientSettings.Builder.transportSettings()`` method instead. + - ``MongoClientSettings.getStreamFactoryFactory()`` method. + Use the ``MongoClientSettings.getTransportSettings()`` method instead. + - ``NettyStreamFactoryFactory`` class. + Instead, call the ``TransportSettings.nettyBuilder()`` method to create + a ``NettyTransportSettings`` object. Then, call the ``MongoClientSettings.Builder.transportSettings()`` + method to apply the settings. + - ``NettyStreamFactory`` class. + - ``AsynchronousSocketChannelStreamFactory`` class. + - ``AsynchronousSocketChannelStreamFactoryFactory`` class. + - ``BufferProvider`` interface. + - ``SocketStreamFactory`` class. + - ``Stream`` interface. + - ``StreamFactory`` interface. + - ``StreamFactoryFactory`` interface. + - ``TlsChannelStreamFactoryFactory`` class. + +.. _scala-breaking-changes-v4.8: + +Version 4.8 Breaking Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This driver version introduces the following breaking changes: + +- Ends support for connecting to {+mdb-server+} versions v3.4 and + earlier. To learn more about this change, see the :ref:`` + section. +- Requires adding an explicit dependency on the ``org.bson.codecs.record`` + module if your application deploys the driver in an OSGi container and + relies on the driver for encoding and decoding Java records. + +- ``RecordCodec`` deserializes POJOs and record classes that are specified + as type parameters of ``List`` or ``Map`` fields to the proper record and POJO types. + Previously, this codec deserialized them as ``Document`` values. + + For example, the following record class definitions show a ``Book`` record + that contains a ``List`` that receives a ``Chapter`` type parameter: + + .. code-block:: java + + public record Book(String title, List chapters) {} + public record Chapter(Integer number, String text) {} + + Starting in this version, the codec deserializes data in the ``List`` into + ``Chapter`` record classes instead of ``Document`` values. + +.. _scala-breaking-changes-v4.7: + +Version 4.7 Breaking Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This driver version introduces the following breaking changes: + +- The ``setWindowFields`` builder API is no longer in beta. The new pipeline + stage builder method breaks binary and source compatibility. See the + `Aggregates `__ + API documentation for information about the new ``setWindowFields()`` method signatures. + + If your application uses this builder in a version earlier than v4.7, update + your source code to use the new method signature and rebuild your binary. + +.. _scala-breaking-changes-v4.2: + +Version 4.2 Breaking Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This driver version introduces the following breaking changes: + +- Updates the ``ObjectId`` class and its ``serialVersionUID`` field + to use a new format that minimizes serialization compatibility issues + across different versions of the driver. + + If an application using driver version 4.2 or later attempts to + perform Java Object Serialization on any objects that contain an + ``ObjectId`` and were serialized by a prior version of the driver, Java + throws an ``InvalidClassException``. + + To learn more about Java Object Serialization, see `Serializable Objects + `__ in + the Java documentation. + +.. _scala-breaking-changes-v4.0: + +Version 4.0 Breaking Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This driver version introduces the following breaking changes: + +- Removes several classes and methods marked as deprecated in the 3.12 release. +- Modifies insert helper methods to return an ``InsertOneResult`` or + ``InsertManyResult`` object instead of ``void``. +- Modifies ``toJson()`` methods in the ``BsonDocument``, ``Document``, and + ``DbObject`` classes to return a relaxed JSON format instead of a strict JSON + format. This makes the JSON documents more readable, but can make it more + difficult to identify the BSON type information, such as the difference + between a 32-bit and 64-bit integer. If your application relies on the + strict JSON format, use the strict mode when reading or writing data. +- Changes the default BSON representation of ``java.util.UUID`` values + from ``JAVA_LEGACY`` to ``UNSPECIFIED``. Applications that store or retrieve + UUID values must explicitly specify which representation to use. You can + specify the representation in the ``uuidRepresentation`` property of + a ``MongoClientSettings`` object. + + The UUID representation that you specify strictly controls how the driver + decodes UUIDs. In version 4.0, the ``JAVA_LEGACY`` representation works + only with subtype 3. In previous versions of the driver, if you specified the + ``JAVA_LEGACY`` representation, the driver would decode binary objects + of subtypes 3 and 4 as UUIDs. + + For a list of members in the ``UuidRepresentation`` enum, see the + `v4.0 API documentation. `__ + +- The connection pool no longer restricts the number of wait queue threads + or asynchronous tasks that require a connection to MongoDB. The + application throttles requests as necessary rather than depending on + the driver to throw a ``MongoWaitQueueFullException``. +- The driver no longer logs using the ``java.util.logging`` package + and only supports the SLF4J logging framework. +- The embedded and Android drivers were removed. If your application + relies on these drivers, you must continue to use a 3.x Java driver + version. +- The uber JARs for the Java driver, ``mongo-java-driver`` and ``mongodb-driver``, are no + longer published. If your application relies on these uber JARs, remove + them as a dependency and use one of the following packages instead: + + - If your application uses the current API, add the ``mongodb-driver-sync`` package + as a dependency. + - If your application uses the legacy API, add the ``mongodb-driver-legacy`` package + as a dependency. + +- Several classes introduce binary compatibility breaks, such + as the method signature change to the insert helper methods. Recompile + any classes that link to the driver against this version or later to ensure + that they continue to work. + +.. _scala-server-release-changes: + +Server Release Compatibility Changes +------------------------------------ + +A server release compatibility change is a modification +to the {+driver-short+} that discontinues support for a set of +{+mdb-server+} versions. + +The driver discontinues support for a {+mdb-server+} version after it reaches +end-of-life (EOL). + +To learn more about the MongoDB support for EOL products, +see the `Legacy Support Policy `__. + +.. _scala-server-8.1-incompatibility: + +Server Version 8.1 Support Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You cannot use a 3.x version of the {+driver-short+} to connect to a +MongoDB deployment running {+mdb-server+} v8.1. Starting in {+mdb-server+} v8.1, +the ``buildinfo`` command requires authentication, causing an +incompatibility with the v3.x driver. + +.. _scala-server-release-change-v5.2: + +Driver Version 5.2 Server Support Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The v5.2 driver drops support for {+mdb-server+} v3.6. To use the +v5.2 driver, your {+mdb-server+} must be v4.0 or later. To learn +how to upgrade your {+mdb-server+} deployment, see :manual:`Release +Notes ` in the {+mdb-server+} manual. + +.. _scala-server-release-change-v4.8: + +Driver Version 4.8 Server Support Changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The v4.8 driver drops support for {+mdb-server+} v3.4 and earlier. +To use the v4.8 driver, your {+mdb-server+} must be v3.6 or later. To learn +how to upgrade your {+mdb-server+} deployment, see :manual:`Release +Notes ` in the {+mdb-server+} manual. From a636436154d702e09183de733f4f88e60fc90b15 Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Thu, 21 Nov 2024 14:57:44 -0500 Subject: [PATCH 41/44] DOCSP-45354: TOC labels (#94) * add labels * LM feedback --- source/connect.txt | 8 ++++---- source/databases-collections.txt | 4 ++-- source/index.txt | 28 ++++++++++++++-------------- source/indexes.txt | 8 ++++---- source/monitoring.txt | 2 +- source/read.txt | 14 +++++++------- source/security.txt | 2 +- source/write.txt | 14 +++++++------- 8 files changed, 40 insertions(+), 40 deletions(-) diff --git a/source/connect.txt b/source/connect.txt index 3208a4b..d4e7858 100644 --- a/source/connect.txt +++ b/source/connect.txt @@ -20,10 +20,10 @@ Connect to MongoDB .. toctree:: - /connect/mongoclient - /connect/stable-api - /connect/connection-targets - /connect/tls + Create a Client + Stable API + Choose a Connection Target + Configure TLS .. /connect/connection-options Overview diff --git a/source/databases-collections.txt b/source/databases-collections.txt index 523fbba..f76b2a6 100644 --- a/source/databases-collections.txt +++ b/source/databases-collections.txt @@ -21,8 +21,8 @@ Databases and Collections :titlesonly: :maxdepth: 1 - /databases-collections/run-command - /databases-collections/time-series + Run a Command + Time Series Overview -------- diff --git a/source/index.txt b/source/index.txt index ff4504d..d638069 100644 --- a/source/index.txt +++ b/source/index.txt @@ -12,20 +12,20 @@ :titlesonly: :maxdepth: 1 - /get-started - /connect - /databases-collections - /read - /write - /indexes - /monitoring - /aggregation - /observables - /security - /whats-new - /upgrade - /read-write-pref - /compatibility + Get Started + Connect + Databases & Collections + Read Data + Write Data + Indexes + Monitor Your Application + Data Aggregation + Observables + Security + What's New + Upgrade + Operations on Replica Sets + Compatibility View the Source API Documentation <{+api+}/index.html> Previous Versions diff --git a/source/indexes.txt b/source/indexes.txt index 28accc7..7fb6be6 100644 --- a/source/indexes.txt +++ b/source/indexes.txt @@ -22,10 +22,10 @@ Optimize Queries by Using Indexes :titlesonly: :maxdepth: 1 - /indexes/single-field-index - /indexes/compound-index - /indexes/multikey-index - /indexes/atlas-search-index + Single Field + Compound + Multikey + Atlas Search Overview -------- diff --git a/source/monitoring.txt b/source/monitoring.txt index dbd1d17..b4af597 100644 --- a/source/monitoring.txt +++ b/source/monitoring.txt @@ -7,4 +7,4 @@ Monitor Your Application .. toctree:: :caption: Monitoring categories - /monitoring/cluster-monitoring \ No newline at end of file + Cluster Monitoring \ No newline at end of file diff --git a/source/read.txt b/source/read.txt index 1366a6c..3415b0e 100644 --- a/source/read.txt +++ b/source/read.txt @@ -22,13 +22,13 @@ Read Data from MongoDB :titlesonly: :maxdepth: 1 - /read/retrieve - /read/specify-a-query - /read/specify-documents-to-return - /read/project - /read/distinct - /read/count - /read/change-streams + Retrieve Data + Specify a Query + Specify Documents to Return + Specify Fields to Return + Distinct Field Values + Count Documents + Monitor Changes Overview -------- diff --git a/source/security.txt b/source/security.txt index bbd41f1..cb9e128 100644 --- a/source/security.txt +++ b/source/security.txt @@ -22,7 +22,7 @@ Secure Your Data :titlesonly: :maxdepth: 1 - /security/auth + Authentication Overview -------- diff --git a/source/write.txt b/source/write.txt index 4a0d9cf..094ccd2 100644 --- a/source/write.txt +++ b/source/write.txt @@ -22,13 +22,13 @@ Write Data to MongoDB :titlesonly: :maxdepth: 1 - /write/insert - /write/replace - /write/update - /write/delete - /write/bulk-write - /write/transactions - /write/gridfs + Insert + Replace + Update + Delete + Bulk Write Operations + Transactions + Store Large Files Overview -------- From f21fa5da0f74d25fd217c2d5619fc514c7aa4384 Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Fri, 22 Nov 2024 10:31:12 -0500 Subject: [PATCH 42/44] DOCSP-45348: Cleanup (#93) * DOCSP-45348: Cleanup * edits * TODOs * add back in-use encryption * edits * sharedinclude fixes * fix link * RR feedback --- snooty.toml | 1 - source/compatibility.txt | 4 +- source/connect/connection-options.txt | 24 ----- source/connect/mongoclient.txt | 10 +- source/databases-collections.txt | 6 +- source/databases-collections/run-command.txt | 10 +- source/databases-collections/time-series.txt | 5 +- source/get-started.txt | 9 +- .../create-a-connection-string.txt | 5 +- source/get-started/next-steps.txt | 24 +++++ .../databases-collections.scala | 29 +++--- .../includes/indexes/atlas-search-index.scala | 25 +++-- .../security/crypt-maven-versioned.rst | 9 ++ .../includes/security/crypt-sbt-versioned.rst | 3 + .../temp/compatibility-table-legend.rst | 18 ++++ source/includes/temp/encrypt-fields.rst | 92 +++++++++++++++++++ .../temp/lifecycle-schedule-callout.rst | 6 ++ source/includes/temp/v5.1.3-wn-items.rst | 2 + source/includes/temp/v5.2-wn-items.rst | 86 +++++++++++++++++ source/index.txt | 49 +++++----- source/indexes/atlas-search-index.txt | 10 +- source/indexes/single-field-index.txt | 6 +- source/read/retrieve.txt | 11 +-- source/security.txt | 4 + source/security/encrypt.txt | 28 ++++++ source/whats-new.txt | 4 +- 26 files changed, 363 insertions(+), 117 deletions(-) delete mode 100644 source/connect/connection-options.txt create mode 100644 source/get-started/next-steps.txt create mode 100644 source/includes/security/crypt-maven-versioned.rst create mode 100644 source/includes/security/crypt-sbt-versioned.rst create mode 100644 source/includes/temp/compatibility-table-legend.rst create mode 100644 source/includes/temp/encrypt-fields.rst create mode 100644 source/includes/temp/lifecycle-schedule-callout.rst create mode 100644 source/includes/temp/v5.1.3-wn-items.rst create mode 100644 source/includes/temp/v5.2-wn-items.rst create mode 100644 source/security/encrypt.txt diff --git a/snooty.toml b/snooty.toml index 1977579..3507966 100644 --- a/snooty.toml +++ b/snooty.toml @@ -11,7 +11,6 @@ sharedinclude_root = "https://raw.githubusercontent.com/10gen/docs-shared/main/" toc_landing_pages = [ "/get-started", - "/connect", "/bson", "/tutorials/connect", "/tutorials/write-ops", diff --git a/source/compatibility.txt b/source/compatibility.txt index b982658..b29c38f 100644 --- a/source/compatibility.txt +++ b/source/compatibility.txt @@ -31,9 +31,9 @@ of the {+driver-long+} for use with a specific version of MongoDB. The first column lists the driver version. -.. sharedinclude:: dbx/lifecycle-schedule-callout.rst +.. include:: includes/temp/lifecycle-schedule-callout.rst -.. sharedinclude:: dbx/compatibility-table-legend.rst +.. include:: includes/temp/compatibility-table-legend.rst .. include:: /includes/mongodb-compatibility-table-scala.rst diff --git a/source/connect/connection-options.txt b/source/connect/connection-options.txt deleted file mode 100644 index 80f1f8a..0000000 --- a/source/connect/connection-options.txt +++ /dev/null @@ -1,24 +0,0 @@ -.. _scala-connection-options: - -========================== -Specify Connection Options -========================== - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: connection string, URI, server, Atlas, settings, client - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Overview --------- - -.. TODO - diff --git a/source/connect/mongoclient.txt b/source/connect/mongoclient.txt index 293f592..b45159b 100644 --- a/source/connect/mongoclient.txt +++ b/source/connect/mongoclient.txt @@ -40,9 +40,6 @@ Connection URI A standard connection string includes the following components: -.. TODO, add this as last sentence for ``username:password`` description once a scala auth page is made: -.. For more information about the ``authSource`` connection option, see :ref:`scala-sync-auth`. - .. list-table:: :widths: 20 80 :header-rows: 1 @@ -59,6 +56,8 @@ A standard connection string includes the following components: - Optional. Authentication credentials. If you include these, the client authenticates the user against the database specified in ``authSource``. + To learn more about the ``authSource`` connection option, see the + :ref:`scala-auth` guide. * - ``host[:port]`` @@ -75,9 +74,8 @@ A standard connection string includes the following components: * - ``?`` - Optional. A query string that specifies connection-specific - options as ``=`` pairs. See - :ref:`scala-connection-options` for a full description of - these options. + options as ``=`` pairs. + .. TODO: See :ref:`scala-connection-options` for a full description of these options. For more information about creating a connection string, see :manual:`Connection Strings ` in the diff --git a/source/databases-collections.txt b/source/databases-collections.txt index f76b2a6..46d962b 100644 --- a/source/databases-collections.txt +++ b/source/databases-collections.txt @@ -138,8 +138,10 @@ To query for only the names of the collections in the database, call the test_collection example_collection -.. TODO -.. For more information about iterating over a cursor, see :ref:`scala-cursors`. +.. tip:: + + For more information about iterating over a ``Future`` instance, see :ref:`scala-observables-futures` + in the Access Data From an Observable guide. Delete a Collection ------------------- diff --git a/source/databases-collections/run-command.txt b/source/databases-collections/run-command.txt index 61cdc16..df3f1bc 100644 --- a/source/databases-collections/run-command.txt +++ b/source/databases-collections/run-command.txt @@ -95,11 +95,11 @@ You can set a read preference for the command execution by passing a ``ReadPreference`` instance as a parameter to ``runCommand()``, as shown in the following code: - .. literalinclude:: /includes/run-command.scala - :language: scala - :dedent: - :start-after: start-readpref - :end-before: end-readpref +.. literalinclude:: /includes/run-command.scala + :language: scala + :dedent: + :start-after: start-readpref + :end-before: end-readpref .. tip:: diff --git a/source/databases-collections/time-series.txt b/source/databases-collections/time-series.txt index 31a532a..2d47b13 100644 --- a/source/databases-collections/time-series.txt +++ b/source/databases-collections/time-series.txt @@ -167,9 +167,8 @@ following {+mdb-server+} manual entries: To learn more about performing read operations, see :ref:`scala-read`. -.. TODO -.. To learn more about performing aggregation operations, see the :ref:`scala-aggregation` -.. guide. +To learn more about performing aggregation operations, see the :ref:`scala-aggregation` +guide. API Documentation ~~~~~~~~~~~~~~~~~ diff --git a/source/get-started.txt b/source/get-started.txt index 32d3e65..042fb97 100644 --- a/source/get-started.txt +++ b/source/get-started.txt @@ -20,10 +20,11 @@ Get Started with the Scala Driver .. toctree:: - /get-started/download-and-install/ - /get-started/create-a-deployment - /get-started/create-a-connection-string - /get-started/connect-to-mongodb + Download & Install + Create a Deployment + Create a Connection String + Connect to MongoDB + Next Steps Overview -------- diff --git a/source/get-started/create-a-connection-string.txt b/source/get-started/create-a-connection-string.txt index e18462b..36f8dff 100644 --- a/source/get-started/create-a-connection-string.txt +++ b/source/get-started/create-a-connection-string.txt @@ -20,9 +20,8 @@ The connection string includes the hostname or IP address and port of your deployment, the authentication mechanism, user credentials when applicable, and connection options. -.. TODO: - To connect to an instance or deployment not hosted on Atlas, see - :ref:`scala-connection-targets`. +To learn how to connect to an instance or deployment not hosted on Atlas, see the +:ref:`scala-connection-targets` guide. .. procedure:: :style: connected diff --git a/source/get-started/next-steps.txt b/source/get-started/next-steps.txt new file mode 100644 index 0000000..a0b2c9e --- /dev/null +++ b/source/get-started/next-steps.txt @@ -0,0 +1,24 @@ +.. _scala-quick-start-next-steps: + +========== +Next Steps +========== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: learn more + +Congratulations on completing the quick start tutorial! + +In this tutorial, you created a {+language+} application that +connects to a MongoDB deployment hosted on MongoDB Atlas +and retrieves a document that matches a query. + +Learn more about {+driver-short+} from the following resources: + +- Learn how to perform read operations in the :ref:`` section. + +- Learn how to perform write operations in the :ref:`` section. \ No newline at end of file diff --git a/source/includes/databases-collections/databases-collections.scala b/source/includes/databases-collections/databases-collections.scala index 456b469..bc5d1d6 100644 --- a/source/includes/databases-collections/databases-collections.scala +++ b/source/includes/databases-collections/databases-collections.scala @@ -25,10 +25,12 @@ object DatabasesCollections { // end-access-collection } - // start-create-collection - val createObservable = database.createCollection("example_collection") - Await.result(createObservable.toFuture(), Duration(10, TimeUnit.SECONDS)) - // end-create-collection + { + // start-create-collection + val createObservable = database.createCollection("example_collection") + Await.result(createObservable.toFuture(), Duration(10, TimeUnit.SECONDS)) + // end-create-collection + } { // start-find-collections @@ -51,16 +53,19 @@ object DatabasesCollections { // end-delete-collection } - // start-database-read-prefs - val databaseWithReadPrefs = - mongoClient.getDatabase("test_database").withReadPreference(ReadPreference.secondary()) - // end-database-read-prefs + { - // start-collection-read-prefs - val collectionWithReadPrefs = - database.getCollection("test_collection").withReadPreference(ReadPreference.secondary()) - // end-collection-read-prefs + // start-database-read-prefs + val databaseWithReadPrefs = + mongoClient.getDatabase("test_database").withReadPreference(ReadPreference.secondary()) + // end-database-read-prefs + // start-collection-read-prefs + val collectionWithReadPrefs = + database.getCollection("test_collection").withReadPreference(ReadPreference.secondary()) + // end-collection-read-prefs + + } { // start-tags diff --git a/source/includes/indexes/atlas-search-index.scala b/source/includes/indexes/atlas-search-index.scala index 88f38d8..0313bfa 100644 --- a/source/includes/indexes/atlas-search-index.scala +++ b/source/includes/indexes/atlas-search-index.scala @@ -16,8 +16,9 @@ object AtlasSearchIndexes { { // start-create-search-index val index = Document("mappings" -> Document("dynamic" -> true)) - val observable = collection.createSearchIndex("", index) - Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) + collection.createSearchIndex("", index) + .subscribe((result: String) => ()) + // end-create-search-index } @@ -25,33 +26,29 @@ object AtlasSearchIndexes { // start-create-search-indexes val indexOne = SearchIndexModel("", Document("mappings" -> Document("dynamic" -> true, "fields" -> Document("field1" -> Document("type" -> "string"))))) val indexTwo = SearchIndexModel("", Document("mappings" -> Document("dynamic" -> false, "fields" -> Document("field2" -> Document("type" -> "string"))))) - val observable = collection.createSearchIndexes(List(indexOne, indexTwo)) - Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) + collection.createSearchIndexes(List(indexOne, indexTwo)) + .subscribe((result: String) => ()) // end-create-search-indexes } { // start-list-search-indexes - val observable = collection.listSearchIndexes() - observable.subscribe(new Observer[Document] { - override def onNext(index: Document): Unit = println(index.toJson()) - override def onError(e: Throwable): Unit = println("Error: " + e.getMessage) - override def onComplete(): Unit = println("Completed") - }) + collection.listSearchIndexes() + .subscribe((result: Document) => println(result.toJson())) // end-list-search-indexes } { // start-update-search-indexes val updateIndex = Document("mappings" -> Document("dynamic" -> false)) - val observable = collection.updateSearchIndex("", updateIndex) - Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) + collection.updateSearchIndex("", updateIndex) + .subscribe((result: Unit) => ()) // end-update-search-indexes } // start-drop-search-index - val observable = collection.dropSearchIndex("") - Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) + collection.dropSearchIndex("") + .subscribe((result: Unit) => ()) // end-drop-search-index Thread.sleep(1000) diff --git a/source/includes/security/crypt-maven-versioned.rst b/source/includes/security/crypt-maven-versioned.rst new file mode 100644 index 0000000..1a87d8a --- /dev/null +++ b/source/includes/security/crypt-maven-versioned.rst @@ -0,0 +1,9 @@ +.. code-block:: xml + + + + org.mongodb + mongodb-crypt + {+mongocrypt-version+} + + \ No newline at end of file diff --git a/source/includes/security/crypt-sbt-versioned.rst b/source/includes/security/crypt-sbt-versioned.rst new file mode 100644 index 0000000..7fb9c62 --- /dev/null +++ b/source/includes/security/crypt-sbt-versioned.rst @@ -0,0 +1,3 @@ +.. code-block:: none + + libraryDependencies += "org.mongodb" % "mongodb-crypt" % "{+mongocrypt-version+}" \ No newline at end of file diff --git a/source/includes/temp/compatibility-table-legend.rst b/source/includes/temp/compatibility-table-legend.rst new file mode 100644 index 0000000..074b63a --- /dev/null +++ b/source/includes/temp/compatibility-table-legend.rst @@ -0,0 +1,18 @@ +Compatibility Table Legend +++++++++++++++++++++++++++ + +.. list-table:: + :header-rows: 1 + :stub-columns: 1 + :class: compatibility + + * - Icon + - Explanation + + * - ✓ + - All features are supported. + * - ⊛ + - The Driver version will work with the MongoDB version, but not all + new MongoDB features are supported. + * - No mark + - The Driver version is not tested with the MongoDB version. \ No newline at end of file diff --git a/source/includes/temp/encrypt-fields.rst b/source/includes/temp/encrypt-fields.rst new file mode 100644 index 0000000..a7c9806 --- /dev/null +++ b/source/includes/temp/encrypt-fields.rst @@ -0,0 +1,92 @@ +================= +In-Use Encryption +================= + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +You can use the {+driver-short+} to encrypt specific document fields by using a +set of features called **in-use encryption**. In-use encryption allows +your application to encrypt data *before* sending it to MongoDB +and query documents with encrypted fields. + +|driver-specific-content| + +In-use encryption prevents unauthorized users from viewing plaintext +data as it is sent to MongoDB or while it is in an encrypted database. To +enable in-use encryption in an application and authorize it to decrypt +data, you must create encryption keys that only your application can +access. Only applications that have access to your encryption +keys can access the decrypted, plaintext data. If an attacker gains +access to the database, they can only see the encrypted ciphertext data +because they lack access to the encryption keys. + +You might use in-use encryption to encrypt fields in your MongoDB +documents that contain the following types of sensitive data: + +- Credit card numbers +- Addresses +- Health information +- Financial information +- Any other sensitive or personally identifiable information (PII) + +MongoDB offers the following features to enable in-use encryption: + +- :ref:`Queryable Encryption ` +- :ref:`Client-side Field Level Encryption ` + +.. _subsection-qe: + +Queryable Encryption +~~~~~~~~~~~~~~~~~~~~ + +Queryable Encryption is the next-generation in-use encryption feature, +first introduced as a preview feature in MongoDB Server version 6.0 and +as a generally available (GA) feature in MongoDB 7.0. Queryable +Encryption supports searching encrypted fields for equality and encrypts +each value uniquely. + +.. important:: Preview Feature Incompatible with MongoDB 7.0 + + The implementation of Queryable Encryption in MongoDB 6.0 is incompatible with the GA version introduced in MongoDB 7.0. The Queryable Encryption preview feature is no longer supported. + +To learn more about Queryable Encryption, see :manual:`Queryable +Encryption ` in the Server manual. + +.. _subsection-csfle: + +Client-side Field Level Encryption +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Client-side Field Level Encryption (CSFLE) was introduced in MongoDB +Server version 4.2 and supports searching encrypted fields for equality. +CSFLE differs from Queryable Encryption in that you can select either a +deterministic or random encryption algorithm to encrypt fields. You can only +query encrypted fields that use a deterministic encryption algorithm when +using CSFLE. When you use a random encryption algorithm to encrypt +fields in CSFLE, they can be decrypted, but you cannot perform equality +queries on those fields. When you use Queryable Encryption, you cannot +specify the encryption algorithm, but you can query all encrypted +fields. + +When you deterministically encrypt a value, the same input value +produces the same output value. While deterministic encryption allows +you to perform queries on those encrypted fields, encrypted data with +low cardinality is susceptible to code breaking by frequency analysis. + +.. tip:: + + To learn more about these concepts, see the following Wikipedia + entries: + + - :wikipedia:`Cardinality ` + - :wikipedia:`Frequency Analysis ` + +To learn more about CSFLE, see :manual:`CSFLE ` in the +Server manual. \ No newline at end of file diff --git a/source/includes/temp/lifecycle-schedule-callout.rst b/source/includes/temp/lifecycle-schedule-callout.rst new file mode 100644 index 0000000..466aea2 --- /dev/null +++ b/source/includes/temp/lifecycle-schedule-callout.rst @@ -0,0 +1,6 @@ +.. important:: + + MongoDB ensures compatibility between the MongoDB Server and the drivers + for three years after the server version's end of life (EOL) date. To learn + more about the MongoDB release and EOL dates, see + `MongoDB Software Lifecycle Schedules `__. \ No newline at end of file diff --git a/source/includes/temp/v5.1.3-wn-items.rst b/source/includes/temp/v5.1.3-wn-items.rst new file mode 100644 index 0000000..d2d64c9 --- /dev/null +++ b/source/includes/temp/v5.1.3-wn-items.rst @@ -0,0 +1,2 @@ +- Fixes an issue that could cause assertion errors when using ``Cursor`` + types. \ No newline at end of file diff --git a/source/includes/temp/v5.2-wn-items.rst b/source/includes/temp/v5.2-wn-items.rst new file mode 100644 index 0000000..6063b51 --- /dev/null +++ b/source/includes/temp/v5.2-wn-items.rst @@ -0,0 +1,86 @@ +- Adds the ``SearchIndexType`` class, which you can pass + when constructing a ``SearchIndexModel`` instance. This change + allows you to specify the index type when creating an Atlas + Search or Vector Search index. To learn more, see |avs-index-link|. + +- Delegates the implementation of the algorithms that implement + the ``SCRAM-SHA-1`` and ``SCRAM-SHA-256`` authentication mechanisms to + the configured JCA provider. This change means that your application + can use a configured FIPS-compliant JCA provider to provide a higher + level of security. + +- Revises the `mongodb-crypt + `__ + dependency versioning to match the versioning for the JVM drivers. + Future versions of ``mongodb-crypt`` will be released alongside the + driver and will share the same version number. You must upgrade your + ``mongodb-crypt`` dependency to v5.2.0 when upgrading your driver for + this release. To learn more, see |encrypt-link|. + +- Performance improvements due to implementation of native cryptography + on all supported platforms. The following list describes the actions + needed to implement this improvement depending on your operating + system: + + - **Windows**: Upgrade your ``mongodb-crypt`` version to v5.2.0. + + - **Mac**: Upgrade your ``mongodb-crypt`` version to v5.2.0. + + - **Linux**: Install ``libmongocrypt.so`` directly on the file system, + instead of using the file that is bundled within the + ``mongodb-crypt`` JAR file. You can find Linux instructions to install + ``libmongocrypt`` in the :manual:`Server manual + `. If you + use a package manager to install ``libmongocrypt``, Java Native + Access (JNA) will find it there without further configuration. + Alternatively, you can specify the search path by setting the + ``LD_LIBRARY_PATH`` environment variable to the file path of the + ``libmongocrypt`` package. + + We recommend direct installation because the bundled shared library + does not link with OpenSSL due to the potential for OpenSSL binary + incompatibilities. + + The shared library loading is handled by JNA. You can view the rules for library + loading search path order in the `NativeLibrary class documentation + `__. + +- Fixes an issue that caused the ``InsertOneResult.getInsertedId()`` and + ``InsertManyResult.getInsertedIds()`` methods to return incorrect document IDs in + some situations. This change is backported to {+driver-short+} v5.1.4 + and v4.11.4. + +- When a sharded cluster operation is unsuccessful, the driver avoids selecting + the same ``mongos`` server for operation retry attempts if other ``mongos`` + servers are available. + +- Adds reachability metadata needed when your application uses GraalVM + Native Image. This metadata replaces the need for collecting + reachability metadata when using the driver libraries. To learn more, + see `Reachability Metadata + `__ + in the GraalVM documentation. + + This change does not add the ``libjnidispatch`` and ``libmongocrypt`` + resource entries, because adding entries for all supported + platforms (targets) significantly affects the size of + native executables built using GraalVM Native Image. View this sample + `resource-config.json + `__ + file in the driver GitHub repository to see how to specify these entries explicitly + if your application depends on the ``org.mongodb:mongodb-crypt`` library. + +- Enables exact vector search by extending the ``VectorSearchOptions`` API + to introduce the following specific option subtypes: + + - ``ExactVectorSearchOptions``: Use this options type to enable + precise matching, ensuring that results are the closest vectors to a + given query vector. + + - ``ApproximateVectorSearchOptions``: Use this options type to + enable searches that might not return the exact closest vectors. You + can pass a ``numCandidates`` parameter when instantiating this type + to specify the number of nearest neighbors to consider. + + To learn more about using the Atlas Vector Search feature, see + |vector-search-link|. \ No newline at end of file diff --git a/source/index.txt b/source/index.txt index d638069..5066e8a 100644 --- a/source/index.txt +++ b/source/index.txt @@ -17,6 +17,7 @@ Databases & Collections Read Data Write Data + Operations on Replica Sets Indexes Monitor Your Application Data Aggregation @@ -24,7 +25,6 @@ Security What's New Upgrade - Operations on Replica Sets Compatibility View the Source API Documentation <{+api+}/index.html> @@ -42,34 +42,45 @@ Get Started Learn how to install the driver, establish a connection to MongoDB, and begin working with data in the :ref:`scala-get-started` tutorial. -.. TODO -.. Connect to MongoDB -.. ------------------ +Connect to MongoDB +------------------ -.. Learn how to create and configure a connection to a MongoDB deployment -.. in the :ref:`scala-connect` section. +Learn how to create and configure a connection to a MongoDB deployment +in the :ref:`scala-connect` section. Databases and Collections ------------------------- + Learn how to use the {+driver-short+} to work with MongoDB databases and collections in the :ref:`scala-databases-collections` section. +Read Data from MongoDB +---------------------- + +Learn how you can retrieve data from MongoDB in the :ref:`scala-read` section. + Write Data to MongoDB --------------------- Learn how you can write data to MongoDB in the :ref:`scala-write` section. -Read Data from MongoDB ----------------------- +Configure Operations on Replica Sets +------------------------------------ -Learn how you can retrieve data from MongoDB in the :ref:`scala-read` section. +Learn how to configure read and write operations on a replica set in the +:ref:`scala-read-write-pref` section. -.. TODO -.. Optimize Queries by Using Indexes -.. --------------------------------- +What's New +---------- -.. Learn how to work with common types of indexes in the :ref:`scala-indexes` -.. section. +For a list of new features and changes in each version, see the :ref:`What's New ` +section. + +Optimize Queries by Using Indexes +--------------------------------- + +Learn how to work with common types of indexes in the :ref:`scala-indexes` +section. Transform Your Data with Aggregation @@ -90,12 +101,6 @@ Secure Your Data Learn how to authenticate your application and encrypt your data in the :ref:`scala-security` section. -Configure Operations on Replica Sets ------------------------------------- - -Learn how to configure read and write operations on a replica set in the -:ref:`scala-read-write-pref` section. - .. TODO .. Issues & Help .. ------------- @@ -109,8 +114,8 @@ What's New For a list of new features and changes in each version, see the :ref:`What's New ` section. -Upgrade Library Versions ------------------------- +Upgrade Driver Versions +----------------------- Learn what changes you must make to your application to upgrade driver versions in the :ref:`scala-upgrade` section. diff --git a/source/indexes/atlas-search-index.txt b/source/indexes/atlas-search-index.txt index 2678f3e..f65fb20 100644 --- a/source/indexes/atlas-search-index.txt +++ b/source/indexes/atlas-search-index.txt @@ -87,13 +87,8 @@ You can use the ``listSearchIndexes()`` method to return all Atlas Search indexe collection. The following code example shows how to print a list of the search indexes in -a collection by using the ``Observable`` returned by the ``listSearchIndexes()``: - -.. .. literalinclude:: /includes/indexes/atlas-search-index.scala -.. :language: scala -.. :start-after: start-list-search-indexes -.. :end-before: end-list-search-indexes -.. :dedent: +a collection by subscribing to the ``Observable`` returned by the ``listSearchIndexes()`` +method: .. io-code-block:: :copyable: true @@ -109,7 +104,6 @@ a collection by using the ``Observable`` returned by the ``listSearchIndexes()`` {"id": "...", "name": "", "type": "search", "status": "READY", "queryable": true, ... } {"id": "...", "name": "", "type": "search", "status": "READY", "queryable": true, ... } - Completed .. _scala-atlas-search-index-update: diff --git a/source/indexes/single-field-index.txt b/source/indexes/single-field-index.txt index 5b5f653..7e9bf4d 100644 --- a/source/indexes/single-field-index.txt +++ b/source/indexes/single-field-index.txt @@ -26,7 +26,7 @@ sort performance. They also support :manual:`TTL Indexes ` that automatically remove documents from a collection after a certain amount of time or at a specified clock time. -When creating a single-field index, you must specify the following +When creating a single field index, you must specify the following details: - The field on which to create the index @@ -35,7 +35,7 @@ details: .. note:: - The default ``_id_`` index is an example of a single-field index. + The default ``_id_`` index is an example of a single field index. This index is automatically created on the ``_id`` field when a new collection is created. @@ -48,7 +48,7 @@ The examples in this guide use the ``movies`` collection in the load the sample datasets, see the :atlas:`Get Started with Atlas ` guide. -Create Single-Field Index +Create Single Field Index ------------------------- To run the examples in this guide, you must include the following import diff --git a/source/read/retrieve.txt b/source/read/retrieve.txt index 86eb89c..3f23e17 100644 --- a/source/read/retrieve.txt +++ b/source/read/retrieve.txt @@ -53,10 +53,9 @@ from which you can access the query results. The ``FindObservable`` class also provides additional methods that you can chain to a ``FindObservable`` instance to modify its behavior, such as ``first()``. -.. TODO: - .. tip:: +.. tip:: - .. To learn more about query filters, see the :ref:`scala-specify-query` guide. + To learn more about query filters, see the :ref:`scala-specify-query` guide. .. _scala-retrieve-find-multiple: @@ -232,10 +231,10 @@ For a full list of ``FindObservable`` member methods, see the API documentation Additional Information ---------------------- -.. TODO: To learn more about query filters, see the :ref:`scala-specify-query` guide. +To learn more about query filters, see the :ref:`scala-specify-query` guide. -.. TODO: To view code examples of retrieving documents with the {+driver-short+}, - see :ref:`scala-read`. +To view code examples that retrieve documents by using the {+driver-short+}, +see :ref:`scala-read`. API Documentation ~~~~~~~~~~~~~~~~~ diff --git a/source/security.txt b/source/security.txt index cb9e128..2fd4b21 100644 --- a/source/security.txt +++ b/source/security.txt @@ -23,6 +23,7 @@ Secure Your Data :maxdepth: 1 Authentication + In-Use Encryption Overview -------- @@ -33,3 +34,6 @@ following sections: - Learn how to use different authentication mechanisms to secure your data in the :ref:`Authentication Mechanisms ` guide. + +- Learn how to encrypt data before sending it to MongoDB in the + :ref:`In-Use Encryption ` guide. diff --git a/source/security/encrypt.txt b/source/security/encrypt.txt new file mode 100644 index 0000000..99b1a52 --- /dev/null +++ b/source/security/encrypt.txt @@ -0,0 +1,28 @@ +.. _scala-encrypt: + +.. include:: includes/temp/encrypt-fields.rst + + .. replacement:: driver-specific-content + + .. important:: Compatible Encryption Library Version + + The {+driver-short+} uses the `mongodb-crypt + `__ + encryption library for in-use encryption. This driver version + is compatible with ``mongodb-crypt`` v{+mongocrypt-version+}. + + Select from the following :guilabel:`Maven` and + :guilabel:`sbt` tabs to see how to add the ``mongodb-crypt`` + dependency to your project by using the specified manager: + + .. tabs:: + + .. tab:: Maven + :tabid: maven-dependency + + .. include:: /includes/security/crypt-maven-versioned.rst + + .. tab:: sbt + :tabid: sbt-dependency + + .. include:: /includes/security/crypt-sbt-versioned.rst \ No newline at end of file diff --git a/source/whats-new.txt b/source/whats-new.txt index e0a938a..1bc43d6 100644 --- a/source/whats-new.txt +++ b/source/whats-new.txt @@ -32,7 +32,7 @@ What's New in 5.2 New features of the 5.2 driver release include: -.. sharedinclude:: dbx/jvm/v5.2-wn-items.rst +.. include:: includes/temp/v5.2-wn-items.rst .. replacement:: avs-index-link @@ -56,7 +56,7 @@ What's New in 5.1.3 The 5.1.3 driver patch release includes the following changes: -.. sharedinclude:: dbx/jvm/v5.1.3-wn-items.rst +.. include:: includes/temp/v5.1.3-wn-items.rst .. _scala-version-5.1.1: From 539bfef36f770cae0ea438e93d66a3a50b0fb6c1 Mon Sep 17 00:00:00 2001 From: norareidy Date: Fri, 22 Nov 2024 11:11:30 -0500 Subject: [PATCH 43/44] staging link From fb0f04e4112d1d2b6a5b67dc55be2dd995e1c7b0 Mon Sep 17 00:00:00 2001 From: norareidy Date: Fri, 22 Nov 2024 11:17:03 -0500 Subject: [PATCH 44/44] fixes --- source/connect/mongoclient.txt | 4 +++- source/index.txt | 10 +++++----- source/whats-new.txt | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/source/connect/mongoclient.txt b/source/connect/mongoclient.txt index b45159b..cfb39a8 100644 --- a/source/connect/mongoclient.txt +++ b/source/connect/mongoclient.txt @@ -75,7 +75,9 @@ A standard connection string includes the following components: - Optional. A query string that specifies connection-specific options as ``=`` pairs. - .. TODO: See :ref:`scala-connection-options` for a full description of these options. + +.. TODO: Add as the last sentence of ``?`` - See :ref:`scala-connection-options` + for a full description of these options. For more information about creating a connection string, see :manual:`Connection Strings ` in the diff --git a/source/index.txt b/source/index.txt index 5066e8a..6085853 100644 --- a/source/index.txt +++ b/source/index.txt @@ -23,6 +23,7 @@ Data Aggregation Observables Security + Issues & Help What's New Upgrade Compatibility @@ -101,12 +102,11 @@ Secure Your Data Learn how to authenticate your application and encrypt your data in the :ref:`scala-security` section. -.. TODO -.. Issues & Help -.. ------------- +Issues & Help +------------- -.. Learn how to report bugs, contribute to the driver, and find more resources for -.. asking questions and receiving help in the :ref:`Issues & Help ` section. +Learn how to report bugs, contribute to the driver, and find more resources for +asking questions and receiving help in the :ref:`Issues & Help ` section. What's New ---------- diff --git a/source/whats-new.txt b/source/whats-new.txt index 44f154e..9e20a3e 100644 --- a/source/whats-new.txt +++ b/source/whats-new.txt @@ -34,7 +34,7 @@ What's New in 5.2 {+driver-short+} v5.2 removes support for {+mdb-server+} 3.6. To learn more about compatible versions of the server, see - :ref:`scala-compatibility-tables`. + :ref:`scala-compatibility`. The 5.2 driver release includes the following changes, fixes, and features: