From 5fae9847eabdd43d58aa56f4867f8c9bf19d67f4 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Tue, 23 Jan 2024 14:44:08 +0530 Subject: [PATCH 01/65] basic native project structure --- .gitattributes | 9 + .gitignore | 20 ++ build-config/checkstyle/build.gradle | 0 build-config/resources/Ballerina.toml | 0 build-config/spotbugs-exclude.xml | 0 build.gradle | 93 +++++++ gradle/libs.versions.toml | 2 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 249 ++++++++++++++++++ gradlew.bat | 92 +++++++ native/build.gradle | 148 +++++++++++ .../stdlib/persist/redis/Constants.java | 3 + .../stdlib/persist/redis/ModuleUtils.java | 3 + .../ballerina/stdlib/persist/redis/Utils.java | 3 + .../redis/datastore/RedisProcessor.java | 3 + native/src/main/java/module-info.java | 3 + settings.gradle | 27 ++ 18 files changed, 662 insertions(+) create mode 100644 .gitattributes create mode 100644 build-config/checkstyle/build.gradle create mode 100644 build-config/resources/Ballerina.toml create mode 100644 build-config/spotbugs-exclude.xml create mode 100644 build.gradle create mode 100644 gradle/libs.versions.toml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 native/build.gradle create mode 100644 native/src/main/java/io/ballerina/stdlib/persist/redis/Constants.java create mode 100644 native/src/main/java/io/ballerina/stdlib/persist/redis/ModuleUtils.java create mode 100644 native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java create mode 100644 native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java create mode 100644 native/src/main/java/module-info.java create mode 100644 settings.gradle diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..097f9f9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/.gitignore b/.gitignore index 524f096..2ce104c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,23 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* replay_pid* + +# Ballerina +Ballerina.lock + +# VS code files +.vscode + +# Generated files +target +.ballerina + +# gradle +.gradle +build/ +gradle-app.setting +!gradle-wrapper.jar +.gradletasknamecache + +# Ignore Gradle build output directory +build diff --git a/build-config/checkstyle/build.gradle b/build-config/checkstyle/build.gradle new file mode 100644 index 0000000..e69de29 diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml new file mode 100644 index 0000000..e69de29 diff --git a/build-config/spotbugs-exclude.xml b/build-config/spotbugs-exclude.xml new file mode 100644 index 0000000..e69de29 diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..8ee4963 --- /dev/null +++ b/build.gradle @@ -0,0 +1,93 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This is a general purpose Gradle build. + * To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.5/samples + */ + +plugins { + id "com.github.spotbugs" version "${githubSpotbugsVersion}" + id "com.github.johnrengelman.shadow" version "${githubJohnrengelmanShadowVersion}" + id "de.undercouch.download" version "${underCouchDownloadVersion}" + id "net.researchgate.release" version "${researchgateReleaseVersion}" +} + +description = 'Ballerina - Persist' + +def packageName = "persist.redis" + +ext.ballerinaLangVersion = project.ballerinaLangVersion + +allprojects { + group = project.group + version = project.version + + apply plugin: 'jacoco' + apply plugin: 'maven-publish' + + repositories { + mavenLocal() + maven { + url = 'https://maven.wso2.org/nexus/content/repositories/releases/' + } + + maven { + url = 'https://maven.wso2.org/nexus/content/groups/wso2-public/' + } + + maven { + url = 'https://repo.maven.apache.org/maven2' + } + + maven { + url = 'https://maven.pkg.github.com/ballerina-platform/*' + credentials { + username System.getenv("packageUser") + password System.getenv("packagePAT") + } + } + } + + ext { + snapshotVersion= '-SNAPSHOT' + timestampedVersionRegex = '.*-\\d{8}-\\d{6}-\\w.*\$' + } +} + +subprojects { + + configurations { + externalJars + ballerinaStdLibs + } + + dependencies { + ballerinaStdLibs "io.ballerina.stdlib:time-ballerina:${stdlibTimeVersion}" + ballerinaStdLibs "io.ballerina.stdlib:persist-ballerina:${stdlibPersistVersion}" + ballerinaStdLibs "io.ballerina.stdlib:observe-ballerina:${observeVersion}" + ballerinaStdLibs "io.ballerina:observe-ballerina:${observeInternalVersion}" + } +} + +def moduleVersion = project.version.replace("-SNAPSHOT", "") + +// release { +// failOnPublishNeeded = false +// failOnSnapshotDependencies = true + +// buildTasks = ['build'] +// versionPropertyFile = 'gradle.properties' +// tagTemplate = 'v$version' + +// git { +// requireBranch = "release-${moduleVersion}" +// pushToRemote = 'origin' +// } +// } + +task build { + // dependsOn(":${packageName}-ballerina:build") +} + +publishToMavenLocal.dependsOn build +publish.dependsOn build \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..4ac3234 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,2 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..d64cd4917707c1f8861d8cb53dd15194d4248596 GIT binary patch literal 43462 zcma&NWl&^owk(X(xVyW%ySuwf;qI=D6|RlDJ2cR^yEKh!@I- zp9QeisK*rlxC>+~7Dk4IxIRsKBHqdR9b3+fyL=ynHmIDe&|>O*VlvO+%z5;9Z$|DJ zb4dO}-R=MKr^6EKJiOrJdLnCJn>np?~vU-1sSFgPu;pthGwf}bG z(1db%xwr#x)r+`4AGu$j7~u2MpVs3VpLp|mx&;>`0p0vH6kF+D2CY0fVdQOZ@h;A` z{infNyvmFUiu*XG}RNMNwXrbec_*a3N=2zJ|Wh5z* z5rAX$JJR{#zP>KY**>xHTuw?|-Rg|o24V)74HcfVT;WtQHXlE+_4iPE8QE#DUm%x0 zEKr75ur~W%w#-My3Tj`hH6EuEW+8K-^5P62$7Sc5OK+22qj&Pd1;)1#4tKihi=~8C zHiQSst0cpri6%OeaR`PY>HH_;CPaRNty%WTm4{wDK8V6gCZlG@U3$~JQZ;HPvDJcT1V{ z?>H@13MJcCNe#5z+MecYNi@VT5|&UiN1D4ATT+%M+h4c$t;C#UAs3O_q=GxK0}8%8 z8J(_M9bayxN}69ex4dzM_P3oh@ZGREjVvn%%r7=xjkqxJP4kj}5tlf;QosR=%4L5y zWhgejO=vao5oX%mOHbhJ8V+SG&K5dABn6!WiKl{|oPkq(9z8l&Mm%(=qGcFzI=eLu zWc_oCLyf;hVlB@dnwY98?75B20=n$>u3b|NB28H0u-6Rpl((%KWEBOfElVWJx+5yg z#SGqwza7f}$z;n~g%4HDU{;V{gXIhft*q2=4zSezGK~nBgu9-Q*rZ#2f=Q}i2|qOp z!!y4p)4o=LVUNhlkp#JL{tfkhXNbB=Ox>M=n6soptJw-IDI|_$is2w}(XY>a=H52d z3zE$tjPUhWWS+5h=KVH&uqQS=$v3nRs&p$%11b%5qtF}S2#Pc`IiyBIF4%A!;AVoI zXU8-Rpv!DQNcF~(qQnyyMy=-AN~U>#&X1j5BLDP{?K!%h!;hfJI>$mdLSvktEr*89 zdJHvby^$xEX0^l9g$xW-d?J;L0#(`UT~zpL&*cEh$L|HPAu=P8`OQZV!-}l`noSp_ zQ-1$q$R-gDL)?6YaM!=8H=QGW$NT2SeZlb8PKJdc=F-cT@j7Xags+Pr*jPtlHFnf- zh?q<6;)27IdPc^Wdy-mX%2s84C1xZq9Xms+==F4);O`VUASmu3(RlgE#0+#giLh-& zcxm3_e}n4{%|X zJp{G_j+%`j_q5}k{eW&TlP}J2wtZ2^<^E(O)4OQX8FDp6RJq!F{(6eHWSD3=f~(h} zJXCf7=r<16X{pHkm%yzYI_=VDP&9bmI1*)YXZeB}F? z(%QsB5fo*FUZxK$oX~X^69;x~j7ms8xlzpt-T15e9}$4T-pC z6PFg@;B-j|Ywajpe4~bk#S6(fO^|mm1hKOPfA%8-_iGCfICE|=P_~e;Wz6my&)h_~ zkv&_xSAw7AZ%ThYF(4jADW4vg=oEdJGVOs>FqamoL3Np8>?!W#!R-0%2Bg4h?kz5I zKV-rKN2n(vUL%D<4oj@|`eJ>0i#TmYBtYmfla;c!ATW%;xGQ0*TW@PTlGG><@dxUI zg>+3SiGdZ%?5N=8uoLA|$4isK$aJ%i{hECP$bK{J#0W2gQ3YEa zZQ50Stn6hqdfxJ*9#NuSLwKFCUGk@c=(igyVL;;2^wi4o30YXSIb2g_ud$ zgpCr@H0qWtk2hK8Q|&wx)}4+hTYlf;$a4#oUM=V@Cw#!$(nOFFpZ;0lc!qd=c$S}Z zGGI-0jg~S~cgVT=4Vo)b)|4phjStD49*EqC)IPwyeKBLcN;Wu@Aeph;emROAwJ-0< z_#>wVm$)ygH|qyxZaet&(Vf%pVdnvKWJn9`%DAxj3ot;v>S$I}jJ$FLBF*~iZ!ZXE zkvui&p}fI0Y=IDX)mm0@tAd|fEHl~J&K}ZX(Mm3cm1UAuwJ42+AO5@HwYfDH7ipIc zmI;1J;J@+aCNG1M`Btf>YT>~c&3j~Qi@Py5JT6;zjx$cvOQW@3oQ>|}GH?TW-E z1R;q^QFjm5W~7f}c3Ww|awg1BAJ^slEV~Pk`Kd`PS$7;SqJZNj->it4DW2l15}xP6 zoCl$kyEF%yJni0(L!Z&14m!1urXh6Btj_5JYt1{#+H8w?5QI%% zo-$KYWNMJVH?Hh@1n7OSu~QhSswL8x0=$<8QG_zepi_`y_79=nK=_ZP_`Em2UI*tyQoB+r{1QYZCpb?2OrgUw#oRH$?^Tj!Req>XiE#~B|~ z+%HB;=ic+R@px4Ld8mwpY;W^A%8%l8$@B@1m5n`TlKI6bz2mp*^^^1mK$COW$HOfp zUGTz-cN9?BGEp}5A!mDFjaiWa2_J2Iq8qj0mXzk; z66JBKRP{p%wN7XobR0YjhAuW9T1Gw3FDvR5dWJ8ElNYF94eF3ebu+QwKjtvVu4L zI9ip#mQ@4uqVdkl-TUQMb^XBJVLW(-$s;Nq;@5gr4`UfLgF$adIhd?rHOa%D);whv z=;krPp~@I+-Z|r#s3yCH+c1US?dnm+C*)r{m+86sTJusLdNu^sqLrfWed^ndHXH`m zd3#cOe3>w-ga(Dus_^ppG9AC>Iq{y%%CK+Cro_sqLCs{VLuK=dev>OL1dis4(PQ5R zcz)>DjEkfV+MO;~>VUlYF00SgfUo~@(&9$Iy2|G0T9BSP?&T22>K46D zL*~j#yJ?)^*%J3!16f)@Y2Z^kS*BzwfAQ7K96rFRIh>#$*$_Io;z>ux@}G98!fWR@ zGTFxv4r~v)Gsd|pF91*-eaZ3Qw1MH$K^7JhWIdX%o$2kCbvGDXy)a?@8T&1dY4`;L z4Kn+f%SSFWE_rpEpL9bnlmYq`D!6F%di<&Hh=+!VI~j)2mfil03T#jJ_s?}VV0_hp z7T9bWxc>Jm2Z0WMU?`Z$xE74Gu~%s{mW!d4uvKCx@WD+gPUQ zV0vQS(Ig++z=EHN)BR44*EDSWIyT~R4$FcF*VEY*8@l=218Q05D2$|fXKFhRgBIEE zdDFB}1dKkoO^7}{5crKX!p?dZWNz$m>1icsXG2N+((x0OIST9Zo^DW_tytvlwXGpn zs8?pJXjEG;T@qrZi%#h93?FP$!&P4JA(&H61tqQi=opRzNpm zkrG}$^t9&XduK*Qa1?355wd8G2CI6QEh@Ua>AsD;7oRUNLPb76m4HG3K?)wF~IyS3`fXuNM>${?wmB zpVz;?6_(Fiadfd{vUCBM*_kt$+F3J+IojI;9L(gc9n3{sEZyzR9o!_mOwFC#tQ{Q~ zP3-`#uK#tP3Q7~Q;4H|wjZHO8h7e4IuBxl&vz2w~D8)w=Wtg31zpZhz%+kzSzL*dV zwp@{WU4i;hJ7c2f1O;7Mz6qRKeASoIv0_bV=i@NMG*l<#+;INk-^`5w@}Dj~;k=|}qM1vq_P z|GpBGe_IKq|LNy9SJhKOQ$c=5L{Dv|Q_lZl=-ky*BFBJLW9&y_C|!vyM~rQx=!vun z?rZJQB5t}Dctmui5i31C_;_}CEn}_W%>oSXtt>@kE1=JW*4*v4tPp;O6 zmAk{)m!)}34pTWg8{i>($%NQ(Tl;QC@J@FfBoc%Gr&m560^kgSfodAFrIjF}aIw)X zoXZ`@IsMkc8_=w%-7`D6Y4e*CG8k%Ud=GXhsTR50jUnm+R*0A(O3UKFg0`K;qp1bl z7``HN=?39ic_kR|^R^~w-*pa?Vj#7|e9F1iRx{GN2?wK!xR1GW!qa=~pjJb-#u1K8 zeR?Y2i-pt}yJq;SCiVHODIvQJX|ZJaT8nO+(?HXbLefulKKgM^B(UIO1r+S=7;kLJ zcH}1J=Px2jsh3Tec&v8Jcbng8;V-`#*UHt?hB(pmOipKwf3Lz8rG$heEB30Sg*2rx zV<|KN86$soN(I!BwO`1n^^uF2*x&vJ$2d$>+`(romzHP|)K_KkO6Hc>_dwMW-M(#S zK(~SiXT1@fvc#U+?|?PniDRm01)f^#55;nhM|wi?oG>yBsa?~?^xTU|fX-R(sTA+5 zaq}-8Tx7zrOy#3*JLIIVsBmHYLdD}!0NP!+ITW+Thn0)8SS!$@)HXwB3tY!fMxc#1 zMp3H?q3eD?u&Njx4;KQ5G>32+GRp1Ee5qMO0lZjaRRu&{W<&~DoJNGkcYF<5(Ab+J zgO>VhBl{okDPn78<%&e2mR{jwVCz5Og;*Z;;3%VvoGo_;HaGLWYF7q#jDX=Z#Ml`H z858YVV$%J|e<1n`%6Vsvq7GmnAV0wW4$5qQ3uR@1i>tW{xrl|ExywIc?fNgYlA?C5 zh$ezAFb5{rQu6i7BSS5*J-|9DQ{6^BVQ{b*lq`xS@RyrsJN?-t=MTMPY;WYeKBCNg z^2|pN!Q^WPJuuO4!|P@jzt&tY1Y8d%FNK5xK(!@`jO2aEA*4 zkO6b|UVBipci?){-Ke=+1;mGlND8)6+P;8sq}UXw2hn;fc7nM>g}GSMWu&v&fqh

iViYT=fZ(|3Ox^$aWPp4a8h24tD<|8-!aK0lHgL$N7Efw}J zVIB!7=T$U`ao1?upi5V4Et*-lTG0XvExbf!ya{cua==$WJyVG(CmA6Of*8E@DSE%L z`V^$qz&RU$7G5mg;8;=#`@rRG`-uS18$0WPN@!v2d{H2sOqP|!(cQ@ zUHo!d>>yFArLPf1q`uBvY32miqShLT1B@gDL4XoVTK&@owOoD)OIHXrYK-a1d$B{v zF^}8D3Y^g%^cnvScOSJR5QNH+BI%d|;J;wWM3~l>${fb8DNPg)wrf|GBP8p%LNGN# z3EaIiItgwtGgT&iYCFy9-LG}bMI|4LdmmJt@V@% zb6B)1kc=T)(|L@0;wr<>=?r04N;E&ef+7C^`wPWtyQe(*pD1pI_&XHy|0gIGHMekd zF_*M4yi6J&Z4LQj65)S zXwdM{SwUo%3SbPwFsHgqF@V|6afT|R6?&S;lw=8% z3}@9B=#JI3@B*#4s!O))~z zc>2_4Q_#&+5V`GFd?88^;c1i7;Vv_I*qt!_Yx*n=;rj!82rrR2rQ8u5(Ejlo{15P% zs~!{%XJ>FmJ})H^I9bn^Re&38H{xA!0l3^89k(oU;bZWXM@kn$#aoS&Y4l^-WEn-fH39Jb9lA%s*WsKJQl?n9B7_~P z-XM&WL7Z!PcoF6_D>V@$CvUIEy=+Z&0kt{szMk=f1|M+r*a43^$$B^MidrT0J;RI` z(?f!O<8UZkm$_Ny$Hth1J#^4ni+im8M9mr&k|3cIgwvjAgjH z8`N&h25xV#v*d$qBX5jkI|xOhQn!>IYZK7l5#^P4M&twe9&Ey@@GxYMxBZq2e7?`q z$~Szs0!g{2fGcp9PZEt|rdQ6bhAgpcLHPz?f-vB?$dc*!9OL?Q8mn7->bFD2Si60* z!O%y)fCdMSV|lkF9w%x~J*A&srMyYY3{=&$}H zGQ4VG_?$2X(0|vT0{=;W$~icCI{b6W{B!Q8xdGhF|D{25G_5_+%s(46lhvNLkik~R z>nr(&C#5wwOzJZQo9m|U<;&Wk!_#q|V>fsmj1g<6%hB{jGoNUPjgJslld>xmODzGjYc?7JSuA?A_QzjDw5AsRgi@Y|Z0{F{!1=!NES-#*f^s4l0Hu zz468))2IY5dmD9pa*(yT5{EyP^G>@ZWumealS-*WeRcZ}B%gxq{MiJ|RyX-^C1V=0 z@iKdrGi1jTe8Ya^x7yyH$kBNvM4R~`fbPq$BzHum-3Zo8C6=KW@||>zsA8-Y9uV5V z#oq-f5L5}V<&wF4@X@<3^C%ptp6+Ce)~hGl`kwj)bsAjmo_GU^r940Z-|`<)oGnh7 zFF0Tde3>ui?8Yj{sF-Z@)yQd~CGZ*w-6p2U<8}JO-sRsVI5dBji`01W8A&3$?}lxBaC&vn0E$c5tW* zX>5(zzZ=qn&!J~KdsPl;P@bmA-Pr8T*)eh_+Dv5=Ma|XSle6t(k8qcgNyar{*ReQ8 zTXwi=8vr>!3Ywr+BhggHDw8ke==NTQVMCK`$69fhzEFB*4+H9LIvdt-#IbhZvpS}} zO3lz;P?zr0*0$%-Rq_y^k(?I{Mk}h@w}cZpMUp|ucs55bcloL2)($u%mXQw({Wzc~ z;6nu5MkjP)0C(@%6Q_I_vsWrfhl7Zpoxw#WoE~r&GOSCz;_ro6i(^hM>I$8y>`!wW z*U^@?B!MMmb89I}2(hcE4zN2G^kwyWCZp5JG>$Ez7zP~D=J^LMjSM)27_0B_X^C(M z`fFT+%DcKlu?^)FCK>QzSnV%IsXVcUFhFdBP!6~se&xxrIxsvySAWu++IrH;FbcY$ z2DWTvSBRfLwdhr0nMx+URA$j3i7_*6BWv#DXfym?ZRDcX9C?cY9sD3q)uBDR3uWg= z(lUIzB)G$Hr!){>E{s4Dew+tb9kvToZp-1&c?y2wn@Z~(VBhqz`cB;{E4(P3N2*nJ z_>~g@;UF2iG{Kt(<1PyePTKahF8<)pozZ*xH~U-kfoAayCwJViIrnqwqO}7{0pHw$ zs2Kx?s#vQr7XZ264>5RNKSL8|Ty^=PsIx^}QqOOcfpGUU4tRkUc|kc7-!Ae6!+B{o~7nFpm3|G5^=0#Bnm6`V}oSQlrX(u%OWnC zoLPy&Q;1Jui&7ST0~#+}I^&?vcE*t47~Xq#YwvA^6^} z`WkC)$AkNub|t@S!$8CBlwbV~?yp&@9h{D|3z-vJXgzRC5^nYm+PyPcgRzAnEi6Q^gslXYRv4nycsy-SJu?lMps-? zV`U*#WnFsdPLL)Q$AmD|0`UaC4ND07+&UmOu!eHruzV|OUox<+Jl|Mr@6~C`T@P%s zW7sgXLF2SSe9Fl^O(I*{9wsFSYb2l%-;&Pi^dpv!{)C3d0AlNY6!4fgmSgj_wQ*7Am7&$z;Jg&wgR-Ih;lUvWS|KTSg!&s_E9_bXBkZvGiC6bFKDWZxsD$*NZ#_8bl zG1P-#@?OQzED7@jlMJTH@V!6k;W>auvft)}g zhoV{7$q=*;=l{O>Q4a@ ziMjf_u*o^PsO)#BjC%0^h>Xp@;5$p{JSYDt)zbb}s{Kbt!T*I@Pk@X0zds6wsefuU zW$XY%yyRGC94=6mf?x+bbA5CDQ2AgW1T-jVAJbm7K(gp+;v6E0WI#kuACgV$r}6L? zd|Tj?^%^*N&b>Dd{Wr$FS2qI#Ucs1yd4N+RBUQiSZGujH`#I)mG&VKoDh=KKFl4=G z&MagXl6*<)$6P}*Tiebpz5L=oMaPrN+caUXRJ`D?=K9!e0f{@D&cZLKN?iNP@X0aF zE(^pl+;*T5qt?1jRC=5PMgV!XNITRLS_=9{CJExaQj;lt!&pdzpK?8p>%Mb+D z?yO*uSung=-`QQ@yX@Hyd4@CI^r{2oiu`%^bNkz+Nkk!IunjwNC|WcqvX~k=><-I3 zDQdbdb|!v+Iz01$w@aMl!R)koD77Xp;eZwzSl-AT zr@Vu{=xvgfq9akRrrM)}=!=xcs+U1JO}{t(avgz`6RqiiX<|hGG1pmop8k6Q+G_mv zJv|RfDheUp2L3=^C=4aCBMBn0aRCU(DQwX-W(RkRwmLeuJYF<0urcaf(=7)JPg<3P zQs!~G)9CT18o!J4{zX{_e}4eS)U-E)0FAt}wEI(c0%HkxgggW;(1E=>J17_hsH^sP z%lT0LGgbUXHx-K*CI-MCrP66UP0PvGqM$MkeLyqHdbgP|_Cm!7te~b8p+e6sQ_3k| zVcwTh6d83ltdnR>D^)BYQpDKlLk3g0Hdcgz2}%qUs9~~Rie)A-BV1mS&naYai#xcZ z(d{8=-LVpTp}2*y)|gR~;qc7fp26}lPcLZ#=JpYcn3AT9(UIdOyg+d(P5T7D&*P}# zQCYplZO5|7+r19%9e`v^vfSS1sbX1c%=w1;oyruXB%Kl$ACgKQ6=qNWLsc=28xJjg zwvsI5-%SGU|3p>&zXVl^vVtQT3o-#$UT9LI@Npz~6=4!>mc431VRNN8od&Ul^+G_kHC`G=6WVWM z%9eWNyy(FTO|A+@x}Ou3CH)oi;t#7rAxdIXfNFwOj_@Y&TGz6P_sqiB`Q6Lxy|Q{`|fgmRG(k+!#b*M+Z9zFce)f-7;?Km5O=LHV9f9_87; zF7%R2B+$?@sH&&-$@tzaPYkw0;=i|;vWdI|Wl3q_Zu>l;XdIw2FjV=;Mq5t1Q0|f< zs08j54Bp`3RzqE=2enlkZxmX6OF+@|2<)A^RNQpBd6o@OXl+i)zO%D4iGiQNuXd+zIR{_lb96{lc~bxsBveIw6umhShTX+3@ZJ=YHh@ zWY3(d0azg;7oHn>H<>?4@*RQbi>SmM=JrHvIG(~BrvI)#W(EAeO6fS+}mxxcc+X~W6&YVl86W9WFSS}Vz-f9vS?XUDBk)3TcF z8V?$4Q)`uKFq>xT=)Y9mMFVTUk*NIA!0$?RP6Ig0TBmUFrq*Q-Agq~DzxjStQyJ({ zBeZ;o5qUUKg=4Hypm|}>>L=XKsZ!F$yNTDO)jt4H0gdQ5$f|d&bnVCMMXhNh)~mN z@_UV6D7MVlsWz+zM+inZZp&P4fj=tm6fX)SG5H>OsQf_I8c~uGCig$GzuwViK54bcgL;VN|FnyQl>Ed7(@>=8$a_UKIz|V6CeVSd2(P z0Uu>A8A+muM%HLFJQ9UZ5c)BSAv_zH#1f02x?h9C}@pN@6{>UiAp>({Fn(T9Q8B z^`zB;kJ5b`>%dLm+Ol}ty!3;8f1XDSVX0AUe5P#@I+FQ-`$(a;zNgz)4x5hz$Hfbg z!Q(z26wHLXko(1`;(BAOg_wShpX0ixfWq3ponndY+u%1gyX)_h=v1zR#V}#q{au6; z!3K=7fQwnRfg6FXtNQmP>`<;!N137paFS%y?;lb1@BEdbvQHYC{976l`cLqn;b8lp zIDY>~m{gDj(wfnK!lpW6pli)HyLEiUrNc%eXTil|F2s(AY+LW5hkKb>TQ3|Q4S9rr zpDs4uK_co6XPsn_z$LeS{K4jFF`2>U`tbgKdyDne`xmR<@6AA+_hPNKCOR-Zqv;xk zu5!HsBUb^!4uJ7v0RuH-7?l?}b=w5lzzXJ~gZcxRKOovSk@|#V+MuX%Y+=;14i*%{)_gSW9(#4%)AV#3__kac1|qUy!uyP{>?U#5wYNq}y$S9pCc zFc~4mgSC*G~j0u#qqp9 z${>3HV~@->GqEhr_Xwoxq?Hjn#=s2;i~g^&Hn|aDKpA>Oc%HlW(KA1?BXqpxB;Ydx)w;2z^MpjJ(Qi(X!$5RC z*P{~%JGDQqojV>2JbEeCE*OEu!$XJ>bWA9Oa_Hd;y)F%MhBRi*LPcdqR8X`NQ&1L# z5#9L*@qxrx8n}LfeB^J{%-?SU{FCwiWyHp682F+|pa+CQa3ZLzBqN1{)h4d6+vBbV zC#NEbQLC;}me3eeYnOG*nXOJZEU$xLZ1<1Y=7r0(-U0P6-AqwMAM`a(Ed#7vJkn6plb4eI4?2y3yOTGmmDQ!z9`wzbf z_OY#0@5=bnep;MV0X_;;SJJWEf^E6Bd^tVJ9znWx&Ks8t*B>AM@?;D4oWUGc z!H*`6d7Cxo6VuyS4Eye&L1ZRhrRmN6Lr`{NL(wDbif|y&z)JN>Fl5#Wi&mMIr5i;x zBx}3YfF>>8EC(fYnmpu~)CYHuHCyr5*`ECap%t@y=jD>!_%3iiE|LN$mK9>- zHdtpy8fGZtkZF?%TW~29JIAfi2jZT8>OA7=h;8T{{k?c2`nCEx9$r zS+*&vt~2o^^J+}RDG@+9&M^K*z4p{5#IEVbz`1%`m5c2};aGt=V?~vIM}ZdPECDI)47|CWBCfDWUbxBCnmYivQ*0Nu_xb*C>~C9(VjHM zxe<*D<#dQ8TlpMX2c@M<9$w!RP$hpG4cs%AI){jp*Sj|*`m)5(Bw*A0$*i-(CA5#%>a)$+jI2C9r6|(>J8InryENI z$NohnxDUB;wAYDwrb*!N3noBTKPpPN}~09SEL18tkG zxgz(RYU_;DPT{l?Q$+eaZaxnsWCA^ds^0PVRkIM%bOd|G2IEBBiz{&^JtNsODs;5z zICt_Zj8wo^KT$7Bg4H+y!Df#3mbl%%?|EXe!&(Vmac1DJ*y~3+kRKAD=Ovde4^^%~ zw<9av18HLyrf*_>Slp;^i`Uy~`mvBjZ|?Ad63yQa#YK`4+c6;pW4?XIY9G1(Xh9WO8{F-Aju+nS9Vmv=$Ac0ienZ+p9*O%NG zMZKy5?%Z6TAJTE?o5vEr0r>f>hb#2w2U3DL64*au_@P!J!TL`oH2r*{>ffu6|A7tv zL4juf$DZ1MW5ZPsG!5)`k8d8c$J$o;%EIL0va9&GzWvkS%ZsGb#S(?{!UFOZ9<$a| zY|a+5kmD5N&{vRqkgY>aHsBT&`rg|&kezoD)gP0fsNYHsO#TRc_$n6Lf1Z{?+DLziXlHrq4sf(!>O{?Tj;Eh@%)+nRE_2VxbN&&%%caU#JDU%vL3}Cb zsb4AazPI{>8H&d=jUaZDS$-0^AxE@utGs;-Ez_F(qC9T=UZX=>ok2k2 ziTn{K?y~a5reD2A)P${NoI^>JXn>`IeArow(41c-Wm~)wiryEP(OS{YXWi7;%dG9v zI?mwu1MxD{yp_rrk!j^cKM)dc4@p4Ezyo%lRN|XyD}}>v=Xoib0gOcdXrQ^*61HNj z=NP|pd>@yfvr-=m{8$3A8TQGMTE7g=z!%yt`8`Bk-0MMwW~h^++;qyUP!J~ykh1GO z(FZ59xuFR$(WE;F@UUyE@Sp>`aVNjyj=Ty>_Vo}xf`e7`F;j-IgL5`1~-#70$9_=uBMq!2&1l zomRgpD58@)YYfvLtPW}{C5B35R;ZVvB<<#)x%srmc_S=A7F@DW8>QOEGwD6suhwCg z>Pa+YyULhmw%BA*4yjDp|2{!T98~<6Yfd(wo1mQ!KWwq0eg+6)o1>W~f~kL<-S+P@$wx*zeI|1t7z#Sxr5 zt6w+;YblPQNplq4Z#T$GLX#j6yldXAqj>4gAnnWtBICUnA&-dtnlh=t0Ho_vEKwV` z)DlJi#!@nkYV#$!)@>udAU*hF?V`2$Hf=V&6PP_|r#Iv*J$9)pF@X3`k;5})9^o4y z&)~?EjX5yX12O(BsFy-l6}nYeuKkiq`u9145&3Ssg^y{5G3Pse z9w(YVa0)N-fLaBq1`P!_#>SS(8fh_5!f{UrgZ~uEdeMJIz7DzI5!NHHqQtm~#CPij z?=N|J>nPR6_sL7!f4hD_|KH`vf8(Wpnj-(gPWH+ZvID}%?~68SwhPTC3u1_cB`otq z)U?6qo!ZLi5b>*KnYHWW=3F!p%h1;h{L&(Q&{qY6)_qxNfbP6E3yYpW!EO+IW3?@J z);4>g4gnl^8klu7uA>eGF6rIGSynacogr)KUwE_R4E5Xzi*Qir@b-jy55-JPC8c~( zo!W8y9OGZ&`xmc8;=4-U9=h{vCqfCNzYirONmGbRQlR`WWlgnY+1wCXbMz&NT~9*| z6@FrzP!LX&{no2!Ln_3|I==_4`@}V?4a;YZKTdw;vT<+K+z=uWbW(&bXEaWJ^W8Td z-3&1bY^Z*oM<=M}LVt>_j+p=2Iu7pZmbXrhQ_k)ysE9yXKygFNw$5hwDn(M>H+e1&9BM5!|81vd%r%vEm zqxY3?F@fb6O#5UunwgAHR9jp_W2zZ}NGp2%mTW@(hz7$^+a`A?mb8|_G*GNMJ) zjqegXQio=i@AINre&%ofexAr95aop5C+0MZ0m-l=MeO8m3epm7U%vZB8+I+C*iNFM z#T3l`gknX;D$-`2XT^Cg*vrv=RH+P;_dfF++cP?B_msQI4j+lt&rX2)3GaJx%W*Nn zkML%D{z5tpHH=dksQ*gzc|}gzW;lwAbxoR07VNgS*-c3d&8J|;@3t^ zVUz*J*&r7DFRuFVDCJDK8V9NN5hvpgGjwx+5n)qa;YCKe8TKtdnh{I7NU9BCN!0dq zczrBk8pE{{@vJa9ywR@mq*J=v+PG;?fwqlJVhijG!3VmIKs>9T6r7MJpC)m!Tc#>g zMtVsU>wbwFJEfwZ{vB|ZlttNe83)$iz`~#8UJ^r)lJ@HA&G#}W&ZH*;k{=TavpjWE z7hdyLZPf*X%Gm}i`Y{OGeeu^~nB8=`{r#TUrM-`;1cBvEd#d!kPqIgYySYhN-*1;L z^byj%Yi}Gx)Wnkosi337BKs}+5H5dth1JA{Ir-JKN$7zC)*}hqeoD(WfaUDPT>0`- z(6sa0AoIqASwF`>hP}^|)a_j2s^PQn*qVC{Q}htR z5-)duBFXT_V56-+UohKXlq~^6uf!6sA#ttk1o~*QEy_Y-S$gAvq47J9Vtk$5oA$Ct zYhYJ@8{hsC^98${!#Ho?4y5MCa7iGnfz}b9jE~h%EAAv~Qxu)_rAV;^cygV~5r_~?l=B`zObj7S=H=~$W zPtI_m%g$`kL_fVUk9J@>EiBH zOO&jtn~&`hIFMS5S`g8w94R4H40mdNUH4W@@XQk1sr17b{@y|JB*G9z1|CrQjd+GX z6+KyURG3;!*BQrentw{B2R&@2&`2}n(z-2&X7#r!{yg@Soy}cRD~j zj9@UBW+N|4HW4AWapy4wfUI- zZ`gSL6DUlgj*f1hSOGXG0IVH8HxK?o2|3HZ;KW{K+yPAlxtb)NV_2AwJm|E)FRs&& z=c^e7bvUsztY|+f^k7NXs$o1EUq>cR7C0$UKi6IooHWlK_#?IWDkvywnzg&ThWo^? z2O_N{5X39#?eV9l)xI(>@!vSB{DLt*oY!K1R8}_?%+0^C{d9a%N4 zoxHVT1&Lm|uDX%$QrBun5e-F`HJ^T$ zmzv)p@4ZHd_w9!%Hf9UYNvGCw2TTTbrj9pl+T9%-_-}L(tES>Or-}Z4F*{##n3~L~TuxjirGuIY#H7{%$E${?p{Q01 zi6T`n;rbK1yIB9jmQNycD~yZq&mbIsFWHo|ZAChSFPQa<(%d8mGw*V3fh|yFoxOOiWJd(qvVb!Z$b88cg->N=qO*4k~6;R==|9ihg&riu#P~s4Oap9O7f%crSr^rljeIfXDEg>wi)&v*a%7zpz<9w z*r!3q9J|390x`Zk;g$&OeN&ctp)VKRpDSV@kU2Q>jtok($Y-*x8_$2piTxun81@vt z!Vj?COa0fg2RPXMSIo26T=~0d`{oGP*eV+$!0I<(4azk&Vj3SiG=Q!6mX0p$z7I}; z9BJUFgT-K9MQQ-0@Z=^7R<{bn2Fm48endsSs`V7_@%8?Bxkqv>BDoVcj?K#dV#uUP zL1ND~?D-|VGKe3Rw_7-Idpht>H6XRLh*U7epS6byiGvJpr%d}XwfusjH9g;Z98H`x zyde%%5mhGOiL4wljCaWCk-&uE4_OOccb9c!ZaWt4B(wYl!?vyzl%7n~QepN&eFUrw zFIOl9c({``6~QD+43*_tzP{f2x41h(?b43^y6=iwyB)2os5hBE!@YUS5?N_tXd=h( z)WE286Fbd>R4M^P{!G)f;h<3Q>Fipuy+d2q-)!RyTgt;wr$(?9ox3;q+{E*ZQHhOn;lM`cjnu9 zXa48ks-v(~b*;MAI<>YZH(^NV8vjb34beE<_cwKlJoR;k6lJNSP6v}uiyRD?|0w+X@o1ONrH8a$fCxXpf? z?$DL0)7|X}Oc%h^zrMKWc-NS9I0Utu@>*j}b@tJ=ixQSJ={4@854wzW@E>VSL+Y{i z#0b=WpbCZS>kUCO_iQz)LoE>P5LIG-hv9E+oG}DtlIDF>$tJ1aw9^LuhLEHt?BCj& z(O4I8v1s#HUi5A>nIS-JK{v!7dJx)^Yg%XjNmlkWAq2*cv#tHgz`Y(bETc6CuO1VkN^L-L3j_x<4NqYb5rzrLC-7uOv z!5e`GZt%B782C5-fGnn*GhDF$%(qP<74Z}3xx+{$4cYKy2ikxI7B2N+2r07DN;|-T->nU&!=Cm#rZt%O_5c&1Z%nlWq3TKAW0w zQqemZw_ue--2uKQsx+niCUou?HjD`xhEjjQd3%rrBi82crq*~#uA4+>vR<_S{~5ce z-2EIl?~s z1=GVL{NxP1N3%=AOaC}j_Fv=ur&THz zyO!d9kHq|c73kpq`$+t+8Bw7MgeR5~`d7ChYyGCBWSteTB>8WAU(NPYt2Dk`@#+}= zI4SvLlyk#pBgVigEe`?NG*vl7V6m+<}%FwPV=~PvvA)=#ths==DRTDEYh4V5}Cf$z@#;< zyWfLY_5sP$gc3LLl2x+Ii)#b2nhNXJ{R~vk`s5U7Nyu^3yFg&D%Txwj6QezMX`V(x z=C`{76*mNb!qHHs)#GgGZ_7|vkt9izl_&PBrsu@}L`X{95-2jf99K)0=*N)VxBX2q z((vkpP2RneSIiIUEnGb?VqbMb=Zia+rF~+iqslydE34cSLJ&BJW^3knX@M;t*b=EA zNvGzv41Ld_T+WT#XjDB840vovUU^FtN_)G}7v)1lPetgpEK9YS^OWFkPoE{ovj^=@ zO9N$S=G$1ecndT_=5ehth2Lmd1II-PuT~C9`XVePw$y8J#dpZ?Tss<6wtVglm(Ok7 z3?^oi@pPio6l&!z8JY(pJvG=*pI?GIOu}e^EB6QYk$#FJQ%^AIK$I4epJ+9t?KjqA+bkj&PQ*|vLttme+`9G=L% ziadyMw_7-M)hS(3E$QGNCu|o23|%O+VN7;Qggp?PB3K-iSeBa2b}V4_wY`G1Jsfz4 z9|SdB^;|I8E8gWqHKx!vj_@SMY^hLEIbSMCuE?WKq=c2mJK z8LoG-pnY!uhqFv&L?yEuxo{dpMTsmCn)95xanqBrNPTgXP((H$9N${Ow~Is-FBg%h z53;|Y5$MUN)9W2HBe2TD`ct^LHI<(xWrw}$qSoei?}s)&w$;&!14w6B6>Yr6Y8b)S z0r71`WmAvJJ`1h&poLftLUS6Ir zC$bG9!Im_4Zjse)#K=oJM9mHW1{%l8sz$1o?ltdKlLTxWWPB>Vk22czVt|1%^wnN@*!l)}?EgtvhC>vlHm^t+ogpgHI1_$1ox9e;>0!+b(tBrmXRB`PY1vp-R**8N7 zGP|QqI$m(Rdu#=(?!(N}G9QhQ%o!aXE=aN{&wtGP8|_qh+7a_j_sU5|J^)vxq;# zjvzLn%_QPHZZIWu1&mRAj;Sa_97p_lLq_{~j!M9N^1yp3U_SxRqK&JnR%6VI#^E12 z>CdOVI^_9aPK2eZ4h&^{pQs}xsijXgFYRIxJ~N7&BB9jUR1fm!(xl)mvy|3e6-B3j zJn#ajL;bFTYJ2+Q)tDjx=3IklO@Q+FFM}6UJr6km7hj7th9n_&JR7fnqC!hTZoM~T zBeaVFp%)0cbPhejX<8pf5HyRUj2>aXnXBqDJe73~J%P(2C?-RT{c3NjE`)om! zl$uewSgWkE66$Kb34+QZZvRn`fob~Cl9=cRk@Es}KQm=?E~CE%spXaMO6YmrMl%9Q zlA3Q$3|L1QJ4?->UjT&CBd!~ru{Ih^in&JXO=|<6J!&qp zRe*OZ*cj5bHYlz!!~iEKcuE|;U4vN1rk$xq6>bUWD*u(V@8sG^7>kVuo(QL@Ki;yL zWC!FT(q{E8#on>%1iAS0HMZDJg{Z{^!De(vSIq&;1$+b)oRMwA3nc3mdTSG#3uYO_ z>+x;7p4I;uHz?ZB>dA-BKl+t-3IB!jBRgdvAbW!aJ(Q{aT>+iz?91`C-xbe)IBoND z9_Xth{6?(y3rddwY$GD65IT#f3<(0o#`di{sh2gm{dw*#-Vnc3r=4==&PU^hCv$qd zjw;>i&?L*Wq#TxG$mFIUf>eK+170KG;~+o&1;Tom9}}mKo23KwdEM6UonXgc z!6N(@k8q@HPw{O8O!lAyi{rZv|DpgfU{py+j(X_cwpKqcalcqKIr0kM^%Br3SdeD> zHSKV94Yxw;pjzDHo!Q?8^0bb%L|wC;4U^9I#pd5O&eexX+Im{ z?jKnCcsE|H?{uGMqVie_C~w7GX)kYGWAg%-?8|N_1#W-|4F)3YTDC+QSq1s!DnOML3@d`mG%o2YbYd#jww|jD$gotpa)kntakp#K;+yo-_ZF9qrNZw<%#C zuPE@#3RocLgPyiBZ+R_-FJ_$xP!RzWm|aN)S+{$LY9vvN+IW~Kf3TsEIvP+B9Mtm! zpfNNxObWQpLoaO&cJh5>%slZnHl_Q~(-Tfh!DMz(dTWld@LG1VRF`9`DYKhyNv z2pU|UZ$#_yUx_B_|MxUq^glT}O5Xt(Vm4Mr02><%C)@v;vPb@pT$*yzJ4aPc_FZ3z z3}PLoMBIM>q_9U2rl^sGhk1VUJ89=*?7|v`{!Z{6bqFMq(mYiA?%KbsI~JwuqVA9$H5vDE+VocjX+G^%bieqx->s;XWlKcuv(s%y%D5Xbc9+ zc(_2nYS1&^yL*ey664&4`IoOeDIig}y-E~_GS?m;D!xv5-xwz+G`5l6V+}CpeJDi^ z%4ed$qowm88=iYG+(`ld5Uh&>Dgs4uPHSJ^TngXP_V6fPyl~>2bhi20QB%lSd#yYn zO05?KT1z@?^-bqO8Cg`;ft>ilejsw@2%RR7;`$Vs;FmO(Yr3Fp`pHGr@P2hC%QcA|X&N2Dn zYf`MqXdHi%cGR@%y7Rg7?d3?an){s$zA{!H;Ie5exE#c~@NhQUFG8V=SQh%UxUeiV zd7#UcYqD=lk-}sEwlpu&H^T_V0{#G?lZMxL7ih_&{(g)MWBnCZxtXg znr#}>U^6!jA%e}@Gj49LWG@*&t0V>Cxc3?oO7LSG%~)Y5}f7vqUUnQ;STjdDU}P9IF9d9<$;=QaXc zL1^X7>fa^jHBu_}9}J~#-oz3Oq^JmGR#?GO7b9a(=R@fw@}Q{{@`Wy1vIQ#Bw?>@X z-_RGG@wt|%u`XUc%W{J z>iSeiz8C3H7@St3mOr_mU+&bL#Uif;+Xw-aZdNYUpdf>Rvu0i0t6k*}vwU`XNO2he z%miH|1tQ8~ZK!zmL&wa3E;l?!!XzgV#%PMVU!0xrDsNNZUWKlbiOjzH-1Uoxm8E#r`#2Sz;-o&qcqB zC-O_R{QGuynW14@)7&@yw1U}uP(1cov)twxeLus0s|7ayrtT8c#`&2~Fiu2=R;1_4bCaD=*E@cYI>7YSnt)nQc zohw5CsK%m?8Ack)qNx`W0_v$5S}nO|(V|RZKBD+btO?JXe|~^Qqur%@eO~<8-L^9d z=GA3-V14ng9L29~XJ>a5k~xT2152zLhM*@zlp2P5Eu}bywkcqR;ISbas&#T#;HZSf z2m69qTV(V@EkY(1Dk3`}j)JMo%ZVJ*5eB zYOjIisi+igK0#yW*gBGj?@I{~mUOvRFQR^pJbEbzFxTubnrw(Muk%}jI+vXmJ;{Q6 zrSobKD>T%}jV4Ub?L1+MGOD~0Ir%-`iTnWZN^~YPrcP5y3VMAzQ+&en^VzKEb$K!Q z<7Dbg&DNXuow*eD5yMr+#08nF!;%4vGrJI++5HdCFcGLfMW!KS*Oi@=7hFwDG!h2< zPunUEAF+HncQkbfFj&pbzp|MU*~60Z(|Ik%Tn{BXMN!hZOosNIseT?R;A`W?=d?5X zK(FB=9mZusYahp|K-wyb={rOpdn=@;4YI2W0EcbMKyo~-#^?h`BA9~o285%oY zfifCh5Lk$SY@|2A@a!T2V+{^!psQkx4?x0HSV`(w9{l75QxMk!)U52Lbhn{8ol?S) zCKo*7R(z!uk<6*qO=wh!Pul{(qq6g6xW;X68GI_CXp`XwO zxuSgPRAtM8K7}5E#-GM!*ydOOG_{A{)hkCII<|2=ma*71ci_-}VPARm3crFQjLYV! z9zbz82$|l01mv`$WahE2$=fAGWkd^X2kY(J7iz}WGS z@%MyBEO=A?HB9=^?nX`@nh;7;laAjs+fbo!|K^mE!tOB>$2a_O0y-*uaIn8k^6Y zSbuv;5~##*4Y~+y7Z5O*3w4qgI5V^17u*ZeupVGH^nM&$qmAk|anf*>r zWc5CV;-JY-Z@Uq1Irpb^O`L_7AGiqd*YpGUShb==os$uN3yYvb`wm6d=?T*it&pDk zo`vhw)RZX|91^^Wa_ti2zBFyWy4cJu#g)_S6~jT}CC{DJ_kKpT`$oAL%b^!2M;JgT zM3ZNbUB?}kP(*YYvXDIH8^7LUxz5oE%kMhF!rnPqv!GiY0o}NR$OD=ITDo9r%4E>E0Y^R(rS^~XjWyVI6 zMOR5rPXhTp*G*M&X#NTL`Hu*R+u*QNoiOKg4CtNPrjgH>c?Hi4MUG#I917fx**+pJfOo!zFM&*da&G_x)L(`k&TPI*t3e^{crd zX<4I$5nBQ8Ax_lmNRa~E*zS-R0sxkz`|>7q_?*e%7bxqNm3_eRG#1ae3gtV9!fQpY z+!^a38o4ZGy9!J5sylDxZTx$JmG!wg7;>&5H1)>f4dXj;B+@6tMlL=)cLl={jLMxY zbbf1ax3S4>bwB9-$;SN2?+GULu;UA-35;VY*^9Blx)Jwyb$=U!D>HhB&=jSsd^6yw zL)?a|>GxU!W}ocTC(?-%z3!IUhw^uzc`Vz_g>-tv)(XA#JK^)ZnC|l1`@CdX1@|!| z_9gQ)7uOf?cR@KDp97*>6X|;t@Y`k_N@)aH7gY27)COv^P3ya9I{4z~vUjLR9~z1Z z5=G{mVtKH*&$*t0@}-i_v|3B$AHHYale7>E+jP`ClqG%L{u;*ff_h@)al?RuL7tOO z->;I}>%WI{;vbLP3VIQ^iA$4wl6@0sDj|~112Y4OFjMs`13!$JGkp%b&E8QzJw_L5 zOnw9joc0^;O%OpF$Qp)W1HI!$4BaXX84`%@#^dk^hFp^pQ@rx4g(8Xjy#!X%+X5Jd@fs3amGT`}mhq#L97R>OwT5-m|h#yT_-v@(k$q7P*9X~T*3)LTdzP!*B} z+SldbVWrrwQo9wX*%FyK+sRXTa@O?WM^FGWOE?S`R(0P{<6p#f?0NJvnBia?k^fX2 zNQs7K-?EijgHJY}&zsr;qJ<*PCZUd*x|dD=IQPUK_nn)@X4KWtqoJNHkT?ZWL_hF? zS8lp2(q>;RXR|F;1O}EE#}gCrY~#n^O`_I&?&z5~7N;zL0)3Tup`%)oHMK-^r$NT% zbFg|o?b9w(q@)6w5V%si<$!U<#}s#x@0aX-hP>zwS#9*75VXA4K*%gUc>+yzupTDBOKH8WR4V0pM(HrfbQ&eJ79>HdCvE=F z|J>s;;iDLB^3(9}?biKbxf1$lI!*Z%*0&8UUq}wMyPs_hclyQQi4;NUY+x2qy|0J; zhn8;5)4ED1oHwg+VZF|80<4MrL97tGGXc5Sw$wAI#|2*cvQ=jB5+{AjMiDHmhUC*a zlmiZ`LAuAn_}hftXh;`Kq0zblDk8?O-`tnilIh|;3lZp@F_osJUV9`*R29M?7H{Fy z`nfVEIDIWXmU&YW;NjU8)EJpXhxe5t+scf|VXM!^bBlwNh)~7|3?fWwo_~ZFk(22% zTMesYw+LNx3J-_|DM~`v93yXe=jPD{q;li;5PD?Dyk+b? zo21|XpT@)$BM$%F=P9J19Vi&1#{jM3!^Y&fr&_`toi`XB1!n>sbL%U9I5<7!@?t)~ z;&H%z>bAaQ4f$wIzkjH70;<8tpUoxzKrPhn#IQfS%9l5=Iu))^XC<58D!-O z{B+o5R^Z21H0T9JQ5gNJnqh#qH^na|z92=hONIM~@_iuOi|F>jBh-?aA20}Qx~EpDGElELNn~|7WRXRFnw+Wdo`|# zBpU=Cz3z%cUJ0mx_1($X<40XEIYz(`noWeO+x#yb_pwj6)R(__%@_Cf>txOQ74wSJ z0#F3(zWWaR-jMEY$7C*3HJrohc79>MCUu26mfYN)f4M~4gD`}EX4e}A!U}QV8!S47 z6y-U-%+h`1n`*pQuKE%Av0@)+wBZr9mH}@vH@i{v(m-6QK7Ncf17x_D=)32`FOjjo zg|^VPf5c6-!FxN{25dvVh#fog=NNpXz zfB$o+0jbRkHH{!TKhE709f+jI^$3#v1Nmf80w`@7-5$1Iv_`)W^px8P-({xwb;D0y z7LKDAHgX<84?l!I*Dvi2#D@oAE^J|g$3!)x1Ua;_;<@#l1fD}lqU2_tS^6Ht$1Wl} zBESo7o^)9-Tjuz$8YQSGhfs{BQV6zW7dA?0b(Dbt=UnQs&4zHfe_sj{RJ4uS-vQpC zX;Bbsuju4%!o8?&m4UZU@~ZZjeFF6ex2ss5_60_JS_|iNc+R0GIjH1@Z z=rLT9%B|WWgOrR7IiIwr2=T;Ne?30M!@{%Qf8o`!>=s<2CBpCK_TWc(DX51>e^xh8 z&@$^b6CgOd7KXQV&Y4%}_#uN*mbanXq(2=Nj`L7H7*k(6F8s6{FOw@(DzU`4-*77{ zF+dxpv}%mFpYK?>N_2*#Y?oB*qEKB}VoQ@bzm>ptmVS_EC(#}Lxxx730trt0G)#$b zE=wVvtqOct1%*9}U{q<)2?{+0TzZzP0jgf9*)arV)*e!f`|jgT{7_9iS@e)recI#z zbzolURQ+TOzE!ymqvBY7+5NnAbWxvMLsLTwEbFqW=CPyCsmJ}P1^V30|D5E|p3BC5 z)3|qgw@ra7aXb-wsa|l^in~1_fm{7bS9jhVRkYVO#U{qMp z)Wce+|DJ}4<2gp8r0_xfZpMo#{Hl2MfjLcZdRB9(B(A(f;+4s*FxV{1F|4d`*sRNd zp4#@sEY|?^FIJ;tmH{@keZ$P(sLh5IdOk@k^0uB^BWr@pk6mHy$qf&~rI>P*a;h0C{%oA*i!VjWn&D~O#MxN&f@1Po# zKN+ zrGrkSjcr?^R#nGl<#Q722^wbYcgW@{+6CBS<1@%dPA8HC!~a`jTz<`g_l5N1M@9wn9GOAZ>nqNgq!yOCbZ@1z`U_N`Z>}+1HIZxk*5RDc&rd5{3qjRh8QmT$VyS;jK z;AF+r6XnnCp=wQYoG|rT2@8&IvKq*IB_WvS%nt%e{MCFm`&W*#LXc|HrD?nVBo=(8*=Aq?u$sDA_sC_RPDUiQ+wnIJET8vx$&fxkW~kP9qXKt zozR)@xGC!P)CTkjeWvXW5&@2?)qt)jiYWWBU?AUtzAN}{JE1I)dfz~7$;}~BmQF`k zpn11qmObXwRB8&rnEG*#4Xax3XBkKlw(;tb?Np^i+H8m(Wyz9k{~ogba@laiEk;2! zV*QV^6g6(QG%vX5Um#^sT&_e`B1pBW5yVth~xUs#0}nv?~C#l?W+9Lsb_5)!71rirGvY zTIJ$OPOY516Y|_014sNv+Z8cc5t_V=i>lWV=vNu#!58y9Zl&GsMEW#pPYPYGHQ|;vFvd*9eM==$_=vc7xnyz0~ zY}r??$<`wAO?JQk@?RGvkWVJlq2dk9vB(yV^vm{=NVI8dhsX<)O(#nr9YD?I?(VmQ z^r7VfUBn<~p3()8yOBjm$#KWx!5hRW)5Jl7wY@ky9lNM^jaT##8QGVsYeaVywmpv>X|Xj7gWE1Ezai&wVLt3p)k4w~yrskT-!PR!kiyQlaxl(( zXhF%Q9x}1TMt3~u@|#wWm-Vq?ZerK={8@~&@9r5JW}r#45#rWii};t`{5#&3$W)|@ zbAf2yDNe0q}NEUvq_Quq3cTjcw z@H_;$hu&xllCI9CFDLuScEMg|x{S7GdV8<&Mq=ezDnRZAyX-8gv97YTm0bg=d)(>N z+B2FcqvI9>jGtnK%eO%y zoBPkJTk%y`8TLf4)IXPBn`U|9>O~WL2C~C$z~9|0m*YH<-vg2CD^SX#&)B4ngOSG$ zV^wmy_iQk>dfN@Pv(ckfy&#ak@MLC7&Q6Ro#!ezM*VEh`+b3Jt%m(^T&p&WJ2Oqvj zs-4nq0TW6cv~(YI$n0UkfwN}kg3_fp?(ijSV#tR9L0}l2qjc7W?i*q01=St0eZ=4h zyGQbEw`9OEH>NMuIe)hVwYHsGERWOD;JxEiO7cQv%pFCeR+IyhwQ|y@&^24k+|8fD zLiOWFNJ2&vu2&`Jv96_z-Cd5RLgmeY3*4rDOQo?Jm`;I_(+ejsPM03!ly!*Cu}Cco zrQSrEDHNyzT(D5s1rZq!8#?f6@v6dB7a-aWs(Qk>N?UGAo{gytlh$%_IhyL7h?DLXDGx zgxGEBQoCAWo-$LRvM=F5MTle`M})t3vVv;2j0HZY&G z22^iGhV@uaJh(XyyY%} zd4iH_UfdV#T=3n}(Lj^|n;O4|$;xhu*8T3hR1mc_A}fK}jfZ7LX~*n5+`8N2q#rI$ z@<_2VANlYF$vIH$ zl<)+*tIWW78IIINA7Rr7i{<;#^yzxoLNkXL)eSs=%|P>$YQIh+ea_3k z_s7r4%j7%&*NHSl?R4k%1>Z=M9o#zxY!n8sL5>BO-ZP;T3Gut>iLS@U%IBrX6BA3k z)&@q}V8a{X<5B}K5s(c(LQ=%v1ocr`t$EqqY0EqVjr65usa=0bkf|O#ky{j3)WBR(((L^wmyHRzoWuL2~WTC=`yZ zn%VX`L=|Ok0v7?s>IHg?yArBcync5rG#^+u)>a%qjES%dRZoIyA8gQ;StH z1Ao7{<&}6U=5}4v<)1T7t!J_CL%U}CKNs-0xWoTTeqj{5{?Be$L0_tk>M9o8 zo371}S#30rKZFM{`H_(L`EM9DGp+Mifk&IP|C2Zu_)Ghr4Qtpmkm1osCf@%Z$%t+7 zYH$Cr)Ro@3-QDeQJ8m+x6%;?YYT;k6Z0E-?kr>x33`H%*ueBD7Zx~3&HtWn0?2Wt} zTG}*|v?{$ajzt}xPzV%lL1t-URi8*Zn)YljXNGDb>;!905Td|mpa@mHjIH%VIiGx- zd@MqhpYFu4_?y5N4xiHn3vX&|e6r~Xt> zZG`aGq|yTNjv;9E+Txuoa@A(9V7g?1_T5FzRI;!=NP1Kqou1z5?%X~Wwb{trRfd>i z8&y^H)8YnKyA_Fyx>}RNmQIczT?w2J4SNvI{5J&}Wto|8FR(W;Qw#b1G<1%#tmYzQ zQ2mZA-PAdi%RQOhkHy9Ea#TPSw?WxwL@H@cbkZwIq0B!@ns}niALidmn&W?!Vd4Gj zO7FiuV4*6Mr^2xlFSvM;Cp_#r8UaqIzHJQg_z^rEJw&OMm_8NGAY2)rKvki|o1bH~ z$2IbfVeY2L(^*rMRU1lM5Y_sgrDS`Z??nR2lX;zyR=c%UyGb*%TC-Dil?SihkjrQy~TMv6;BMs7P8il`H7DmpVm@rJ;b)hW)BL)GjS154b*xq-NXq2cwE z^;VP7ua2pxvCmxrnqUYQMH%a%nHmwmI33nJM(>4LznvY*k&C0{8f*%?zggpDgkuz&JBx{9mfb@wegEl2v!=}Sq2Gaty0<)UrOT0{MZtZ~j5y&w zXlYa_jY)I_+VA-^#mEox#+G>UgvM!Ac8zI<%JRXM_73Q!#i3O|)lOP*qBeJG#BST0 zqohi)O!|$|2SeJQo(w6w7%*92S})XfnhrH_Z8qe!G5>CglP=nI7JAOW?(Z29;pXJ9 zR9`KzQ=WEhy*)WH>$;7Cdz|>*i>=##0bB)oU0OR>>N<21e4rMCHDemNi2LD>Nc$;& zQRFthpWniC1J6@Zh~iJCoLOxN`oCKD5Q4r%ynwgUKPlIEd#?QViIqovY|czyK8>6B zSP%{2-<;%;1`#0mG^B(8KbtXF;Nf>K#Di72UWE4gQ%(_26Koiad)q$xRL~?pN71ZZ zujaaCx~jXjygw;rI!WB=xrOJO6HJ!!w}7eiivtCg5K|F6$EXa)=xUC za^JXSX98W`7g-tm@uo|BKj39Dl;sg5ta;4qjo^pCh~{-HdLl6qI9Ix6f$+qiZ$}s= zNguKrU;u+T@ko(Vr1>)Q%h$?UKXCY>3se%&;h2osl2D zE4A9bd7_|^njDd)6cI*FupHpE3){4NQ*$k*cOWZ_?CZ>Z4_fl@n(mMnYK62Q1d@+I zr&O))G4hMihgBqRIAJkLdk(p(D~X{-oBUA+If@B}j& zsHbeJ3RzTq96lB7d($h$xTeZ^gP0c{t!Y0c)aQE;$FY2!mACg!GDEMKXFOPI^)nHZ z`aSPJpvV0|bbrzhWWkuPURlDeN%VT8tndV8?d)eN*i4I@u zVKl^6{?}A?P)Fsy?3oi#clf}L18t;TjNI2>eI&(ezDK7RyqFxcv%>?oxUlonv(px) z$vnPzRH`y5A(x!yOIfL0bmgeMQB$H5wenx~!ujQK*nUBW;@Em&6Xv2%s(~H5WcU2R z;%Nw<$tI)a`Ve!>x+qegJnQsN2N7HaKzrFqM>`6R*gvh%O*-%THt zrB$Nk;lE;z{s{r^PPm5qz(&lM{sO*g+W{sK+m3M_z=4=&CC>T`{X}1Vg2PEfSj2x_ zmT*(x;ov%3F?qoEeeM>dUn$a*?SIGyO8m806J1W1o+4HRhc2`9$s6hM#qAm zChQ87b~GEw{ADfs+5}FJ8+|bIlIv(jT$Ap#hSHoXdd9#w<#cA<1Rkq^*EEkknUd4& zoIWIY)sAswy6fSERVm&!SO~#iN$OgOX*{9@_BWFyJTvC%S++ilSfCrO(?u=Dc?CXZ zzCG&0yVR{Z`|ZF0eEApWEo#s9osV>F{uK{QA@BES#&;#KsScf>y zvs?vIbI>VrT<*!;XmQS=bhq%46-aambZ(8KU-wOO2=en~D}MCToB_u;Yz{)1ySrPZ z@=$}EvjTdzTWU7c0ZI6L8=yP+YRD_eMMos}b5vY^S*~VZysrkq<`cK3>>v%uy7jgq z0ilW9KjVDHLv0b<1K_`1IkbTOINs0=m-22c%M~l=^S}%hbli-3?BnNq?b`hx^HX2J zIe6ECljRL0uBWb`%{EA=%!i^4sMcj+U_TaTZRb+~GOk z^ZW!nky0n*Wb*r+Q|9H@ml@Z5gU&W`(z4-j!OzC1wOke`TRAYGZVl$PmQ16{3196( zO*?`--I}Qf(2HIwb2&1FB^!faPA2=sLg(@6P4mN)>Dc3i(B0;@O-y2;lM4akD>@^v z=u>*|!s&9zem70g7zfw9FXl1bpJW(C#5w#uy5!V?Q(U35A~$dR%LDVnq@}kQm13{} zd53q3N(s$Eu{R}k2esbftfjfOITCL;jWa$}(mmm}d(&7JZ6d3%IABCapFFYjdEjdK z&4Edqf$G^MNAtL=uCDRs&Fu@FXRgX{*0<(@c3|PNHa>L%zvxWS={L8%qw`STm+=Rd zA}FLspESSIpE_^41~#5yI2bJ=9`oc;GIL!JuW&7YetZ?0H}$$%8rW@*J37L-~Rsx!)8($nI4 zZhcZ2^=Y+p4YPl%j!nFJA|*M^gc(0o$i3nlphe+~-_m}jVkRN{spFs(o0ajW@f3K{ zDV!#BwL322CET$}Y}^0ixYj2w>&Xh12|R8&yEw|wLDvF!lZ#dOTHM9pK6@Nm-@9Lnng4ZHBgBSrr7KI8YCC9DX5Kg|`HsiwJHg2(7#nS;A{b3tVO?Z% za{m5b3rFV6EpX;=;n#wltDv1LE*|g5pQ+OY&*6qCJZc5oDS6Z6JD#6F)bWxZSF@q% z+1WV;m!lRB!n^PC>RgQCI#D1br_o^#iPk>;K2hB~0^<~)?p}LG%kigm@moD#q3PE+ zA^Qca)(xnqw6x>XFhV6ku9r$E>bWNrVH9fum0?4s?Rn2LG{Vm_+QJHse6xa%nzQ?k zKug4PW~#Gtb;#5+9!QBgyB@q=sk9=$S{4T>wjFICStOM?__fr+Kei1 z3j~xPqW;W@YkiUM;HngG!;>@AITg}vAE`M2Pj9Irl4w1fo4w<|Bu!%rh%a(Ai^Zhi zs92>v5;@Y(Zi#RI*ua*h`d_7;byQSa*v9E{2x$<-_=5Z<7{%)}4XExANcz@rK69T0x3%H<@frW>RA8^swA+^a(FxK| zFl3LD*ImHN=XDUkrRhp6RY5$rQ{bRgSO*(vEHYV)3Mo6Jy3puiLmU&g82p{qr0F?ohmbz)f2r{X2|T2 z$4fdQ=>0BeKbiVM!e-lIIs8wVTuC_m7}y4A_%ikI;Wm5$9j(^Y z(cD%U%k)X>_>9~t8;pGzL6L-fmQO@K; zo&vQzMlgY95;1BSkngY)e{`n0!NfVgf}2mB3t}D9@*N;FQ{HZ3Pb%BK6;5#-O|WI( zb6h@qTLU~AbVW#_6?c!?Dj65Now7*pU{h!1+eCV^KCuPAGs28~3k@ueL5+u|Z-7}t z9|lskE`4B7W8wMs@xJa{#bsCGDFoRSNSnmNYB&U7 zVGKWe%+kFB6kb)e;TyHfqtU6~fRg)f|>=5(N36)0+C z`hv65J<$B}WUc!wFAb^QtY31yNleq4dzmG`1wHTj=c*=hay9iD071Hc?oYoUk|M*_ zU1GihAMBsM@5rUJ(qS?9ZYJ6@{bNqJ`2Mr+5#hKf?doa?F|+^IR!8lq9)wS3tF_9n zW_?hm)G(M+MYb?V9YoX^_mu5h-LP^TL^!Q9Z7|@sO(rg_4+@=PdI)WL(B7`!K^ND- z-uIuVDCVEdH_C@c71YGYT^_Scf_dhB8Z2Xy6vGtBSlYud9vggOqv^L~F{BraSE_t} zIkP+Hp2&nH^-MNEs}^`oMLy11`PQW$T|K(`Bu*(f@)mv1-qY(_YG&J2M2<7k;;RK~ zL{Fqj9yCz8(S{}@c)S!65aF<=&eLI{hAMErCx&>i7OeDN>okvegO87OaG{Jmi<|}D zaT@b|0X{d@OIJ7zvT>r+eTzgLq~|Dpu)Z&db-P4z*`M$UL51lf>FLlq6rfG)%doyp z)3kk_YIM!03eQ8Vu_2fg{+osaEJPtJ-s36R+5_AEG12`NG)IQ#TF9c@$99%0iye+ zUzZ57=m2)$D(5Nx!n)=5Au&O0BBgwxIBaeI(mro$#&UGCr<;C{UjJVAbVi%|+WP(a zL$U@TYCxJ=1{Z~}rnW;7UVb7+ZnzgmrogDxhjLGo>c~MiJAWs&&;AGg@%U?Y^0JhL ze(x6Z74JG6FlOFK(T}SXQfhr}RIFl@QXKnIcXYF)5|V~e-}suHILKT-k|<*~Ij|VF zC;t@=uj=hot~*!C68G8hTA%8SzOfETOXQ|3FSaIEjvBJp(A)7SWUi5!Eu#yWgY+;n zlm<$+UDou*V+246_o#V4kMdto8hF%%Lki#zPh}KYXmMf?hrN0;>Mv%`@{0Qn`Ujp) z=lZe+13>^Q!9zT);H<(#bIeRWz%#*}sgUX9P|9($kexOyKIOc`dLux}c$7It4u|Rl z6SSkY*V~g_B-hMPo_ak>>z@AVQ(_N)VY2kB3IZ0G(iDUYw+2d7W^~(Jq}KY=JnWS( z#rzEa&0uNhJ>QE8iiyz;n2H|SV#Og+wEZv=f2%1ELX!SX-(d3tEj$5$1}70Mp<&eI zCkfbByL7af=qQE@5vDVxx1}FSGt_a1DoE3SDI+G)mBAna)KBG4p8Epxl9QZ4BfdAN zFnF|Y(umr;gRgG6NLQ$?ZWgllEeeq~z^ZS7L?<(~O&$5|y)Al^iMKy}&W+eMm1W z7EMU)u^ke(A1#XCV>CZ71}P}0x)4wtHO8#JRG3MA-6g=`ZM!FcICCZ{IEw8Dm2&LQ z1|r)BUG^0GzI6f946RrBlfB1Vs)~8toZf~7)+G;pv&XiUO(%5bm)pl=p>nV^o*;&T z;}@oZSibzto$arQgfkp|z4Z($P>dTXE{4O=vY0!)kDO* zGF8a4wq#VaFpLfK!iELy@?-SeRrdz%F*}hjKcA*y@mj~VD3!it9lhRhX}5YOaR9$} z3mS%$2Be7{l(+MVx3 z(4?h;P!jnRmX9J9sYN#7i=iyj_5q7n#X(!cdqI2lnr8T$IfOW<_v`eB!d9xY1P=2q&WtOXY=D9QYteP)De?S4}FK6#6Ma z=E*V+#s8>L;8aVroK^6iKo=MH{4yEZ_>N-N z`(|;aOATba1^asjxlILk<4}f~`39dBFlxj>Dw(hMYKPO3EEt1@S`1lxFNM+J@uB7T zZ8WKjz7HF1-5&2=l=fqF-*@>n5J}jIxdDwpT?oKM3s8Nr`x8JnN-kCE?~aM1H!hAE z%%w(3kHfGwMnMmNj(SU(w42OrC-euI>Dsjk&jz3ts}WHqmMpzQ3vZrsXrZ|}+MHA7 z068obeXZTsO*6RS@o3x80E4ok``rV^Y3hr&C1;|ZZ0|*EKO`$lECUYG2gVFtUTw)R z4Um<0ZzlON`zTdvVdL#KFoMFQX*a5wM0Czp%wTtfK4Sjs)P**RW&?lP$(<}q%r68Z zS53Y!d@&~ne9O)A^tNrXHhXBkj~$8j%pT1%%mypa9AW5E&s9)rjF4@O3ytH{0z6riz|@< zB~UPh*wRFg2^7EbQrHf0y?E~dHlkOxof_a?M{LqQ^C!i2dawHTPYUE=X@2(3<=OOxs8qn_(y>pU>u^}3y&df{JarR0@VJn0f+U%UiF=$Wyq zQvnVHESil@d|8&R<%}uidGh7@u^(%?$#|&J$pvFC-n8&A>utA=n3#)yMkz+qnG3wd zP7xCnF|$9Dif@N~L)Vde3hW8W!UY0BgT2v(wzp;tlLmyk2%N|0jfG$%<;A&IVrOI< z!L)o>j>;dFaqA3pL}b-Je(bB@VJ4%!JeX@3x!i{yIeIso^=n?fDX`3bU=eG7sTc%g%ye8$v8P@yKE^XD=NYxTb zbf!Mk=h|otpqjFaA-vs5YOF-*GwWPc7VbaOW&stlANnCN8iftFMMrUdYNJ_Bnn5Vt zxfz@Ah|+4&P;reZxp;MmEI7C|FOv8NKUm8njF7Wb6Gi7DeODLl&G~}G4be&*Hi0Qw z5}77vL0P+7-B%UL@3n1&JPxW^d@vVwp?u#gVcJqY9#@-3X{ok#UfW3<1fb%FT`|)V~ggq z(3AUoUS-;7)^hCjdT0Kf{i}h)mBg4qhtHHBti=~h^n^OTH5U*XMgDLIR@sre`AaB$ zg)IGBET_4??m@cx&c~bA80O7B8CHR7(LX7%HThkeC*@vi{-pL%e)yXp!B2InafbDF zjPXf1mko3h59{lT6EEbxKO1Z5GF71)WwowO6kY|6tjSVSWdQ}NsK2x{>i|MKZK8%Q zfu&_0D;CO-Jg0#YmyfctyJ!mRJp)e#@O0mYdp|8x;G1%OZQ3Q847YWTyy|%^cpA;m zze0(5p{tMu^lDkpe?HynyO?a1$_LJl2L&mpeKu%8YvgRNr=%2z${%WThHG=vrWY@4 zsA`OP#O&)TetZ>s%h!=+CE15lOOls&nvC~$Qz0Ph7tHiP;O$i|eDwpT{cp>+)0-|; zY$|bB+Gbel>5aRN3>c0x)4U=|X+z+{ zn*_p*EQoquRL+=+p;=lm`d71&1NqBz&_ph)MXu(Nv6&XE7(RsS)^MGj5Q?Fwude-(sq zjJ>aOq!7!EN>@(fK7EE#;i_BGvli`5U;r!YA{JRodLBc6-`n8K+Fjgwb%sX;j=qHQ z7&Tr!)!{HXoO<2BQrV9Sw?JRaLXV8HrsNevvnf>Y-6|{T!pYLl7jp$-nEE z#X!4G4L#K0qG_4Z;Cj6=;b|Be$hi4JvMH!-voxqx^@8cXp`B??eFBz2lLD8RRaRGh zn7kUfy!YV~p(R|p7iC1Rdgt$_24i0cd-S8HpG|`@my70g^y`gu%#Tf_L21-k?sRRZHK&at(*ED0P8iw{7?R$9~OF$Ko;Iu5)ur5<->x!m93Eb zFYpIx60s=Wxxw=`$aS-O&dCO_9?b1yKiPCQmSQb>T)963`*U+Ydj5kI(B(B?HNP8r z*bfSBpSu)w(Z3j7HQoRjUG(+d=IaE~tv}y14zHHs|0UcN52fT8V_<@2ep_ee{QgZG zmgp8iv4V{k;~8@I%M3<#B;2R>Ef(Gg_cQM7%}0s*^)SK6!Ym+~P^58*wnwV1BW@eG z4sZLqsUvBbFsr#8u7S1r4teQ;t)Y@jnn_m5jS$CsW1um!p&PqAcc8!zyiXHVta9QC zY~wCwCF0U%xiQPD_INKtTb;A|Zf29(mu9NI;E zc-e>*1%(LSXB`g}kd`#}O;veb<(sk~RWL|f3ljxCnEZDdNSTDV6#Td({6l&y4IjKF z^}lIUq*ZUqgTPumD)RrCN{M^jhY>E~1pn|KOZ5((%F)G|*ZQ|r4zIbrEiV%42hJV8 z3xS)=!X1+=olbdGJ=yZil?oXLct8FM{(6ikLL3E%=q#O6(H$p~gQu6T8N!plf!96| z&Q3=`L~>U0zZh;z(pGR2^S^{#PrPxTRHD1RQOON&f)Siaf`GLj#UOk&(|@0?zm;Sx ztsGt8=29-MZs5CSf1l1jNFtNt5rFNZxJPvkNu~2}7*9468TWm>nN9TP&^!;J{-h)_ z7WsHH9|F%I`Pb!>KAS3jQWKfGivTVkMJLO-HUGM_a4UQ_%RgL6WZvrW+Z4ujZn;y@ zz9$=oO!7qVTaQAA^BhX&ZxS*|5dj803M=k&2%QrXda`-Q#IoZL6E(g+tN!6CA!CP* zCpWtCujIea)ENl0liwVfj)Nc<9mV%+e@=d`haoZ*`B7+PNjEbXBkv=B+Pi^~L#EO$D$ZqTiD8f<5$eyb54-(=3 zh)6i8i|jp(@OnRrY5B8t|LFXFQVQ895n*P16cEKTrT*~yLH6Z4e*bZ5otpRDri&+A zfNbK1D5@O=sm`fN=WzWyse!za5n%^+6dHPGX#8DyIK>?9qyX}2XvBWVqbP%%D)7$= z=#$WulZlZR<{m#gU7lwqK4WS1Ne$#_P{b17qe$~UOXCl>5b|6WVh;5vVnR<%d+Lnp z$uEmML38}U4vaW8>shm6CzB(Wei3s#NAWE3)a2)z@i{4jTn;;aQS)O@l{rUM`J@K& l00vQ5JBs~;vo!vr%%-k{2_Fq1Mn4QF81S)AQ99zk{{c4yR+0b! literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1af9e09 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1aa94a4 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/native/build.gradle b/native/build.gradle new file mode 100644 index 0000000..69f0840 --- /dev/null +++ b/native/build.gradle @@ -0,0 +1,148 @@ + +plugins { + id 'java' + id 'com.github.spotbugs' + id 'checkstyle' + id 'jacoco' + // id 'maven-publish' +} + +description = 'Ballerina - Persist Java Native' + +configurations { + jacocoRuntime +} + +dependencies { + jacocoRuntime "org.jacoco:org.jacoco.agent:${jacoco.toolVersion}:runtime" + + checkstyle project(":checkstyle") + checkstyle "com.puppycrawl.tools:checkstyle:${puppycrawlCheckstyleVersion}" + implementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}" + implementation group: 'org.ballerinalang', name: 'ballerina-runtime', version: "${ballerinaLangVersion}" + implementation group: 'io.ballerina.stdlib', name: 'persist-native', version: "${stdlibPersistVersion}" +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +// sourceCompatibility = JavaVersion.VERSION_17 + +jacoco { + toolVersion = "0.8.6" +} + +test { + useTestNG() { + suites 'src/test/resources/testng.xml' + } + testLogging.showStandardStreams = true + testLogging { + events "PASSED", "FAILED", "SKIPPED" + afterSuite { desc, result -> + if (!desc.parent) { // will match the outermost suite + def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)" + def startItem = '| ', endItem = ' |' + def repeatLength = startItem.length() + output.length() + endItem.length() + println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength)) + } + } + } + finalizedBy jacocoTestReport +} + +jacocoTestReport { + dependsOn test + reports { + xml.required = true + } +} + +spotbugsMain { + ignoreFailures = true + effort = "max" + reportLevel = "low" + reportsDir = file("$project.buildDir/reports/spotbugs") + def excludeFile = file("${rootDir}/build-config/spotbugs-exclude.xml") + if (excludeFile.exists()) { + it.excludeFilter = excludeFile + } + reports { + text.enabled = true + } +} + +spotbugsTest { + enabled = false +} + +task validateSpotbugs() { + doLast { + if (spotbugsMain.reports.size() > 0 && + spotbugsMain.reports[0].destination.exists() && + spotbugsMain.reports[0].destination.text.readLines().size() > 0) { + spotbugsMain.reports[0].destination?.eachLine { + println 'Failure: ' + it + } + throw new GradleException("Spotbugs rule violations were found."); + } + } +} + +checkstyle { + toolVersion "${checkstyleToolVersion}" + configFile file("${rootDir}/build-config/checkstyle/build/checkstyle.xml") + configProperties = ["suppressionFile": file("${rootDir}/build-config/checkstyle/build/suppressions.xml")] +} + +tasks.withType(Checkstyle) { + exclude '**/module-info.java' +} + +spotbugsMain.finalizedBy validateSpotbugs +checkstyleMain.dependsOn ':checkstyle:downloadCheckstyleRuleFiles' +checkstyleTest.dependsOn ':checkstyle:downloadCheckstyleRuleFiles' + +// publishing { +// publications { +// mavenJava(MavenPublication) { +// groupId project.group +// artifactId "persist.redis-native" +// version = project.version +// artifact jar +// } +// } + +// repositories { +// maven { +// name = "GitHubPackages" +// url = uri("https://maven.pkg.github.com/ballerina-platform/module-ballerina-persist") +// credentials { +// username = System.getenv("publishUser") +// password = System.getenv("publishPAT") +// } +// } +// maven { +// name = "WSO2Nexus" +// if(project.version.endsWith('-SNAPSHOT')) { +// url "https://maven.wso2.org/nexus/content/repositories/snapshots/" +// } else { +// url "https://maven.wso2.org/nexus/service/local/staging/deploy/maven2/" +// } +// credentials { +// username System.getenv("nexusUser") +// password System.getenv("nexusPassword") +// } +// } +// } +// } + +compileJava { + doFirst { + options.compilerArgs = [ + '--module-path', classpath.asPath, + ] + classpath = files() + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/Constants.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/Constants.java new file mode 100644 index 0000000..cce9259 --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/Constants.java @@ -0,0 +1,3 @@ +public class Constants { + +} diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/ModuleUtils.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/ModuleUtils.java new file mode 100644 index 0000000..17a3f4d --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/ModuleUtils.java @@ -0,0 +1,3 @@ +public class ModuleUtils { + +} diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java new file mode 100644 index 0000000..ba790dc --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java @@ -0,0 +1,3 @@ +public class Utils { + +} diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java new file mode 100644 index 0000000..65da3de --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java @@ -0,0 +1,3 @@ +public class RedisProcessor { + +} diff --git a/native/src/main/java/module-info.java b/native/src/main/java/module-info.java new file mode 100644 index 0000000..c39b4c9 --- /dev/null +++ b/native/src/main/java/module-info.java @@ -0,0 +1,3 @@ +module name { + +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..6b49e6d --- /dev/null +++ b/settings.gradle @@ -0,0 +1,27 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.5/userguide/building_swift_projects.html in the Gradle documentation. + */ + +plugins { + id "com.gradle.enterprise" version "3.13.2" +} + +rootProject.name = 'ballerinax-persist.redis' + +include ':checkstyle' +include ':persist.redis-native' +include ':persist.redis-ballerina' + +project(':checkstyle').projectDir = file("build-config${File.separator}checkstyle") +project(':persist.redis-native').projectDir = file('native') +project(':persist.redis-ballerina').projectDir = file('ballerina') + +gradleEnterprise { + buildScan { + termsOfServiceUrl = 'https://gradle.com/terms-of-service' + termsOfServiceAgree = 'yes' + } +} \ No newline at end of file From d7b2457b0523c7affeb491d0939f881aaaf01a50 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 24 Jan 2024 10:58:04 +0530 Subject: [PATCH 02/65] added native module-info.java, Utils.java and Constants.java files --- gradle.properties | 30 +++++++++++++++++++ .../stdlib/persist/redis/Constants.java | 8 ++++- .../stdlib/persist/redis/ModuleUtils.java | 18 ++++++++++- native/src/main/java/module-info.java | 8 +++-- 4 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 gradle.properties diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..c49143f --- /dev/null +++ b/gradle.properties @@ -0,0 +1,30 @@ +group=io.ballerina.stdlib +version=1.2.1-SNAPSHOT + +puppycrawlCheckstyleVersion=10.12.0 +checkstyleToolVersion=10.12.0 +githubSpotbugsVersion=5.0.14 +githubJohnrengelmanShadowVersion=8.1.1 +underCouchDownloadVersion=5.4.0 +researchgateReleaseVersion=2.8.0 +testngVersion=7.6.1 +gsonVersion=2.10 +ballerinaGradlePluginVersion=2.0.1 + +ballerinaLangVersion=2201.8.0 + +# Direct Dependencies + +# Level 01 +stdlibTimeVersion=2.4.0 + +# Level 08 +stdlibPersistVersion=1.2.0 + +# Ballerinax Observer +observeVersion=1.2.0 +observeInternalVersion=1.2.0 + +# Enabled publishing insecure checksums, due to fail to publish to maven central +# Refer https://github.com/gradle/gradle/issues/11308 +systemProp.org.gradle.internal.publish.checksums.insecure=true \ No newline at end of file diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/Constants.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/Constants.java index cce9259..eb68b72 100644 --- a/native/src/main/java/io/ballerina/stdlib/persist/redis/Constants.java +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/Constants.java @@ -1,3 +1,9 @@ +package io.ballerina.stdlib.persist.redis; + public class Constants { - + private Constants() { + + } + + public static final String PERSIST_REDIS_STREAM = "PersistRedisStream"; } diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/ModuleUtils.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/ModuleUtils.java index 17a3f4d..eb35d4f 100644 --- a/native/src/main/java/io/ballerina/stdlib/persist/redis/ModuleUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/ModuleUtils.java @@ -1,3 +1,19 @@ +package io.ballerina.stdlib.persist.redis; + +import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.Module; + public class ModuleUtils { - + private static Module redisModule; + + private ModuleUtils() { + } + + public static void setModule(Environment env) { + redisModule = env.getCurrentModule(); + } + + public static Module getModule() { + return redisModule; + } } diff --git a/native/src/main/java/module-info.java b/native/src/main/java/module-info.java index c39b4c9..4723395 100644 --- a/native/src/main/java/module-info.java +++ b/native/src/main/java/module-info.java @@ -1,3 +1,5 @@ -module name { - -} +module io.ballerina.stdlib.persist.redis { + requires io.ballerina.runtime; + requires io.ballerina.lang; + requires io.ballerina.stdlib.persist; +} \ No newline at end of file From f6267e3965de227cd2d3713f4fc39bed11e80de2 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 24 Jan 2024 10:59:12 +0530 Subject: [PATCH 03/65] added content for Utils.java file --- .../ballerina/stdlib/persist/redis/Utils.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java index ba790dc..766c6a4 100644 --- a/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java @@ -1,3 +1,71 @@ +package io.ballerina.stdlib.persist.redis; + +import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.PredefinedTypes; +import io.ballerina.runtime.api.creators.TypeCreator; +import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.types.Field; +import io.ballerina.runtime.api.types.MapType; +import io.ballerina.runtime.api.types.RecordType; +import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.utils.TypeUtils; +import io.ballerina.runtime.api.values.BArray; +import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BStream; +import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.api.values.BTypedesc; + +import java.util.Locale; +import java.util.Map; + +import static io.ballerina.runtime.api.utils.StringUtils.fromString; +import static io.ballerina.stdlib.persist.redis.Constants.PERSIST_REDIS_STREAM; +import static io.ballerina.stdlib.persist.redis.ModuleUtils.getModule; + + public class Utils { + + public static BString getEntityFromStreamMethod(Environment env) { + String functionName = env.getFunctionName(); + String entity = functionName.substring(5, functionName.length() - 6).toLowerCase(Locale.ENGLISH); + return fromString(entity); + } + public static BMap getFieldTypes(RecordType recordType) { + MapType stringMapType = TypeCreator.createMapType(PredefinedTypes.TYPE_STRING); + BMap typeMap = ValueCreator.createMapValue(stringMapType); + Map fieldsMap = recordType.getFields(); + for (Field field : fieldsMap.values()) { + + Type type = field.getFieldType(); + String fieldName = field.getFieldName(); + typeMap.put(StringUtils.fromString(fieldName), StringUtils.fromString(type.getName())); + } + return typeMap; + } + + private static BObject createPersistRedisStream(BStream sqlStream, BTypedesc targetType, BArray fields, + BArray includes, BArray typeDescriptions, BObject persistClient, + BError persistError) { + return ValueCreator.createObjectValue(getModule(), PERSIST_REDIS_STREAM, + sqlStream, targetType, fields, includes, typeDescriptions, persistClient, persistError); + } + + private static BStream createPersistRedisStreamValue(BTypedesc targetType, BObject persistSQLStream) { + RecordType streamConstraint = + (RecordType) TypeUtils.getReferredType(targetType.getDescribingType()); + return ValueCreator.createStreamValue( + TypeCreator.createStreamType(streamConstraint, PredefinedTypes.TYPE_NULL), persistSQLStream); + } + + public static BStream createPersistRedisStreamValue(BStream sqlStream, BTypedesc targetType, BArray fields, + BArray includes, BArray typeDescriptions, BObject persistClient, + BError persistError) { + BObject persistSQLStream = createPersistRedisStream(sqlStream, targetType, fields, includes, + typeDescriptions, persistClient, persistError); + return createPersistRedisStreamValue(targetType, persistSQLStream); + } } From f2a54f3554e2a27cc3ad34a397dc98d350f3c0c4 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 24 Jan 2024 11:00:23 +0530 Subject: [PATCH 04/65] added the initial query method in RedisProcessor.java --- .../redis/datastore/RedisProcessor.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java index 65da3de..ad8fe04 100644 --- a/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java @@ -1,3 +1,90 @@ + +package io.ballerina.stdlib.persist.redis.datastore; + +import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.Future; +import io.ballerina.runtime.api.PredefinedTypes; +import io.ballerina.runtime.api.async.Callback; +import io.ballerina.runtime.api.creators.TypeCreator; +import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.types.RecordType; +import io.ballerina.runtime.api.types.StreamType; +import io.ballerina.runtime.api.values.BArray; +import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BStream; +import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.api.values.BTypedesc; +import io.ballerina.stdlib.persist.Constants; +import io.ballerina.stdlib.persist.redis.Utils; + +import java.util.Map; + +import static io.ballerina.stdlib.persist.Constants.KEY_FIELDS; +import static io.ballerina.stdlib.persist.ErrorGenerator.wrapError; +import static io.ballerina.stdlib.persist.Utils.getEntity; +import static io.ballerina.stdlib.persist.Utils.getMetadata; +import static io.ballerina.stdlib.persist.Utils.getPersistClient; +import static io.ballerina.stdlib.persist.Utils.getRecordTypeWithKeyFields; +import static io.ballerina.stdlib.persist.Utils.getTransactionContextProperties; + public class RedisProcessor { + + private RedisProcessor() { + + } + static BStream query(Environment env, BObject client, BTypedesc targetType) { + // This method will return `stream` + + BString entity = getEntity(env); + BObject persistClient = getPersistClient(client, entity); + BArray keyFields = (BArray) persistClient.get(KEY_FIELDS); + RecordType recordType = (RecordType) targetType.getDescribingType(); + + RecordType recordTypeWithIdFields = getRecordTypeWithKeyFields(keyFields, recordType); + BTypedesc targetTypeWithIdFields = ValueCreator.createTypedescValue(recordTypeWithIdFields); + StreamType streamTypeWithIdFields = TypeCreator.createStreamType(recordTypeWithIdFields, + PredefinedTypes.TYPE_NULL); + + Map trxContextProperties = getTransactionContextProperties(); + String strandName = env.getStrandName().isPresent() ? env.getStrandName().get() : null; + + BArray[] metadata = getMetadata(recordType); + BArray fields = metadata[0]; + BArray includes = metadata[1]; + BArray typeDescriptions = metadata[2]; + + Future balFuture = env.markAsync(); + env.getRuntime().invokeMethodAsyncSequentially( + // Call `SQLClient.runReadQuery( + // typedesc rowType, string[] fields = [], string[] include = [] + // )` + // which returns `stream|persist:Error` + + persistClient, Constants.RUN_READ_QUERY_METHOD, strandName, env.getStrandMetadata(), new Callback() { + @Override + public void notifySuccess(Object o) { + if (o instanceof BStream) { // stream + BStream sqlStream = (BStream) o; + balFuture.complete(Utils.createPersistRedisStreamValue(sqlStream, targetType, fields, + includes, typeDescriptions, persistClient, null)); + } else { // persist:Error + balFuture.complete(Utils.createPersistRedisStreamValue(null, targetType, fields, includes, + typeDescriptions, persistClient, (BError) o)); + } + } + + @Override + public void notifyFailure(BError bError) { + balFuture.complete(Utils.createPersistRedisStreamValue(null, targetType, fields, includes, + typeDescriptions, persistClient, wrapError(bError))); + } + }, trxContextProperties, streamTypeWithIdFields, + targetTypeWithIdFields, true, fields, true, includes, true + ); + + return null; + } + } From 8fe49ec48e3462cf35238a33c1ea411fae0d4cea Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 24 Jan 2024 11:11:03 +0530 Subject: [PATCH 05/65] added build-config basic content --- build-config/checkstyle/build.gradle | 31 +++++++++++++++++++++++++++ build-config/resources/Ballerina.toml | 25 +++++++++++++++++++++ build-config/spotbugs-exclude.xml | 2 ++ 3 files changed, 58 insertions(+) diff --git a/build-config/checkstyle/build.gradle b/build-config/checkstyle/build.gradle index e69de29..99da6ca 100644 --- a/build-config/checkstyle/build.gradle +++ b/build-config/checkstyle/build.gradle @@ -0,0 +1,31 @@ +plugins { + id "de.undercouch.download" +} + +apply plugin: 'java' + +task downloadCheckstyleRuleFiles(type: Download) { + src([ + 'https://raw.githubusercontent.com/wso2/code-quality-tools/v1.4/checkstyle/jdk-17/checkstyle.xml', + 'https://raw.githubusercontent.com/wso2/code-quality-tools/v1.4/checkstyle/jdk-17/suppressions.xml' + ]) + overwrite false + onlyIfNewer true + dest buildDir +} + +jar { + enabled = false +} + +clean { + enabled = false +} + +artifacts.add('default', file("$project.buildDir/checkstyle.xml")) { + builtBy('downloadCheckstyleRuleFiles') +} + +artifacts.add('default', file("$project.buildDir/suppressions.xml")) { + builtBy('downloadCheckstyleRuleFiles') +} diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index e69de29..862fa2d 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -0,0 +1,25 @@ +[package] +org = "ballerinax" +name = "persist.redis" +version = "@toml.version@" +authors = ["Ballerina"] +keywords = ["persist", "redis"] +repository = "https://github.com/ballerina-platform/module-ballerinax-persist.redis" +icon = "icon.png" +license = ["Apache-2.0"] +distribution = "2201.8.0" + +[platform.java17] +graalvmCompatible = true + +[[platform.java17.dependency]] +groupId = "io.ballerina.persist" +artifactId = "persist-redis-native" +version = "@toml.version@" +path = "../native/build/libs/persist.redis-native-@project.version@.jar" + +[[platform.java17.dependency]] +groupId = "io.ballerina.stdlib" +artifactId = "persist-native" +version = "@persist.version@" +path = "./lib/persist-native-@persist.native.version@.jar" diff --git a/build-config/spotbugs-exclude.xml b/build-config/spotbugs-exclude.xml index e69de29..2536805 100644 --- a/build-config/spotbugs-exclude.xml +++ b/build-config/spotbugs-exclude.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From fcca68b2c020bd66527812ddf3679aea07ac6ce2 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Fri, 26 Jan 2024 09:40:22 +0530 Subject: [PATCH 06/65] added initial files and content for the redis generic client library module --- ballerina/.devcontainer.json | 4 + ballerina/.gitignore | 3 + ballerina/Ballerina.toml | 20 +++++ ballerina/Module.md | 6 ++ ballerina/Package.md | 5 ++ ballerina/ballerina.bal | 10 +++ ballerina/build.gradle | 105 ++++++++++++++++++++++++++ ballerina/init.bal | 9 +++ ballerina/metadata_types.bal | 88 +++++++++++++++++++++ ballerina/redis_client.bal | 0 ballerina/resources/.keep | 0 ballerina/stream_types.bal | 0 ballerina/tests/lib_test.bal | 34 +++++++++ build-config/resources/Ballerina.toml | 5 +- gradle.properties | 2 +- 15 files changed, 287 insertions(+), 4 deletions(-) create mode 100644 ballerina/.devcontainer.json create mode 100644 ballerina/.gitignore create mode 100644 ballerina/Ballerina.toml create mode 100644 ballerina/Module.md create mode 100644 ballerina/Package.md create mode 100644 ballerina/ballerina.bal create mode 100644 ballerina/build.gradle create mode 100644 ballerina/init.bal create mode 100644 ballerina/metadata_types.bal create mode 100644 ballerina/redis_client.bal create mode 100644 ballerina/resources/.keep create mode 100644 ballerina/stream_types.bal create mode 100644 ballerina/tests/lib_test.bal diff --git a/ballerina/.devcontainer.json b/ballerina/.devcontainer.json new file mode 100644 index 0000000..e058e4f --- /dev/null +++ b/ballerina/.devcontainer.json @@ -0,0 +1,4 @@ +{ + "image": "ballerina/ballerina-devcontainer:2201.8.2", + "extensions": ["WSO2.ballerina"], +} diff --git a/ballerina/.gitignore b/ballerina/.gitignore new file mode 100644 index 0000000..7512ebe --- /dev/null +++ b/ballerina/.gitignore @@ -0,0 +1,3 @@ +target +generated +Config.toml diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml new file mode 100644 index 0000000..8d9f7d9 --- /dev/null +++ b/ballerina/Ballerina.toml @@ -0,0 +1,20 @@ +[package] +org = "dinuka_amarasinghe" +name = "persist.redis" +version = "0.1.0" +distribution = "2201.8.2" + +[platform.java17] +graalvmCompatible = true + +[[platform.java17.dependency]] +groupId = "io.ballerina.persist" +artifactId = "persist-redis-native" +version = "1.2.1" +path = "../native/build/libs/persist.redis-native-1.2.1-SNAPSHOT.jar" + +[[platform.java17.dependency]] +groupId = "io.ballerina.stdlib" +artifactId = "persist-native" +version = "1.2.0" +# path = "./lib/persist-native-1.2.0.jar" \ No newline at end of file diff --git a/ballerina/Module.md b/ballerina/Module.md new file mode 100644 index 0000000..8a69f51 --- /dev/null +++ b/ballerina/Module.md @@ -0,0 +1,6 @@ +Prints "Hello, World!" with a main function. +[//]: # (above is the module summary) + +# Module Overview +Provides an overview about the module when generating the API documentations. +For example, refer to https://lib.ballerina.io/ballerina/io/latest diff --git a/ballerina/Package.md b/ballerina/Package.md new file mode 100644 index 0000000..fc8b1b6 --- /dev/null +++ b/ballerina/Package.md @@ -0,0 +1,5 @@ +Prints "Hello, World!" with a hello function. +[//]: # (above is the package summary) + +# Package Overview +Prints "Hello, World!" as the output to the command line using a hello function. diff --git a/ballerina/ballerina.bal b/ballerina/ballerina.bal new file mode 100644 index 0000000..7a0681e --- /dev/null +++ b/ballerina/ballerina.bal @@ -0,0 +1,10 @@ +# Returns the string `Hello` with the input string name. +# +# + name - name as a string +# + return - "Hello, " with the input string name +public function hello(string name) returns string { + if !(name is "") { + return "Hello, " + name; + } + return "Hello, World!"; +} diff --git a/ballerina/build.gradle b/ballerina/build.gradle new file mode 100644 index 0000000..991e1d5 --- /dev/null +++ b/ballerina/build.gradle @@ -0,0 +1,105 @@ +import org.apache.tools.ant.taskdefs.condition.Os + +buildscript { + repositories { + maven { + url = 'https://maven.pkg.github.com/ballerina-platform/plugin-gradle' + credentials { + username System.getenv('packageUser') + password System.getenv('packagePAT') + } + } + } + dependencies { + classpath "io.ballerina:plugin-gradle:${project.ballerinaGradlePluginVersion}" + } +} + +description = 'Ballerina - Persist Ballerina Generator' + +def packageName = 'persist.redis' +def packageOrg = 'ballerinax' +def tomlVersion = stripBallerinaExtensionVersion("${project.version}") + +def ballerinaTomlFilePlaceHolder = new File("${project.rootDir}/build-config/resources/Ballerina.toml") +def ballerinaTomlFile = new File("$project.projectDir/Ballerina.toml") + +def stripBallerinaExtensionVersion(String extVersion) { + if (extVersion.matches(project.ext.timestampedVersionRegex)) { + def splitVersion = extVersion.split('-') + if (splitVersion.length > 3) { + def strippedValues = splitVersion[0..-4] + return strippedValues.join('-') + } else { + return extVersion + } + } else { + return extVersion.replace("${project.ext.snapshotVersion}", '') + } +} + +apply plugin: 'io.ballerina.plugin' + +ballerina { + packageOrganization = packageOrg + module = packageName + langVersion = ballerinaLangVersion + testCoverageParam = "--code-coverage --coverage-format=xml --includes=*" +} + +dependencies { + externalJars(group: 'io.ballerina.stdlib', name: 'persist-native', version: "${stdlibPersistVersion}") { + transitive = false + } +} + +task updateTomlFiles { + doLast { + def stdlibDependentPersistVersion = stripBallerinaExtensionVersion(project.stdlibPersistVersion) + + def newConfig = ballerinaTomlFilePlaceHolder.text.replace('@project.version@', project.version.toString()) + newConfig = newConfig.replace('@toml.version@', tomlVersion) + newConfig = newConfig.replace('@persist.version@', stdlibDependentPersistVersion) + newConfig = newConfig.replace('@persist.native.version@', project.stdlibPersistVersion) + ballerinaTomlFile.text = newConfig + } +} + +task commitTomlFiles { + doLast { + project.exec { + ignoreExitValue true + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + commandLine 'cmd', '/c', "git commit -m \"[Automated] Update native jar versions in toml files\" Ballerina.toml Dependencies.toml" + } else { + commandLine 'sh', '-c', "git commit -m \"[Automated] Update native jar versions in toml files\" Ballerina.toml Dependencies.toml" + } + } + } +} + +publishing { + publications { + maven(MavenPublication) { + artifact source: createArtifactZip, extension: 'zip' + } + } + repositories { + maven { + name = 'GitHubPackages' + url = uri("https://maven.pkg.github.com/ballerina-platform/module-${packageOrg}-${packageName}") + credentials { + username = System.getenv('publishUser') + password = System.getenv('publishPAT') + } + } + } +} + + +updateTomlFiles.dependsOn copyStdlibs + +build.dependsOn "generatePomFileForMavenPublication" +build.dependsOn ":${packageName}-native:build" + +test.dependsOn ":${packageName}-native:build" diff --git a/ballerina/init.bal b/ballerina/init.bal new file mode 100644 index 0000000..bf01a14 --- /dev/null +++ b/ballerina/init.bal @@ -0,0 +1,9 @@ +import ballerina/jballerina.java; + +isolated function init() { + setModule(); +} + +isolated function setModule() = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.ModuleUtils" +} external; \ No newline at end of file diff --git a/ballerina/metadata_types.bal b/ballerina/metadata_types.bal new file mode 100644 index 0000000..7aa5ff9 --- /dev/null +++ b/ballerina/metadata_types.bal @@ -0,0 +1,88 @@ +# Represents the metadata of an entity. +# +# + entityName - Name of the entity +# + collectionName - Collection name of the entity +# + fieldMetadata - Metadata of all the fields of the entity +# + keyFields - Names of the identity fields +public type RedisMetadata record {| + string entityName; + string collectionName; + map fieldMetadata; + string[] keyFields; +|}; + +# Represents the metadata associated with a field from a related entity. +# +# + relation - The relational metadata associated with the field +public type EntityFieldMetadata record {| + RelationMetadata relation; +|}; + +# Represents the metadata associated with a simple field in the entity record. +# +# + fieldName - The name of the Redis document field to which the object field is mapped +# + fieldDataType - The data type of the object field to which the Redis document field mapped +public type SimpleFieldMetadata record {| + string fieldName; + DataType fieldDataType; +|}; + +# Represents the metadata associated with a field of an entity. +# Only used by the generated persist clients and `persist:RQLClient`. +# +public type FieldMetadata SimpleFieldMetadata|EntityFieldMetadata; + +# Represents the metadata associated with a relation. +# Only used by the generated persist clients and `persist:RQLClient`. +# +# + entityName - The name of the entity represented in the relation +# + refField - The name of the referenced field in the Redis document +# + refFieldDataType - The data type of the object field to which the referenced field in +# Redis document is mapped + +public type RelationMetadata record {| + string entityName; + string refField; + DataType refFieldDataType; +|}; + +# Represents the type of the relation used in a `JOIN` operation. +# Only used by the generated persist clients and `rql:SQLClient`. +# +# + ONE_TO_ONE - The association type is a one-to-one association +# + ONE_TO_MANY - The entity is in the 'one' side of a one-to-many association +# + MANY_TO_ONE - The entity is in the 'many' side of a one-to-many association +public enum JoinType { + ONE_TO_ONE, + ONE_TO_MANY, + MANY_TO_ONE +} + + +# Represents the type of the field data. +# Only used by the generated persist clients and `rql:RQLClient`. +# +# + INT - `int` type +# + STRING - `string` type +# + FLOAT - `float` type +# + BOOLEAN - `boolean` type +# + TIME - `time/date` type +public enum DataType { + INT, + STRING, + FLOAT, + BOOLEAN, + TIME +} + +# Represents the types of Metadata a RQL client can hold. +# Only used by the generated persist clients and `rql:RQLClient`. +# +# + FIELD_DATA_TYPE - `int` type +# + RELATION - `string` type +# + REF_FIELD_DATA_TYPE - `float` type +public enum MetaData { + FIELD_DATA_TYPE = "fieldDataType", + RELATION = "relation", + REF_FIELD_DATA_TYPE = "refFieldDataType" +} \ No newline at end of file diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal new file mode 100644 index 0000000..e69de29 diff --git a/ballerina/resources/.keep b/ballerina/resources/.keep new file mode 100644 index 0000000..e69de29 diff --git a/ballerina/stream_types.bal b/ballerina/stream_types.bal new file mode 100644 index 0000000..e69de29 diff --git a/ballerina/tests/lib_test.bal b/ballerina/tests/lib_test.bal new file mode 100644 index 0000000..31ce10d --- /dev/null +++ b/ballerina/tests/lib_test.bal @@ -0,0 +1,34 @@ +import ballerina/io; +import ballerina/test; + +// Before Suite Function + +@test:BeforeSuite +function beforeSuiteFunc() { + io:println("I'm the before suite function!"); +} + +// Test function + +@test:Config {} +function testFunction() { + string name = "John"; + string welcomeMsg = hello(name); + test:assertEquals("Hello, John", welcomeMsg); +} + +// Negative Test function + +@test:Config {} +function negativeTestFunction() { + string name = ""; + string welcomeMsg = hello(name); + test:assertEquals("Hello, World!", welcomeMsg); +} + +// After Suite Function + +@test:AfterSuite +function afterSuiteFunc() { + io:println("I'm the after suite function!"); +} diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index 862fa2d..4300601 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -5,7 +5,6 @@ version = "@toml.version@" authors = ["Ballerina"] keywords = ["persist", "redis"] repository = "https://github.com/ballerina-platform/module-ballerinax-persist.redis" -icon = "icon.png" license = ["Apache-2.0"] distribution = "2201.8.0" @@ -16,10 +15,10 @@ graalvmCompatible = true groupId = "io.ballerina.persist" artifactId = "persist-redis-native" version = "@toml.version@" -path = "../native/build/libs/persist.redis-native-@project.version@.jar" +# path = "../native/build/libs/persist.redis-native-@project.version@.jar" [[platform.java17.dependency]] groupId = "io.ballerina.stdlib" artifactId = "persist-native" version = "@persist.version@" -path = "./lib/persist-native-@persist.native.version@.jar" +# path = "./lib/persist-native-@persist.native.version@.jar" diff --git a/gradle.properties b/gradle.properties index c49143f..5cccac8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ testngVersion=7.6.1 gsonVersion=2.10 ballerinaGradlePluginVersion=2.0.1 -ballerinaLangVersion=2201.8.0 +ballerinaLangVersion=2201.8.2 # Direct Dependencies From d279676bc2984fb69bb88c1d5f9c8b40c5ff104c Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Fri, 26 Jan 2024 09:44:14 +0530 Subject: [PATCH 07/65] added init constructor for the RedisClient --- ballerina/redis_client.bal | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index e69de29..b47dc05 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -0,0 +1,29 @@ +import ballerinax/redis; +import ballerina/persist; + +# The client used by the generated persist clients to abstract and +# execute Redis queries that are required to perform CRUD operations. +public isolated client class RedisClient { + + private final redis:Client dbClient; + + private final string & readonly entityName; + private final string & readonly collectionName; + private final map & readonly fieldMetadata; + private final string[] & readonly keyFields; + + # Initializes the `RedisClient`. + # + # + dbClient - The `redis:Client`, which is used to execute Redis queries + # + metadata - Metadata of the entity + # + return - A `persist:Error` if the client creation fails + public isolated function init(redis:Client dbClient, RedisMetadata & readonly metadata) returns persist:Error? { + self.entityName = metadata.entityName; + self.collectionName = metadata.collectionName; + self.fieldMetadata = metadata.fieldMetadata; + self.keyFields = metadata.keyFields; + self.dbClient = dbClient; + } + + +} \ No newline at end of file From e83c7fbd70de781d62f73f01bda30839c561a483 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Fri, 26 Jan 2024 09:47:12 +0530 Subject: [PATCH 08/65] added the runReadByKeyQuery method and it's helper methods --- ballerina/redis_client.bal | 145 +++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index b47dc05..29c9fdb 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -25,5 +25,150 @@ public isolated client class RedisClient { self.dbClient = dbClient; } + # Performs a batch `HGET` operation to get entity instances as a stream + # + # + rowType - The type description of the entity to be retrieved + # + key - Key for the record + # + fields - The fields to be retrieved + # + include - The associations to be retrieved + # + typeDescriptions - The type descriptions of the relations to be retrieved + # + return - An `record{||} & readonly` containing the requested record + public isolated function runReadByKeyQuery(typedesc rowType, anydata key, string[] fields = [], string[] include = [], typedesc[] typeDescriptions = []) returns record{}|error { + + if key is string[]{ + string recordKey = self.collectionName; + // assume the key fields are in the same order as when inserting a new record + foreach string keyField in key{ + recordKey += ":"+keyField; + } + + do { + // return check self.querySimpleFieldsByKey(recordKey, fields).cloneWithType(rowType); + record{} 'object = check self.querySimpleFieldsByKey(recordKey, fields); + check self.getManyRelations('object, fields, include); + self.removeUnwantedFields('object, fields); + return check 'object.cloneWithType(rowType); + } on fail error e { + return e; + } + }else{ + return error("Invalid data type for key"); + } + } + + public isolated function querySimpleFieldsByKey(string key, string[] fields) returns record {}|persist:Error{ + // hadling the simple fields + string[] simpleFields = self.getSimpleFields(fields); + if simpleFields == [] { // then add all the fields by default + foreach [string, FieldMetadata & readonly] metaDataEntry in self.fieldMetadata.entries() { + FieldMetadata & readonly fieldMetadataValue = metaDataEntry[1]; + + // if the field is a simple field + if(fieldMetadataValue is SimpleFieldMetadata){ + simpleFields.push(fieldMetadataValue.fieldName); + } + } + } + + do { + + map value = check self.dbClient->hMGet(key, simpleFields); + record{} valueToRecord = {}; + foreach string fieldKey in value.keys() { + // convert the data type from 'any' to required type + valueToRecord[fieldKey] = check self.dataConverter(self.fieldMetadata[fieldKey], value[fieldKey]); + } + return valueToRecord; + } on fail var e { + return e; + } + } + + public isolated function getSimpleFields(string[] fields) returns string[] { + string[] simpleFields = from string 'field in fields + where !'field.includes("[].") + select 'field; + return simpleFields; + } + + public isolated function getManyRelations(record {} 'object,string[] fields, string[] include) returns persist:Error? { + foreach int i in 0 ..< include.length() { + string entity = include[i]; + string[] relationFields = from string 'field in fields + where 'field.startsWith(entity + "[].") + select 'field.substring(entity.length() + 3, 'field.length()); + + if relationFields.length() is 0 { + continue; + } + + string[]keys = check self.dbClient->keys(entity.substring(0,1).toUpperAscii()+entity.substring(1)+":*"); + + // Get data one by one using the key + record{}[] associatedRecords = []; + foreach string key in keys { + // handling simple fields + record{} valueToRecord = check self.querySimpleFieldsByKey(key, relationFields); + + foreach string fieldKey in valueToRecord.keys() { + // convert the data type from 'any' to required type + valueToRecord[fieldKey] = check self.dataConverter(self.fieldMetadata[entity+"[]."+fieldKey], valueToRecord[fieldKey]); + } + + // check whether the record is associated with the current object + boolean isAssociated = true; + foreach string keyField in self.keyFields{ + boolean isSimilar = valueToRecord[entity+keyField.substring(0,1).toUpperAscii()+keyField.substring(1)] == 'object[keyField]; + if !isSimilar { + isAssociated = false; + } + } + + if isAssociated { + associatedRecords.push(valueToRecord); + } + + } + + 'object[entity] = associatedRecords; + } on fail var e { + return e; + } + } + + private isolated function removeUnwantedFields(record {} 'object, string[] fields) { + string[] keyFields = self.keyFields; + + foreach string keyField in keyFields { + if fields.indexOf(keyField) is () { + _ = 'object.remove(keyField); + } + } + } + + public isolated function dataConverter(FieldMetadata & readonly fieldMetaData, any value) returns ()|boolean|string|float|error|int { + + // Return nil if value is nil + if(value is ()){ + return (); + } + + if((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == INT) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == INT)){ + return check int:fromString(value); + }else if((fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == STRING)) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == STRING)){ + return value; + }else if((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == FLOAT) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == FLOAT)){ + return check float:fromString(value); + }else if((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == BOOLEAN) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == BOOLEAN)){ + return check boolean:fromString(value); + }else{ + return error("Unsupported Data Format"); + } + } + } \ No newline at end of file From d41297ca6613486a59e12e3cf21460556a58fcf7 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Fri, 26 Jan 2024 09:48:20 +0530 Subject: [PATCH 09/65] added the runReadQuery for batch read --- ballerina/redis_client.bal | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index 29c9fdb..1e265c8 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -56,6 +56,36 @@ public isolated client class RedisClient { } } + # Performs a batch `HGET` operation to get entity instances as a stream + # + # + rowType - The type description of the entity to be retrieved + # + fields - The fields to be retrieved + # + include - The associations to be retrieved + # + return - A stream of `record{||} & readonly` containing the requested records + public isolated function runReadQuery(typedesc rowType, string[] fields = [], string[] include = []) returns stream|error { + // Get all the keys + string[]keys = check self.dbClient->keys(self.collectionName+":*"); + + // Get data one by one using the key + record{}[] result = []; + foreach string key in keys { + do{ + // handling simple fields + record{} 'object = check self.querySimpleFieldsByKey(key, fields); + check self.getManyRelations('object, fields, include); + self.removeUnwantedFields('object, fields); + + result.push(check 'object.cloneWithType(rowType)); + // handling relation fields later + } on fail error e { + return e; + } + + } + + return stream from record{} rec in result select rec; + } + public isolated function querySimpleFieldsByKey(string key, string[] fields) returns record {}|persist:Error{ // hadling the simple fields string[] simpleFields = self.getSimpleFields(fields); From 8753d36976322a6353f1fd2cd1163840cf219efd Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Fri, 26 Jan 2024 09:49:07 +0530 Subject: [PATCH 10/65] added the runBatchInsertQuery for batch reads --- ballerina/redis_client.bal | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index 1e265c8..b75f9b8 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -86,6 +86,45 @@ public isolated client class RedisClient { return stream from record{} rec in result select rec; } + # Performs a batch `HMSET` operation to insert entity instances into a table. + # + # + insertRecords - The entity records to be inserted into the table + # + return - An `sql:ExecutionResult[]` containing the metadata of the query execution + # or a `persist:Error` if the operation fails + public isolated function runBatchInsertQuery(record {}[] insertRecords) returns string|persist:Error|error { + + string|error result; + + // for each record do HMSET + foreach var insertRecord in insertRecords { + + // Create the key + string key = ""; + foreach string keyField in self.keyFields { + key = key + ":" + insertRecord[keyField].toString(); // get the key field value by member access method. + } + + // check for duplicate keys withing the collection + int isKeyExists = check self.dbClient->exists([self.collectionName+key]); + if isKeyExists != 0 { + return persist:getAlreadyExistsError(self.collectionName, key); + } + + // inserting the object + result = self.dbClient->hMSet(self.collectionName+key, insertRecord); + if result is error{ + return error persist:Error(result.message()); + } + } + + // Decide how to log queries + // logQuery("RQL insert query: ", insertQueries); + if result is string { + return result; + } + return error persist:Error(result.message()); + } + public isolated function querySimpleFieldsByKey(string key, string[] fields) returns record {}|persist:Error{ // hadling the simple fields string[] simpleFields = self.getSimpleFields(fields); From 1aead8d0d8733b2011a3732df0728ad019a14b35 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Fri, 26 Jan 2024 09:57:37 +0530 Subject: [PATCH 11/65] added update and delete methods for the redis client --- ballerina/redis_client.bal | 61 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index b75f9b8..ba7e7b3 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -86,10 +86,10 @@ public isolated client class RedisClient { return stream from record{} rec in result select rec; } - # Performs a batch `HMSET` operation to insert entity instances into a table. + # Performs a batch `HMSET` operation to insert entity instances into a collection. # - # + insertRecords - The entity records to be inserted into the table - # + return - An `sql:ExecutionResult[]` containing the metadata of the query execution + # + insertRecords - The entity records to be inserted into the collection + # + return - A `string` containing the information of the query execution # or a `persist:Error` if the operation fails public isolated function runBatchInsertQuery(record {}[] insertRecords) returns string|persist:Error|error { @@ -125,6 +125,61 @@ public isolated client class RedisClient { return error persist:Error(result.message()); } + # Performs redis `DEL` operation to delete an entity record from the database. + # + # + keyFieldValues - The ordered keys used to delete an entity record + # + return - `()` if the operation is performed successfully or a `persist:Error` if the operation fails + public isolated function runDeleteQuery(any [] keyFieldValues) returns persist:Error?|error { + // Validate fields + if (keyFieldValues.length() != self.keyFields.length()){ + return error("Missing keyfields"); + } + + // Generate the key + string recordKey = self.collectionName; + foreach any value in keyFieldValues{ + recordKey += ":"+value.toString(); + } + + // Delete the record + _ = check self.dbClient->del([recordKey]); + } + + # Performs redis `HSET` operation to delete an entity record from the database. + # + # + keyFieldValues - The ordered keys used to update an entity record + # + updateRecord - The new record to be updated + # + return - An Error if the new record is missing a keyfield + public isolated function runUpdateQuery(any [] keyFieldValues, record {} updateRecord) returns error? { + // Validate fields + if (keyFieldValues.length() != self.keyFields.length()){ + return error("Missing keyfields"); + } + + // Generate the key + string key = self.collectionName; + foreach any keyFieldValue in keyFieldValues{ + key += ":"+keyFieldValue.toString(); + } + + // decide on how to update only the given fields that is not equals to () + foreach [string, FieldMetadata & readonly] metaDataEntry in self.fieldMetadata.entries() { + FieldMetadata & readonly fieldMetadataValue = metaDataEntry[1]; + + // if the field is a simple field + if(fieldMetadataValue is SimpleFieldMetadata){ + if (updateRecord.hasKey(fieldMetadataValue.fieldName) && updateRecord[fieldMetadataValue.fieldName] != ()){ + // updating the object + _ = check self.dbClient->hSet(key, fieldMetadataValue.fieldName, updateRecord[fieldMetadataValue.fieldName].toString()); + } + } + // if the field is a relation field + else{ + + } + } + } + public isolated function querySimpleFieldsByKey(string key, string[] fields) returns record {}|persist:Error{ // hadling the simple fields string[] simpleFields = self.getSimpleFields(fields); From db52ca015c924ec1833f18cdab07c6ae530df1f8 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Fri, 26 Jan 2024 10:03:57 +0530 Subject: [PATCH 12/65] added getKeyFields public method and PersistRedisStream class --- ballerina/redis_client.bal | 4 +++ ballerina/stream_types.bal | 68 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index ba7e7b3..3f19a78 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -180,6 +180,10 @@ public isolated client class RedisClient { } } + public isolated function getKeyFields() returns string[] { + return self.keyFields; + } + public isolated function querySimpleFieldsByKey(string key, string[] fields) returns record {}|persist:Error{ // hadling the simple fields string[] simpleFields = self.getSimpleFields(fields); diff --git a/ballerina/stream_types.bal b/ballerina/stream_types.bal index e69de29..e02ea81 100644 --- a/ballerina/stream_types.bal +++ b/ballerina/stream_types.bal @@ -0,0 +1,68 @@ +import ballerina/persist; + +public class PersistRedisStream { + + private stream? anydataStream; + private persist:Error? err; + private string[] fields; + private string[] include; + private typedesc[] typeDescriptions; + private RedisClient? persistClient; + private typedesc targetType; + + public isolated function init(stream? anydataStream, typedesc targetType, string[] fields, string[] include, any[] typeDescriptions, RedisClient persistClient, persist:Error? err = ()) { + self.anydataStream = anydataStream; + self.fields = fields; + self.include = include; + self.targetType = targetType; + + typedesc[] typeDescriptionsArray = []; + foreach any typeDescription in typeDescriptions { + typeDescriptionsArray.push(>typeDescription); + } + self.typeDescriptions = typeDescriptionsArray; + + self.persistClient = persistClient; + self.err = err; + } + + public isolated function next() returns record {|record {} value;|}|persist:Error? { + if self.err is persist:Error { + return self.err; + } else if self.anydataStream is stream { + var anydataStream = >self.anydataStream; + var streamValue = anydataStream.next(); + if streamValue is () { + return streamValue; + } else if (streamValue is error) { + return error(streamValue.message()); + } else { + record {}|error value = streamValue.value; + if value is error { + return error(value.message()); + } + check (self.persistClient).getManyRelations(value, self.fields, self.include); + + string[] keyFields = (self.persistClient).getKeyFields(); + foreach string keyField in keyFields { + if self.fields.indexOf(keyField) is () { + _ = value.remove(keyField); + } + } + record {|record {} value;|} nextRecord = {value: checkpanic value.cloneWithType(self.targetType)}; + return nextRecord; + } + } else { + return (); + } + } + + public isolated function close() returns persist:Error? { + if self.anydataStream is stream { + error? e = (>self.anydataStream).close(); + if e is error { + return error(e.message()); + } + } + } +} \ No newline at end of file From 8ca4a585bc317499687c20221da3b254b30e761b Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Tue, 30 Jan 2024 10:08:38 +0530 Subject: [PATCH 13/65] added and tested native query method in redisProcessor for entities with only simple fields --- ballerina/.gitignore | 1 + ballerina/Ballerina.toml | 12 ++- ballerina/Dependencies.toml | 96 +++++++++++++++++++ ballerina/redis_client.bal | 76 ++++++++++----- ballerina/stream_types.bal | 10 +- build-config/resources/Ballerina.toml | 4 +- build.gradle | 2 +- native/build.gradle | 70 +++++++------- .../ballerina/stdlib/persist/redis/Utils.java | 14 +-- .../redis/datastore/RedisProcessor.java | 21 ++-- 10 files changed, 222 insertions(+), 84 deletions(-) create mode 100644 ballerina/Dependencies.toml diff --git a/ballerina/.gitignore b/ballerina/.gitignore index 7512ebe..1ded4a8 100644 --- a/ballerina/.gitignore +++ b/ballerina/.gitignore @@ -1,3 +1,4 @@ target generated Config.toml +.devcontainer.json \ No newline at end of file diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 8d9f7d9..4f0e6d2 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,8 +1,12 @@ [package] -org = "dinuka_amarasinghe" +org = "ballerinax" name = "persist.redis" -version = "0.1.0" -distribution = "2201.8.2" +version = "1.2.1" +authors = ["Ballerina"] +keywords = ["persist", "redis"] +repository = "https://github.com/ballerina-platform/module-ballerinax-persist.redis" +license = ["Apache-2.0"] +distribution = "2201.8.0" [platform.java17] graalvmCompatible = true @@ -17,4 +21,4 @@ path = "../native/build/libs/persist.redis-native-1.2.1-SNAPSHOT.jar" groupId = "io.ballerina.stdlib" artifactId = "persist-native" version = "1.2.0" -# path = "./lib/persist-native-1.2.0.jar" \ No newline at end of file +path = "./lib/persist-native-1.2.0.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml new file mode 100644 index 0000000..28b5208 --- /dev/null +++ b/ballerina/Dependencies.toml @@ -0,0 +1,96 @@ +# AUTO-GENERATED FILE. DO NOT MODIFY. + +# This file is auto-generated by Ballerina for managing dependency versions. +# It should not be modified by hand. + +[ballerina] +dependencies-toml-version = "2" +distribution-version = "2201.8.2" + +[[package]] +org = "ballerina" +name = "io" +version = "1.4.1" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + +[[package]] +org = "ballerina" +name = "jballerina.java" +version = "0.0.0" +modules = [ + {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.error" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "persist" +version = "1.2.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "persist", moduleName = "persist"} +] + +[[package]] +org = "ballerina" +name = "test" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.error"} +] +modules = [ + {org = "ballerina", packageName = "test", moduleName = "test"} +] + +[[package]] +org = "ballerinax" +name = "persist.redis" +version = "1.2.1" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "persist"}, + {org = "ballerina", name = "test"}, + {org = "ballerinax", name = "redis"} +] +modules = [ + {org = "ballerinax", packageName = "persist.redis", moduleName = "persist.redis"} +] + +[[package]] +org = "ballerinax" +name = "redis" +version = "2.5.1" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerinax", packageName = "redis", moduleName = "redis"} +] + diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index 3f19a78..ff4a2f8 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -1,5 +1,6 @@ import ballerinax/redis; import ballerina/persist; +import ballerina/io; # The client used by the generated persist clients to abstract and # execute Redis queries that are required to perform CRUD operations. @@ -28,13 +29,13 @@ public isolated client class RedisClient { # Performs a batch `HGET` operation to get entity instances as a stream # # + rowType - The type description of the entity to be retrieved + # + typeMap - The data type map of the target type # + key - Key for the record # + fields - The fields to be retrieved # + include - The associations to be retrieved # + typeDescriptions - The type descriptions of the relations to be retrieved # + return - An `record{||} & readonly` containing the requested record - public isolated function runReadByKeyQuery(typedesc rowType, anydata key, string[] fields = [], string[] include = [], typedesc[] typeDescriptions = []) returns record{}|error { - + public isolated function runReadByKeyQuery(typedesc rowType, map typeMap, anydata key, string[] fields = [], string[] include = [], typedesc[] typeDescriptions = []) returns record{}|error { if key is string[]{ string recordKey = self.collectionName; // assume the key fields are in the same order as when inserting a new record @@ -44,8 +45,8 @@ public isolated client class RedisClient { do { // return check self.querySimpleFieldsByKey(recordKey, fields).cloneWithType(rowType); - record{} 'object = check self.querySimpleFieldsByKey(recordKey, fields); - check self.getManyRelations('object, fields, include); + record{} 'object = check self.querySimpleFieldsByKey(typeMap, recordKey, fields); + check self.getManyRelations('object, typeMap, fields, include); self.removeUnwantedFields('object, fields); return check 'object.cloneWithType(rowType); } on fail error e { @@ -58,28 +59,27 @@ public isolated client class RedisClient { # Performs a batch `HGET` operation to get entity instances as a stream # - # + rowType - The type description of the entity to be retrieved + # + typeMap - The data types of the record # + fields - The fields to be retrieved # + include - The associations to be retrieved # + return - A stream of `record{||} & readonly` containing the requested records - public isolated function runReadQuery(typedesc rowType, string[] fields = [], string[] include = []) returns stream|error { + public isolated function runReadQuery(map typeMap, string[] fields = [], string[] include = []) returns stream|persist:Error { // Get all the keys - string[]keys = check self.dbClient->keys(self.collectionName+":*"); + string[]|error keys = self.dbClient->keys(self.collectionName+":*"); + if keys is error { + return error persist:Error(keys.message()); + } // Get data one by one using the key record{}[] result = []; foreach string key in keys { - do{ - // handling simple fields - record{} 'object = check self.querySimpleFieldsByKey(key, fields); - check self.getManyRelations('object, fields, include); - self.removeUnwantedFields('object, fields); + // handling simple fields + record{} 'object = check self.querySimpleFieldsByKey(typeMap, key, fields); + // check self.getManyRelations('object, fields, include); + self.removeUnwantedFields('object, fields); + self.removeNonExistOptionalFields('object); - result.push(check 'object.cloneWithType(rowType)); - // handling relation fields later - } on fail error e { - return e; - } + result.push('object); } @@ -184,9 +184,9 @@ public isolated client class RedisClient { return self.keyFields; } - public isolated function querySimpleFieldsByKey(string key, string[] fields) returns record {}|persist:Error{ + public isolated function querySimpleFieldsByKey(map typeMap, string key, string[] fields) returns record {}|persist:Error{ // hadling the simple fields - string[] simpleFields = self.getSimpleFields(fields); + string[] simpleFields = self.getTargetSimpleFields(fields, typeMap); if simpleFields == [] { // then add all the fields by default foreach [string, FieldMetadata & readonly] metaDataEntry in self.fieldMetadata.entries() { FieldMetadata & readonly fieldMetadataValue = metaDataEntry[1]; @@ -212,14 +212,14 @@ public isolated client class RedisClient { } } - public isolated function getSimpleFields(string[] fields) returns string[] { + public isolated function getTargetSimpleFields(string[] fields, map typeMap) returns string[] { string[] simpleFields = from string 'field in fields - where !'field.includes("[].") + where !'field.includes("[].") && typeMap.hasKey('field) select 'field; return simpleFields; } - public isolated function getManyRelations(record {} 'object,string[] fields, string[] include) returns persist:Error? { + public isolated function getManyRelations(map typeMap, record {} 'object,string[] fields, string[] include) returns persist:Error? { foreach int i in 0 ..< include.length() { string entity = include[i]; string[] relationFields = from string 'field in fields @@ -236,7 +236,7 @@ public isolated client class RedisClient { record{}[] associatedRecords = []; foreach string key in keys { // handling simple fields - record{} valueToRecord = check self.querySimpleFieldsByKey(key, relationFields); + record{} valueToRecord = check self.querySimpleFieldsByKey(typeMap, key, relationFields); foreach string fieldKey in valueToRecord.keys() { // convert the data type from 'any' to required type @@ -264,6 +264,14 @@ public isolated client class RedisClient { } } + private isolated function removeNonExistOptionalFields(record {} 'object){ + foreach string key in 'object.keys(){ + if 'object[key] == () { + _ = 'object.remove(key); + } + } + } + private isolated function removeUnwantedFields(record {} 'object, string[] fields) { string[] keyFields = self.keyFields; @@ -298,5 +306,27 @@ public isolated client class RedisClient { } } + public isolated function objectConverter(record {} 'object, map typeMap) returns record {}|error{ + foreach string key in typeMap.keys(){ + if (typeMap[key] == "string" && 'object[key] != ()){ + io:println("it's the string"); + 'object[key] = 'object[key].toString(); + }else if(typeMap[key] == "int" && 'object[key] != ()){ + io:println("it's the int"); + 'object[key] = check int:fromString('object[key].toString()); + }else if(typeMap[key] == "float" && 'object[key] != ()){ + io:println("it's the float"); + 'object[key] = check float:fromString('object[key].toString()); + }else if(typeMap[key] == "boolean" && 'object[key] != ()){ + io:println("it's the boolean"); + // 'object[key] = check boolean:fromString('object[key]); + }else{ + // many other data types + 'object[key] = (); + } + } + + return 'object; + } } \ No newline at end of file diff --git a/ballerina/stream_types.bal b/ballerina/stream_types.bal index e02ea81..b0d5124 100644 --- a/ballerina/stream_types.bal +++ b/ballerina/stream_types.bal @@ -1,4 +1,5 @@ import ballerina/persist; +import ballerina/io; public class PersistRedisStream { @@ -9,12 +10,14 @@ public class PersistRedisStream { private typedesc[] typeDescriptions; private RedisClient? persistClient; private typedesc targetType; + private map typeMap; - public isolated function init(stream? anydataStream, typedesc targetType, string[] fields, string[] include, any[] typeDescriptions, RedisClient persistClient, persist:Error? err = ()) { + public isolated function init(stream? anydataStream, typedesc targetType, map typeMap, string[] fields, string[] include, any[] typeDescriptions, RedisClient persistClient, persist:Error? err = ()) { self.anydataStream = anydataStream; self.fields = fields; self.include = include; self.targetType = targetType; + self.typeMap = typeMap; typedesc[] typeDescriptionsArray = []; foreach any typeDescription in typeDescriptions { @@ -41,7 +44,7 @@ public class PersistRedisStream { if value is error { return error(value.message()); } - check (self.persistClient).getManyRelations(value, self.fields, self.include); + check (self.persistClient).getManyRelations(self.typeMap, value, self.fields, self.include); string[] keyFields = (self.persistClient).getKeyFields(); foreach string keyField in keyFields { @@ -49,7 +52,10 @@ public class PersistRedisStream { _ = value.remove(keyField); } } + // return value; + io:println(value); record {|record {} value;|} nextRecord = {value: checkpanic value.cloneWithType(self.targetType)}; + // record {|record {} value;|} nextRecord = {value: value}; return nextRecord; } } else { diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index 4300601..5d3986d 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -15,10 +15,10 @@ graalvmCompatible = true groupId = "io.ballerina.persist" artifactId = "persist-redis-native" version = "@toml.version@" -# path = "../native/build/libs/persist.redis-native-@project.version@.jar" +path = "../native/build/libs/persist.redis-native-@project.version@.jar" [[platform.java17.dependency]] groupId = "io.ballerina.stdlib" artifactId = "persist-native" version = "@persist.version@" -# path = "./lib/persist-native-@persist.native.version@.jar" +path = "./lib/persist-native-@persist.native.version@.jar" diff --git a/build.gradle b/build.gradle index 8ee4963..3e763ae 100644 --- a/build.gradle +++ b/build.gradle @@ -86,7 +86,7 @@ def moduleVersion = project.version.replace("-SNAPSHOT", "") // } task build { - // dependsOn(":${packageName}-ballerina:build") + dependsOn(":${packageName}-ballerina:build") } publishToMavenLocal.dependsOn build diff --git a/native/build.gradle b/native/build.gradle index 69f0840..5dbb917 100644 --- a/native/build.gradle +++ b/native/build.gradle @@ -4,7 +4,7 @@ plugins { id 'com.github.spotbugs' id 'checkstyle' id 'jacoco' - // id 'maven-publish' + id 'maven-publish' } description = 'Ballerina - Persist Java Native' @@ -27,7 +27,7 @@ tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } -// sourceCompatibility = JavaVersion.VERSION_17 +sourceCompatibility = JavaVersion.VERSION_17 jacoco { toolVersion = "0.8.6" @@ -104,39 +104,39 @@ spotbugsMain.finalizedBy validateSpotbugs checkstyleMain.dependsOn ':checkstyle:downloadCheckstyleRuleFiles' checkstyleTest.dependsOn ':checkstyle:downloadCheckstyleRuleFiles' -// publishing { -// publications { -// mavenJava(MavenPublication) { -// groupId project.group -// artifactId "persist.redis-native" -// version = project.version -// artifact jar -// } -// } - -// repositories { -// maven { -// name = "GitHubPackages" -// url = uri("https://maven.pkg.github.com/ballerina-platform/module-ballerina-persist") -// credentials { -// username = System.getenv("publishUser") -// password = System.getenv("publishPAT") -// } -// } -// maven { -// name = "WSO2Nexus" -// if(project.version.endsWith('-SNAPSHOT')) { -// url "https://maven.wso2.org/nexus/content/repositories/snapshots/" -// } else { -// url "https://maven.wso2.org/nexus/service/local/staging/deploy/maven2/" -// } -// credentials { -// username System.getenv("nexusUser") -// password System.getenv("nexusPassword") -// } -// } -// } -// } +publishing { + publications { + mavenJava(MavenPublication) { + groupId project.group + artifactId "persist.redis-native" + version = project.version + artifact jar + } + } + + repositories { + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/ballerina-platform/module-ballerina-persist") + credentials { + username = System.getenv("publishUser") + password = System.getenv("publishPAT") + } + } + maven { + name = "WSO2Nexus" + if(project.version.endsWith('-SNAPSHOT')) { + url "https://maven.wso2.org/nexus/content/repositories/snapshots/" + } else { + url "https://maven.wso2.org/nexus/service/local/staging/deploy/maven2/" + } + credentials { + username System.getenv("nexusUser") + password System.getenv("nexusPassword") + } + } + } +} compileJava { doFirst { diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java index 766c6a4..1d6264a 100644 --- a/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java @@ -47,25 +47,25 @@ public static BMap getFieldTypes(RecordType recordType) { return typeMap; } - private static BObject createPersistRedisStream(BStream sqlStream, BTypedesc targetType, BArray fields, + private static BObject createPersistRedisStream(BStream redisStream, BTypedesc targetType, BMap typeMap, BArray fields, BArray includes, BArray typeDescriptions, BObject persistClient, BError persistError) { return ValueCreator.createObjectValue(getModule(), PERSIST_REDIS_STREAM, - sqlStream, targetType, fields, includes, typeDescriptions, persistClient, persistError); + redisStream, targetType, typeMap, fields, includes, typeDescriptions, persistClient, persistError); } - private static BStream createPersistRedisStreamValue(BTypedesc targetType, BObject persistSQLStream) { + private static BStream createPersistRedisStreamValue(BTypedesc targetType, BObject persistRedisStream) { RecordType streamConstraint = (RecordType) TypeUtils.getReferredType(targetType.getDescribingType()); return ValueCreator.createStreamValue( - TypeCreator.createStreamType(streamConstraint, PredefinedTypes.TYPE_NULL), persistSQLStream); + TypeCreator.createStreamType(streamConstraint, PredefinedTypes.TYPE_NULL), persistRedisStream); } - public static BStream createPersistRedisStreamValue(BStream sqlStream, BTypedesc targetType, BArray fields, + public static BStream createPersistRedisStreamValue(BStream redisStream, BTypedesc targetType, BArray fields, BArray includes, BArray typeDescriptions, BObject persistClient, BError persistError) { - BObject persistSQLStream = createPersistRedisStream(sqlStream, targetType, fields, includes, + BObject persistRedisStream = createPersistRedisStream(redisStream, targetType, Utils.getFieldTypes((RecordType) targetType.getDescribingType()), fields, includes, typeDescriptions, persistClient, persistError); - return createPersistRedisStreamValue(targetType, persistSQLStream); + return createPersistRedisStreamValue(targetType, persistRedisStream); } } diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java index ad8fe04..e1d271f 100644 --- a/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java @@ -6,11 +6,11 @@ import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.async.Callback; import io.ballerina.runtime.api.creators.TypeCreator; -import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.StreamType; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BObject; import io.ballerina.runtime.api.values.BStream; import io.ballerina.runtime.api.values.BString; @@ -27,6 +27,7 @@ import static io.ballerina.stdlib.persist.Utils.getPersistClient; import static io.ballerina.stdlib.persist.Utils.getRecordTypeWithKeyFields; import static io.ballerina.stdlib.persist.Utils.getTransactionContextProperties; +import static io.ballerina.stdlib.persist.redis.Utils.getFieldTypes; public class RedisProcessor { @@ -34,7 +35,7 @@ private RedisProcessor() { } - static BStream query(Environment env, BObject client, BTypedesc targetType) { + public static BStream query(Environment env, BObject client, BTypedesc targetType) { // This method will return `stream` BString entity = getEntity(env); @@ -43,7 +44,6 @@ static BStream query(Environment env, BObject client, BTypedesc targetType) { RecordType recordType = (RecordType) targetType.getDescribingType(); RecordType recordTypeWithIdFields = getRecordTypeWithKeyFields(keyFields, recordType); - BTypedesc targetTypeWithIdFields = ValueCreator.createTypedescValue(recordTypeWithIdFields); StreamType streamTypeWithIdFields = TypeCreator.createStreamType(recordTypeWithIdFields, PredefinedTypes.TYPE_NULL); @@ -54,20 +54,21 @@ static BStream query(Environment env, BObject client, BTypedesc targetType) { BArray fields = metadata[0]; BArray includes = metadata[1]; BArray typeDescriptions = metadata[2]; + BMap typeMap = getFieldTypes(recordType); Future balFuture = env.markAsync(); env.getRuntime().invokeMethodAsyncSequentially( - // Call `SQLClient.runReadQuery( - // typedesc rowType, string[] fields = [], string[] include = [] + // Call `RedisClient.runReadQuery( + // map typeMap, string[] fields = [], string[] include = [] // )` - // which returns `stream|persist:Error` + // which returns `stream|persist:Error` persistClient, Constants.RUN_READ_QUERY_METHOD, strandName, env.getStrandMetadata(), new Callback() { @Override public void notifySuccess(Object o) { - if (o instanceof BStream) { // stream - BStream sqlStream = (BStream) o; - balFuture.complete(Utils.createPersistRedisStreamValue(sqlStream, targetType, fields, + if (o instanceof BStream) { // stream + BStream redisStream = (BStream) o; + balFuture.complete(Utils.createPersistRedisStreamValue(redisStream, targetType, fields, includes, typeDescriptions, persistClient, null)); } else { // persist:Error balFuture.complete(Utils.createPersistRedisStreamValue(null, targetType, fields, includes, @@ -81,7 +82,7 @@ public void notifyFailure(BError bError) { typeDescriptions, persistClient, wrapError(bError))); } }, trxContextProperties, streamTypeWithIdFields, - targetTypeWithIdFields, true, fields, true, includes, true + typeMap, true, fields, true, includes, true ); return null; From 4f5dce2ea64d08f303bc72e1891d1b8c24a66455 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Tue, 30 Jan 2024 15:34:29 +0530 Subject: [PATCH 14/65] added queryOne method in RedisProcessor for records without associations --- ballerina/redis_client.bal | 67 ++++++++++++------- .../redis/datastore/RedisProcessor.java | 55 +++++++++++++++ 2 files changed, 96 insertions(+), 26 deletions(-) diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index ff4a2f8..3b0de0c 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -1,6 +1,7 @@ import ballerinax/redis; import ballerina/persist; import ballerina/io; +// import ballerina/io; # The client used by the generated persist clients to abstract and # execute Redis queries that are required to perform CRUD operations. @@ -35,25 +36,18 @@ public isolated client class RedisClient { # + include - The associations to be retrieved # + typeDescriptions - The type descriptions of the relations to be retrieved # + return - An `record{||} & readonly` containing the requested record - public isolated function runReadByKeyQuery(typedesc rowType, map typeMap, anydata key, string[] fields = [], string[] include = [], typedesc[] typeDescriptions = []) returns record{}|error { - if key is string[]{ - string recordKey = self.collectionName; - // assume the key fields are in the same order as when inserting a new record - foreach string keyField in key{ - recordKey += ":"+keyField; - } - - do { - // return check self.querySimpleFieldsByKey(recordKey, fields).cloneWithType(rowType); - record{} 'object = check self.querySimpleFieldsByKey(typeMap, recordKey, fields); - check self.getManyRelations('object, typeMap, fields, include); - self.removeUnwantedFields('object, fields); - return check 'object.cloneWithType(rowType); - } on fail error e { - return e; - } - }else{ - return error("Invalid data type for key"); + public isolated function runReadByKeyQuery(typedesc rowType, map typeMap, anydata key, string[] fields = [], string[] include = [], typedesc[] typeDescriptions = []) returns record {|anydata...;|}|error { + string recordKey = self.collectionName; + // assume the key fields are in the same order as when inserting a new record + recordKey += self.getKey(key); + do { + record{} 'object = check self.querySimpleFieldsByKey(typeMap, recordKey, fields); + // check self.getManyRelations('object, typeMap, fields, include); + self.removeUnwantedFields('object, fields); + self.removeNonExistOptionalFields('object); + return check 'object.cloneWithType(rowType); + } on fail error e { + return e; } } @@ -78,7 +72,6 @@ public isolated client class RedisClient { // check self.getManyRelations('object, fields, include); self.removeUnwantedFields('object, fields); self.removeNonExistOptionalFields('object); - result.push('object); } @@ -180,11 +173,23 @@ public isolated client class RedisClient { } } + public isolated function getKey(anydata key) returns string { + string keyValue = ""; + if key is map { + foreach string compositeKey in key.keys(){ + keyValue += ":"+key[compositeKey].toString(); + } + return keyValue; + } else { + return ":"+key.toString(); + } + } + public isolated function getKeyFields() returns string[] { return self.keyFields; } - public isolated function querySimpleFieldsByKey(map typeMap, string key, string[] fields) returns record {}|persist:Error{ + public isolated function querySimpleFieldsByKey(map typeMap, string key, string[] fields) returns record {|anydata...;|}|persist:Error{ // hadling the simple fields string[] simpleFields = self.getTargetSimpleFields(fields, typeMap); if simpleFields == [] { // then add all the fields by default @@ -201,6 +206,10 @@ public isolated client class RedisClient { do { map value = check self.dbClient->hMGet(key, simpleFields); + if self.isNoRecordFound(value) { + return error persist:Error("No "+self.entityName+" found for the given key"); + } + io:println(value); record{} valueToRecord = {}; foreach string fieldKey in value.keys() { // convert the data type from 'any' to required type @@ -272,6 +281,16 @@ public isolated client class RedisClient { } } + private isolated function isNoRecordFound(map value) returns boolean{ + boolean isNoRecordExists = true; + foreach string key in value.keys(){ + if value[key] != () { + isNoRecordExists = false; + } + } + return isNoRecordExists; + } + private isolated function removeUnwantedFields(record {} 'object, string[] fields) { string[] keyFields = self.keyFields; @@ -309,16 +328,12 @@ public isolated client class RedisClient { public isolated function objectConverter(record {} 'object, map typeMap) returns record {}|error{ foreach string key in typeMap.keys(){ if (typeMap[key] == "string" && 'object[key] != ()){ - io:println("it's the string"); 'object[key] = 'object[key].toString(); }else if(typeMap[key] == "int" && 'object[key] != ()){ - io:println("it's the int"); 'object[key] = check int:fromString('object[key].toString()); }else if(typeMap[key] == "float" && 'object[key] != ()){ - io:println("it's the float"); 'object[key] = check float:fromString('object[key].toString()); }else if(typeMap[key] == "boolean" && 'object[key] != ()){ - io:println("it's the boolean"); // 'object[key] = check boolean:fromString('object[key]); }else{ // many other data types @@ -329,4 +344,4 @@ public isolated client class RedisClient { return 'object; } -} \ No newline at end of file +} diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java index e1d271f..105308f 100644 --- a/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java @@ -6,8 +6,10 @@ import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.async.Callback; import io.ballerina.runtime.api.creators.TypeCreator; +import io.ballerina.runtime.api.types.ErrorType; import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.StreamType; +import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.values.BArray; import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; @@ -15,14 +17,18 @@ import io.ballerina.runtime.api.values.BStream; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BTypedesc; +import io.ballerina.stdlib.persist.ModuleUtils; import io.ballerina.stdlib.persist.Constants; import io.ballerina.stdlib.persist.redis.Utils; import java.util.Map; +import static io.ballerina.stdlib.persist.Constants.ERROR; import static io.ballerina.stdlib.persist.Constants.KEY_FIELDS; +import static io.ballerina.stdlib.persist.Constants.RUN_READ_BY_KEY_QUERY_METHOD; import static io.ballerina.stdlib.persist.ErrorGenerator.wrapError; import static io.ballerina.stdlib.persist.Utils.getEntity; +import static io.ballerina.stdlib.persist.Utils.getKey; import static io.ballerina.stdlib.persist.Utils.getMetadata; import static io.ballerina.stdlib.persist.Utils.getPersistClient; import static io.ballerina.stdlib.persist.Utils.getRecordTypeWithKeyFields; @@ -88,4 +94,53 @@ public void notifyFailure(BError bError) { return null; } + public static Object queryOne(Environment env, BObject client, BArray path, BTypedesc targetType) { + // This method will return `targetType|persist:Error` + + BString entity = getEntity(env); + BObject persistClient = getPersistClient(client, entity); + BArray keyFields = (BArray) persistClient.get(KEY_FIELDS); + RecordType recordType = (RecordType) targetType.getDescribingType(); + + RecordType recordTypeWithIdFields = getRecordTypeWithKeyFields(keyFields, recordType); + ErrorType persistErrorType = TypeCreator.createErrorType(ERROR, ModuleUtils.getModule()); + Type unionType = TypeCreator.createUnionType(recordTypeWithIdFields, persistErrorType); + + BArray[] metadata = getMetadata(recordType); + BArray fields = metadata[0]; + BArray includes = metadata[1]; + BArray typeDescriptions = metadata[2]; + BMap typeMap = getFieldTypes(recordType); + + Object key = getKey(env, path); + + Map trxContextProperties = getTransactionContextProperties(); + String strandName = env.getStrandName().isPresent() ? env.getStrandName().get() : null; + + Future balFuture = env.markAsync(); + env.getRuntime().invokeMethodAsyncSequentially( + // Call `RedisClient.runReadByKeyQuery( + // typedesc rowType, anydata key, string[] fields = [], string[] include = [], + // typedesc[] typeDescriptions = [] + // )` + // which returns `record {}|persist:Error` + + persistClient, RUN_READ_BY_KEY_QUERY_METHOD, strandName, env.getStrandMetadata(), + new Callback() { + @Override + public void notifySuccess(Object o) { + balFuture.complete(o); + } + + @Override + public void notifyFailure(BError bError) { + balFuture.complete(wrapError(bError)); + } + }, trxContextProperties, unionType, + targetType, true, typeMap, true, key, true, fields, true, includes, true, + typeDescriptions, true + ); + + return null; + } } From 456e26d09d6330db6e6959d2589224a15d71484b Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 31 Jan 2024 11:14:38 +0530 Subject: [PATCH 15/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 28b5208..fe5f9f7 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -11,6 +11,7 @@ distribution-version = "2201.8.2" org = "ballerina" name = "io" version = "1.4.1" +scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.value"} @@ -40,6 +41,7 @@ dependencies = [ org = "ballerina" name = "lang.value" version = "0.0.0" +scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] From 4c7c00b87f0320270169e2da688802a4d468f685 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 31 Jan 2024 11:19:13 +0530 Subject: [PATCH 16/65] bug fixes in one to many relationships in getManyRelations method --- ballerina/redis_client.bal | 156 ++++++++++-------- ballerina/stream_types.bal | 4 +- .../redis/datastore/RedisProcessor.java | 2 +- 3 files changed, 85 insertions(+), 77 deletions(-) diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index 3b0de0c..c2235d7 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -1,7 +1,5 @@ import ballerinax/redis; import ballerina/persist; -import ballerina/io; -// import ballerina/io; # The client used by the generated persist clients to abstract and # execute Redis queries that are required to perform CRUD operations. @@ -42,7 +40,7 @@ public isolated client class RedisClient { recordKey += self.getKey(key); do { record{} 'object = check self.querySimpleFieldsByKey(typeMap, recordKey, fields); - // check self.getManyRelations('object, typeMap, fields, include); + check self.getManyRelations(typeMap, 'object, fields, include); self.removeUnwantedFields('object, fields); self.removeNonExistOptionalFields('object); return check 'object.cloneWithType(rowType); @@ -53,11 +51,12 @@ public isolated client class RedisClient { # Performs a batch `HGET` operation to get entity instances as a stream # + # + rowType - The type description of the entity to be retrieved # + typeMap - The data types of the record # + fields - The fields to be retrieved # + include - The associations to be retrieved # + return - A stream of `record{||} & readonly` containing the requested records - public isolated function runReadQuery(map typeMap, string[] fields = [], string[] include = []) returns stream|persist:Error { + public isolated function runReadQuery(typedesc rowType, map typeMap, string[] fields = [], string[] include = []) returns stream|persist:Error { // Get all the keys string[]|error keys = self.dbClient->keys(self.collectionName+":*"); if keys is error { @@ -69,7 +68,6 @@ public isolated client class RedisClient { foreach string key in keys { // handling simple fields record{} 'object = check self.querySimpleFieldsByKey(typeMap, key, fields); - // check self.getManyRelations('object, fields, include); self.removeUnwantedFields('object, fields); self.removeNonExistOptionalFields('object); result.push('object); @@ -173,7 +171,11 @@ public isolated client class RedisClient { } } - public isolated function getKey(anydata key) returns string { + public isolated function getKeyFields() returns string[] { + return self.keyFields; + } + + private isolated function getKey(anydata key) returns string { string keyValue = ""; if key is map { foreach string compositeKey in key.keys(){ @@ -185,11 +187,53 @@ public isolated client class RedisClient { } } - public isolated function getKeyFields() returns string[] { - return self.keyFields; + public isolated function getManyRelations(map typeMap, record {} 'object, string[] fields, string[] include) returns persist:Error? { + foreach int i in 0 ..< include.length() { + string entity = include[i]; + string[] relationFields = from string 'field in fields + where 'field.startsWith(entity + "[].") + select 'field.substring(entity.length() + 3, 'field.length()); + + if relationFields.length() is 0 { + continue; + } + + string[]keys = check self.dbClient->keys(entity.substring(0,1).toUpperAscii()+entity.substring(1)+":*"); + + // Get data one by one using the key + record{}[] associatedRecords = []; + foreach string key in keys { + // handling simple fields + record{} valueToRecord = check self.queryRelationFieldsByKey(entity, key, relationFields); + + // check whether the record is associated with the current object + boolean isAssociated = true; + foreach string keyField in self.keyFields{ + string refField = self.entityName.substring(0,1).toLowerAscii()+self.entityName.substring(1)+keyField.substring(0,1).toUpperAscii()+keyField.substring(1); + boolean isSimilar = valueToRecord[refField] == 'object[keyField]; + if !isSimilar { + isAssociated = false; + } + } + if isAssociated { + + foreach string refField in valueToRecord.keys() { + if relationFields.indexOf(refField) is () { + _ = valueToRecord.remove(refField); + } + } + associatedRecords.push(valueToRecord); + } + + } + + 'object[entity] = associatedRecords; + } on fail var e { + return e; + } } - public isolated function querySimpleFieldsByKey(map typeMap, string key, string[] fields) returns record {|anydata...;|}|persist:Error{ + private isolated function querySimpleFieldsByKey(map typeMap, string key, string[] fields) returns record {|anydata...;|}|persist:Error{ // hadling the simple fields string[] simpleFields = self.getTargetSimpleFields(fields, typeMap); if simpleFields == [] { // then add all the fields by default @@ -209,7 +253,6 @@ public isolated client class RedisClient { if self.isNoRecordFound(value) { return error persist:Error("No "+self.entityName+" found for the given key"); } - io:println(value); record{} valueToRecord = {}; foreach string fieldKey in value.keys() { // convert the data type from 'any' to required type @@ -221,58 +264,44 @@ public isolated client class RedisClient { } } - public isolated function getTargetSimpleFields(string[] fields, map typeMap) returns string[] { - string[] simpleFields = from string 'field in fields - where !'field.includes("[].") && typeMap.hasKey('field) - select 'field; - return simpleFields; - } - - public isolated function getManyRelations(map typeMap, record {} 'object,string[] fields, string[] include) returns persist:Error? { - foreach int i in 0 ..< include.length() { - string entity = include[i]; - string[] relationFields = from string 'field in fields - where 'field.startsWith(entity + "[].") - select 'field.substring(entity.length() + 3, 'field.length()); - - if relationFields.length() is 0 { - continue; + private isolated function queryRelationFieldsByKey(string entity, string key, string[] fields) returns record {|anydata...;|}|persist:Error{ + // if the field doesn't containes reference fields + // add them here + string[] relationFields = fields.clone(); + foreach string keyField in self.keyFields { + string refField = self.entityName.substring(0,1).toLowerAscii()+self.entityName.substring(1) + +keyField.substring(0,1).toUpperAscii()+keyField.substring(1); + if relationFields.indexOf(refField) is () { + relationFields.push(refField); } + } - string[]keys = check self.dbClient->keys(entity.substring(0,1).toUpperAscii()+entity.substring(1)+":*"); - - // Get data one by one using the key - record{}[] associatedRecords = []; - foreach string key in keys { - // handling simple fields - record{} valueToRecord = check self.querySimpleFieldsByKey(typeMap, key, relationFields); - - foreach string fieldKey in valueToRecord.keys() { - // convert the data type from 'any' to required type - valueToRecord[fieldKey] = check self.dataConverter(self.fieldMetadata[entity+"[]."+fieldKey], valueToRecord[fieldKey]); - } - - // check whether the record is associated with the current object - boolean isAssociated = true; - foreach string keyField in self.keyFields{ - boolean isSimilar = valueToRecord[entity+keyField.substring(0,1).toUpperAscii()+keyField.substring(1)] == 'object[keyField]; - if !isSimilar { - isAssociated = false; - } - } - - if isAssociated { - associatedRecords.push(valueToRecord); - } - + do { + map value = check self.dbClient->hMGet(key, relationFields); + if self.isNoRecordFound(value) { + return error persist:Error("No "+self.entityName+" found for the given key"); } - 'object[entity] = associatedRecords; + record{} valueToRecord = {}; + string fieldMetadataKeyPrefix = entity+"[]."; + foreach string fieldKey in value.keys() { + // convert the data type from 'any' to required type + valueToRecord[fieldKey] = check self.dataConverter(self.fieldMetadata[fieldMetadataKeyPrefix+fieldKey], value[fieldKey]); + } + return valueToRecord; } on fail var e { return e; } } + private isolated function getTargetSimpleFields(string[] fields, map typeMap) returns string[] { + string[] simpleFields = from string 'field in fields + where !'field.includes("[].") && typeMap.hasKey('field) + select 'field; + return simpleFields; + } + + private isolated function removeNonExistOptionalFields(record {} 'object){ foreach string key in 'object.keys(){ if 'object[key] == () { @@ -301,7 +330,7 @@ public isolated client class RedisClient { } } - public isolated function dataConverter(FieldMetadata & readonly fieldMetaData, any value) returns ()|boolean|string|float|error|int { + private isolated function dataConverter(FieldMetadata & readonly fieldMetaData, any value) returns ()|boolean|string|float|error|int { // Return nil if value is nil if(value is ()){ @@ -325,23 +354,4 @@ public isolated client class RedisClient { } } - public isolated function objectConverter(record {} 'object, map typeMap) returns record {}|error{ - foreach string key in typeMap.keys(){ - if (typeMap[key] == "string" && 'object[key] != ()){ - 'object[key] = 'object[key].toString(); - }else if(typeMap[key] == "int" && 'object[key] != ()){ - 'object[key] = check int:fromString('object[key].toString()); - }else if(typeMap[key] == "float" && 'object[key] != ()){ - 'object[key] = check float:fromString('object[key].toString()); - }else if(typeMap[key] == "boolean" && 'object[key] != ()){ - // 'object[key] = check boolean:fromString('object[key]); - }else{ - // many other data types - 'object[key] = (); - } - } - - return 'object; - } - } diff --git a/ballerina/stream_types.bal b/ballerina/stream_types.bal index b0d5124..89f6d07 100644 --- a/ballerina/stream_types.bal +++ b/ballerina/stream_types.bal @@ -1,5 +1,5 @@ import ballerina/persist; -import ballerina/io; +// import ballerina/io; public class PersistRedisStream { @@ -53,9 +53,7 @@ public class PersistRedisStream { } } // return value; - io:println(value); record {|record {} value;|} nextRecord = {value: checkpanic value.cloneWithType(self.targetType)}; - // record {|record {} value;|} nextRecord = {value: value}; return nextRecord; } } else { diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java index 105308f..2fc1ca3 100644 --- a/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java @@ -88,7 +88,7 @@ public void notifyFailure(BError bError) { typeDescriptions, persistClient, wrapError(bError))); } }, trxContextProperties, streamTypeWithIdFields, - typeMap, true, fields, true, includes, true + targetType, true, typeMap, true, fields, true, includes, true ); return null; From 379cdf91d109d57a943c14998ef4c3c72908c18b Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 31 Jan 2024 17:44:50 +0530 Subject: [PATCH 17/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index fe5f9f7..28b5208 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -11,7 +11,6 @@ distribution-version = "2201.8.2" org = "ballerina" name = "io" version = "1.4.1" -scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.value"} @@ -41,7 +40,6 @@ dependencies = [ org = "ballerina" name = "lang.value" version = "0.0.0" -scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] From b2edbeec17ab0d573ed5f3358423e520a89dd171 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 31 Jan 2024 18:01:03 +0530 Subject: [PATCH 18/65] added one to many relationship --- ballerina/metadata_types.bal | 3 ++- ballerina/redis_client.bal | 45 ++++++++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/ballerina/metadata_types.bal b/ballerina/metadata_types.bal index 7aa5ff9..f5648a5 100644 --- a/ballerina/metadata_types.bal +++ b/ballerina/metadata_types.bal @@ -72,7 +72,8 @@ public enum DataType { STRING, FLOAT, BOOLEAN, - TIME + TIME, + ENUM } # Represents the types of Metadata a RQL client can hold. diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index c2235d7..eff3a50 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -1,5 +1,6 @@ import ballerinax/redis; import ballerina/persist; +import ballerina/io; # The client used by the generated persist clients to abstract and # execute Redis queries that are required to perform CRUD operations. @@ -190,9 +191,23 @@ public isolated client class RedisClient { public isolated function getManyRelations(map typeMap, record {} 'object, string[] fields, string[] include) returns persist:Error? { foreach int i in 0 ..< include.length() { string entity = include[i]; + + JoinType joinType = ONE_TO_MANY; + // checking for one to many relationships string[] relationFields = from string 'field in fields where 'field.startsWith(entity + "[].") select 'field.substring(entity.length() + 3, 'field.length()); + io:println(relationFields); + // checking for one to one relationships + if relationFields.length() == 0 { + + relationFields = from string 'field in fields + where 'field.startsWith(entity + ".") + select 'field.substring(entity.length() + 1, 'field.length()); + if relationFields.length() != 0{ + joinType = ONE_TO_ONE; + } + } if relationFields.length() is 0 { continue; @@ -204,12 +219,13 @@ public isolated client class RedisClient { record{}[] associatedRecords = []; foreach string key in keys { // handling simple fields - record{} valueToRecord = check self.queryRelationFieldsByKey(entity, key, relationFields); + record{} valueToRecord = check self.queryRelationFieldsByKey(entity, joinType, key, relationFields); // check whether the record is associated with the current object boolean isAssociated = true; foreach string keyField in self.keyFields{ - string refField = self.entityName.substring(0,1).toLowerAscii()+self.entityName.substring(1)+keyField.substring(0,1).toUpperAscii()+keyField.substring(1); + string refField = self.entityName.substring(0,1).toLowerAscii()+self.entityName.substring(1) + +keyField.substring(0,1).toUpperAscii()+keyField.substring(1); boolean isSimilar = valueToRecord[refField] == 'object[keyField]; if !isSimilar { isAssociated = false; @@ -226,8 +242,12 @@ public isolated client class RedisClient { } } - - 'object[entity] = associatedRecords; + + if(joinType == ONE_TO_ONE && associatedRecords.length() != 0){ + 'object[entity] = associatedRecords[0]; + }else{ + 'object[entity] = associatedRecords; + } } on fail var e { return e; } @@ -264,7 +284,7 @@ public isolated client class RedisClient { } } - private isolated function queryRelationFieldsByKey(string entity, string key, string[] fields) returns record {|anydata...;|}|persist:Error{ + private isolated function queryRelationFieldsByKey(string entity, JoinType joinType, string key, string[] fields) returns record {|anydata...;|}|persist:Error{ // if the field doesn't containes reference fields // add them here string[] relationFields = fields.clone(); @@ -283,9 +303,17 @@ public isolated client class RedisClient { } record{} valueToRecord = {}; - string fieldMetadataKeyPrefix = entity+"[]."; + + string fieldMetadataKeyPrefix = entity; + if(joinType == ONE_TO_MANY){ + fieldMetadataKeyPrefix += "[]."; + }else{ + fieldMetadataKeyPrefix += "."; + } + foreach string fieldKey in value.keys() { // convert the data type from 'any' to required type + io:println(fieldMetadataKeyPrefix+fieldKey); valueToRecord[fieldKey] = check self.dataConverter(self.fieldMetadata[fieldMetadataKeyPrefix+fieldKey], value[fieldKey]); } return valueToRecord; @@ -296,7 +324,7 @@ public isolated client class RedisClient { private isolated function getTargetSimpleFields(string[] fields, map typeMap) returns string[] { string[] simpleFields = from string 'field in fields - where !'field.includes("[].") && typeMap.hasKey('field) + where !'field.includes(".") && typeMap.hasKey('field) select 'field; return simpleFields; } @@ -349,6 +377,9 @@ public isolated client class RedisClient { }else if((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == BOOLEAN) || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == BOOLEAN)){ return check boolean:fromString(value); + }else if((fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == ENUM)) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == ENUM)){ + return value; }else{ return error("Unsupported Data Format"); } From 8325105d1c0ff78a692bef528feb3faa5f519579 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Thu, 1 Feb 2024 07:42:35 +0530 Subject: [PATCH 19/65] added minor changes to redis_client.bal --- ballerina/redis_client.bal | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index eff3a50..ab0bc15 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -1,6 +1,6 @@ import ballerinax/redis; import ballerina/persist; -import ballerina/io; +// import ballerina/io; # The client used by the generated persist clients to abstract and # execute Redis queries that are required to perform CRUD operations. @@ -40,8 +40,11 @@ public isolated client class RedisClient { // assume the key fields are in the same order as when inserting a new record recordKey += self.getKey(key); do { + // handling simple fields record{} 'object = check self.querySimpleFieldsByKey(typeMap, recordKey, fields); + // handling relation fields check self.getManyRelations(typeMap, 'object, fields, include); + self.removeUnwantedFields('object, fields); self.removeNonExistOptionalFields('object); return check 'object.cloneWithType(rowType); @@ -67,8 +70,9 @@ public isolated client class RedisClient { // Get data one by one using the key record{}[] result = []; foreach string key in keys { - // handling simple fields + // handling simple fields only for batch read record{} 'object = check self.querySimpleFieldsByKey(typeMap, key, fields); + self.removeUnwantedFields('object, fields); self.removeNonExistOptionalFields('object); result.push('object); @@ -110,7 +114,6 @@ public isolated client class RedisClient { } // Decide how to log queries - // logQuery("RQL insert query: ", insertQueries); if result is string { return result; } @@ -197,7 +200,6 @@ public isolated client class RedisClient { string[] relationFields = from string 'field in fields where 'field.startsWith(entity + "[].") select 'field.substring(entity.length() + 3, 'field.length()); - io:println(relationFields); // checking for one to one relationships if relationFields.length() == 0 { @@ -276,7 +278,9 @@ public isolated client class RedisClient { record{} valueToRecord = {}; foreach string fieldKey in value.keys() { // convert the data type from 'any' to required type - valueToRecord[fieldKey] = check self.dataConverter(self.fieldMetadata[fieldKey], value[fieldKey]); + valueToRecord[fieldKey] = check self.dataConverter( + self.fieldMetadata[fieldKey] + , value[fieldKey]); } return valueToRecord; } on fail var e { @@ -313,8 +317,9 @@ public isolated client class RedisClient { foreach string fieldKey in value.keys() { // convert the data type from 'any' to required type - io:println(fieldMetadataKeyPrefix+fieldKey); - valueToRecord[fieldKey] = check self.dataConverter(self.fieldMetadata[fieldMetadataKeyPrefix+fieldKey], value[fieldKey]); + valueToRecord[fieldKey] = check self.dataConverter( + self.fieldMetadata[fieldMetadataKeyPrefix+fieldKey] + , value[fieldKey]); } return valueToRecord; } on fail var e { From 769a2cf829a9525227a6a79c3e9e16b336f4e0ce Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Thu, 1 Feb 2024 08:52:47 +0530 Subject: [PATCH 20/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 28b5208..fe5f9f7 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -11,6 +11,7 @@ distribution-version = "2201.8.2" org = "ballerina" name = "io" version = "1.4.1" +scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.value"} @@ -40,6 +41,7 @@ dependencies = [ org = "ballerina" name = "lang.value" version = "0.0.0" +scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] From 80c373c95f931d77f0578a2ec126af65111e4373 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Thu, 1 Feb 2024 10:51:24 +0530 Subject: [PATCH 21/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index fe5f9f7..28b5208 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -11,7 +11,6 @@ distribution-version = "2201.8.2" org = "ballerina" name = "io" version = "1.4.1" -scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.value"} @@ -41,7 +40,6 @@ dependencies = [ org = "ballerina" name = "lang.value" version = "0.0.0" -scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] From b1382682a841542ac1a64fd88227721a7bb7970c Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Thu, 1 Feb 2024 14:30:41 +0530 Subject: [PATCH 22/65] updated constraint fail and already exist error types --- ballerina/errors.bal | 14 ++++++++ ballerina/metadata_types.bal | 28 +++++++++++++++- ballerina/redis_client.bal | 64 ++++++++++++++++++++++++++++++++++-- 3 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 ballerina/errors.bal diff --git a/ballerina/errors.bal b/ballerina/errors.bal new file mode 100644 index 0000000..d986fda --- /dev/null +++ b/ballerina/errors.bal @@ -0,0 +1,14 @@ +import ballerina/persist; + + +public type ConstraintViolationError distinct persist:ConstraintViolationError; + +# Generates a new `persist:AlreadyExistsError` with the given parameters. +# +# + entity - The name of the entity +# + refEntity - The entity is being reffered +# + return - The generated `persist:ConstraintViolationError` +public isolated function getConstraintViolationError(string entity, string refEntity) returns ConstraintViolationError { + string message = string `A relationship constrant failed between entities '${entity}' and '${refEntity}'`; + return error ConstraintViolationError(message); +} \ No newline at end of file diff --git a/ballerina/metadata_types.bal b/ballerina/metadata_types.bal index f5648a5..6234b1a 100644 --- a/ballerina/metadata_types.bal +++ b/ballerina/metadata_types.bal @@ -4,11 +4,13 @@ # + collectionName - Collection name of the entity # + fieldMetadata - Metadata of all the fields of the entity # + keyFields - Names of the identity fields +# + refMetadata - Metadata of the fields that is being reffered public type RedisMetadata record {| string entityName; string collectionName; map fieldMetadata; string[] keyFields; + map refMetadata?; |}; # Represents the metadata associated with a field from a related entity. @@ -46,8 +48,32 @@ public type RelationMetadata record {| DataType refFieldDataType; |}; +# Represents the metadata associated with relations +# Only used by the generated persist clients and `persist:RedisClient`. +# +# + entity - The name of the entity that is being joined +# + fieldName - The name of the field in the `entity` that is being joined +# + refCollection - The name of the Redis collection to be joined +# + refFields - The names of the referenced columns of the referenced table +# + joinFields - The names of the join columns +# + joinCollection - The name of the joining table used for a many-to-many relation +# + joiningRefFields - The names of the referenced columns in the joining table +# + joiningJoinFields - The names of the join columns in the joining table +# + 'type - The type of the relation +public type RefMetadata record {| + typedesc entity; + string fieldName; + string refCollection; + string[] refFields; + string[] joinFields; + string joinCollection?; + string[] joiningRefFields?; + string[] joiningJoinFields?; + JoinType 'type; +|}; + # Represents the type of the relation used in a `JOIN` operation. -# Only used by the generated persist clients and `rql:SQLClient`. +# Only used by the generated persist clients and `persist.redis:RedisClient`. # # + ONE_TO_ONE - The association type is a one-to-one association # + ONE_TO_MANY - The entity is in the 'one' side of a one-to-many association diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index ab0bc15..65a634f 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -1,5 +1,6 @@ import ballerinax/redis; import ballerina/persist; +import ballerina/io; // import ballerina/io; # The client used by the generated persist clients to abstract and @@ -12,6 +13,7 @@ public isolated client class RedisClient { private final string & readonly collectionName; private final map & readonly fieldMetadata; private final string[] & readonly keyFields; + private final map & readonly refMetadata; # Initializes the `RedisClient`. # @@ -24,6 +26,11 @@ public isolated client class RedisClient { self.fieldMetadata = metadata.fieldMetadata; self.keyFields = metadata.keyFields; self.dbClient = dbClient; + if metadata.refMetadata is map { + self.refMetadata = & readonly>metadata.refMetadata; + } else { + self.refMetadata = {}; + } } # Performs a batch `HGET` operation to get entity instances as a stream @@ -35,7 +42,7 @@ public isolated client class RedisClient { # + include - The associations to be retrieved # + typeDescriptions - The type descriptions of the relations to be retrieved # + return - An `record{||} & readonly` containing the requested record - public isolated function runReadByKeyQuery(typedesc rowType, map typeMap, anydata key, string[] fields = [], string[] include = [], typedesc[] typeDescriptions = []) returns record {|anydata...;|}|error { + public isolated function runReadByKeyQuery(typedesc rowType, map typeMap, anydata key, string[] fields = [], string[] include = [], typedesc[] typeDescriptions = []) returns record {|anydata...;|}|persist:Error { string recordKey = self.collectionName; // assume the key fields are in the same order as when inserting a new record recordKey += self.getKey(key); @@ -103,9 +110,12 @@ public isolated client class RedisClient { // check for duplicate keys withing the collection int isKeyExists = check self.dbClient->exists([self.collectionName+key]); if isKeyExists != 0 { - return persist:getAlreadyExistsError(self.collectionName, key); + return persist:getAlreadyExistsError(self.collectionName, self.collectionName+key); } + // check for any relation field constraints + check self.checkRelationFieldConstraints(insertRecord); + // inserting the object result = self.dbClient->hMSet(self.collectionName+key, insertRecord); if result is error{ @@ -273,7 +283,7 @@ public isolated client class RedisClient { map value = check self.dbClient->hMGet(key, simpleFields); if self.isNoRecordFound(value) { - return error persist:Error("No "+self.entityName+" found for the given key"); + return persist:getNotFoundError(self.entityName, key); } record{} valueToRecord = {}; foreach string fieldKey in value.keys() { @@ -363,6 +373,54 @@ public isolated client class RedisClient { } } + private isolated function checkRelationFieldConstraints(record {} insertRecord) returns persist:Error? { + + // check if refMetaData has mappings + // if a mapping exist + // if the 'type is MANY_TO_ONE ingore that. + // if the 'type is ONE_TO_MANY OR ONE_TO_ONE and the joinField exist in the record + // verify whether refered collection has a record with that key. + // if not return foreignKeyFail error + io:println("checking constraints"); + if self.refMetadata != {} { + foreach RefMetadata & readonly refMetadataValue in self.refMetadata { + // if the entity is not the relation owner + if refMetadataValue.joinFields == self.keyFields { + continue; + } + + boolean isRelationConstraintFalied = true; + io:println(refMetadataValue.'type); + //if the mandatory reference field is not exist or being null + foreach string joinField in refMetadataValue.joinFields { + if (insertRecord.hasKey(joinField) && insertRecord[joinField] != ()) { + + isRelationConstraintFalied = false; + break; + } + } + + if !isRelationConstraintFalied { + // generate the key to reference record + string refRecordKey = refMetadataValue.refCollection; + foreach string joinField in refMetadataValue.joinFields { + refRecordKey += ":"+insertRecord[joinField].toString(); + } + io:println(refRecordKey); + + map value = check self.dbClient->hMGet(refRecordKey, refMetadataValue.refFields); + io:println(value); + if self.isNoRecordFound(value) { + io:println("this should throw a foreign key constraint fail"); + return getConstraintViolationError(self.entityName, refMetadataValue.refCollection); + } + } + } on fail var e { + return e; + } + } + } + private isolated function dataConverter(FieldMetadata & readonly fieldMetaData, any value) returns ()|boolean|string|float|error|int { // Return nil if value is nil From d85d7f6dcf69a791de6d54ae01e0cba065c37508 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Fri, 2 Feb 2024 10:54:20 +0530 Subject: [PATCH 23/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 28b5208..fe5f9f7 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -11,6 +11,7 @@ distribution-version = "2201.8.2" org = "ballerina" name = "io" version = "1.4.1" +scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.value"} @@ -40,6 +41,7 @@ dependencies = [ org = "ballerina" name = "lang.value" version = "0.0.0" +scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] From 94f42bf5d37a10707309b789fe8913c9aa6a38f4 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Fri, 2 Feb 2024 11:03:15 +0530 Subject: [PATCH 24/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index fe5f9f7..28b5208 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -11,7 +11,6 @@ distribution-version = "2201.8.2" org = "ballerina" name = "io" version = "1.4.1" -scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.value"} @@ -41,7 +40,6 @@ dependencies = [ org = "ballerina" name = "lang.value" version = "0.0.0" -scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] From 9edb544b8a37da0ce77ac5f670b526e1e3e9c54f Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Fri, 2 Feb 2024 15:24:54 +0530 Subject: [PATCH 25/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 28b5208..fe5f9f7 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -11,6 +11,7 @@ distribution-version = "2201.8.2" org = "ballerina" name = "io" version = "1.4.1" +scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.value"} @@ -40,6 +41,7 @@ dependencies = [ org = "ballerina" name = "lang.value" version = "0.0.0" +scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] From 48156e9b85eb62c82fdef96ae6e644e67626211f Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Fri, 2 Feb 2024 20:56:14 +0530 Subject: [PATCH 26/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 46 ------------------------------------- 1 file changed, 46 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index fe5f9f7..fa1a2d7 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,19 +7,6 @@ dependencies-toml-version = "2" distribution-version = "2201.8.2" -[[package]] -org = "ballerina" -name = "io" -version = "1.4.1" -scope = "testOnly" -dependencies = [ - {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "lang.value"} -] -modules = [ - {org = "ballerina", packageName = "io", moduleName = "io"} -] - [[package]] org = "ballerina" name = "jballerina.java" @@ -28,24 +15,6 @@ modules = [ {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} ] -[[package]] -org = "ballerina" -name = "lang.error" -version = "0.0.0" -scope = "testOnly" -dependencies = [ - {org = "ballerina", name = "jballerina.java"} -] - -[[package]] -org = "ballerina" -name = "lang.value" -version = "0.0.0" -scope = "testOnly" -dependencies = [ - {org = "ballerina", name = "jballerina.java"} -] - [[package]] org = "ballerina" name = "persist" @@ -57,28 +26,13 @@ modules = [ {org = "ballerina", packageName = "persist", moduleName = "persist"} ] -[[package]] -org = "ballerina" -name = "test" -version = "0.0.0" -scope = "testOnly" -dependencies = [ - {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "lang.error"} -] -modules = [ - {org = "ballerina", packageName = "test", moduleName = "test"} -] - [[package]] org = "ballerinax" name = "persist.redis" version = "1.2.1" dependencies = [ - {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, - {org = "ballerina", name = "test"}, {org = "ballerinax", name = "redis"} ] modules = [ From 0ba124dadf2081d3a913121c1b5858b0d4d26933 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Fri, 2 Feb 2024 20:57:11 +0530 Subject: [PATCH 27/65] added one to one relationship constraints on non owner of the relationship --- ballerina/ballerina.bal | 10 - ballerina/metadata_types.bal | 44 +-- ballerina/redis_client.bal | 251 +++++++++++------- ballerina/stream_types.bal | 2 - ballerina/tests/lib_test.bal | 34 --- .../ballerina/stdlib/persist/redis/Utils.java | 6 +- .../redis/datastore/RedisProcessor.java | 4 +- 7 files changed, 190 insertions(+), 161 deletions(-) delete mode 100644 ballerina/ballerina.bal delete mode 100644 ballerina/tests/lib_test.bal diff --git a/ballerina/ballerina.bal b/ballerina/ballerina.bal deleted file mode 100644 index 7a0681e..0000000 --- a/ballerina/ballerina.bal +++ /dev/null @@ -1,10 +0,0 @@ -# Returns the string `Hello` with the input string name. -# -# + name - name as a string -# + return - "Hello, " with the input string name -public function hello(string name) returns string { - if !(name is "") { - return "Hello, " + name; - } - return "Hello, World!"; -} diff --git a/ballerina/metadata_types.bal b/ballerina/metadata_types.bal index 6234b1a..a6c2440 100644 --- a/ballerina/metadata_types.bal +++ b/ballerina/metadata_types.bal @@ -30,16 +30,16 @@ public type SimpleFieldMetadata record {| |}; # Represents the metadata associated with a field of an entity. -# Only used by the generated persist clients and `persist:RQLClient`. +# Only used by the generated persist clients and `persist:RedisClient`. # public type FieldMetadata SimpleFieldMetadata|EntityFieldMetadata; # Represents the metadata associated with a relation. -# Only used by the generated persist clients and `persist:RQLClient`. +# Only used by the generated persist clients and `persist:RedisClient`. # # + entityName - The name of the entity represented in the relation -# + refField - The name of the referenced field in the Redis document -# + refFieldDataType - The data type of the object field to which the referenced field in +# + refField - The name of the refered field in the Redis document +# + refFieldDataType - The data type of the object field to which the refered field in # Redis document is mapped public type RelationMetadata record {| @@ -54,11 +54,11 @@ public type RelationMetadata record {| # + entity - The name of the entity that is being joined # + fieldName - The name of the field in the `entity` that is being joined # + refCollection - The name of the Redis collection to be joined -# + refFields - The names of the referenced columns of the referenced table -# + joinFields - The names of the join columns -# + joinCollection - The name of the joining table used for a many-to-many relation -# + joiningRefFields - The names of the referenced columns in the joining table -# + joiningJoinFields - The names of the join columns in the joining table +# + refFields - The names of the fields of the refered collection +# + joinFields - The names of the join fields +# + joinCollection - The name of the joining collection used for a many-to-many relation +# + joiningRefFields - The names of the refered fields in the joining collection +# + joiningJoinFields - The names of the join fields in the joining collection # + 'type - The type of the relation public type RefMetadata record {| typedesc entity; @@ -69,16 +69,16 @@ public type RefMetadata record {| string joinCollection?; string[] joiningRefFields?; string[] joiningJoinFields?; - JoinType 'type; + CardinalityType 'type; |}; -# Represents the type of the relation used in a `JOIN` operation. -# Only used by the generated persist clients and `persist.redis:RedisClient`. +# Represents the cardinality of the relationship +# Only used by the generated persist clients and `persist:RedisClient`. # # + ONE_TO_ONE - The association type is a one-to-one association # + ONE_TO_MANY - The entity is in the 'one' side of a one-to-many association # + MANY_TO_ONE - The entity is in the 'many' side of a one-to-many association -public enum JoinType { +public enum CardinalityType { ONE_TO_ONE, ONE_TO_MANY, MANY_TO_ONE @@ -86,7 +86,7 @@ public enum JoinType { # Represents the type of the field data. -# Only used by the generated persist clients and `rql:RQLClient`. +# Only used by the generated persist clients and `persist:RedisClient`. # # + INT - `int` type # + STRING - `string` type @@ -102,8 +102,20 @@ public enum DataType { ENUM } -# Represents the types of Metadata a RQL client can hold. -# Only used by the generated persist clients and `rql:RQLClient`. +# Represents the type of the redis supported data structures. +# Only used by the generated persist clients and `persist:RedisClient`. +# +# + REDIS_STRING - `string` type +# + REDIS_SET - `set` type +# + REDIS_HASH - `hash` type +public enum RedisDataType { + REDIS_STRING = "string", + REDIS_SET = "set", + REDIS_HASH = "hash" +} + +# Represents the types of Metadata in a `persist:RedisClient`. +# Only used by the generated persist clients and `persist:RedisClient`. # # + FIELD_DATA_TYPE - `int` type # + RELATION - `string` type diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index 65a634f..ceff810 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -1,10 +1,8 @@ import ballerinax/redis; import ballerina/persist; -import ballerina/io; -// import ballerina/io; # The client used by the generated persist clients to abstract and -# execute Redis queries that are required to perform CRUD operations. +# execute Redis database operations that are required to perform CRUD operations. public isolated client class RedisClient { private final redis:Client dbClient; @@ -17,7 +15,7 @@ public isolated client class RedisClient { # Initializes the `RedisClient`. # - # + dbClient - The `redis:Client`, which is used to execute Redis queries + # + dbClient - The `redis:Client`, which is used to execute Redis database operations. # + metadata - Metadata of the entity # + return - A `persist:Error` if the client creation fails public isolated function init(redis:Client dbClient, RedisMetadata & readonly metadata) returns persist:Error? { @@ -41,15 +39,16 @@ public isolated client class RedisClient { # + fields - The fields to be retrieved # + include - The associations to be retrieved # + typeDescriptions - The type descriptions of the relations to be retrieved - # + return - An `record{||} & readonly` containing the requested record + # + return - An `record {|anydata...;|}` containing the requested record + # or a `persist:Error` if the operation fails public isolated function runReadByKeyQuery(typedesc rowType, map typeMap, anydata key, string[] fields = [], string[] include = [], typedesc[] typeDescriptions = []) returns record {|anydata...;|}|persist:Error { string recordKey = self.collectionName; - // assume the key fields are in the same order as when inserting a new record + // Assuming the key fields are ordered recordKey += self.getKey(key); do { - // handling simple fields + // Handling simple fields record{} 'object = check self.querySimpleFieldsByKey(typeMap, recordKey, fields); - // handling relation fields + // Handling relation fields check self.getManyRelations(typeMap, 'object, fields, include); self.removeUnwantedFields('object, fields); @@ -66,7 +65,8 @@ public isolated client class RedisClient { # + typeMap - The data types of the record # + fields - The fields to be retrieved # + include - The associations to be retrieved - # + return - A stream of `record{||} & readonly` containing the requested records + # + return - A stream of `stream` containing the requested records + # or a `persist:Error` if the operation fails public isolated function runReadQuery(typedesc rowType, map typeMap, string[] fields = [], string[] include = []) returns stream|persist:Error { // Get all the keys string[]|error keys = self.dbClient->keys(self.collectionName+":*"); @@ -77,13 +77,24 @@ public isolated client class RedisClient { // Get data one by one using the key record{}[] result = []; foreach string key in keys { - // handling simple fields only for batch read + + // Verifying the key belongs to a hash + string redisType = check self.dbClient->redisType(key); + if redisType != REDIS_HASH { + continue; + } + + // Handling simple fields only for batch read record{} 'object = check self.querySimpleFieldsByKey(typeMap, key, fields); + // check self.getManyRelations(typeMap, 'object, fields, include); self.removeUnwantedFields('object, fields); self.removeNonExistOptionalFields('object); result.push('object); + // result.push(check 'object.cloneWithType(rowType)); + } on fail var e { + return e; } return stream from record{} rec in result select rec; @@ -92,38 +103,41 @@ public isolated client class RedisClient { # Performs a batch `HMSET` operation to insert entity instances into a collection. # # + insertRecords - The entity records to be inserted into the collection - # + return - A `string` containing the information of the query execution + # + return - A `string` containing the information of the database operation execution # or a `persist:Error` if the operation fails - public isolated function runBatchInsertQuery(record {}[] insertRecords) returns string|persist:Error|error { + public isolated function runBatchInsertQuery(record {}[] insertRecords) returns string|persist:Error { string|error result; - // for each record do HMSET + // For each record, do HMSET foreach var insertRecord in insertRecords { - // Create the key + // Generate the key string key = ""; foreach string keyField in self.keyFields { - key = key + ":" + insertRecord[keyField].toString(); // get the key field value by member access method. + key = key + ":" + insertRecord[keyField].toString(); } - // check for duplicate keys withing the collection + // Check for duplicate keys withing the collection int isKeyExists = check self.dbClient->exists([self.collectionName+key]); if isKeyExists != 0 { return persist:getAlreadyExistsError(self.collectionName, self.collectionName+key); } - // check for any relation field constraints - check self.checkRelationFieldConstraints(insertRecord); + // Check for any relation field constraints + check self.checkRelationFieldConstraints(key, insertRecord); - // inserting the object + // Insert the object result = self.dbClient->hMSet(self.collectionName+key, insertRecord); if result is error{ return error persist:Error(result.message()); } + } on fail var e { + return e; } // Decide how to log queries + if result is string { return result; } @@ -133,8 +147,10 @@ public isolated client class RedisClient { # Performs redis `DEL` operation to delete an entity record from the database. # # + keyFieldValues - The ordered keys used to delete an entity record - # + return - `()` if the operation is performed successfully or a `persist:Error` if the operation fails - public isolated function runDeleteQuery(any [] keyFieldValues) returns persist:Error?|error { + # + return - `()` if the operation is performed successfully + # or a `persist:Error` if the operation fails + public isolated function runDeleteQuery(any [] keyFieldValues) returns persist:Error? { + // Validate fields if (keyFieldValues.length() != self.keyFields.length()){ return error("Missing keyfields"); @@ -146,8 +162,12 @@ public isolated client class RedisClient { recordKey += ":"+value.toString(); } - // Delete the record - _ = check self.dbClient->del([recordKey]); + do { + // Delete the record + _ = check self.dbClient->del([recordKey]); + } on fail var e { + return e; + } } # Performs redis `HSET` operation to delete an entity record from the database. @@ -156,6 +176,7 @@ public isolated client class RedisClient { # + updateRecord - The new record to be updated # + return - An Error if the new record is missing a keyfield public isolated function runUpdateQuery(any [] keyFieldValues, record {} updateRecord) returns error? { + // Validate fields if (keyFieldValues.length() != self.keyFields.length()){ return error("Missing keyfields"); @@ -167,57 +188,52 @@ public isolated client class RedisClient { key += ":"+keyFieldValue.toString(); } - // decide on how to update only the given fields that is not equals to () + // Update only the given fields that is not nil foreach [string, FieldMetadata & readonly] metaDataEntry in self.fieldMetadata.entries() { FieldMetadata & readonly fieldMetadataValue = metaDataEntry[1]; - // if the field is a simple field + // If the field is a simple field if(fieldMetadataValue is SimpleFieldMetadata){ if (updateRecord.hasKey(fieldMetadataValue.fieldName) && updateRecord[fieldMetadataValue.fieldName] != ()){ // updating the object _ = check self.dbClient->hSet(key, fieldMetadataValue.fieldName, updateRecord[fieldMetadataValue.fieldName].toString()); } } - // if the field is a relation field - else{ - - } - } - } - public isolated function getKeyFields() returns string[] { - return self.keyFields; - } + // If the field is a relation field + else{ - private isolated function getKey(anydata key) returns string { - string keyValue = ""; - if key is map { - foreach string compositeKey in key.keys(){ - keyValue += ":"+key[compositeKey].toString(); } - return keyValue; - } else { - return ":"+key.toString(); } } + # Retrieves all the associations of a given object + # + # + typeMap - The data types of the record + # + object - The object of the interest + # + fields - The fields to be retrieved + # + include - The associations to be retrieved + # + return - A `persist:Error` if the operation fails public isolated function getManyRelations(map typeMap, record {} 'object, string[] fields, string[] include) returns persist:Error? { + foreach int i in 0 ..< include.length() { string entity = include[i]; + CardinalityType cardinalityType = ONE_TO_MANY; - JoinType joinType = ONE_TO_MANY; // checking for one to many relationships string[] relationFields = from string 'field in fields where 'field.startsWith(entity + "[].") select 'field.substring(entity.length() + 3, 'field.length()); + // checking for one to one relationships if relationFields.length() == 0 { relationFields = from string 'field in fields where 'field.startsWith(entity + ".") select 'field.substring(entity.length() + 1, 'field.length()); + if relationFields.length() != 0{ - joinType = ONE_TO_ONE; + cardinalityType = ONE_TO_ONE; } } @@ -225,15 +241,23 @@ public isolated client class RedisClient { continue; } - string[]keys = check self.dbClient->keys(entity.substring(0,1).toUpperAscii()+entity.substring(1)+":*"); + string[]|error keys = self.dbClient->keys(entity.substring(0,1).toUpperAscii()+entity.substring(1)+":*"); + if keys is error || (keys.length() == 0) { + if cardinalityType == ONE_TO_MANY{ + 'object[entity] = []; + } else { + 'object[entity] = {}; + } + continue; + } // Get data one by one using the key record{}[] associatedRecords = []; foreach string key in keys { - // handling simple fields - record{} valueToRecord = check self.queryRelationFieldsByKey(entity, joinType, key, relationFields); + // Handling simple fields + record{} valueToRecord = check self.queryRelationFieldsByKey(entity, cardinalityType, key, relationFields); - // check whether the record is associated with the current object + // Check whether the record is associated with the current object boolean isAssociated = true; foreach string keyField in self.keyFields{ string refField = self.entityName.substring(0,1).toLowerAscii()+self.entityName.substring(1) @@ -243,6 +267,7 @@ public isolated client class RedisClient { isAssociated = false; } } + if isAssociated { foreach string refField in valueToRecord.keys() { @@ -255,24 +280,46 @@ public isolated client class RedisClient { } - if(joinType == ONE_TO_ONE && associatedRecords.length() != 0){ - 'object[entity] = associatedRecords[0]; - }else{ - 'object[entity] = associatedRecords; + if associatedRecords.length() > 0 { + if cardinalityType == ONE_TO_ONE { + 'object[entity] = associatedRecords[0]; + }else { + 'object[entity] = associatedRecords; + } } + } on fail var e { return e; } } - private isolated function querySimpleFieldsByKey(map typeMap, string key, string[] fields) returns record {|anydata...;|}|persist:Error{ + // Private helper methods + public isolated function getKeyFields() returns string[] { + return self.keyFields; + } + + private isolated function getKey(anydata key) returns string { + string keyValue = ""; + if key is map { + foreach string compositeKey in key.keys(){ + keyValue += ":"+key[compositeKey].toString(); + } + return keyValue; + } else { + return ":"+key.toString(); + } + } + + private isolated function querySimpleFieldsByKey(map typeMap, string key, string[] fields) returns record {|anydata...;|}|error{ + // hadling the simple fields string[] simpleFields = self.getTargetSimpleFields(fields, typeMap); - if simpleFields == [] { // then add all the fields by default + // If no simpleFields given, then add all the fields by default + if simpleFields == [] { foreach [string, FieldMetadata & readonly] metaDataEntry in self.fieldMetadata.entries() { FieldMetadata & readonly fieldMetadataValue = metaDataEntry[1]; - // if the field is a simple field + // If the field is a simple field if(fieldMetadataValue is SimpleFieldMetadata){ simpleFields.push(fieldMetadataValue.fieldName); } @@ -280,11 +327,14 @@ public isolated client class RedisClient { } do { - + + // Retrieve the record map value = check self.dbClient->hMGet(key, simpleFields); + if self.isNoRecordFound(value) { return persist:getNotFoundError(self.entityName, key); } + record{} valueToRecord = {}; foreach string fieldKey in value.keys() { // convert the data type from 'any' to required type @@ -298,9 +348,9 @@ public isolated client class RedisClient { } } - private isolated function queryRelationFieldsByKey(string entity, JoinType joinType, string key, string[] fields) returns record {|anydata...;|}|persist:Error{ - // if the field doesn't containes reference fields - // add them here + private isolated function queryRelationFieldsByKey(string entity, CardinalityType cardinalityType, string key, string[] fields) returns record {|anydata...;|}|persist:Error{ + + // If the field doesn't containes reference fields, add them here string[] relationFields = fields.clone(); foreach string keyField in self.keyFields { string refField = self.entityName.substring(0,1).toLowerAscii()+self.entityName.substring(1) @@ -311,15 +361,16 @@ public isolated client class RedisClient { } do { + // Retrieve related records map value = check self.dbClient->hMGet(key, relationFields); if self.isNoRecordFound(value) { - return error persist:Error("No "+self.entityName+" found for the given key"); + return error persist:Error("No `"+self.entityName+"` found for the given key"); } record{} valueToRecord = {}; string fieldMetadataKeyPrefix = entity; - if(joinType == ONE_TO_MANY){ + if(cardinalityType == ONE_TO_MANY){ fieldMetadataKeyPrefix += "[]."; }else{ fieldMetadataKeyPrefix += "."; @@ -373,52 +424,57 @@ public isolated client class RedisClient { } } - private isolated function checkRelationFieldConstraints(record {} insertRecord) returns persist:Error? { + private isolated function checkRelationFieldConstraints(string key, record {} insertRecord) returns persist:Error? { - // check if refMetaData has mappings - // if a mapping exist - // if the 'type is MANY_TO_ONE ingore that. - // if the 'type is ONE_TO_MANY OR ONE_TO_ONE and the joinField exist in the record - // verify whether refered collection has a record with that key. - // if not return foreignKeyFail error - io:println("checking constraints"); if self.refMetadata != {} { - foreach RefMetadata & readonly refMetadataValue in self.refMetadata { - // if the entity is not the relation owner - if refMetadataValue.joinFields == self.keyFields { - continue; - } + foreach RefMetadata & readonly refMetadataValue in self.refMetadata { + // If the entity is not the relation owner + if refMetadataValue.joinFields == self.keyFields { + continue; + } - boolean isRelationConstraintFalied = true; - io:println(refMetadataValue.'type); - //if the mandatory reference field is not exist or being null - foreach string joinField in refMetadataValue.joinFields { - if (insertRecord.hasKey(joinField) && insertRecord[joinField] != ()) { + boolean isRelationConstraintFalied = true; + // If the mandatory reference field is not exist or being null + foreach string joinField in refMetadataValue.joinFields { + if (insertRecord.hasKey(joinField) && insertRecord[joinField] != ()) { - isRelationConstraintFalied = false; - break; - } + isRelationConstraintFalied = false; + break; } + } - if !isRelationConstraintFalied { - // generate the key to reference record - string refRecordKey = refMetadataValue.refCollection; - foreach string joinField in refMetadataValue.joinFields { - refRecordKey += ":"+insertRecord[joinField].toString(); - } - io:println(refRecordKey); + if !isRelationConstraintFalied { - map value = check self.dbClient->hMGet(refRecordKey, refMetadataValue.refFields); - io:println(value); - if self.isNoRecordFound(value) { - io:println("this should throw a foreign key constraint fail"); + // Generate the key to reference record + string refRecordKey = refMetadataValue.refCollection; + foreach string joinField in refMetadataValue.joinFields { + refRecordKey += ":"+insertRecord[joinField].toString(); + } + + // Check the cardinality of refered entity object + int|error sCard = self.dbClient->sCard(refRecordKey+":"+self.collectionName); + if sCard is int { + if sCard > 0 && refMetadataValue.'type == ONE_TO_ONE { + // If the refered object is already in a relationship return getConstraintViolationError(self.entityName, refMetadataValue.refCollection); } } - } on fail var e { - return e; + + // Relate current record with the refered record in the database + int|error sAdd = self.dbClient->sAdd(refRecordKey+":"+self.collectionName, [key]); + if sAdd is error { + return sAdd; + } + + map value = check self.dbClient->hMGet(refRecordKey, refMetadataValue.refFields); + if self.isNoRecordFound(value) { + return getConstraintViolationError(self.entityName, refMetadataValue.refCollection); + } } + } on fail var e { + return e; } + } } private isolated function dataConverter(FieldMetadata & readonly fieldMetaData, any value) returns ()|boolean|string|float|error|int { @@ -431,18 +487,23 @@ public isolated client class RedisClient { if((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == INT) || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == INT)){ return check int:fromString(value); + }else if((fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == STRING)) || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == STRING)){ return value; + }else if((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == FLOAT) || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == FLOAT)){ return check float:fromString(value); + }else if((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == BOOLEAN) || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == BOOLEAN)){ return check boolean:fromString(value); + }else if((fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == ENUM)) || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == ENUM)){ return value; + }else{ return error("Unsupported Data Format"); } diff --git a/ballerina/stream_types.bal b/ballerina/stream_types.bal index 89f6d07..f7f5069 100644 --- a/ballerina/stream_types.bal +++ b/ballerina/stream_types.bal @@ -1,5 +1,4 @@ import ballerina/persist; -// import ballerina/io; public class PersistRedisStream { @@ -52,7 +51,6 @@ public class PersistRedisStream { _ = value.remove(keyField); } } - // return value; record {|record {} value;|} nextRecord = {value: checkpanic value.cloneWithType(self.targetType)}; return nextRecord; } diff --git a/ballerina/tests/lib_test.bal b/ballerina/tests/lib_test.bal deleted file mode 100644 index 31ce10d..0000000 --- a/ballerina/tests/lib_test.bal +++ /dev/null @@ -1,34 +0,0 @@ -import ballerina/io; -import ballerina/test; - -// Before Suite Function - -@test:BeforeSuite -function beforeSuiteFunc() { - io:println("I'm the before suite function!"); -} - -// Test function - -@test:Config {} -function testFunction() { - string name = "John"; - string welcomeMsg = hello(name); - test:assertEquals("Hello, John", welcomeMsg); -} - -// Negative Test function - -@test:Config {} -function negativeTestFunction() { - string name = ""; - string welcomeMsg = hello(name); - test:assertEquals("Hello, World!", welcomeMsg); -} - -// After Suite Function - -@test:AfterSuite -function afterSuiteFunc() { - io:println("I'm the after suite function!"); -} diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java index 1d6264a..69640f6 100644 --- a/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java @@ -47,7 +47,8 @@ public static BMap getFieldTypes(RecordType recordType) { return typeMap; } - private static BObject createPersistRedisStream(BStream redisStream, BTypedesc targetType, BMap typeMap, BArray fields, + private static BObject createPersistRedisStream(BStream redisStream, + BTypedesc targetType, BMap typeMap, BArray fields, BArray includes, BArray typeDescriptions, BObject persistClient, BError persistError) { return ValueCreator.createObjectValue(getModule(), PERSIST_REDIS_STREAM, @@ -64,7 +65,8 @@ private static BStream createPersistRedisStreamValue(BTypedesc targetType, BObje public static BStream createPersistRedisStreamValue(BStream redisStream, BTypedesc targetType, BArray fields, BArray includes, BArray typeDescriptions, BObject persistClient, BError persistError) { - BObject persistRedisStream = createPersistRedisStream(redisStream, targetType, Utils.getFieldTypes((RecordType) targetType.getDescribingType()), fields, includes, + BObject persistRedisStream = createPersistRedisStream(redisStream, targetType, + Utils.getFieldTypes((RecordType) targetType.getDescribingType()), fields, includes, typeDescriptions, persistClient, persistError); return createPersistRedisStreamValue(targetType, persistRedisStream); } diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java index 2fc1ca3..ca19f08 100644 --- a/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java @@ -17,8 +17,8 @@ import io.ballerina.runtime.api.values.BStream; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BTypedesc; -import io.ballerina.stdlib.persist.ModuleUtils; import io.ballerina.stdlib.persist.Constants; +import io.ballerina.stdlib.persist.ModuleUtils; import io.ballerina.stdlib.persist.redis.Utils; import java.util.Map; @@ -65,7 +65,7 @@ public static BStream query(Environment env, BObject client, BTypedesc targetTyp Future balFuture = env.markAsync(); env.getRuntime().invokeMethodAsyncSequentially( // Call `RedisClient.runReadQuery( - // map typeMap, string[] fields = [], string[] include = [] + // typedesc rowType, map typeMap, string[] fields = [], string[] include = [] // )` // which returns `stream|persist:Error` From 637e8c4ab2f39c6e3b62d10be13c0d403636bfac Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Tue, 6 Feb 2024 11:40:08 +0530 Subject: [PATCH 28/65] [Automated] Update native jar versions in toml files --- ballerina/Ballerina.toml | 6 +++--- ballerina/Dependencies.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 4f0e6d2..befd511 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerinax" name = "persist.redis" -version = "1.2.1" +version = "0.1.0" authors = ["Ballerina"] keywords = ["persist", "redis"] repository = "https://github.com/ballerina-platform/module-ballerinax-persist.redis" @@ -14,8 +14,8 @@ graalvmCompatible = true [[platform.java17.dependency]] groupId = "io.ballerina.persist" artifactId = "persist-redis-native" -version = "1.2.1" -path = "../native/build/libs/persist.redis-native-1.2.1-SNAPSHOT.jar" +version = "0.1.0" +path = "../native/build/libs/persist.redis-native-0.1.0-SNAPSHOT.jar" [[platform.java17.dependency]] groupId = "io.ballerina.stdlib" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index fa1a2d7..1c2c7c7 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -29,7 +29,7 @@ modules = [ [[package]] org = "ballerinax" name = "persist.redis" -version = "1.2.1" +version = "0.1.0" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, From 68a65027d320e449e675260552b38320af5fed5a Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Tue, 6 Feb 2024 13:22:35 +0530 Subject: [PATCH 29/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 1c2c7c7..64001f7 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,6 +7,18 @@ dependencies-toml-version = "2" distribution-version = "2201.8.2" +[[package]] +org = "ballerina" +name = "io" +version = "1.4.1" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + [[package]] org = "ballerina" name = "jballerina.java" @@ -15,6 +27,14 @@ modules = [ {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "persist" @@ -31,6 +51,7 @@ org = "ballerinax" name = "persist.redis" version = "0.1.0" dependencies = [ + {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, {org = "ballerinax", name = "redis"} From fe0d23551c148eec5a8c437b9608dcca6cbdfb16 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Tue, 6 Feb 2024 13:41:17 +0530 Subject: [PATCH 30/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 64001f7..1c2c7c7 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,18 +7,6 @@ dependencies-toml-version = "2" distribution-version = "2201.8.2" -[[package]] -org = "ballerina" -name = "io" -version = "1.4.1" -dependencies = [ - {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "lang.value"} -] -modules = [ - {org = "ballerina", packageName = "io", moduleName = "io"} -] - [[package]] org = "ballerina" name = "jballerina.java" @@ -27,14 +15,6 @@ modules = [ {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} ] -[[package]] -org = "ballerina" -name = "lang.value" -version = "0.0.0" -dependencies = [ - {org = "ballerina", name = "jballerina.java"} -] - [[package]] org = "ballerina" name = "persist" @@ -51,7 +31,6 @@ org = "ballerinax" name = "persist.redis" version = "0.1.0" dependencies = [ - {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, {org = "ballerinax", name = "redis"} From 39a7dbd3a385b9fb4a5e3200fcbe290f4fde85ea Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Tue, 6 Feb 2024 13:54:00 +0530 Subject: [PATCH 31/65] [Automated] Update native jar versions in toml files --- ballerina/Ballerina.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index befd511..4e89a00 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -3,7 +3,7 @@ org = "ballerinax" name = "persist.redis" version = "0.1.0" authors = ["Ballerina"] -keywords = ["persist", "redis"] +keywords = ["persist", "redis", "experimental"] repository = "https://github.com/ballerina-platform/module-ballerinax-persist.redis" license = ["Apache-2.0"] distribution = "2201.8.0" From 3a7997fe92563de0a29e6613834bc0b6a4a945f3 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Tue, 6 Feb 2024 14:10:22 +0530 Subject: [PATCH 32/65] fixed the issues related to licensing and code formatting --- ballerina/.gitignore | 2 +- ballerina/Module.md | 50 +++++- ballerina/Package.md | 57 ++++++- ballerina/build.gradle | 16 ++ ballerina/constants.bal | 19 +++ ballerina/errors.bal | 21 ++- ballerina/init.bal | 18 ++- ballerina/metadata_types.bal | 18 ++- ballerina/redis_client.bal | 142 +++++++++--------- ballerina/stream_types.bal | 19 ++- build-config/resources/Ballerina.toml | 2 +- build.gradle | 23 ++- gradle.properties | 4 +- native/build.gradle | 15 ++ .../stdlib/persist/redis/Constants.java | 18 +++ .../stdlib/persist/redis/ModuleUtils.java | 18 +++ .../ballerina/stdlib/persist/redis/Utils.java | 20 ++- .../redis/datastore/RedisProcessor.java | 17 +++ native/src/main/java/module-info.java | 20 ++- settings.gradle | 2 +- 20 files changed, 396 insertions(+), 105 deletions(-) create mode 100644 ballerina/constants.bal diff --git a/ballerina/.gitignore b/ballerina/.gitignore index 1ded4a8..39c9ccc 100644 --- a/ballerina/.gitignore +++ b/ballerina/.gitignore @@ -1,4 +1,4 @@ target generated Config.toml -.devcontainer.json \ No newline at end of file +.devcontainer.json diff --git a/ballerina/Module.md b/ballerina/Module.md index 8a69f51..afd083c 100644 --- a/ballerina/Module.md +++ b/ballerina/Module.md @@ -1,6 +1,46 @@ -Prints "Hello, World!" with a main function. -[//]: # (above is the module summary) - # Module Overview -Provides an overview about the module when generating the API documentations. -For example, refer to https://lib.ballerina.io/ballerina/io/latest + +This module provide redis database support for the `bal persist` feature, which provides functionality to store and query data from a redis database through a data model instead of writing redis commands. + +Since redis is not the default datastore for `bal persist` you need to explicitly specify the data store when initializing `bal persist` in your application. as follows, + +``` +$ bal persist init --datastore redis +``` + +## Supported Ballerina Types +The following table lists the Ballerina types supported by the Redis data store. Following data types will be converted to string when inserting data and converted back to relevent data types in ballerina when retrieving. + +| Ballerina Type | +|:----------------:| +| int | +| float | +| string | +| boolean | +| enum | +| () | + +## Configuration + +You need to set values for the following basic configuration parameters in the `Config.toml` file in your project to use the Redis data store. + +| Parameter | Description | +|:----------:|:------------------------------------:| +| host | The hostname of the DB server. | +| port | The port of the DB server. | +| password | The password of the DB server. | + +The following is a sample `Config.toml` file with the Redis data store configuration. This is generated by the `bal persist generate` command. + +```toml +[.] +host = "localhost" +port = 6379 +password = "" +``` + +## How To Setup +Use docker as follows to create a DB server deployment. + +* Run `docker pull redis` to pull the official Redis Docker image from the Docker Hub +* Run `docker run --name -p 6379:6379 -d redis diff --git a/ballerina/Package.md b/ballerina/Package.md index fc8b1b6..aef7a71 100644 --- a/ballerina/Package.md +++ b/ballerina/Package.md @@ -1,5 +1,54 @@ -Prints "Hello, World!" with a hello function. -[//]: # (above is the package summary) +# Module Overview -# Package Overview -Prints "Hello, World!" as the output to the command line using a hello function. +This module provide redis database support for the `bal persist` feature, which provides functionality to store and query data from a redis database through a data model instead of writing redis commands. + +Since redis is not the default datastore for `bal persist` you need to explicitly specify the data store when initializing `bal persist` in your application. as follows, + +``` +$ bal persist init --datastore redis +``` + +## Supported Ballerina Types +The following table lists the Ballerina types supported by the Redis data store. Following data types will be converted to string when inserting data and converted back to relevent data types in ballerina when retrieving. + +| Ballerina Type | +|:----------------:| +| int | +| float | +| string | +| boolean | +| enum | +| () | + +## Configuration + +You need to set values for the following basic configuration parameters in the `Config.toml` file in your project to use the Redis data store. + +| Parameter | Description | +|:----------:|:------------------------------------:| +| host | The hostname of the DB server. | +| port | The port of the DB server. | +| password | The password of the DB server. | + +The following is a sample `Config.toml` file with the Redis data store configuration. This is generated by the `bal persist generate` command. + +```toml +[.] +host = "localhost" +port = 6379 +password = "" +``` + +## How To Setup +Use docker as follows to create a DB server deployment. + +* Run `docker pull redis` to pull the official Redis Docker image from the Docker Hub +* Run `docker run --name -p 6379:6379 -d redis + +## Report issues + +To report bugs, request new features, start new discussions, view project boards, etc., go to the [Ballerina standard library parent repository](https://github.com/ballerina-platform/ballerina-standard-library). + +## Useful links +- Chat live with us via our [Discord server](https://discord.gg/ballerinalang). +- Post all technical questions on Stack Overflow with the [#ballerina](https://stackoverflow.com/questions/tagged/ballerina) tag. diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 991e1d5..684559f 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -1,3 +1,19 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + import org.apache.tools.ant.taskdefs.condition.Os buildscript { diff --git a/ballerina/constants.bal b/ballerina/constants.bal new file mode 100644 index 0000000..40e4c59 --- /dev/null +++ b/ballerina/constants.bal @@ -0,0 +1,19 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +public const string KEY_SEPERATOR = ":"; +public const string MANY_ASSOCIATION_SEPERATOR = "[]."; +public const string ASSOCIATION_SEPERATOR = "."; \ No newline at end of file diff --git a/ballerina/errors.bal b/ballerina/errors.bal index d986fda..3f1fc6f 100644 --- a/ballerina/errors.bal +++ b/ballerina/errors.bal @@ -1,5 +1,20 @@ -import ballerina/persist; +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +import ballerina/persist; public type ConstraintViolationError distinct persist:ConstraintViolationError; @@ -9,6 +24,6 @@ public type ConstraintViolationError distinct persist:ConstraintViolationError; # + refEntity - The entity is being reffered # + return - The generated `persist:ConstraintViolationError` public isolated function getConstraintViolationError(string entity, string refEntity) returns ConstraintViolationError { - string message = string `A relationship constrant failed between entities '${entity}' and '${refEntity}'`; + string message = string `An association constraint failed between entities '${entity}' and '${refEntity}'`; return error ConstraintViolationError(message); -} \ No newline at end of file +} diff --git a/ballerina/init.bal b/ballerina/init.bal index bf01a14..81166ba 100644 --- a/ballerina/init.bal +++ b/ballerina/init.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + import ballerina/jballerina.java; isolated function init() { @@ -6,4 +22,4 @@ isolated function init() { isolated function setModule() = @java:Method { 'class: "io.ballerina.stdlib.persist.redis.ModuleUtils" -} external; \ No newline at end of file +} external; diff --git a/ballerina/metadata_types.bal b/ballerina/metadata_types.bal index a6c2440..4bc292d 100644 --- a/ballerina/metadata_types.bal +++ b/ballerina/metadata_types.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + # Represents the metadata of an entity. # # + entityName - Name of the entity @@ -124,4 +140,4 @@ public enum MetaData { FIELD_DATA_TYPE = "fieldDataType", RELATION = "relation", REF_FIELD_DATA_TYPE = "refFieldDataType" -} \ No newline at end of file +} diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index ceff810..ad9698e 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + import ballerinax/redis; import ballerina/persist; @@ -50,7 +66,6 @@ public isolated client class RedisClient { record{} 'object = check self.querySimpleFieldsByKey(typeMap, recordKey, fields); // Handling relation fields check self.getManyRelations(typeMap, 'object, fields, include); - self.removeUnwantedFields('object, fields); self.removeNonExistOptionalFields('object); return check 'object.cloneWithType(rowType); @@ -69,7 +84,7 @@ public isolated client class RedisClient { # or a `persist:Error` if the operation fails public isolated function runReadQuery(typedesc rowType, map typeMap, string[] fields = [], string[] include = []) returns stream|persist:Error { // Get all the keys - string[]|error keys = self.dbClient->keys(self.collectionName+":*"); + string[]|error keys = self.dbClient->keys(string `${self.collectionName}${KEY_SEPERATOR}*`); if keys is error { return error persist:Error(keys.message()); } @@ -77,7 +92,6 @@ public isolated client class RedisClient { // Get data one by one using the key record{}[] result = []; foreach string key in keys { - // Verifying the key belongs to a hash string redisType = check self.dbClient->redisType(key); if redisType != REDIS_HASH { @@ -86,17 +100,14 @@ public isolated client class RedisClient { // Handling simple fields only for batch read record{} 'object = check self.querySimpleFieldsByKey(typeMap, key, fields); - // check self.getManyRelations(typeMap, 'object, fields, include); self.removeUnwantedFields('object, fields); self.removeNonExistOptionalFields('object); result.push('object); // result.push(check 'object.cloneWithType(rowType)); - } on fail var e { return e; } - return stream from record{} rec in result select rec; } @@ -106,16 +117,14 @@ public isolated client class RedisClient { # + return - A `string` containing the information of the database operation execution # or a `persist:Error` if the operation fails public isolated function runBatchInsertQuery(record {}[] insertRecords) returns string|persist:Error { - string|error result; - // For each record, do HMSET foreach var insertRecord in insertRecords { // Generate the key string key = ""; foreach string keyField in self.keyFields { - key = key + ":" + insertRecord[keyField].toString(); + key += string `${KEY_SEPERATOR}${insertRecord[keyField].toString()}`; } // Check for duplicate keys withing the collection @@ -129,7 +138,7 @@ public isolated client class RedisClient { // Insert the object result = self.dbClient->hMSet(self.collectionName+key, insertRecord); - if result is error{ + if result is error { return error persist:Error(result.message()); } } on fail var e { @@ -152,14 +161,14 @@ public isolated client class RedisClient { public isolated function runDeleteQuery(any [] keyFieldValues) returns persist:Error? { // Validate fields - if (keyFieldValues.length() != self.keyFields.length()){ + if keyFieldValues.length() != self.keyFields.length() { return error("Missing keyfields"); } // Generate the key string recordKey = self.collectionName; - foreach any value in keyFieldValues{ - recordKey += ":"+value.toString(); + foreach any value in keyFieldValues { + recordKey += string `${KEY_SEPERATOR}${value.toString()}`; } do { @@ -178,14 +187,14 @@ public isolated client class RedisClient { public isolated function runUpdateQuery(any [] keyFieldValues, record {} updateRecord) returns error? { // Validate fields - if (keyFieldValues.length() != self.keyFields.length()){ + if keyFieldValues.length() != self.keyFields.length() { return error("Missing keyfields"); } // Generate the key string key = self.collectionName; - foreach any keyFieldValue in keyFieldValues{ - key += ":"+keyFieldValue.toString(); + foreach any keyFieldValue in keyFieldValues { + key += string `${KEY_SEPERATOR}${keyFieldValue.toString()}`; } // Update only the given fields that is not nil @@ -193,16 +202,13 @@ public isolated client class RedisClient { FieldMetadata & readonly fieldMetadataValue = metaDataEntry[1]; // If the field is a simple field - if(fieldMetadataValue is SimpleFieldMetadata){ - if (updateRecord.hasKey(fieldMetadataValue.fieldName) && updateRecord[fieldMetadataValue.fieldName] != ()){ + if fieldMetadataValue is SimpleFieldMetadata { + if (updateRecord.hasKey(fieldMetadataValue.fieldName) && updateRecord[fieldMetadataValue.fieldName] != ()) { // updating the object _ = check self.dbClient->hSet(key, fieldMetadataValue.fieldName, updateRecord[fieldMetadataValue.fieldName].toString()); } - } - - // If the field is a relation field - else{ - + } else { + // If the field is a relation field } } } @@ -222,17 +228,16 @@ public isolated client class RedisClient { // checking for one to many relationships string[] relationFields = from string 'field in fields - where 'field.startsWith(entity + "[].") + where 'field.startsWith(string `${entity}${MANY_ASSOCIATION_SEPERATOR}`) select 'field.substring(entity.length() + 3, 'field.length()); // checking for one to one relationships if relationFields.length() == 0 { - relationFields = from string 'field in fields - where 'field.startsWith(entity + ".") + where 'field.startsWith(string `${entity}${ASSOCIATION_SEPERATOR}`) select 'field.substring(entity.length() + 1, 'field.length()); - if relationFields.length() != 0{ + if relationFields.length() != 0 { cardinalityType = ONE_TO_ONE; } } @@ -241,9 +246,9 @@ public isolated client class RedisClient { continue; } - string[]|error keys = self.dbClient->keys(entity.substring(0,1).toUpperAscii()+entity.substring(1)+":*"); + string[]|error keys = self.dbClient->keys(string `${entity.substring(0,1).toUpperAscii()}${entity.substring(1)}${KEY_SEPERATOR}*`); if keys is error || (keys.length() == 0) { - if cardinalityType == ONE_TO_MANY{ + if cardinalityType == ONE_TO_MANY { 'object[entity] = []; } else { 'object[entity] = {}; @@ -259,7 +264,7 @@ public isolated client class RedisClient { // Check whether the record is associated with the current object boolean isAssociated = true; - foreach string keyField in self.keyFields{ + foreach string keyField in self.keyFields { string refField = self.entityName.substring(0,1).toLowerAscii()+self.entityName.substring(1) +keyField.substring(0,1).toUpperAscii()+keyField.substring(1); boolean isSimilar = valueToRecord[refField] == 'object[keyField]; @@ -269,7 +274,6 @@ public isolated client class RedisClient { } if isAssociated { - foreach string refField in valueToRecord.keys() { if relationFields.indexOf(refField) is () { _ = valueToRecord.remove(refField); @@ -277,41 +281,38 @@ public isolated client class RedisClient { } associatedRecords.push(valueToRecord); } - } if associatedRecords.length() > 0 { if cardinalityType == ONE_TO_ONE { 'object[entity] = associatedRecords[0]; - }else { + } else { 'object[entity] = associatedRecords; } } - } on fail var e { return e; } } - // Private helper methods public isolated function getKeyFields() returns string[] { return self.keyFields; } + // Private helper methods private isolated function getKey(anydata key) returns string { string keyValue = ""; if key is map { - foreach string compositeKey in key.keys(){ - keyValue += ":"+key[compositeKey].toString(); + foreach string compositeKey in key.keys() { + keyValue += string `${KEY_SEPERATOR}${key[compositeKey].toString()}`; } return keyValue; } else { - return ":"+key.toString(); + return string `${KEY_SEPERATOR}${key.toString()}`; } } - private isolated function querySimpleFieldsByKey(map typeMap, string key, string[] fields) returns record {|anydata...;|}|error{ - + private isolated function querySimpleFieldsByKey(map typeMap, string key, string[] fields) returns record {|anydata...;|}|error { // hadling the simple fields string[] simpleFields = self.getTargetSimpleFields(fields, typeMap); // If no simpleFields given, then add all the fields by default @@ -320,21 +321,18 @@ public isolated client class RedisClient { FieldMetadata & readonly fieldMetadataValue = metaDataEntry[1]; // If the field is a simple field - if(fieldMetadataValue is SimpleFieldMetadata){ + if fieldMetadataValue is SimpleFieldMetadata { simpleFields.push(fieldMetadataValue.fieldName); } } } do { - // Retrieve the record map value = check self.dbClient->hMGet(key, simpleFields); - if self.isNoRecordFound(value) { return persist:getNotFoundError(self.entityName, key); } - record{} valueToRecord = {}; foreach string fieldKey in value.keys() { // convert the data type from 'any' to required type @@ -348,8 +346,7 @@ public isolated client class RedisClient { } } - private isolated function queryRelationFieldsByKey(string entity, CardinalityType cardinalityType, string key, string[] fields) returns record {|anydata...;|}|persist:Error{ - + private isolated function queryRelationFieldsByKey(string entity, CardinalityType cardinalityType, string key, string[] fields) returns record {|anydata...;|}|persist:Error { // If the field doesn't containes reference fields, add them here string[] relationFields = fields.clone(); foreach string keyField in self.keyFields { @@ -364,16 +361,15 @@ public isolated client class RedisClient { // Retrieve related records map value = check self.dbClient->hMGet(key, relationFields); if self.isNoRecordFound(value) { - return error persist:Error("No `"+self.entityName+"` found for the given key"); + return error persist:Error(string `No '${self.entityName}' found for the given key`); } record{} valueToRecord = {}; - string fieldMetadataKeyPrefix = entity; - if(cardinalityType == ONE_TO_MANY){ - fieldMetadataKeyPrefix += "[]."; - }else{ - fieldMetadataKeyPrefix += "."; + if cardinalityType == ONE_TO_MANY { + fieldMetadataKeyPrefix += MANY_ASSOCIATION_SEPERATOR; + } else { + fieldMetadataKeyPrefix += ASSOCIATION_SEPERATOR; } foreach string fieldKey in value.keys() { @@ -396,17 +392,17 @@ public isolated client class RedisClient { } - private isolated function removeNonExistOptionalFields(record {} 'object){ - foreach string key in 'object.keys(){ + private isolated function removeNonExistOptionalFields(record {} 'object) { + foreach string key in 'object.keys() { if 'object[key] == () { _ = 'object.remove(key); } } } - private isolated function isNoRecordFound(map value) returns boolean{ + private isolated function isNoRecordFound(map value) returns boolean { boolean isNoRecordExists = true; - foreach string key in value.keys(){ + foreach string key in value.keys() { if value[key] != () { isNoRecordExists = false; } @@ -416,7 +412,6 @@ public isolated client class RedisClient { private isolated function removeUnwantedFields(record {} 'object, string[] fields) { string[] keyFields = self.keyFields; - foreach string keyField in keyFields { if fields.indexOf(keyField) is () { _ = 'object.remove(keyField); @@ -425,7 +420,6 @@ public isolated client class RedisClient { } private isolated function checkRelationFieldConstraints(string key, record {} insertRecord) returns persist:Error? { - if self.refMetadata != {} { foreach RefMetadata & readonly refMetadataValue in self.refMetadata { // If the entity is not the relation owner @@ -437,22 +431,20 @@ public isolated client class RedisClient { // If the mandatory reference field is not exist or being null foreach string joinField in refMetadataValue.joinFields { if (insertRecord.hasKey(joinField) && insertRecord[joinField] != ()) { - isRelationConstraintFalied = false; break; } } if !isRelationConstraintFalied { - // Generate the key to reference record string refRecordKey = refMetadataValue.refCollection; foreach string joinField in refMetadataValue.joinFields { - refRecordKey += ":"+insertRecord[joinField].toString(); + refRecordKey += string `${KEY_SEPERATOR}${insertRecord[joinField].toString()}`; } // Check the cardinality of refered entity object - int|error sCard = self.dbClient->sCard(refRecordKey+":"+self.collectionName); + int|error sCard = self.dbClient->sCard(string `${refRecordKey}${KEY_SEPERATOR}${self.collectionName}`); if sCard is int { if sCard > 0 && refMetadataValue.'type == ONE_TO_ONE { // If the refered object is already in a relationship @@ -461,7 +453,7 @@ public isolated client class RedisClient { } // Relate current record with the refered record in the database - int|error sAdd = self.dbClient->sAdd(refRecordKey+":"+self.collectionName, [key]); + int|error sAdd = self.dbClient->sAdd(string `${refRecordKey}${KEY_SEPERATOR}${self.collectionName}`, [key]); if sAdd is error { return sAdd; } @@ -480,31 +472,31 @@ public isolated client class RedisClient { private isolated function dataConverter(FieldMetadata & readonly fieldMetaData, any value) returns ()|boolean|string|float|error|int { // Return nil if value is nil - if(value is ()){ + if value is () { return (); } - if((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == INT) - || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == INT)){ + if ((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == INT) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == INT)) { return check int:fromString(value); - }else if((fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == STRING)) - || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == STRING)){ + } else if((fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == STRING)) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == STRING)) { return value; - }else if((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == FLOAT) - || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == FLOAT)){ + } else if((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == FLOAT) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == FLOAT)) { return check float:fromString(value); - }else if((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == BOOLEAN) - || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == BOOLEAN)){ + } else if((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == BOOLEAN) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == BOOLEAN)) { return check boolean:fromString(value); - }else if((fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == ENUM)) - || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == ENUM)){ + } else if((fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == ENUM)) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == ENUM)) { return value; - }else{ + } else { return error("Unsupported Data Format"); } } diff --git a/ballerina/stream_types.bal b/ballerina/stream_types.bal index f7f5069..bec2f76 100644 --- a/ballerina/stream_types.bal +++ b/ballerina/stream_types.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + import ballerina/persist; public class PersistRedisStream { @@ -23,7 +39,6 @@ public class PersistRedisStream { typeDescriptionsArray.push(>typeDescription); } self.typeDescriptions = typeDescriptionsArray; - self.persistClient = persistClient; self.err = err; } @@ -67,4 +82,4 @@ public class PersistRedisStream { } } } -} \ No newline at end of file +} diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index 5d3986d..79f4a4e 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -3,7 +3,7 @@ org = "ballerinax" name = "persist.redis" version = "@toml.version@" authors = ["Ballerina"] -keywords = ["persist", "redis"] +keywords = ["persist", "redis", "experimental"] repository = "https://github.com/ballerina-platform/module-ballerinax-persist.redis" license = ["Apache-2.0"] distribution = "2201.8.0" diff --git a/build.gradle b/build.gradle index 3e763ae..746ceee 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,18 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * This is a general purpose Gradle build. - * To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.5/samples - */ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. plugins { id "com.github.spotbugs" version "${githubSpotbugsVersion}" @@ -90,4 +99,4 @@ task build { } publishToMavenLocal.dependsOn build -publish.dependsOn build \ No newline at end of file +publish.dependsOn build diff --git a/gradle.properties b/gradle.properties index 5cccac8..97c2266 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=io.ballerina.stdlib -version=1.2.1-SNAPSHOT +version=0.1.0-SNAPSHOT puppycrawlCheckstyleVersion=10.12.0 checkstyleToolVersion=10.12.0 @@ -27,4 +27,4 @@ observeInternalVersion=1.2.0 # Enabled publishing insecure checksums, due to fail to publish to maven central # Refer https://github.com/gradle/gradle/issues/11308 -systemProp.org.gradle.internal.publish.checksums.insecure=true \ No newline at end of file +systemProp.org.gradle.internal.publish.checksums.insecure=true diff --git a/native/build.gradle b/native/build.gradle index 5dbb917..ff96f14 100644 --- a/native/build.gradle +++ b/native/build.gradle @@ -1,3 +1,18 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. plugins { id 'java' diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/Constants.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/Constants.java index eb68b72..293c6db 100644 --- a/native/src/main/java/io/ballerina/stdlib/persist/redis/Constants.java +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/Constants.java @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package io.ballerina.stdlib.persist.redis; public class Constants { diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/ModuleUtils.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/ModuleUtils.java index eb35d4f..1990eef 100644 --- a/native/src/main/java/io/ballerina/stdlib/persist/redis/ModuleUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/ModuleUtils.java @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package io.ballerina.stdlib.persist.redis; import io.ballerina.runtime.api.Environment; diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java index 69640f6..25a04b5 100644 --- a/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java @@ -1,4 +1,22 @@ -package io.ballerina.stdlib.persist.redis; +/* + * Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + package io.ballerina.stdlib.persist.redis; import io.ballerina.runtime.api.Environment; import io.ballerina.runtime.api.PredefinedTypes; diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java index ca19f08..077c7cd 100644 --- a/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java @@ -1,3 +1,20 @@ +/* + * Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package io.ballerina.stdlib.persist.redis.datastore; diff --git a/native/src/main/java/module-info.java b/native/src/main/java/module-info.java index 4723395..8b9ef11 100644 --- a/native/src/main/java/module-info.java +++ b/native/src/main/java/module-info.java @@ -1,5 +1,23 @@ +/* + * Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + module io.ballerina.stdlib.persist.redis { requires io.ballerina.runtime; requires io.ballerina.lang; requires io.ballerina.stdlib.persist; -} \ No newline at end of file +} diff --git a/settings.gradle b/settings.gradle index 6b49e6d..e474ede 100644 --- a/settings.gradle +++ b/settings.gradle @@ -24,4 +24,4 @@ gradleEnterprise { termsOfServiceUrl = 'https://gradle.com/terms-of-service' termsOfServiceAgree = 'yes' } -} \ No newline at end of file +} From c713119a02a979f33f04f46609da304a22003740 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Tue, 6 Feb 2024 14:23:33 +0530 Subject: [PATCH 33/65] added document formatting --- ballerina/constants.bal | 2 +- ballerina/errors.bal | 1 - ballerina/init.bal | 1 - ballerina/metadata_types.bal | 1 - ballerina/redis_client.bal | 93 +++++++++---------- ballerina/stream_types.bal | 1 - .../ballerina/stdlib/persist/redis/Utils.java | 24 +++-- .../redis/datastore/RedisProcessor.java | 18 ++-- 8 files changed, 67 insertions(+), 74 deletions(-) diff --git a/ballerina/constants.bal b/ballerina/constants.bal index 40e4c59..732070c 100644 --- a/ballerina/constants.bal +++ b/ballerina/constants.bal @@ -16,4 +16,4 @@ public const string KEY_SEPERATOR = ":"; public const string MANY_ASSOCIATION_SEPERATOR = "[]."; -public const string ASSOCIATION_SEPERATOR = "."; \ No newline at end of file +public const string ASSOCIATION_SEPERATOR = "."; diff --git a/ballerina/errors.bal b/ballerina/errors.bal index 3f1fc6f..b4332d9 100644 --- a/ballerina/errors.bal +++ b/ballerina/errors.bal @@ -13,7 +13,6 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. - import ballerina/persist; public type ConstraintViolationError distinct persist:ConstraintViolationError; diff --git a/ballerina/init.bal b/ballerina/init.bal index 81166ba..c0be223 100644 --- a/ballerina/init.bal +++ b/ballerina/init.bal @@ -13,7 +13,6 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. - import ballerina/jballerina.java; isolated function init() { diff --git a/ballerina/metadata_types.bal b/ballerina/metadata_types.bal index 4bc292d..3081d4a 100644 --- a/ballerina/metadata_types.bal +++ b/ballerina/metadata_types.bal @@ -100,7 +100,6 @@ public enum CardinalityType { MANY_TO_ONE } - # Represents the type of the field data. # Only used by the generated persist clients and `persist:RedisClient`. # diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index ad9698e..19de0a6 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -1,3 +1,4 @@ +import ballerina/persist; // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. // // WSO2 LLC. licenses this file to you under the Apache License, @@ -13,9 +14,7 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. - import ballerinax/redis; -import ballerina/persist; # The client used by the generated persist clients to abstract and # execute Redis database operations that are required to perform CRUD operations. @@ -48,7 +47,7 @@ public isolated client class RedisClient { } # Performs a batch `HGET` operation to get entity instances as a stream - # + # # + rowType - The type description of the entity to be retrieved # + typeMap - The data type map of the target type # + key - Key for the record @@ -63,7 +62,7 @@ public isolated client class RedisClient { recordKey += self.getKey(key); do { // Handling simple fields - record{} 'object = check self.querySimpleFieldsByKey(typeMap, recordKey, fields); + record {} 'object = check self.querySimpleFieldsByKey(typeMap, recordKey, fields); // Handling relation fields check self.getManyRelations(typeMap, 'object, fields, include); self.removeUnwantedFields('object, fields); @@ -75,14 +74,14 @@ public isolated client class RedisClient { } # Performs a batch `HGET` operation to get entity instances as a stream - # + # # + rowType - The type description of the entity to be retrieved # + typeMap - The data types of the record # + fields - The fields to be retrieved # + include - The associations to be retrieved # + return - A stream of `stream` containing the requested records # or a `persist:Error` if the operation fails - public isolated function runReadQuery(typedesc rowType, map typeMap, string[] fields = [], string[] include = []) returns stream|persist:Error { + public isolated function runReadQuery(typedesc rowType, map typeMap, string[] fields = [], string[] include = []) returns stream|persist:Error { // Get all the keys string[]|error keys = self.dbClient->keys(string `${self.collectionName}${KEY_SEPERATOR}*`); if keys is error { @@ -90,7 +89,7 @@ public isolated client class RedisClient { } // Get data one by one using the key - record{}[] result = []; + record {}[] result = []; foreach string key in keys { // Verifying the key belongs to a hash string redisType = check self.dbClient->redisType(key); @@ -99,16 +98,17 @@ public isolated client class RedisClient { } // Handling simple fields only for batch read - record{} 'object = check self.querySimpleFieldsByKey(typeMap, key, fields); + record {} 'object = check self.querySimpleFieldsByKey(typeMap, key, fields); // check self.getManyRelations(typeMap, 'object, fields, include); self.removeUnwantedFields('object, fields); self.removeNonExistOptionalFields('object); result.push('object); // result.push(check 'object.cloneWithType(rowType)); } on fail var e { - return e; + return e; } - return stream from record{} rec in result select rec; + return stream from record {} rec in result + select rec; } # Performs a batch `HMSET` operation to insert entity instances into a collection. @@ -128,21 +128,21 @@ public isolated client class RedisClient { } // Check for duplicate keys withing the collection - int isKeyExists = check self.dbClient->exists([self.collectionName+key]); + int isKeyExists = check self.dbClient->exists([self.collectionName + key]); if isKeyExists != 0 { - return persist:getAlreadyExistsError(self.collectionName, self.collectionName+key); + return persist:getAlreadyExistsError(self.collectionName, self.collectionName + key); } // Check for any relation field constraints check self.checkRelationFieldConstraints(key, insertRecord); // Insert the object - result = self.dbClient->hMSet(self.collectionName+key, insertRecord); + result = self.dbClient->hMSet(self.collectionName + key, insertRecord); if result is error { return error persist:Error(result.message()); } } on fail var e { - return e; + return e; } // Decide how to log queries @@ -158,7 +158,7 @@ public isolated client class RedisClient { # + keyFieldValues - The ordered keys used to delete an entity record # + return - `()` if the operation is performed successfully # or a `persist:Error` if the operation fails - public isolated function runDeleteQuery(any [] keyFieldValues) returns persist:Error? { + public isolated function runDeleteQuery(any[] keyFieldValues) returns persist:Error? { // Validate fields if keyFieldValues.length() != self.keyFields.length() { @@ -172,10 +172,10 @@ public isolated client class RedisClient { } do { - // Delete the record - _ = check self.dbClient->del([recordKey]); + // Delete the record + _ = check self.dbClient->del([recordKey]); } on fail var e { - return e; + return e; } } @@ -184,7 +184,7 @@ public isolated client class RedisClient { # + keyFieldValues - The ordered keys used to update an entity record # + updateRecord - The new record to be updated # + return - An Error if the new record is missing a keyfield - public isolated function runUpdateQuery(any [] keyFieldValues, record {} updateRecord) returns error? { + public isolated function runUpdateQuery(any[] keyFieldValues, record {} updateRecord) returns error? { // Validate fields if keyFieldValues.length() != self.keyFields.length() { @@ -214,7 +214,7 @@ public isolated client class RedisClient { } # Retrieves all the associations of a given object - # + # # + typeMap - The data types of the record # + object - The object of the interest # + fields - The fields to be retrieved @@ -230,12 +230,12 @@ public isolated client class RedisClient { string[] relationFields = from string 'field in fields where 'field.startsWith(string `${entity}${MANY_ASSOCIATION_SEPERATOR}`) select 'field.substring(entity.length() + 3, 'field.length()); - + // checking for one to one relationships if relationFields.length() == 0 { relationFields = from string 'field in fields - where 'field.startsWith(string `${entity}${ASSOCIATION_SEPERATOR}`) - select 'field.substring(entity.length() + 1, 'field.length()); + where 'field.startsWith(string `${entity}${ASSOCIATION_SEPERATOR}`) + select 'field.substring(entity.length() + 1, 'field.length()); if relationFields.length() != 0 { cardinalityType = ONE_TO_ONE; @@ -246,7 +246,7 @@ public isolated client class RedisClient { continue; } - string[]|error keys = self.dbClient->keys(string `${entity.substring(0,1).toUpperAscii()}${entity.substring(1)}${KEY_SEPERATOR}*`); + string[]|error keys = self.dbClient->keys(string `${entity.substring(0, 1).toUpperAscii()}${entity.substring(1)}${KEY_SEPERATOR}*`); if keys is error || (keys.length() == 0) { if cardinalityType == ONE_TO_MANY { 'object[entity] = []; @@ -257,16 +257,16 @@ public isolated client class RedisClient { } // Get data one by one using the key - record{}[] associatedRecords = []; + record {}[] associatedRecords = []; foreach string key in keys { // Handling simple fields - record{} valueToRecord = check self.queryRelationFieldsByKey(entity, cardinalityType, key, relationFields); + record {} valueToRecord = check self.queryRelationFieldsByKey(entity, cardinalityType, key, relationFields); // Check whether the record is associated with the current object boolean isAssociated = true; foreach string keyField in self.keyFields { - string refField = self.entityName.substring(0,1).toLowerAscii()+self.entityName.substring(1) - +keyField.substring(0,1).toUpperAscii()+keyField.substring(1); + string refField = self.entityName.substring(0, 1).toLowerAscii() + self.entityName.substring(1) + + keyField.substring(0, 1).toUpperAscii() + keyField.substring(1); boolean isSimilar = valueToRecord[refField] == 'object[keyField]; if !isSimilar { isAssociated = false; @@ -282,7 +282,7 @@ public isolated client class RedisClient { associatedRecords.push(valueToRecord); } } - + if associatedRecords.length() > 0 { if cardinalityType == ONE_TO_ONE { 'object[entity] = associatedRecords[0]; @@ -291,7 +291,7 @@ public isolated client class RedisClient { } } } on fail var e { - return e; + return e; } } @@ -329,11 +329,11 @@ public isolated client class RedisClient { do { // Retrieve the record - map value = check self.dbClient->hMGet(key, simpleFields); + map value = check self.dbClient->hMGet(key, simpleFields); if self.isNoRecordFound(value) { return persist:getNotFoundError(self.entityName, key); } - record{} valueToRecord = {}; + record {} valueToRecord = {}; foreach string fieldKey in value.keys() { // convert the data type from 'any' to required type valueToRecord[fieldKey] = check self.dataConverter( @@ -342,7 +342,7 @@ public isolated client class RedisClient { } return valueToRecord; } on fail var e { - return e; + return e; } } @@ -350,8 +350,8 @@ public isolated client class RedisClient { // If the field doesn't containes reference fields, add them here string[] relationFields = fields.clone(); foreach string keyField in self.keyFields { - string refField = self.entityName.substring(0,1).toLowerAscii()+self.entityName.substring(1) - +keyField.substring(0,1).toUpperAscii()+keyField.substring(1); + string refField = self.entityName.substring(0, 1).toLowerAscii() + self.entityName.substring(1) + + keyField.substring(0, 1).toUpperAscii() + keyField.substring(1); if relationFields.indexOf(refField) is () { relationFields.push(refField); } @@ -359,12 +359,12 @@ public isolated client class RedisClient { do { // Retrieve related records - map value = check self.dbClient->hMGet(key, relationFields); + map value = check self.dbClient->hMGet(key, relationFields); if self.isNoRecordFound(value) { return error persist:Error(string `No '${self.entityName}' found for the given key`); } - record{} valueToRecord = {}; + record {} valueToRecord = {}; string fieldMetadataKeyPrefix = entity; if cardinalityType == ONE_TO_MANY { fieldMetadataKeyPrefix += MANY_ASSOCIATION_SEPERATOR; @@ -375,12 +375,12 @@ public isolated client class RedisClient { foreach string fieldKey in value.keys() { // convert the data type from 'any' to required type valueToRecord[fieldKey] = check self.dataConverter( - self.fieldMetadata[fieldMetadataKeyPrefix+fieldKey] + self.fieldMetadata[fieldMetadataKeyPrefix + fieldKey] , value[fieldKey]); } return valueToRecord; } on fail var e { - return e; + return e; } } @@ -391,7 +391,6 @@ public isolated client class RedisClient { return simpleFields; } - private isolated function removeNonExistOptionalFields(record {} 'object) { foreach string key in 'object.keys() { if 'object[key] == () { @@ -442,7 +441,7 @@ public isolated client class RedisClient { foreach string joinField in refMetadataValue.joinFields { refRecordKey += string `${KEY_SEPERATOR}${insertRecord[joinField].toString()}`; } - + // Check the cardinality of refered entity object int|error sCard = self.dbClient->sCard(string `${refRecordKey}${KEY_SEPERATOR}${self.collectionName}`); if sCard is int { @@ -475,27 +474,27 @@ public isolated client class RedisClient { if value is () { return (); } - + if ((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == INT) || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == INT)) { return check int:fromString(value); - } else if((fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == STRING)) + } else if ((fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == STRING)) || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == STRING)) { return value; - } else if((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == FLOAT) + } else if ((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == FLOAT) || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == FLOAT)) { return check float:fromString(value); - } else if((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == BOOLEAN) + } else if ((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == BOOLEAN) || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == BOOLEAN)) { return check boolean:fromString(value); - } else if((fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == ENUM)) + } else if ((fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == ENUM)) || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == ENUM)) { return value; - + } else { return error("Unsupported Data Format"); } diff --git a/ballerina/stream_types.bal b/ballerina/stream_types.bal index bec2f76..80aeca0 100644 --- a/ballerina/stream_types.bal +++ b/ballerina/stream_types.bal @@ -13,7 +13,6 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. - import ballerina/persist; public class PersistRedisStream { diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java index 25a04b5..f4b4716 100644 --- a/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/Utils.java @@ -16,7 +16,7 @@ * under the License. */ - package io.ballerina.stdlib.persist.redis; +package io.ballerina.stdlib.persist.redis; import io.ballerina.runtime.api.Environment; import io.ballerina.runtime.api.PredefinedTypes; @@ -43,7 +43,6 @@ import static io.ballerina.stdlib.persist.redis.Constants.PERSIST_REDIS_STREAM; import static io.ballerina.stdlib.persist.redis.ModuleUtils.getModule; - public class Utils { public static BString getEntityFromStreamMethod(Environment env) { @@ -51,7 +50,7 @@ public static BString getEntityFromStreamMethod(Environment env) { String entity = functionName.substring(5, functionName.length() - 6).toLowerCase(Locale.ENGLISH); return fromString(entity); } - + public static BMap getFieldTypes(RecordType recordType) { MapType stringMapType = TypeCreator.createMapType(PredefinedTypes.TYPE_STRING); BMap typeMap = ValueCreator.createMapValue(stringMapType); @@ -65,25 +64,24 @@ public static BMap getFieldTypes(RecordType recordType) { return typeMap; } - private static BObject createPersistRedisStream(BStream redisStream, - BTypedesc targetType, BMap typeMap, BArray fields, - BArray includes, BArray typeDescriptions, BObject persistClient, - BError persistError) { + private static BObject createPersistRedisStream(BStream redisStream, + BTypedesc targetType, BMap typeMap, BArray fields, + BArray includes, BArray typeDescriptions, BObject persistClient, + BError persistError) { return ValueCreator.createObjectValue(getModule(), PERSIST_REDIS_STREAM, - redisStream, targetType, typeMap, fields, includes, typeDescriptions, persistClient, persistError); + redisStream, targetType, typeMap, fields, includes, typeDescriptions, persistClient, persistError); } private static BStream createPersistRedisStreamValue(BTypedesc targetType, BObject persistRedisStream) { - RecordType streamConstraint = - (RecordType) TypeUtils.getReferredType(targetType.getDescribingType()); + RecordType streamConstraint = (RecordType) TypeUtils.getReferredType(targetType.getDescribingType()); return ValueCreator.createStreamValue( TypeCreator.createStreamType(streamConstraint, PredefinedTypes.TYPE_NULL), persistRedisStream); } public static BStream createPersistRedisStreamValue(BStream redisStream, BTypedesc targetType, BArray fields, - BArray includes, BArray typeDescriptions, BObject persistClient, - BError persistError) { - BObject persistRedisStream = createPersistRedisStream(redisStream, targetType, + BArray includes, BArray typeDescriptions, BObject persistClient, + BError persistError) { + BObject persistRedisStream = createPersistRedisStream(redisStream, targetType, Utils.getFieldTypes((RecordType) targetType.getDescribingType()), fields, includes, typeDescriptions, persistClient, persistError); return createPersistRedisStreamValue(targetType, persistRedisStream); diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java index 077c7cd..d3a281b 100644 --- a/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java @@ -57,7 +57,7 @@ public class RedisProcessor { private RedisProcessor() { } - + public static BStream query(Environment env, BObject client, BTypedesc targetType) { // This method will return `stream` @@ -82,7 +82,8 @@ public static BStream query(Environment env, BObject client, BTypedesc targetTyp Future balFuture = env.markAsync(); env.getRuntime().invokeMethodAsyncSequentially( // Call `RedisClient.runReadQuery( - // typedesc rowType, map typeMap, string[] fields = [], string[] include = [] + // typedesc rowType, map typeMap, string[] fields = [], + // string[] include = [] // )` // which returns `stream|persist:Error` @@ -105,8 +106,7 @@ public void notifyFailure(BError bError) { typeDescriptions, persistClient, wrapError(bError))); } }, trxContextProperties, streamTypeWithIdFields, - targetType, true, typeMap, true, fields, true, includes, true - ); + targetType, true, typeMap, true, fields, true, includes, true); return null; } @@ -137,8 +137,9 @@ public static Object queryOne(Environment env, BObject client, BArray path, BTyp Future balFuture = env.markAsync(); env.getRuntime().invokeMethodAsyncSequentially( // Call `RedisClient.runReadByKeyQuery( - // typedesc rowType, anydata key, string[] fields = [], string[] include = [], - // typedesc[] typeDescriptions = [] + // typedesc rowType, anydata key, string[] fields = [], string[] + // include = [], + // typedesc[] typeDescriptions = [] // )` // which returns `record {}|persist:Error` @@ -153,10 +154,9 @@ public void notifySuccess(Object o) { public void notifyFailure(BError bError) { balFuture.complete(wrapError(bError)); } - }, trxContextProperties, unionType, + }, trxContextProperties, unionType, targetType, true, typeMap, true, key, true, fields, true, includes, true, - typeDescriptions, true - ); + typeDescriptions, true); return null; } From b7367645c6df041c84cf09ef322cf81a97048fcd Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 7 Feb 2024 15:32:30 +0530 Subject: [PATCH 34/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 1c2c7c7..64001f7 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,6 +7,18 @@ dependencies-toml-version = "2" distribution-version = "2201.8.2" +[[package]] +org = "ballerina" +name = "io" +version = "1.4.1" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + [[package]] org = "ballerina" name = "jballerina.java" @@ -15,6 +27,14 @@ modules = [ {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "persist" @@ -31,6 +51,7 @@ org = "ballerinax" name = "persist.redis" version = "0.1.0" dependencies = [ + {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, {org = "ballerinax", name = "redis"} From c3ed7043e0779c949ef2b1ce27a8dbdd89e46046 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Thu, 8 Feb 2024 09:45:55 +0530 Subject: [PATCH 35/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 64001f7..0a71641 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -46,6 +46,17 @@ modules = [ {org = "ballerina", packageName = "persist", moduleName = "persist"} ] +[[package]] +org = "ballerina" +name = "time" +version = "2.4.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "time", moduleName = "time"} +] + [[package]] org = "ballerinax" name = "persist.redis" @@ -54,6 +65,7 @@ dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, + {org = "ballerina", name = "time"}, {org = "ballerinax", name = "redis"} ] modules = [ From 781fc177fe31bbb78adb68d4e43593e866aad7e6 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Thu, 8 Feb 2024 17:17:26 +0530 Subject: [PATCH 36/65] added time:Date and time:TimeOfDay data types as supported datatypes --- ballerina/build.gradle | 63 +++++++ ballerina/field_types.bal | 32 ++++ ballerina/metadata_types.bal | 7 +- ballerina/redis_client.bal | 238 +++++++++++++++++---------- ballerina/stream_types.bal | 16 +- ballerina/tests/resources/Dockerfile | 18 ++ 6 files changed, 274 insertions(+), 100 deletions(-) create mode 100644 ballerina/field_types.bal create mode 100644 ballerina/tests/resources/Dockerfile diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 684559f..f487041 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -112,10 +112,73 @@ publishing { } } +task createRedisTestDockerImage(type: Exec) { + if (!Os.isFamily(Os.FAMILY_WINDOWS)) { + def standardOutput = new ByteArrayOutputStream() + commandLine 'sh', '-c', "docker build -f ${project.projectDir}/tests/resources/Dockerfile -t ballerina-persist-redis" + + " -q ${project.projectDir}/tests/resources" + doLast { + checkExecResult(executionResult, 'Error', standardOutput) + sleep(10 * 1000) + } + } +} + +def checkRedisTestDockerContainerStatus(containerName) { + if (!Os.isFamily(Os.FAMILY_WINDOWS)) { + try { + return exec { + commandLine 'sh', '-c', "docker exec -it ${containerName} redis-cli" + }.exitValue + } catch (all) { + return 1; + } + } +} + +task startRedisTestDockerContainer(type: Exec) { + if (!Os.isFamily(Os.FAMILY_WINDOWS)) { + def standardOutput = new ByteArrayOutputStream() + commandLine 'sh', '-c', + "docker run --rm -d --name ballerina-persist-redis -p 6379:6379 -d ballerina-persist-redis" + def healthCheck = 1; + def counter = 0; + doLast { + checkExecResult(executionResult, 'Error', standardOutput) + while (healthCheck != 0 && counter < 12) { + sleep(5 * 1000) + healthCheck = checkRedisTestDockerContainerStatus("ballerina-persist-redis") + counter = counter + 1; + } + if (healthCheck != 0) { + throw new GradleException("Docker container 'ballerina-persist-redis' health test exceeded timeout!") + } + } + } +} + +task stopRedisTestDockerContainer() { + doLast { + if (!Os.isFamily(Os.FAMILY_WINDOWS)) { + try { + def stdOut = new ByteArrayOutputStream() + exec { + commandLine 'sh', '-c', "docker stop ballerina-persist-redis" + standardOutput = stdOut + } + } catch (all) { + println("Process can safely ignore stopRedisTestDockerContainer task") + } + } + } +} updateTomlFiles.dependsOn copyStdlibs +startRedisTestDockerContainer.dependsOn createRedisTestDockerImage build.dependsOn "generatePomFileForMavenPublication" build.dependsOn ":${packageName}-native:build" +build.finalizedBy stopRedisTestDockerContainer test.dependsOn ":${packageName}-native:build" +test.dependsOn startRedisTestDockerContainer diff --git a/ballerina/field_types.bal b/ballerina/field_types.bal new file mode 100644 index 0000000..99d41e5 --- /dev/null +++ b/ballerina/field_types.bal @@ -0,0 +1,32 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +import ballerina/time; + +# Generic type that can used to store any of the types supported by Redis +# +public type RedisFieldType string|int|decimal|boolean|float|time:Date|time:TimeOfDay|time:Civil|time:Utc; + +# Generic type that can used to store any of time types supported by Redis +# +public type RedisTimeType time:Date|time:TimeOfDay; + +# Generic type that can used to store any of basic numeric types supported by Redis +# +public type RedisNumericType int|decimal|boolean|float; + +# Generic type that can used to store any of basic types supported by Redis +# +public type RedisBasicType int|string|decimal|boolean|float; diff --git a/ballerina/metadata_types.bal b/ballerina/metadata_types.bal index 3081d4a..5214116 100644 --- a/ballerina/metadata_types.bal +++ b/ballerina/metadata_types.bal @@ -57,7 +57,6 @@ public type FieldMetadata SimpleFieldMetadata|EntityFieldMetadata; # + refField - The name of the refered field in the Redis document # + refFieldDataType - The data type of the object field to which the refered field in # Redis document is mapped - public type RelationMetadata record {| string entityName; string refField; @@ -112,8 +111,12 @@ public enum DataType { INT, STRING, FLOAT, + DECIMAL, BOOLEAN, - TIME, + DATE, + TIME_OF_DAY, + CIVIL, + UTC, ENUM } diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index 19de0a6..4c5dfb3 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -1,4 +1,3 @@ -import ballerina/persist; // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. // // WSO2 LLC. licenses this file to you under the Apache License, @@ -14,7 +13,11 @@ import ballerina/persist; // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. + +import ballerina/persist; import ballerinax/redis; +import ballerina/io; +import ballerina/time; # The client used by the generated persist clients to abstract and # execute Redis database operations that are required to perform CRUD operations. @@ -22,8 +25,8 @@ public isolated client class RedisClient { private final redis:Client dbClient; - private final string & readonly entityName; - private final string & readonly collectionName; + private final string entityName; + private final string collectionName; private final map & readonly fieldMetadata; private final string[] & readonly keyFields; private final map & readonly refMetadata; @@ -39,8 +42,9 @@ public isolated client class RedisClient { self.fieldMetadata = metadata.fieldMetadata; self.keyFields = metadata.keyFields; self.dbClient = dbClient; - if metadata.refMetadata is map { - self.refMetadata = & readonly>metadata.refMetadata; + (map & readonly)? refMetadata = metadata.refMetadata; + if refMetadata is map & readonly { + self.refMetadata = refMetadata; } else { self.refMetadata = {}; } @@ -56,10 +60,11 @@ public isolated client class RedisClient { # + typeDescriptions - The type descriptions of the relations to be retrieved # + return - An `record {|anydata...;|}` containing the requested record # or a `persist:Error` if the operation fails - public isolated function runReadByKeyQuery(typedesc rowType, map typeMap, anydata key, string[] fields = [], string[] include = [], typedesc[] typeDescriptions = []) returns record {|anydata...;|}|persist:Error { - string recordKey = self.collectionName; - // Assuming the key fields are ordered - recordKey += self.getKey(key); + public isolated function runReadByKeyQuery(typedesc rowType, map typeMap, anydata key, + string[] fields = [], string[] include = [], typedesc[] typeDescriptions = []) + returns record {|anydata...;|}|persist:Error { + // Generate the key + string recordKey = string `${self.collectionName}${self.getKey(key)}`; do { // Handling simple fields record {} 'object = check self.querySimpleFieldsByKey(typeMap, recordKey, fields); @@ -69,7 +74,7 @@ public isolated client class RedisClient { self.removeNonExistOptionalFields('object); return check 'object.cloneWithType(rowType); } on fail error e { - return e; + return error persist:Error(e.message()); } } @@ -103,9 +108,8 @@ public isolated client class RedisClient { self.removeUnwantedFields('object, fields); self.removeNonExistOptionalFields('object); result.push('object); - // result.push(check 'object.cloneWithType(rowType)); - } on fail var e { - return e; + } on fail error e { + return error persist:Error(e.message()); } return stream from record {} rec in result select rec; @@ -117,10 +121,14 @@ public isolated client class RedisClient { # + return - A `string` containing the information of the database operation execution # or a `persist:Error` if the operation fails public isolated function runBatchInsertQuery(record {}[] insertRecords) returns string|persist:Error { + string|error result; // For each record, do HMSET foreach var insertRecord in insertRecords { + // Convert tye 'time:Date' and 'time:TimeOfDay' data types to string + record {} newInsertRecord = check self.getRecordWithDateTime(insertRecord); + // Generate the key string key = ""; foreach string keyField in self.keyFields { @@ -137,12 +145,12 @@ public isolated client class RedisClient { check self.checkRelationFieldConstraints(key, insertRecord); // Insert the object - result = self.dbClient->hMSet(self.collectionName + key, insertRecord); + result = self.dbClient->hMSet(self.collectionName + key, newInsertRecord); if result is error { return error persist:Error(result.message()); } - } on fail var e { - return e; + } on fail error e { + return error persist:Error(e.message()); } // Decide how to log queries @@ -155,47 +163,29 @@ public isolated client class RedisClient { # Performs redis `DEL` operation to delete an entity record from the database. # - # + keyFieldValues - The ordered keys used to delete an entity record + # + key - The ordered keys used to delete an entity record # + return - `()` if the operation is performed successfully # or a `persist:Error` if the operation fails - public isolated function runDeleteQuery(any[] keyFieldValues) returns persist:Error? { - - // Validate fields - if keyFieldValues.length() != self.keyFields.length() { - return error("Missing keyfields"); - } - + public isolated function runDeleteQuery(anydata key) returns persist:Error? { // Generate the key - string recordKey = self.collectionName; - foreach any value in keyFieldValues { - recordKey += string `${KEY_SEPERATOR}${value.toString()}`; - } + string recordKey = string `${self.collectionName}${self.getKey(key)}`; do { // Delete the record _ = check self.dbClient->del([recordKey]); - } on fail var e { - return e; + } on fail error e { + return error persist:Error(e.message()); } } # Performs redis `HSET` operation to delete an entity record from the database. # - # + keyFieldValues - The ordered keys used to update an entity record + # + key - The ordered keys used to update an entity record # + updateRecord - The new record to be updated # + return - An Error if the new record is missing a keyfield - public isolated function runUpdateQuery(any[] keyFieldValues, record {} updateRecord) returns error? { - - // Validate fields - if keyFieldValues.length() != self.keyFields.length() { - return error("Missing keyfields"); - } - + public isolated function runUpdateQuery(anydata key, record {} updateRecord) returns error? { // Generate the key - string key = self.collectionName; - foreach any keyFieldValue in keyFieldValues { - key += string `${KEY_SEPERATOR}${keyFieldValue.toString()}`; - } + string recordKey = string `${self.collectionName}${self.getKey(key)}`; // Update only the given fields that is not nil foreach [string, FieldMetadata & readonly] metaDataEntry in self.fieldMetadata.entries() { @@ -203,9 +193,11 @@ public isolated client class RedisClient { // If the field is a simple field if fieldMetadataValue is SimpleFieldMetadata { - if (updateRecord.hasKey(fieldMetadataValue.fieldName) && updateRecord[fieldMetadataValue.fieldName] != ()) { + if updateRecord.hasKey(fieldMetadataValue.fieldName) + && updateRecord[fieldMetadataValue.fieldName] != () { // updating the object - _ = check self.dbClient->hSet(key, fieldMetadataValue.fieldName, updateRecord[fieldMetadataValue.fieldName].toString()); + _ = check self.dbClient->hSet(recordKey, fieldMetadataValue.fieldName, + updateRecord[fieldMetadataValue.fieldName].toString()); } } else { // If the field is a relation field @@ -220,7 +212,8 @@ public isolated client class RedisClient { # + fields - The fields to be retrieved # + include - The associations to be retrieved # + return - A `persist:Error` if the operation fails - public isolated function getManyRelations(map typeMap, record {} 'object, string[] fields, string[] include) returns persist:Error? { + public isolated function getManyRelations(map typeMap, record {} 'object, string[] fields, + string[] include) returns persist:Error? { foreach int i in 0 ..< include.length() { string entity = include[i]; @@ -242,12 +235,13 @@ public isolated client class RedisClient { } } - if relationFields.length() is 0 { + if relationFields.length() == 0 { continue; } - string[]|error keys = self.dbClient->keys(string `${entity.substring(0, 1).toUpperAscii()}${entity.substring(1)}${KEY_SEPERATOR}*`); - if keys is error || (keys.length() == 0) { + string[]|error keys = self.dbClient->keys( + string `${entity.substring(0, 1).toUpperAscii()}${entity.substring(1)}${KEY_SEPERATOR}*`); + if keys is error || keys.length() == 0 { if cardinalityType == ONE_TO_MANY { 'object[entity] = []; } else { @@ -290,8 +284,8 @@ public isolated client class RedisClient { 'object[entity] = associatedRecords; } } - } on fail var e { - return e; + } on fail persist:Error e { + return e; } } @@ -302,17 +296,17 @@ public isolated client class RedisClient { // Private helper methods private isolated function getKey(anydata key) returns string { string keyValue = ""; - if key is map { - foreach string compositeKey in key.keys() { + if key is map { + foreach string compositeKey in self.keyFields { keyValue += string `${KEY_SEPERATOR}${key[compositeKey].toString()}`; } return keyValue; - } else { - return string `${KEY_SEPERATOR}${key.toString()}`; } + return string `${KEY_SEPERATOR}${key.toString()}`; } - private isolated function querySimpleFieldsByKey(map typeMap, string key, string[] fields) returns record {|anydata...;|}|error { + private isolated function querySimpleFieldsByKey(map typeMap, string key, string[] fields) + returns record {|anydata...;|}|error { // hadling the simple fields string[] simpleFields = self.getTargetSimpleFields(fields, typeMap); // If no simpleFields given, then add all the fields by default @@ -337,12 +331,11 @@ public isolated client class RedisClient { foreach string fieldKey in value.keys() { // convert the data type from 'any' to required type valueToRecord[fieldKey] = check self.dataConverter( - self.fieldMetadata[fieldKey] - , value[fieldKey]); + self.fieldMetadata[fieldKey], value[fieldKey]); } return valueToRecord; - } on fail var e { - return e; + } on fail error e { + return error persist:Error(e.message()); } } @@ -375,20 +368,18 @@ public isolated client class RedisClient { foreach string fieldKey in value.keys() { // convert the data type from 'any' to required type valueToRecord[fieldKey] = check self.dataConverter( - self.fieldMetadata[fieldMetadataKeyPrefix + fieldKey] - , value[fieldKey]); + self.fieldMetadata[fieldMetadataKeyPrefix + fieldKey], value[fieldKey]); } return valueToRecord; - } on fail var e { - return e; + } on fail error e { + return error persist:Error(e.message()); } } private isolated function getTargetSimpleFields(string[] fields, map typeMap) returns string[] { - string[] simpleFields = from string 'field in fields + return from string 'field in fields where !'field.includes(".") && typeMap.hasKey('field) select 'field; - return simpleFields; } private isolated function removeNonExistOptionalFields(record {} 'object) { @@ -400,13 +391,12 @@ public isolated client class RedisClient { } private isolated function isNoRecordFound(map value) returns boolean { - boolean isNoRecordExists = true; foreach string key in value.keys() { if value[key] != () { - isNoRecordExists = false; + return false; } } - return isNoRecordExists; + return true; } private isolated function removeUnwantedFields(record {} 'object, string[] fields) { @@ -429,7 +419,7 @@ public isolated client class RedisClient { boolean isRelationConstraintFalied = true; // If the mandatory reference field is not exist or being null foreach string joinField in refMetadataValue.joinFields { - if (insertRecord.hasKey(joinField) && insertRecord[joinField] != ()) { + if insertRecord.hasKey(joinField) && insertRecord[joinField] != () { isRelationConstraintFalied = false; break; } @@ -443,18 +433,18 @@ public isolated client class RedisClient { } // Check the cardinality of refered entity object - int|error sCard = self.dbClient->sCard(string `${refRecordKey}${KEY_SEPERATOR}${self.collectionName}`); - if sCard is int { - if sCard > 0 && refMetadataValue.'type == ONE_TO_ONE { - // If the refered object is already in a relationship - return getConstraintViolationError(self.entityName, refMetadataValue.refCollection); - } + int|error sCard = self.dbClient->sCard( + string `${refRecordKey}${KEY_SEPERATOR}${self.collectionName}`); + if sCard is int && sCard > 0 && refMetadataValue.'type == ONE_TO_ONE { + // If the refered object is already in a relationship + return getConstraintViolationError(self.entityName, refMetadataValue.refCollection); } // Relate current record with the refered record in the database - int|error sAdd = self.dbClient->sAdd(string `${refRecordKey}${KEY_SEPERATOR}${self.collectionName}`, [key]); + int|error sAdd = self.dbClient->sAdd( + string `${refRecordKey}${KEY_SEPERATOR}${self.collectionName}`, [key]); if sAdd is error { - return sAdd; + return error persist:Error(sAdd.message()); } map value = check self.dbClient->hMGet(refRecordKey, refMetadataValue.refFields); @@ -462,41 +452,107 @@ public isolated client class RedisClient { return getConstraintViolationError(self.entityName, refMetadataValue.refCollection); } } - } on fail var e { - return e; + } on fail error e { + return error persist:Error(e.message()); } } } - private isolated function dataConverter(FieldMetadata & readonly fieldMetaData, any value) returns ()|boolean|string|float|error|int { + private isolated function getRecordWithDateTime(record {} insertRecord) returns record {}|persist:Error { + record {} newRecord = {}; + foreach string recordfield in insertRecord.keys() { + (FieldMetadata & readonly)? fieldMetaDataValue = self.fieldMetadata[recordfield]; + if fieldMetaDataValue is SimpleFieldMetadata { + DataType dataType = fieldMetaDataValue.fieldDataType; + + if dataType == DATE || dataType == TIME_OF_DAY { + RedisTimeType|error timeValue = insertRecord.get(recordfield).ensureType(); + if timeValue is error { + return error persist:Error(timeValue.message()); + } + + string|persist:Error? timeValueInString = self.timeToString(timeValue); + if timeValueInString is persist:Error { + return timeValueInString; + } + + io:println(typeof insertRecord[recordfield]); + newRecord[recordfield] = timeValueInString; + }else{ + newRecord[recordfield] = insertRecord[recordfield]; + } + } + } + return newRecord; + } + + private isolated function timeToString(RedisTimeType timeValue) returns string|persist:Error? { + if timeValue is time:Date { + return string `${timeValue.day}-${timeValue.month}-${timeValue.year}`; + } + + if timeValue is time:TimeOfDay { + return string `${timeValue.hour}-${timeValue.minute}-${(timeValue.second).toString()}`; + } + + return error persist:Error("Error: unsupported time format"); + } + + private isolated function stringToTime(string timeValue, DataType dataType) returns RedisTimeType|error { + if dataType == TIME_OF_DAY { + string[] timeValues = re `-`.split(timeValue); + time:TimeOfDay output = {hour: check int:fromString(timeValues[0]), minute: check int:fromString(timeValues[1]), second: check decimal:fromString(timeValues[2])}; + return output; + } else if dataType == DATE { + string[] timeValues = re `-`.split(timeValue); + time:Date output = {day: check int:fromString(timeValues[0]), month: check int:fromString(timeValues[1]), year: check int:fromString(timeValues[2])}; + return output; + } else { + return error persist:Error("Error: unsupported time format"); + } + } + + private isolated function dataConverter(FieldMetadata & readonly fieldMetaData, anydata value) returns ()|boolean|string|float|decimal|int|RedisTimeType|error { // Return nil if value is nil if value is () { return (); } - if ((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == INT) - || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == INT)) { + if (fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == INT) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == INT) { return check int:fromString(value); - } else if ((fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == STRING)) - || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == STRING)) { + } else if (fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == STRING)) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == STRING) { return value; - } else if ((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == FLOAT) - || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == FLOAT)) { + } else if (fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == FLOAT) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == FLOAT) { return check float:fromString(value); - } else if ((fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == BOOLEAN) - || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == BOOLEAN)) { + }else if (fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == DECIMAL) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == DECIMAL) { + return check decimal:fromString(value); + + } else if (fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == BOOLEAN) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == BOOLEAN) { return check boolean:fromString(value); - } else if ((fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == ENUM)) - || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == ENUM)) { + } else if (fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == ENUM)) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == ENUM) { return value; + } else if (fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == DATE)) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == DATE) { + return self.stringToTime(value, DATE); + + } else if (fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == TIME_OF_DAY)) + || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == TIME_OF_DAY) { + return self.stringToTime(value, TIME_OF_DAY); + } else { - return error("Unsupported Data Format"); + return error persist:Error("Unsupported Data Format"); } } diff --git a/ballerina/stream_types.bal b/ballerina/stream_types.bal index 80aeca0..59bd9c5 100644 --- a/ballerina/stream_types.bal +++ b/ballerina/stream_types.bal @@ -44,20 +44,21 @@ public class PersistRedisStream { public isolated function next() returns record {|record {} value;|}|persist:Error? { if self.err is persist:Error { - return self.err; + return self.err; } else if self.anydataStream is stream { var anydataStream = >self.anydataStream; var streamValue = anydataStream.next(); if streamValue is () { return streamValue; } else if (streamValue is error) { - return error(streamValue.message()); + return error persist:Error(streamValue.message()); } else { record {}|error value = streamValue.value; if value is error { - return error(value.message()); + return error persist:Error(value.message()); } - check (self.persistClient).getManyRelations(self.typeMap, value, self.fields, self.include); + check (self.persistClient).getManyRelations(self.typeMap, value, self.fields, + self.include); string[] keyFields = (self.persistClient).getKeyFields(); foreach string keyField in keyFields { @@ -74,10 +75,11 @@ public class PersistRedisStream { } public isolated function close() returns persist:Error? { - if self.anydataStream is stream { - error? e = (>self.anydataStream).close(); + (stream)? str = self.anydataStream; + if str is stream { + error? e = str.close(); if e is error { - return error(e.message()); + return error persist:Error(e.message()); } } } diff --git a/ballerina/tests/resources/Dockerfile b/ballerina/tests/resources/Dockerfile new file mode 100644 index 0000000..f686ce5 --- /dev/null +++ b/ballerina/tests/resources/Dockerfile @@ -0,0 +1,18 @@ +# Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +# +# WSO2 LLC. licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +FROM redis:latest +EXPOSE 6379 From 4a402b31aab2587f7a584565fc8d4a580e265f0f Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Mon, 12 Feb 2024 04:51:52 +0530 Subject: [PATCH 37/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 0a71641..404feeb 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -27,6 +27,15 @@ modules = [ {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "lang.error" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "lang.value" @@ -46,6 +55,19 @@ modules = [ {org = "ballerina", packageName = "persist", moduleName = "persist"} ] +[[package]] +org = "ballerina" +name = "test" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.error"} +] +modules = [ + {org = "ballerina", packageName = "test", moduleName = "test"} +] + [[package]] org = "ballerina" name = "time" @@ -65,6 +87,7 @@ dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, + {org = "ballerina", name = "test"}, {org = "ballerina", name = "time"}, {org = "ballerinax", name = "redis"} ] From 0ced4d5b5859a31f9bd71ebede5b514cbdfdc9f4 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Tue, 13 Feb 2024 11:27:47 +0530 Subject: [PATCH 38/65] added test cases --- ballerina/build.gradle | 68 +- ballerina/errors.bal | 11 +- ballerina/redis_client.bal | 26 +- ballerina/stream_types.bal | 6 + ballerina/tests/init-test.bal | 725 ++++++++++++++++++ ballerina/tests/persist/rainier.bal | 67 ++ ballerina/tests/persist/test_entities.bal | 85 ++ ballerina/tests/rainier_generated_types.bal | 172 +++++ ballerina/tests/redis-all-types-tests.bal | 216 ++++++ ballerina/tests/redis-associations-tests.bal | 288 +++++++ ballerina/tests/redis-building-tests.bal | 226 ++++++ ballerina/tests/redis-composite-key-tests.bal | 196 +++++ ballerina/tests/redis-department-tests.bal | 217 ++++++ ballerina/tests/redis-employee-tests.bal | 260 +++++++ ballerina/tests/redis-id-fields-tests.bal | 477 ++++++++++++ ballerina/tests/redis-transaction-tests.bal | 75 ++ ballerina/tests/redis-workspace-tests.bal | 251 ++++++ .../tests/redis_rainier_generated_client.bal | 360 +++++++++ .../redis_test_entities_generated_client.bal | 496 ++++++++++++ .../tests/test_entities_generated_types.bal | 289 +++++++ .../redis/datastore/RedisProcessor.java | 4 +- 21 files changed, 4500 insertions(+), 15 deletions(-) create mode 100644 ballerina/tests/init-test.bal create mode 100644 ballerina/tests/persist/rainier.bal create mode 100644 ballerina/tests/persist/test_entities.bal create mode 100644 ballerina/tests/rainier_generated_types.bal create mode 100644 ballerina/tests/redis-all-types-tests.bal create mode 100644 ballerina/tests/redis-associations-tests.bal create mode 100644 ballerina/tests/redis-building-tests.bal create mode 100644 ballerina/tests/redis-composite-key-tests.bal create mode 100644 ballerina/tests/redis-department-tests.bal create mode 100644 ballerina/tests/redis-employee-tests.bal create mode 100644 ballerina/tests/redis-id-fields-tests.bal create mode 100644 ballerina/tests/redis-transaction-tests.bal create mode 100644 ballerina/tests/redis-workspace-tests.bal create mode 100644 ballerina/tests/redis_rainier_generated_client.bal create mode 100644 ballerina/tests/redis_test_entities_generated_client.bal create mode 100644 ballerina/tests/test_entities_generated_types.bal diff --git a/ballerina/build.gradle b/ballerina/build.gradle index f487041..2e39528 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -112,6 +112,21 @@ publishing { } } +static def checkExecResult(executionResult, failText, standardOutput) { + if (executionResult != null) { + Provider execResultProvider = executionResult.getProvider() + int exitCode = execResultProvider.get().getExitValue() + if (exitCode != 0) { + throw new GradleException('Non-zero exit value: ' + exitCode) + } + if (standardOutput.toString().contains(failText)) { + throw new GradleException('"' + failText + '" string in output: ' + standardOutput.toString()) + } + } else { + throw new GradleException('Returned a null execResult object') + } +} + task createRedisTestDockerImage(type: Exec) { if (!Os.isFamily(Os.FAMILY_WINDOWS)) { def standardOutput = new ByteArrayOutputStream() @@ -121,6 +136,14 @@ task createRedisTestDockerImage(type: Exec) { checkExecResult(executionResult, 'Error', standardOutput) sleep(10 * 1000) } + } else { + def standardOutput = new ByteArrayOutputStream() + commandLine 'cmd', '/c', "docker build -f ${project.projectDir}/tests/resources/Dockerfile -t ballerina-persist-redis" + + " -q ${project.projectDir}/tests/resources" + doLast { + checkExecResult(executionResult, 'Error', standardOutput) + sleep(10 * 1000) + } } } @@ -128,7 +151,15 @@ def checkRedisTestDockerContainerStatus(containerName) { if (!Os.isFamily(Os.FAMILY_WINDOWS)) { try { return exec { - commandLine 'sh', '-c', "docker exec -it ${containerName} redis-cli" + commandLine 'sh', '-c', "docker exec ${containerName} redis-cli" + }.exitValue + } catch (all) { + return 1; + } + } else { + try { + return exec { + commandLine 'cmd', '/c', "docker exec ${containerName} redis-cli" }.exitValue } catch (all) { return 1; @@ -154,6 +185,23 @@ task startRedisTestDockerContainer(type: Exec) { throw new GradleException("Docker container 'ballerina-persist-redis' health test exceeded timeout!") } } + } else { + def standardOutput = new ByteArrayOutputStream() + commandLine 'cmd', '/c', + "docker run --rm -d --name ballerina-persist-redis -p 6379:6379 -d ballerina-persist-redis" + def healthCheck = 1; + def counter = 0; + doLast { + checkExecResult(executionResult, 'Error', standardOutput) + while (healthCheck != 0 && counter < 12) { + sleep(5 * 1000) + healthCheck = checkRedisTestDockerContainerStatus("ballerina-persist-redis") + counter = counter + 1; + } + if (healthCheck != 0) { + throw new GradleException("Docker container 'ballerina-persist-redis' health test exceeded timeout!") + } + } } } @@ -169,16 +217,26 @@ task stopRedisTestDockerContainer() { } catch (all) { println("Process can safely ignore stopRedisTestDockerContainer task") } + } else { + try { + def stdOut = new ByteArrayOutputStream() + exec { + commandLine 'cmd', '/c', "docker stop ballerina-persist-redis" + standardOutput = stdOut + } + } catch (all) { + println("Process can safely ignore stopRedisTestDockerContainer task") + } } } } updateTomlFiles.dependsOn copyStdlibs -startRedisTestDockerContainer.dependsOn createRedisTestDockerImage +// startRedisTestDockerContainer.dependsOn createRedisTestDockerImage -build.dependsOn "generatePomFileForMavenPublication" +// build.dependsOn "generatePomFileForMavenPublication" build.dependsOn ":${packageName}-native:build" -build.finalizedBy stopRedisTestDockerContainer +// build.finalizedBy stopRedisTestDockerContainer test.dependsOn ":${packageName}-native:build" -test.dependsOn startRedisTestDockerContainer +// test.dependsOn startRedisTestDockerContainer diff --git a/ballerina/errors.bal b/ballerina/errors.bal index b4332d9..baf89a1 100644 --- a/ballerina/errors.bal +++ b/ballerina/errors.bal @@ -15,14 +15,17 @@ // under the License. import ballerina/persist; -public type ConstraintViolationError distinct persist:ConstraintViolationError; +// public type ConstraintViolationError { +// *persist:ConstraintViolationError; +// }; -# Generates a new `persist:AlreadyExistsError` with the given parameters. +# Generates a new `persist:ConstraintViolationError` with the given parameters. # # + entity - The name of the entity # + refEntity - The entity is being reffered # + return - The generated `persist:ConstraintViolationError` -public isolated function getConstraintViolationError(string entity, string refEntity) returns ConstraintViolationError { +public isolated function getConstraintViolationError(string entity, string refEntity) +returns persist:ConstraintViolationError { string message = string `An association constraint failed between entities '${entity}' and '${refEntity}'`; - return error ConstraintViolationError(message); + return error persist:ConstraintViolationError(message); } diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index 4c5dfb3..af02106 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -72,6 +72,8 @@ public isolated client class RedisClient { check self.getManyRelations(typeMap, 'object, fields, include); self.removeUnwantedFields('object, fields); self.removeNonExistOptionalFields('object); + io:println("cloneType in read by key"); + io:println(rowType); return check 'object.cloneWithType(rowType); } on fail error e { return error persist:Error(e.message()); @@ -127,7 +129,7 @@ public isolated client class RedisClient { foreach var insertRecord in insertRecords { // Convert tye 'time:Date' and 'time:TimeOfDay' data types to string - record {} newInsertRecord = check self.getRecordWithDateTime(insertRecord); + record {} newInsertRecord = check self.newRecordWithDateTime(insertRecord); // Generate the key string key = ""; @@ -142,7 +144,10 @@ public isolated client class RedisClient { } // Check for any relation field constraints - check self.checkRelationFieldConstraints(key, insertRecord); + persist:Error? checkConstraints = self.checkRelationFieldConstraints(key, insertRecord); + if checkConstraints is persist:ConstraintViolationError{ + return checkConstraints; + } // Insert the object result = self.dbClient->hMSet(self.collectionName + key, newInsertRecord); @@ -183,7 +188,7 @@ public isolated client class RedisClient { # + key - The ordered keys used to update an entity record # + updateRecord - The new record to be updated # + return - An Error if the new record is missing a keyfield - public isolated function runUpdateQuery(anydata key, record {} updateRecord) returns error? { + public isolated function runUpdateQuery(anydata key, record {} updateRecord) returns persist:Error? { // Generate the key string recordKey = string `${self.collectionName}${self.getKey(key)}`; @@ -202,6 +207,8 @@ public isolated client class RedisClient { } else { // If the field is a relation field } + } on fail error e { + return error persist:Error(e.message()); } } @@ -214,7 +221,7 @@ public isolated client class RedisClient { # + return - A `persist:Error` if the operation fails public isolated function getManyRelations(map typeMap, record {} 'object, string[] fields, string[] include) returns persist:Error? { - + io:println(include); foreach int i in 0 ..< include.length() { string entity = include[i]; CardinalityType cardinalityType = ONE_TO_MANY; @@ -223,6 +230,8 @@ public isolated client class RedisClient { string[] relationFields = from string 'field in fields where 'field.startsWith(string `${entity}${MANY_ASSOCIATION_SEPERATOR}`) select 'field.substring(entity.length() + 3, 'field.length()); + io:println("one to many check"); + io:println(relationFields); // checking for one to one relationships if relationFields.length() == 0 { @@ -234,6 +243,8 @@ public isolated client class RedisClient { cardinalityType = ONE_TO_ONE; } } + io:println("one to one check"); + io:println(relationFields); if relationFields.length() == 0 { continue; @@ -241,8 +252,11 @@ public isolated client class RedisClient { string[]|error keys = self.dbClient->keys( string `${entity.substring(0, 1).toUpperAscii()}${entity.substring(1)}${KEY_SEPERATOR}*`); + io:println(keys); if keys is error || keys.length() == 0 { + io:println("this should be printed"); if cardinalityType == ONE_TO_MANY { + io:println("this should be printed too"); 'object[entity] = []; } else { 'object[entity] = {}; @@ -282,6 +296,8 @@ public isolated client class RedisClient { 'object[entity] = associatedRecords[0]; } else { 'object[entity] = associatedRecords; + io:println("Object with one to many associations"); + io:println('object); } } } on fail persist:Error e { @@ -458,7 +474,7 @@ public isolated client class RedisClient { } } - private isolated function getRecordWithDateTime(record {} insertRecord) returns record {}|persist:Error { + private isolated function newRecordWithDateTime(record {} insertRecord) returns record {}|persist:Error { record {} newRecord = {}; foreach string recordfield in insertRecord.keys() { (FieldMetadata & readonly)? fieldMetaDataValue = self.fieldMetadata[recordfield]; diff --git a/ballerina/stream_types.bal b/ballerina/stream_types.bal index 59bd9c5..c081f2a 100644 --- a/ballerina/stream_types.bal +++ b/ballerina/stream_types.bal @@ -14,6 +14,7 @@ // specific language governing permissions and limitations // under the License. import ballerina/persist; +import ballerina/io; public class PersistRedisStream { @@ -66,6 +67,11 @@ public class PersistRedisStream { _ = value.remove(keyField); } } + + io:println("value in the strea_types.bal"); + io:println(value); + io:println(self.targetType); + // return {value: value}; record {|record {} value;|} nextRecord = {value: checkpanic value.cloneWithType(self.targetType)}; return nextRecord; } diff --git a/ballerina/tests/init-test.bal b/ballerina/tests/init-test.bal new file mode 100644 index 0000000..ab7457a --- /dev/null +++ b/ballerina/tests/init-test.bal @@ -0,0 +1,725 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/time; +import ballerinax/redis; + +configurable record {| + int port; + string host; + string password; + redis:Options connectionOptions = {}; +|} redis = ?; + +@test:BeforeSuite +function initTests() returns error? { + + redis:Client redisDbClient = check new (config = {host: string `${redis.host}:${redis.port}`, password: redis.password, options: redis.connectionOptions}); + redisDbClient.stop(); +} + +AllTypes allTypes1 = { + id: 1, + booleanType: false, + intType: 5, + floatType: 6.0, + decimalType: 23.44, + stringType: "test-2", + dateType: {year: 1993, month: 11, day: 3}, + timeOfDayType: {hour: 12, minute: 32, second: 34}, + booleanTypeOptional: false, + intTypeOptional: 5, + floatTypeOptional: 6.0, + decimalTypeOptional: 23.44, + stringTypeOptional: "test", + dateTypeOptional: {year: 1993, month: 11, day: 3}, + timeOfDayTypeOptional: {hour: 12, minute: 32, second: 34}, + enumType: "TYPE_3", + enumTypeOptional: "TYPE_2" +}; + +AllTypes allTypes1Expected = { + id: allTypes1.id, + booleanType: allTypes1.booleanType, + intType: allTypes1.intType, + floatType: allTypes1.floatType, + decimalType: allTypes1.decimalType, + stringType: allTypes1.stringType, + dateType: allTypes1.dateType, + timeOfDayType: allTypes1.timeOfDayType, + booleanTypeOptional: allTypes1.booleanTypeOptional, + intTypeOptional: allTypes1.intTypeOptional, + floatTypeOptional: allTypes1.floatTypeOptional, + decimalTypeOptional: allTypes1.decimalTypeOptional, + stringTypeOptional: allTypes1.stringTypeOptional, + dateTypeOptional: allTypes1.dateTypeOptional, + timeOfDayTypeOptional: allTypes1.timeOfDayTypeOptional, + enumType: allTypes1.enumType, + enumTypeOptional: allTypes1.enumTypeOptional +}; + +AllTypes allTypes2 = { + id: 2, + booleanType: true, + intType: 35, + floatType: 63.0, + decimalType: 233.44, + stringType: "test2", + dateType: {year: 1996, month: 11, day: 3}, + timeOfDayType: {hour: 17, minute: 32, second: 34}, + booleanTypeOptional: true, + intTypeOptional: 6, + floatTypeOptional: 66.0, + decimalTypeOptional: 233.44, + stringTypeOptional: "test2", + dateTypeOptional: {year: 1293, month: 11, day: 3}, + timeOfDayTypeOptional: {hour: 19, minute: 32, second: 34}, + enumType: "TYPE_1", + enumTypeOptional: "TYPE_3" +}; + +AllTypes allTypes2Expected = { + id: allTypes2.id, + booleanType: allTypes2.booleanType, + intType: allTypes2.intType, + floatType: allTypes2.floatType, + decimalType: allTypes2.decimalType, + stringType: allTypes2.stringType, + dateType: allTypes2.dateType, + timeOfDayType: allTypes2.timeOfDayType, + booleanTypeOptional: allTypes2.booleanTypeOptional, + intTypeOptional: allTypes2.intTypeOptional, + floatTypeOptional: allTypes2.floatTypeOptional, + decimalTypeOptional: allTypes2.decimalTypeOptional, + stringTypeOptional: allTypes2.stringTypeOptional, + dateTypeOptional: allTypes2.dateTypeOptional, + timeOfDayTypeOptional: allTypes2.timeOfDayTypeOptional, + enumType: allTypes2.enumType, + enumTypeOptional: allTypes2.enumTypeOptional +}; + +AllTypes allTypes3 = { + id: 3, + booleanType: true, + intType: 35, + floatType: 63.0, + decimalType: 233.44, + stringType: "test2", + dateType: {year: 1996, month: 11, day: 3}, + timeOfDayType: {hour: 17, minute: 32, second: 34}, + booleanTypeOptional: (), + intTypeOptional: (), + floatTypeOptional: (), + decimalTypeOptional: (), + stringTypeOptional: (), + dateTypeOptional: (), + timeOfDayTypeOptional: (), + enumType: "TYPE_1", + enumTypeOptional: () +}; + +AllTypes allTypes3Expected = { + id: allTypes3.id, + booleanType: allTypes3.booleanType, + intType: allTypes3.intType, + floatType: allTypes3.floatType, + decimalType: allTypes3.decimalType, + stringType: allTypes3.stringType, + dateType: allTypes3.dateType, + timeOfDayType: allTypes3.timeOfDayType, + booleanTypeOptional: allTypes3.booleanTypeOptional, + intTypeOptional: allTypes3.intTypeOptional, + floatTypeOptional: allTypes3.floatTypeOptional, + decimalTypeOptional: allTypes3.decimalTypeOptional, + stringTypeOptional: allTypes3.stringTypeOptional, + dateTypeOptional: allTypes3.dateTypeOptional, + timeOfDayTypeOptional: allTypes3.timeOfDayTypeOptional, + enumType: allTypes3.enumType, + enumTypeOptional: allTypes3.enumTypeOptional +}; + +AllTypes allTypes1Updated = { + id: 1, + booleanType: true, + intType: 99, + floatType: 63.0, + decimalType: 53.44, + stringType: "testUpdate", + dateType: {year: 1996, month: 12, day: 13}, + timeOfDayType: {hour: 16, minute: 12, second: 14}, + booleanTypeOptional: true, + intTypeOptional: 53, + floatTypeOptional: 26.0, + decimalTypeOptional: 223.44, + stringTypeOptional: "testUpdate", + dateTypeOptional: {year: 1923, month: 11, day: 3}, + timeOfDayTypeOptional: {hour: 18, minute: 32, second: 34}, + enumType: "TYPE_4", + enumTypeOptional: "TYPE_4" +}; + +AllTypes allTypes1UpdatedExpected = { + id: allTypes1Updated.id, + booleanType: allTypes1Updated.booleanType, + intType: allTypes1Updated.intType, + floatType: allTypes1Updated.floatType, + decimalType: allTypes1Updated.decimalType, + stringType: allTypes1Updated.stringType, + dateType: allTypes1Updated.dateType, + timeOfDayType: allTypes1Updated.timeOfDayType, + booleanTypeOptional: allTypes1Updated.booleanTypeOptional, + intTypeOptional: allTypes1Updated.intTypeOptional, + floatTypeOptional: allTypes1Updated.floatTypeOptional, + decimalTypeOptional: allTypes1Updated.decimalTypeOptional, + stringTypeOptional: allTypes1Updated.stringTypeOptional, + dateTypeOptional: allTypes1Updated.dateTypeOptional, + timeOfDayTypeOptional: allTypes1Updated.timeOfDayTypeOptional, + enumType: allTypes1Updated.enumType, + enumTypeOptional: allTypes1Updated.enumTypeOptional +}; + +public type AllTypesDependent record {| + boolean booleanType; + int intType; + float floatType; + decimal decimalType; + string stringType; + time:Date dateType; + time:TimeOfDay timeOfDayType; + boolean? booleanTypeOptional; + int? intTypeOptional; + float? floatTypeOptional; + decimal? decimalTypeOptional; + string? stringTypeOptional; + time:Date? dateTypeOptional; + time:TimeOfDay? timeOfDayTypeOptional; +|}; + +OrderItemExtended orderItemExtended1 = { + orderId: "order-1", + itemId: "item-1", + CustomerId: 1, + paid: false, + ammountPaid: 10.5f, + ammountPaidDecimal: 10.5, + arivalTimeDate: {year: 2021, month: 4, day: 12}, + arivalTimeTimeOfDay: {hour: 17, minute: 50, second: 50.52}, + orderType: INSTORE +}; + +OrderItemExtended orderItemExtendedRetrieved = { + orderId: "order-1", + itemId: "item-1", + CustomerId: 1, + paid: false, + ammountPaid: 10.5f, + ammountPaidDecimal: 10.5, + arivalTimeDate: {year: 2021, month: 4, day: 12}, + arivalTimeTimeOfDay: {hour: 17, minute: 50, second: 50.52}, + orderType: INSTORE +}; + +OrderItemExtended orderItemExtended2 = { + orderId: "order-2", + itemId: "item-2", + CustomerId: 1, + paid: false, + ammountPaid: 10.5f, + ammountPaidDecimal: 10.5, + arivalTimeDate: {year: 2021, month: 4, day: 12}, + arivalTimeTimeOfDay: {hour: 17, minute: 50, second: 50.52}, + orderType: ONLINE +}; + +public type EmployeeInfo record {| + string firstName; + string lastName; + record {| + string deptName; + |} department; + Workspace workspace; +|}; + +OrderItemExtended orderItemExtended2Retrieved = { + orderId: "order-2", + itemId: "item-2", + CustomerId: 1, + paid: false, + ammountPaid: 10.5f, + ammountPaidDecimal: 10.5, + arivalTimeDate: {year: 2021, month: 4, day: 12}, + arivalTimeTimeOfDay: {hour: 17, minute: 50, second: 50.52}, + orderType: ONLINE +}; + +OrderItemExtended orderItemExtended3 = { + orderId: "order-3", + itemId: "item-3", + CustomerId: 4, + paid: true, + ammountPaid: 20.5f, + ammountPaidDecimal: 20.5, + arivalTimeDate: {year: 2021, month: 4, day: 12}, + arivalTimeTimeOfDay: {hour: 17, minute: 50, second: 50.52}, + orderType: INSTORE +}; + +OrderItemExtended orderItemExtended3Retrieved = { + orderId: "order-2", + itemId: "item-2", + CustomerId: 1, + paid: true, + ammountPaid: 10.5f, + ammountPaidDecimal: 10.5, + arivalTimeDate: {year: 2021, month: 4, day: 12}, + arivalTimeTimeOfDay: {hour: 17, minute: 50, second: 50.52}, + orderType: ONLINE +}; + +public type DepartmentInfo record {| + string deptNo; + string deptName; + record {| + string firstName; + string lastName; + |}[] employees; +|}; + +public type WorkspaceInfo record {| + string workspaceType; + Building location; + Employee[] employees; +|}; + +public type BuildingInfo record {| + string buildingCode; + string city; + string state; + string country; + string postalCode; + string 'type; + Workspace[] workspaces; +|}; + +Building building1 = { + buildingCode: "building-1", + city: "Colombo", + state: "Western Province", + country: "Sri Lanka", + postalCode: "10370", + 'type: "rented" +}; + +Building invalidBuilding = { + buildingCode: "building-invalid-extra-characters-to-force-failure", + city: "Colombo", + state: "Western Province", + country: "Sri Lanka", + postalCode: "10370", + 'type: "owned" +}; + +BuildingInsert building2 = { + buildingCode: "building-2", + city: "Manhattan", + state: "New York", + country: "USA", + postalCode: "10570", + 'type: "owned" +}; + +BuildingInsert building3 = { + buildingCode: "building-3", + city: "London", + state: "London", + country: "United Kingdom", + postalCode: "39202", + 'type: "rented" +}; + +Building updatedBuilding1 = { + buildingCode: "building-1", + city: "Galle", + state: "Southern Province", + country: "Sri Lanka", + postalCode: "10890", + 'type: "owned" +}; + +Department department1 = { + deptNo: "department-1", + deptName: "Finance" +}; + +Department invalidDepartment = { + deptNo: "invalid-department-extra-characters-to-force-failure", + deptName: "Finance" +}; + +Department department2 = { + deptNo: "department-2", + deptName: "Marketing" +}; + +Department department3 = { + deptNo: "department-3", + deptName: "Engineering" +}; + +Department updatedDepartment1 = { + deptNo: "department-1", + deptName: "Finance & Legalities" +}; + +Employee employee1 = { + empNo: "employee-1", + firstName: "Tom", + lastName: "Scott", + birthDate: {year: 1992, month: 11, day: 13}, + gender: MALE, + hireDate: {year: 2022, month: 8, day: 1}, + departmentDeptNo: "department-2", + workspaceWorkspaceId: "workspace-2" +}; + +Employee invalidEmployee = { + empNo: "invalid-employee-no-extra-characters-to-force-failure", + firstName: "Tom", + lastName: "Scott", + birthDate: {year: 1992, month: 11, day: 13}, + gender: MALE, + hireDate: {year: 2022, month: 8, day: 1}, + departmentDeptNo: "department-2", + workspaceWorkspaceId: "workspace-2" +}; + +Employee employee2 = { + empNo: "employee-2", + firstName: "Jane", + lastName: "Doe", + birthDate: {year: 1996, month: 9, day: 15}, + gender: FEMALE, + hireDate: {year: 2022, month: 6, day: 1}, + departmentDeptNo: "department-2", + workspaceWorkspaceId: "workspace-2" +}; + +Employee employee3 = { + empNo: "employee-3", + firstName: "Hugh", + lastName: "Smith", + birthDate: {year: 1986, month: 9, day: 15}, + gender: FEMALE, + hireDate: {year: 2021, month: 6, day: 1}, + departmentDeptNo: "department-3", + workspaceWorkspaceId: "workspace-3" +}; + +Employee updatedEmployee1 = { + empNo: "employee-1", + firstName: "Tom", + lastName: "Jones", + birthDate: {year: 1994, month: 11, day: 13}, + gender: MALE, + hireDate: {year: 2022, month: 8, day: 1}, + departmentDeptNo: "department-3", + workspaceWorkspaceId: "workspace-2" +}; + +public type IntIdRecordDependent record {| + string randomField; +|}; + +public type StringIdRecordDependent record {| + string randomField; +|}; + +public type FloatIdRecordDependent record {| + string randomField; +|}; + +public type DecimalIdRecordDependent record {| + string randomField; +|}; + +public type BooleanIdRecordDependent record {| + string randomField; +|}; + +public type AllTypesIdRecordDependent record {| + string randomField; +|}; + +public type CompositeAssociationRecordDependent record {| + string randomField; + int alltypesidrecordIntType; + decimal alltypesidrecordDecimalType; + record {| + int intType; + string stringType; + boolean booleanType; + string randomField; + |} allTypesIdRecord; +|}; + +Workspace workspace1 = { + workspaceId: "workspace-1", + workspaceType: "small", + locationBuildingCode: "building-2" +}; + +Workspace invalidWorkspace = { + workspaceId: "invalid-workspace-extra-characters-to-force-failure", + workspaceType: "small", + locationBuildingCode: "building-2" +}; + +Workspace workspace2 = { + workspaceId: "workspace-2", + workspaceType: "medium", + locationBuildingCode: "building-2" +}; + +Workspace workspace3 = { + workspaceId: "workspace-3", + workspaceType: "small", + locationBuildingCode: "building-2" +}; + +Workspace updatedWorkspace1 = { + workspaceId: "workspace-1", + workspaceType: "large", + locationBuildingCode: "building-2" +}; + +public type EmployeeName record {| + string firstName; + string lastName; +|}; + +public type EmployeeInfo2 record {| + readonly string empNo; + time:Date birthDate; + string departmentDeptNo; + string workspaceWorkspaceId; +|}; + +public type WorkspaceInfo2 record {| + string workspaceType; + string locationBuildingCode; +|}; + +public type DepartmentInfo2 record {| + string deptName; +|}; + +public type BuildingInfo2 record {| + string city; + string state; + string country; + string postalCode; + string 'type; +|}; + +OrderItem orderItem1 = { + orderId: "order-1", + itemId: "item-1", + quantity: 5, + notes: "none" +}; + +OrderItem orderItem2 = { + orderId: "order-2", + itemId: "item-2", + quantity: 10, + notes: "more" +}; + +OrderItem orderItem2Updated = { + orderId: "order-2", + itemId: "item-2", + quantity: 20, + notes: "more than more" +}; + +Building building31 = { + buildingCode: "building-31", + city: "Colombo", + state: "Western Province", + country: "Sri Lanka", + postalCode: "10370", + 'type: "rented" +}; + +BuildingInsert building32 = { + buildingCode: "building-32", + city: "Manhattan", + state: "New York", + country: "USA", + postalCode: "10570", + 'type: "owned" +}; + +BuildingInsert building33 = { + buildingCode: "building-33", + city: "Manhattan", + state: "New York", + country: "USA", + postalCode: "10570", + 'type: "owned" +}; + +Building building33Updated = { + buildingCode: "building-33", + city: "ColomboUpdated", + state: "Western ProvinceUpdated", + country: "Sri LankaUpdated", + postalCode: "10570", + 'type: "owned" +}; + +Department departmentNative1 = { + deptNo: "department-native-1", + deptName: "Finance" +}; + +Department departmentNative2 = { + deptNo: "department-native-2", + deptName: "HR" +}; + +Department departmentNative3 = { + deptNo: "department-native-3", + deptName: "Marketing" +}; + +Building buildingNative1 = { + buildingCode: "building-native-1", + city: "Colombo", + state: "Western", + country: "Sri Lanka", + postalCode: "10370", + 'type: "office" +}; + +Building buildingNative2 = { + buildingCode: "building-native-2", + city: "Kandy", + state: "Central", + country: "Sri Lanka", + postalCode: "20000", + 'type: "coworking space" +}; + +Building buildingNative3 = { + buildingCode: "building-native-3", + city: "San Francisco", + state: "California", + country: "USA", + postalCode: "80000", + 'type: "office" +}; + +Workspace workspaceNative1 = { + workspaceId: "workspace-native-1", + workspaceType: "hot seat", + locationBuildingCode: "building-native-2" +}; + +Workspace workspaceNative2 = { + workspaceId: "workspace-native-2", + workspaceType: "dedicated", + locationBuildingCode: "building-native-2" +}; + +Workspace workspaceNative3 = { + workspaceId: "workspace-native-3", + workspaceType: "hot seat", + locationBuildingCode: "building-native-3" +}; + +Employee employeeNative1 = { + empNo: "employee-native-1", + firstName: "John", + lastName: "Doe", + birthDate: {year: 1994, month: 10, day: 30}, + gender: MALE, + hireDate: {year: 2020, month: 10, day: 30}, + departmentDeptNo: "department-native-1", + workspaceWorkspaceId: "workspace-native-1" +}; + +Employee employeeNative2 = { + empNo: "employee-native-2", + firstName: "Jane", + lastName: "Doe", + birthDate: {year: 1996, month: 8, day: 12}, + gender: FEMALE, + hireDate: {year: 2021, month: 10, day: 30}, + departmentDeptNo: "department-native-2", + workspaceWorkspaceId: "workspace-native-2" +}; + +Employee employeeNative3 = { + empNo: "employee-native-3", + firstName: "Sam", + lastName: "Smith", + birthDate: {year: 1991, month: 8, day: 12}, + gender: MALE, + hireDate: {year: 2019, month: 10, day: 30}, + departmentDeptNo: "department-native-3", + workspaceWorkspaceId: "workspace-native-3" +}; + +EmployeeInfo employeeInfoNative1 = { + firstName: "John", + lastName: "Doe", + department: { + deptName: "Finance" + }, + workspace: { + workspaceId: "workspace-native-1", + workspaceType: "hot seat", + locationBuildingCode: "building-native-2" + } +}; + +EmployeeInfo employeeInfoNative2 = { + firstName: "Jane", + lastName: "Doe", + department: { + deptName: "HR" + }, + workspace: { + workspaceId: "workspace-native-2", + workspaceType: "dedicated", + locationBuildingCode: "building-native-2" + } +}; + +EmployeeInfo employeeInfoNative3 = { + firstName: "Sam", + lastName: "Smith", + department: { + deptName: "Marketing" + }, + workspace: { + workspaceId: "workspace-native-3", + workspaceType: "hot seat", + locationBuildingCode: "building-native-3" + } +}; diff --git a/ballerina/tests/persist/rainier.bal b/ballerina/tests/persist/rainier.bal new file mode 100644 index 0000000..78339f9 --- /dev/null +++ b/ballerina/tests/persist/rainier.bal @@ -0,0 +1,67 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/time; + +enum Gender { + MALE, + FEMALE +} + +type Employee record {| + readonly string empNo; + string firstName; + string lastName; + time:Date birthDate; + Gender gender; + time:Date hireDate; + + Department department; + Workspace workspace; +|}; + +type Workspace record {| + readonly string workspaceId; + string workspaceType; + + Building location; + Employee[] employees; +|}; + +type Building record {| + readonly string buildingCode; + string city; + string state; + string country; + string postalCode; + string 'type; + + Workspace[] workspaces; +|}; + +type Department record {| + readonly string deptNo; + string deptName; + + Employee[] employees; +|}; + +type OrderItem record {| + readonly string orderId; + readonly string itemId; + int quantity; + string notes; +|}; diff --git a/ballerina/tests/persist/test_entities.bal b/ballerina/tests/persist/test_entities.bal new file mode 100644 index 0000000..7ad161f --- /dev/null +++ b/ballerina/tests/persist/test_entities.bal @@ -0,0 +1,85 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/time; + +enum EnumType { + TYPE_1, + TYPE_2, + TYPE_3, + TYPE_4 +} + +type AllTypes record {| + readonly int id; + boolean booleanType; + int intType; + float floatType; + decimal decimalType; + string stringType; + time:Date dateType; + time:TimeOfDay timeOfDayType; + boolean? booleanTypeOptional; + int? intTypeOptional; + float? floatTypeOptional; + decimal? decimalTypeOptional; + string? stringTypeOptional; + time:Date? dateTypeOptional; + time:TimeOfDay? timeOfDayTypeOptional; + EnumType enumType; + EnumType? enumTypeOptional; +|}; + +type StringIdRecord record {| + readonly string id; + string randomField; +|}; + +type IntIdRecord record {| + readonly int id; + string randomField; +|}; + +type FloatIdRecord record {| + readonly float id; + string randomField; +|}; + +type DecimalIdRecord record {| + readonly decimal id; + string randomField; +|}; + +type BooleanIdRecord record {| + readonly boolean id; + string randomField; +|}; + +type CompositeAssociationRecord record {| + readonly string id; + string randomField; + AllTypesIdRecord allTypesIdRecord; +|}; + +type AllTypesIdRecord record {| + readonly boolean booleanType; + readonly int intType; + readonly float floatType; + readonly decimal decimalType; + readonly string stringType; + string randomField; + CompositeAssociationRecord? compositeAssociationRecord; +|}; diff --git a/ballerina/tests/rainier_generated_types.bal b/ballerina/tests/rainier_generated_types.bal new file mode 100644 index 0000000..597818a --- /dev/null +++ b/ballerina/tests/rainier_generated_types.bal @@ -0,0 +1,172 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/time; + +public enum Gender { + MALE, + FEMALE +} + +public type Employee record {| + readonly string empNo; + string firstName; + string lastName; + time:Date birthDate; + Gender gender; + time:Date hireDate; + string departmentDeptNo; + string workspaceWorkspaceId; +|}; + +public type EmployeeOptionalized record {| + string empNo?; + string firstName?; + string lastName?; + time:Date birthDate?; + Gender gender?; + time:Date hireDate?; + string departmentDeptNo?; + string workspaceWorkspaceId?; +|}; + +public type EmployeeWithRelations record {| + *EmployeeOptionalized; + DepartmentOptionalized department?; + WorkspaceOptionalized workspace?; +|}; + +public type EmployeeTargetType typedesc; + +public type EmployeeInsert Employee; + +public type EmployeeUpdate record {| + string firstName?; + string lastName?; + time:Date birthDate?; + Gender gender?; + time:Date hireDate?; + string departmentDeptNo?; + string workspaceWorkspaceId?; +|}; + +public type Workspace record {| + readonly string workspaceId; + string workspaceType; + string locationBuildingCode; +|}; + +public type WorkspaceOptionalized record {| + string workspaceId?; + string workspaceType?; + string locationBuildingCode?; +|}; + +public type WorkspaceWithRelations record {| + *WorkspaceOptionalized; + BuildingOptionalized location?; + EmployeeOptionalized[] employees?; +|}; + +public type WorkspaceTargetType typedesc; + +public type WorkspaceInsert Workspace; + +public type WorkspaceUpdate record {| + string workspaceType?; + string locationBuildingCode?; +|}; + +public type Building record {| + readonly string buildingCode; + string city; + string state; + string country; + string postalCode; + string 'type; +|}; + +public type BuildingOptionalized record {| + string buildingCode?; + string city?; + string state?; + string country?; + string postalCode?; + string 'type?; +|}; + +public type BuildingWithRelations record {| + *BuildingOptionalized; + WorkspaceOptionalized[] workspaces?; +|}; + +public type BuildingTargetType typedesc; + +public type BuildingInsert Building; + +public type BuildingUpdate record {| + string city?; + string state?; + string country?; + string postalCode?; + string 'type?; +|}; + +public type Department record {| + readonly string deptNo; + string deptName; +|}; + +public type DepartmentOptionalized record {| + string deptNo?; + string deptName?; +|}; + +public type DepartmentWithRelations record {| + *DepartmentOptionalized; + EmployeeOptionalized[] employees?; +|}; + +public type DepartmentTargetType typedesc; + +public type DepartmentInsert Department; + +public type DepartmentUpdate record {| + string deptName?; +|}; + +public type OrderItem record {| + readonly string orderId; + readonly string itemId; + int quantity; + string notes; +|}; + +public type OrderItemOptionalized record {| + string orderId?; + string itemId?; + int quantity?; + string notes?; +|}; + +public type OrderItemTargetType typedesc; + +public type OrderItemInsert OrderItem; + +public type OrderItemUpdate record {| + int quantity?; + string notes?; +|}; diff --git a/ballerina/tests/redis-all-types-tests.bal b/ballerina/tests/redis-all-types-tests.bal new file mode 100644 index 0000000..e223232 --- /dev/null +++ b/ballerina/tests/redis-all-types-tests.bal @@ -0,0 +1,216 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/persist; + +@test:Config { + groups: ["all-types", "redis"] +} +function redisAllTypesCreateTest() returns error? { + RedisTestEntitiesClient testEntitiesClient = check new (); + + int[] ids = check testEntitiesClient->/alltypes.post([allTypes1, allTypes2]); + test:assertEquals(ids, [allTypes1.id, allTypes2.id]); + + AllTypes allTypesRetrieved = check testEntitiesClient->/alltypes/[allTypes1.id].get(); + test:assertEquals(allTypesRetrieved, allTypes1Expected); + + allTypesRetrieved = check testEntitiesClient->/alltypes/[allTypes2.id].get(); + test:assertEquals(allTypesRetrieved, allTypes2Expected); + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["all-types", "redis"] +} +function redisAllTypesCreateOptionalTest() returns error? { + RedisTestEntitiesClient testEntitiesClient = check new (); + + int[] ids = check testEntitiesClient->/alltypes.post([allTypes3]); + test:assertEquals(ids, [allTypes3.id]); + + AllTypes allTypesRetrieved = check testEntitiesClient->/alltypes/[allTypes3.id].get(); + test:assertEquals(allTypesRetrieved, allTypes3Expected); + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["all-types", "redis"], + dependsOn: [redisAllTypesCreateTest, redisAllTypesCreateOptionalTest] +} +function redisAllTypesReadTest() returns error? { + RedisTestEntitiesClient testEntitiesClient = check new (); + + stream allTypesStream = testEntitiesClient->/alltypes.get(); + AllTypes[] allTypes = check from AllTypes allTypesRecord in allTypesStream + select allTypesRecord; + + test:assertEquals(allTypes, [allTypes3Expected, allTypes1Expected, allTypes2Expected]); + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["all-types", "redis", "dependent"], + dependsOn: [redisAllTypesCreateTest, redisAllTypesCreateOptionalTest] +} +function redisAllTypesReadDependentTest() returns error? { + RedisTestEntitiesClient testEntitiesClient = check new (); + + stream allTypesStream = testEntitiesClient->/alltypes.get(); + AllTypesDependent[] allTypes = check from AllTypesDependent allTypesRecord in allTypesStream + select allTypesRecord; + + test:assertEquals(allTypes, [ + { + booleanType: allTypes3Expected.booleanType, + intType: allTypes3Expected.intType, + floatType: allTypes3Expected.floatType, + decimalType: allTypes3Expected.decimalType, + stringType: allTypes3Expected.stringType, + dateType: allTypes3Expected.dateType, + timeOfDayType: allTypes3Expected.timeOfDayType, + booleanTypeOptional: allTypes3Expected.booleanTypeOptional, + intTypeOptional: allTypes3Expected.intTypeOptional, + floatTypeOptional: allTypes3Expected.floatTypeOptional, + decimalTypeOptional: allTypes3Expected.decimalTypeOptional, + stringTypeOptional: allTypes3Expected.stringTypeOptional, + dateTypeOptional: allTypes3Expected.dateTypeOptional, + timeOfDayTypeOptional: allTypes3Expected.timeOfDayTypeOptional + }, + { + booleanType: allTypes1Expected.booleanType, + intType: allTypes1Expected.intType, + floatType: allTypes1Expected.floatType, + decimalType: allTypes1Expected.decimalType, + stringType: allTypes1Expected.stringType, + dateType: allTypes1Expected.dateType, + timeOfDayType: allTypes1Expected.timeOfDayType, + booleanTypeOptional: allTypes1Expected.booleanTypeOptional, + intTypeOptional: allTypes1Expected.intTypeOptional, + floatTypeOptional: allTypes1Expected.floatTypeOptional, + decimalTypeOptional: allTypes1Expected.decimalTypeOptional, + stringTypeOptional: allTypes1Expected.stringTypeOptional, + dateTypeOptional: allTypes1Expected.dateTypeOptional, + timeOfDayTypeOptional: allTypes1Expected.timeOfDayTypeOptional + }, + { + booleanType: allTypes2Expected.booleanType, + intType: allTypes2Expected.intType, + floatType: allTypes2Expected.floatType, + decimalType: allTypes2Expected.decimalType, + stringType: allTypes2Expected.stringType, + dateType: allTypes2Expected.dateType, + timeOfDayType: allTypes2Expected.timeOfDayType, + booleanTypeOptional: allTypes2Expected.booleanTypeOptional, + intTypeOptional: allTypes2Expected.intTypeOptional, + floatTypeOptional: allTypes2Expected.floatTypeOptional, + decimalTypeOptional: allTypes2Expected.decimalTypeOptional, + stringTypeOptional: allTypes2Expected.stringTypeOptional, + dateTypeOptional: allTypes2Expected.dateTypeOptional, + timeOfDayTypeOptional: allTypes2Expected.timeOfDayTypeOptional + } + ]); + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["all-types", "redis"], + dependsOn: [redisAllTypesCreateTest, redisAllTypesCreateOptionalTest] +} +function redisAllTypesReadOneTest() returns error? { + RedisTestEntitiesClient testEntitiesClient = check new (); + + AllTypes allTypesRetrieved = check testEntitiesClient->/alltypes/[allTypes1.id].get(); + test:assertEquals(allTypesRetrieved, allTypes1Expected); + + allTypesRetrieved = check testEntitiesClient->/alltypes/[allTypes2.id].get(); + test:assertEquals(allTypesRetrieved, allTypes2Expected); + + allTypesRetrieved = check testEntitiesClient->/alltypes/[allTypes3.id].get(); + test:assertEquals(allTypesRetrieved, allTypes3Expected); + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["all-types", "redis"] +} +function redisAllTypesReadOneTestNegative() returns error? { + RedisTestEntitiesClient testEntitiesClient = check new (); + + AllTypes|persist:Error allTypesRetrieved = testEntitiesClient->/alltypes/[4].get(); + if allTypesRetrieved is persist:NotFoundError { + test:assertEquals(allTypesRetrieved.message(), "A record with the key '4' does not exist for the entity 'AllTypes'."); + } + else { + test:assertFail("persist:NotFoundError expected."); + } + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["all-types", "redis"], + dependsOn: [redisAllTypesReadOneTest, redisAllTypesReadTest, redisAllTypesReadDependentTest] +} +function redisAllTypesUpdateTest() returns error? { + RedisTestEntitiesClient testEntitiesClient = check new (); + + AllTypes allTypes = check testEntitiesClient->/alltypes/[allTypes1.id].put({ + booleanType: allTypes3.booleanType, + intType: allTypes1Updated.intType, + floatType: allTypes1Updated.floatType, + decimalType: allTypes1Updated.decimalType, + stringType: allTypes1Updated.stringType, + dateType: allTypes1Updated.dateType, + timeOfDayType: allTypes1Updated.timeOfDayType, + booleanTypeOptional: allTypes1Updated.booleanTypeOptional, + intTypeOptional: allTypes1Updated.intTypeOptional, + floatTypeOptional: allTypes1Updated.floatTypeOptional, + decimalTypeOptional: allTypes1Updated.decimalTypeOptional, + stringTypeOptional: allTypes1Updated.stringTypeOptional, + dateTypeOptional: allTypes1Updated.dateTypeOptional, + timeOfDayTypeOptional: allTypes1Updated.timeOfDayTypeOptional, + enumType: allTypes1Updated.enumType, + enumTypeOptional: allTypes1Updated.enumTypeOptional + }); + test:assertEquals(allTypes, allTypes1UpdatedExpected); + + AllTypes allTypesRetrieved = check testEntitiesClient->/alltypes/[allTypes1.id].get(); + test:assertEquals(allTypesRetrieved, allTypes1UpdatedExpected); + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["all-types", "redis"], + dependsOn: [redisAllTypesUpdateTest] +} +function redisAllTypesDeleteTest() returns error? { + RedisTestEntitiesClient testEntitiesClient = check new (); + + AllTypes allTypes = check testEntitiesClient->/alltypes/[allTypes2.id].delete(); + test:assertEquals(allTypes, allTypes2Expected); + + stream allTypesStream = testEntitiesClient->/alltypes.get(); + AllTypes[] allTypesCollection = check from AllTypes allTypesRecord in allTypesStream + select allTypesRecord; + + test:assertEquals(allTypesCollection, [allTypes3Expected, allTypes1UpdatedExpected]); + check testEntitiesClient.close(); +} diff --git a/ballerina/tests/redis-associations-tests.bal b/ballerina/tests/redis-associations-tests.bal new file mode 100644 index 0000000..dbc8778 --- /dev/null +++ b/ballerina/tests/redis-associations-tests.bal @@ -0,0 +1,288 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/persist; + +@test:Config { + groups: ["associations", "redis"], + dependsOn: [redisEmployeeDeleteTestNegative] +} +function redisEmployeeRelationsTest() returns error? { + RedisRainierClient rainierClient = check new (); + + Employee employee21 = { + empNo: "employee-21", + firstName: "Tom", + lastName: "Scott", + birthDate: {year: 1992, month: 11, day: 13}, + gender: MALE, + hireDate: {year: 2022, month: 8, day: 1}, + departmentDeptNo: "department-22", + workspaceWorkspaceId: "workspace-22" + }; + + Workspace workspace22 = { + workspaceId: "workspace-22", + workspaceType: "medium", + locationBuildingCode: "building-22" + }; + + BuildingInsert building22 = { + buildingCode: "building-22", + city: "Manhattan", + state: "New York", + country: "USA", + postalCode: "10570", + 'type: "owned" + }; + + Department department22 = { + deptNo: "department-22", + deptName: "Marketing" + }; + + _ = check rainierClient->/buildings.post([building22]); + _ = check rainierClient->/departments.post([department22]); + _ = check rainierClient->/workspaces.post([workspace22]); + _ = check rainierClient->/employees.post([employee21]); + + stream employeeStream = rainierClient->/employees.get(); + EmployeeInfo[] employees = check from EmployeeInfo employee in employeeStream + select employee; + + EmployeeInfo retrieved = check rainierClient->/employees/["employee-21"].get(); + + EmployeeInfo expected = { + firstName: "Tom", + lastName: "Scott", + department: { + deptName: "Marketing" + }, + workspace: { + workspaceId: "workspace-22", + workspaceType: "medium", + locationBuildingCode: "building-22" + } + }; + + test:assertTrue(employees.indexOf(expected) is int, "Expected EmployeeInfo not found."); + test:assertEquals(retrieved, expected); + check rainierClient.close(); +} + +@test:Config { + groups: ["associations", "redis"], + dependsOn: [redisEmployeeDeleteTestNegative] +} +function redisDepartmentRelationsTest() returns error? { + RedisRainierClient rainierClient = check new (); + + Employee employee11 = { + empNo: "employee-11", + firstName: "Tom", + lastName: "Scott", + birthDate: {year: 1992, month: 11, day: 13}, + gender: MALE, + hireDate: {year: 2022, month: 8, day: 1}, + departmentDeptNo: "department-12", + workspaceWorkspaceId: "workspace-12" + }; + + Employee employee12 = { + empNo: "employee-12", + firstName: "Jane", + lastName: "Doe", + birthDate: {year: 1996, month: 9, day: 15}, + gender: FEMALE, + hireDate: {year: 2022, month: 6, day: 1}, + departmentDeptNo: "department-12", + workspaceWorkspaceId: "workspace-12" + }; + + Workspace workspace12 = { + workspaceId: "workspace-12", + workspaceType: "medium", + locationBuildingCode: "building-12" + }; + + BuildingInsert building12 = { + buildingCode: "building-12", + city: "Manhattan", + state: "New York", + country: "USA", + postalCode: "10570", + 'type: "owned" + }; + + Department department12 = { + deptNo: "department-12", + deptName: "Marketing" + }; + + _ = check rainierClient->/buildings.post([building12]); + _ = check rainierClient->/departments.post([department12]); + _ = check rainierClient->/workspaces.post([workspace12]); + _ = check rainierClient->/employees.post([employee11, employee12]); + + stream departmentStream = rainierClient->/departments.get(); + DepartmentInfo[] departments = check from DepartmentInfo department in departmentStream + select department; + + DepartmentInfo retrieved = check rainierClient->/departments/["department-12"].get(); + + DepartmentInfo expected = { + deptNo: "department-12", + deptName: "Marketing", + employees: [ + { + firstName: "Tom", + lastName: "Scott" + }, + { + firstName: "Jane", + lastName: "Doe" + } + ] + }; + + test:assertTrue(departments.indexOf(expected) is int, "Expected DepartmentInfo not found."); + test:assertEquals(retrieved, expected); + check rainierClient.close(); +} + +@test:Config { + groups: ["associations", "redis"], + dependsOn: [redisEmployeeRelationsTest] +} +function redisWorkspaceRelationsTest() returns error? { + RedisRainierClient rainierClient = check new (); + + Employee employee22 = { + empNo: "employee-22", + firstName: "James", + lastName: "David", + birthDate: {year: 1996, month: 11, day: 13}, + gender: FEMALE, + hireDate: {year: 2021, month: 8, day: 1}, + departmentDeptNo: "department-22", + workspaceWorkspaceId: "workspace-22" + }; + _ = check rainierClient->/employees.post([employee22]); + + stream workspaceStream = rainierClient->/workspaces.get(); + WorkspaceInfo[] workspaces = check from WorkspaceInfo workspace in workspaceStream + select workspace; + + WorkspaceInfo retrieved = check rainierClient->/workspaces/["workspace-22"].get(); + + WorkspaceInfo expected = { + workspaceType: "medium", + location: { + buildingCode: "building-22", + city: "Manhattan", + state: "New York", + country: "USA", + postalCode: "10570", + 'type: "owned" + }, + employees: [ + { + empNo: "employee-21", + firstName: "Tom", + lastName: "Scott", + birthDate: {year: 1992, month: 11, day: 13}, + gender: MALE, + hireDate: {year: 2022, month: 8, day: 1}, + departmentDeptNo: "department-22", + workspaceWorkspaceId: "workspace-22" + }, + { + empNo: "employee-22", + firstName: "James", + lastName: "David", + birthDate: {year: 1996, month: 11, day: 13}, + gender: FEMALE, + hireDate: {year: 2021, month: 8, day: 1}, + departmentDeptNo: "department-22", + workspaceWorkspaceId: "workspace-22" + } + ] + }; + + boolean found = false; + _ = from WorkspaceInfo workspace in workspaces + do { + if workspace == expected { + found = true; + } + }; + + if !found { + test:assertFail("Expected WorkspaceInfo not found."); + } + + test:assertEquals(retrieved, expected); + + check rainierClient.close(); +} + +@test:Config { + groups: ["associations", "redis"], + dependsOn: [redisEmployeeRelationsTest] +} +function redisBuildingRelationsTest() returns error? { + RedisRainierClient rainierClient = check new (); + + stream buildingStream = rainierClient->/buildings.get(); + BuildingInfo[] buildings = check from BuildingInfo building in buildingStream + select building; + + BuildingInfo retrieved = check rainierClient->/buildings/["building-22"].get(); + + BuildingInfo expected = { + buildingCode: "building-22", + city: "Manhattan", + state: "New York", + country: "USA", + postalCode: "10570", + 'type: "owned", + workspaces: [ + { + workspaceId: "workspace-22", + workspaceType: "medium", + locationBuildingCode: "building-22" + } + ] + }; + + boolean found = false; + _ = from BuildingInfo building in buildings + do { + if (building.buildingCode == "building-22") { + found = true; + test:assertEquals(building, expected); + } + }; + + if !found { + test:assertFail("Expected BuildingInfo not found."); + } + + test:assertEquals(retrieved, expected); + + check rainierClient.close(); +} diff --git a/ballerina/tests/redis-building-tests.bal b/ballerina/tests/redis-building-tests.bal new file mode 100644 index 0000000..2756383 --- /dev/null +++ b/ballerina/tests/redis-building-tests.bal @@ -0,0 +1,226 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/persist; + +@test:Config { + groups: ["building", "redis"] +} +function redisBuildingCreateTest() returns error? { + RedisRainierClient rainierClient = check new (); + + string[] buildingCodes = check rainierClient->/buildings.post([building1]); + test:assertEquals(buildingCodes, [building1.buildingCode]); + + Building buildingRetrieved = check rainierClient->/buildings/[building1.buildingCode].get(); + test:assertEquals(buildingRetrieved, building1); + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "redis"] +} +function redisBuildingCreateTest2() returns error? { + RedisRainierClient rainierClient = check new (); + + string[] buildingCodes = check rainierClient->/buildings.post([building2, building3]); + + test:assertEquals(buildingCodes, [building2.buildingCode, building3.buildingCode]); + + Building buildingRetrieved = check rainierClient->/buildings/[building2.buildingCode].get(); + test:assertEquals(buildingRetrieved, building2); + + buildingRetrieved = check rainierClient->/buildings/[building3.buildingCode].get(); + test:assertEquals(buildingRetrieved, building3); + + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "redis"] +} +function redisBuildingCreateTestNegative() returns error? { + RedisRainierClient rainierClient = check new (); + + string[]|error building = rainierClient->/buildings.post([invalidBuilding]); + if building is persist:Error { + test:assertTrue(building.message().includes("value too long for type character varying")); + } else { + test:assertFail("Error expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "redis"], + dependsOn: [redisBuildingCreateTest] +} +function redisBuildingReadOneTest() returns error? { + RedisRainierClient rainierClient = check new (); + + Building buildingRetrieved = check rainierClient->/buildings/[building1.buildingCode].get(); + test:assertEquals(buildingRetrieved, building1); + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "redis"], + dependsOn: [redisBuildingCreateTest] +} +function redisBuildingReadOneTestNegative() returns error? { + RedisRainierClient rainierClient = check new (); + + Building|error buildingRetrieved = rainierClient->/buildings/["invalid-building-code"].get(); + if buildingRetrieved is persist:NotFoundError { + test:assertEquals(buildingRetrieved.message(), "A record with the key 'invalid-building-code' does not exist for the entity 'Building'."); + } else { + test:assertFail("persist:NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "redis"], + dependsOn: [redisBuildingCreateTest, redisBuildingCreateTest2] +} +function redisBuildingReadManyTest() returns error? { + RedisRainierClient rainierClient = check new (); + + stream buildingStream = rainierClient->/buildings.get(); + Building[] buildings = check from Building building in buildingStream + select building; + + test:assertEquals(buildings, [building1, building2, building3]); + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "redis", "dependent"], + dependsOn: [redisBuildingCreateTest, redisBuildingCreateTest2] +} +function redisBuildingReadManyDependentTest() returns error? { + RedisRainierClient rainierClient = check new (); + + stream buildingStream = rainierClient->/buildings.get(); + BuildingInfo2[] buildings = check from BuildingInfo2 building in buildingStream + select building; + + test:assertEquals(buildings, [ + {city: building1.city, state: building1.state, country: building1.country, postalCode: building1.postalCode, 'type: building1.'type}, + {city: building2.city, state: building2.state, country: building2.country, postalCode: building2.postalCode, 'type: building2.'type}, + {city: building3.city, state: building3.state, country: building3.country, postalCode: building3.postalCode, 'type: building3.'type} + ]); + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "redis"], + dependsOn: [redisBuildingReadOneTest, redisBuildingReadManyTest, redisBuildingReadManyDependentTest] +} +function redisBuildingUpdateTest() returns error? { + RedisRainierClient rainierClient = check new (); + + Building building = check rainierClient->/buildings/[building1.buildingCode].put({ + city: "Galle", + state: "Southern Province", + postalCode: "10890", + 'type: "owned" + }); + + test:assertEquals(building, updatedBuilding1); + + Building buildingRetrieved = check rainierClient->/buildings/[building1.buildingCode].get(); + test:assertEquals(buildingRetrieved, updatedBuilding1); + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "redis"], + dependsOn: [redisBuildingReadOneTest, redisBuildingReadManyTest, redisBuildingReadManyDependentTest] +} +function redisBuildingUpdateTestNegative1() returns error? { + RedisRainierClient rainierClient = check new (); + + Building|error building = rainierClient->/buildings/["invalid-building-code"].put({ + city: "Galle", + state: "Southern Province", + postalCode: "10890" + }); + + if building is persist:NotFoundError { + test:assertEquals(building.message(), "A record with the key 'invalid-building-code' does not exist for the entity 'Building'."); + } else { + test:assertFail("persist:NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "redis"], + dependsOn: [redisBuildingReadOneTest, redisBuildingReadManyTest, redisBuildingReadManyDependentTest] +} +function redisBuildingUpdateTestNegative2() returns error? { + RedisRainierClient rainierClient = check new (); + + Building|error building = rainierClient->/buildings/[building1.buildingCode].put({ + city: "unncessarily-long-city-name-to-force-error-on-update", + state: "Southern Province", + postalCode: "10890" + }); + + if building is persist:Error { + test:assertTrue(building.message().includes("value too long for type character varying")); + } else { + test:assertFail("persist:NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "redis"], + dependsOn: [redisBuildingUpdateTest, redisBuildingUpdateTestNegative2] +} +function redisBuildingDeleteTest() returns error? { + RedisRainierClient rainierClient = check new (); + + Building building = check rainierClient->/buildings/[building1.buildingCode].delete(); + test:assertEquals(building, updatedBuilding1); + + stream buildingStream = rainierClient->/buildings.get(); + Building[] buildings = check from Building building2 in buildingStream + select building2; + + test:assertEquals(buildings, [building2, building3]); + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "redis"], + dependsOn: [redisBuildingDeleteTest] +} +function redisBuildingDeleteTestNegative() returns error? { + RedisRainierClient rainierClient = check new (); + + Building|error building = rainierClient->/buildings/[building1.buildingCode].delete(); + + if building is error { + test:assertEquals(building.message(), string `A record with the key '${building1.buildingCode}' does not exist for the entity 'Building'.`); + } else { + test:assertFail("persist:NotFoundError expected."); + } + check rainierClient.close(); +} diff --git a/ballerina/tests/redis-composite-key-tests.bal b/ballerina/tests/redis-composite-key-tests.bal new file mode 100644 index 0000000..3720c89 --- /dev/null +++ b/ballerina/tests/redis-composite-key-tests.bal @@ -0,0 +1,196 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/persist; + +@test:Config { + groups: ["composite-key", "redis"] +} +function redisCompositeKeyCreateTest() returns error? { + RedisRainierClient rainierClient = check new (); + + [string, string][] ids = check rainierClient->/orderitems.post([orderItem1, orderItem2]); + test:assertEquals(ids, [[orderItem1.orderId, orderItem1.itemId], [orderItem2.orderId, orderItem2.itemId]]); + + OrderItem orderItemRetrieved = check rainierClient->/orderitems/[orderItem1.orderId]/[orderItem1.itemId].get(); + test:assertEquals(orderItemRetrieved, orderItem1); + + orderItemRetrieved = check rainierClient->/orderitems/[orderItem2.orderId]/[orderItem2.itemId].get(); + test:assertEquals(orderItemRetrieved, orderItem2); + + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "redis"], + dependsOn: [redisCompositeKeyCreateTest] +} +function redisCompositeKeyCreateTestNegative() returns error? { + RedisRainierClient rainierClient = check new (); + + [string, string][]|error ids = rainierClient->/orderitems.post([orderItem1]); + if ids is persist:AlreadyExistsError { + test:assertEquals(ids.message(), "A record with the key '(\"orderId\", \"itemId\")=(order-1, item-1)' already exists for the entity 'OrderItem'."); + } else { + test:assertFail("persist:AlreadyExistsError expected"); + } + + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "redis"], + dependsOn: [redisCompositeKeyCreateTest] +} +function redisCompositeKeyReadManyTest() returns error? { + RedisRainierClient rainierClient = check new (); + + stream orderItemStream = rainierClient->/orderitems.get(); + OrderItem[] orderitem = check from OrderItem orderItem in orderItemStream + select orderItem; + + test:assertEquals(orderitem, [orderItem1, orderItem2]); + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "redis"], + dependsOn: [redisCompositeKeyCreateTest] +} +function redisCompositeKeyReadOneTest() returns error? { + RedisRainierClient rainierClient = check new (); + OrderItem orderItem = check rainierClient->/orderitems/[orderItem1.orderId]/[orderItem1.itemId].get(); + test:assertEquals(orderItem, orderItem1); + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key2"], + dependsOn: [redisCompositeKeyCreateTest] +} +function redisCompositeKeyReadOneTest2() returns error? { + RedisRainierClient rainierClient = check new (); + OrderItem orderItem = check rainierClient->/orderitems/[orderItem1.orderId]/[orderItem1.itemId].get(); + test:assertEquals(orderItem, orderItem1); + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "redis"], + dependsOn: [redisCompositeKeyCreateTest] +} +function redisCompositeKeyReadOneTestNegative1() returns error? { + RedisRainierClient rainierClient = check new (); + OrderItem|error orderItem = rainierClient->/orderitems/["invalid-order-id"]/[orderItem1.itemId].get(); + + if orderItem is persist:NotFoundError { + test:assertEquals(orderItem.message(), "A record with the key '{\"orderId\":\"invalid-order-id\",\"itemId\":\"item-1\"}' does not exist for the entity 'OrderItem'."); + } else { + test:assertFail("Error expected."); + } + + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "redis"], + dependsOn: [redisCompositeKeyCreateTest] +} +function redisCompositeKeyReadOneTestNegative2() returns error? { + RedisRainierClient rainierClient = check new (); + OrderItem|error orderItem = rainierClient->/orderitems/[orderItem1.orderId]/["invalid-item-id"].get(); + + if orderItem is persist:NotFoundError { + test:assertEquals(orderItem.message(), "A record with the key '{\"orderId\":\"order-1\",\"itemId\":\"invalid-item-id\"}' does not exist for the entity 'OrderItem'."); + } else { + test:assertFail("Error expected."); + } + + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "redis"], + dependsOn: [redisCompositeKeyCreateTest, redisCompositeKeyReadOneTest, redisCompositeKeyReadManyTest, redisCompositeKeyReadOneTest2] +} +function redisCompositeKeyUpdateTest() returns error? { + RedisRainierClient rainierClient = check new (); + + OrderItem orderItem = check rainierClient->/orderitems/[orderItem2.orderId]/[orderItem2.itemId].put({ + quantity: orderItem2Updated.quantity, + notes: orderItem2Updated.notes + }); + test:assertEquals(orderItem, orderItem2Updated); + + orderItem = check rainierClient->/orderitems/[orderItem2.orderId]/[orderItem2.itemId].get(); + test:assertEquals(orderItem, orderItem2Updated); + + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "redis"], + dependsOn: [redisCompositeKeyCreateTest, redisCompositeKeyReadOneTest, redisCompositeKeyReadManyTest, redisCompositeKeyReadOneTest2] +} +function redisCompositeKeyUpdateTestNegative() returns error? { + RedisRainierClient rainierClient = check new (); + + OrderItem|error orderItem = rainierClient->/orderitems/[orderItem1.orderId]/[orderItem2.itemId].put({ + quantity: 239, + notes: "updated notes" + }); + if orderItem is persist:NotFoundError { + test:assertEquals(orderItem.message(), "A record with the key '{\"orderId\":\"order-1\",\"itemId\":\"item-2\"}' does not exist for the entity 'OrderItem'."); + } else { + test:assertFail("Error expected."); + } + + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "redis"], + dependsOn: [redisCompositeKeyUpdateTest] +} +function redisCompositeKeyDeleteTest() returns error? { + RedisRainierClient rainierClient = check new (); + + OrderItem orderItem = check rainierClient->/orderitems/[orderItem2.orderId]/[orderItem2.itemId].delete(); + test:assertEquals(orderItem, orderItem2Updated); + + OrderItem|error orderItemRetrieved = rainierClient->/orderitems/[orderItem2.orderId]/[orderItem2.itemId].get(); + test:assertTrue(orderItemRetrieved is persist:NotFoundError); + + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "redis"], + dependsOn: [redisCompositeKeyUpdateTest] +} +function redisCompositeKeyDeleteTestNegative() returns error? { + RedisRainierClient rainierClient = check new (); + + OrderItem|error orderItem = rainierClient->/orderitems/["invalid-order-id"]/[orderItem2.itemId].delete(); + if orderItem is persist:NotFoundError { + test:assertEquals(orderItem.message(), "A record with the key '{\"orderId\":\"invalid-order-id\",\"itemId\":\"item-2\"}' does not exist for the entity 'OrderItem'."); + } else { + test:assertFail("Error expected."); + } + + check rainierClient.close(); +} diff --git a/ballerina/tests/redis-department-tests.bal b/ballerina/tests/redis-department-tests.bal new file mode 100644 index 0000000..9756dbb --- /dev/null +++ b/ballerina/tests/redis-department-tests.bal @@ -0,0 +1,217 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/persist; + +@test:Config { + groups: ["department", "redis"] +} +function redisDepartmentCreateTest() returns error? { + RedisRainierClient rainierClient = check new (); + + string[] deptNos = check rainierClient->/departments.post([department1]); + test:assertEquals(deptNos, [department1.deptNo]); + + Department departmentRetrieved = check rainierClient->/departments/[department1.deptNo].get(); + test:assertEquals(departmentRetrieved, department1); + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "redis"] +} +function redisDepartmentCreateTest2() returns error? { + RedisRainierClient rainierClient = check new (); + + string[] deptNos = check rainierClient->/departments.post([department2, department3]); + + test:assertEquals(deptNos, [department2.deptNo, department3.deptNo]); + + Department departmentRetrieved = check rainierClient->/departments/[department2.deptNo].get(); + test:assertEquals(departmentRetrieved, department2); + + departmentRetrieved = check rainierClient->/departments/[department3.deptNo].get(); + test:assertEquals(departmentRetrieved, department3); + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "redis"] +} +function redisDepartmentCreateTestNegative() returns error? { + RedisRainierClient rainierClient = check new (); + + string[]|error department = rainierClient->/departments.post([invalidDepartment]); + if department is persist:Error { + test:assertTrue(department.message().includes("value too long for type character varying")); + } else { + test:assertFail("Error expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "redis"], + dependsOn: [redisDepartmentCreateTest] +} +function redisDepartmentReadOneTest() returns error? { + RedisRainierClient rainierClient = check new (); + + Department departmentRetrieved = check rainierClient->/departments/[department1.deptNo].get(); + test:assertEquals(departmentRetrieved, department1); + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "redis"], + dependsOn: [redisDepartmentCreateTest] +} +function redisDepartmentReadOneTestNegative() returns error? { + RedisRainierClient rainierClient = check new (); + + Department|error departmentRetrieved = rainierClient->/departments/["invalid-department-id"].get(); + if departmentRetrieved is persist:NotFoundError { + test:assertEquals(departmentRetrieved.message(), "A record with the key 'invalid-department-id' does not exist for the entity 'Department'."); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "redis"], + dependsOn: [redisDepartmentCreateTest, redisDepartmentCreateTest2] +} +function redisDepartmentReadManyTest() returns error? { + RedisRainierClient rainierClient = check new (); + stream departmentStream = rainierClient->/departments.get(); + Department[] departments = check from Department department in departmentStream + select department; + + test:assertEquals(departments, [department1, department2, department3]); + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "redis", "dependent"], + dependsOn: [redisDepartmentCreateTest, redisDepartmentCreateTest2] +} +function redisDepartmentReadManyTestDependent() returns error? { + RedisRainierClient rainierClient = check new (); + + stream departmentStream = rainierClient->/departments.get(); + DepartmentInfo2[] departments = check from DepartmentInfo2 department in departmentStream + select department; + + test:assertEquals(departments, [ + {deptName: department1.deptName}, + {deptName: department2.deptName}, + {deptName: department3.deptName} + ]); + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "redis"], + dependsOn: [redisDepartmentReadOneTest, redisDepartmentReadManyTest, redisDepartmentReadManyTestDependent] +} +function redisDepartmentUpdateTest() returns error? { + RedisRainierClient rainierClient = check new (); + + Department department = check rainierClient->/departments/[department1.deptNo].put({ + deptName: "Finance & Legalities" + }); + + test:assertEquals(department, updatedDepartment1); + + Department departmentRetrieved = check rainierClient->/departments/[department1.deptNo].get(); + test:assertEquals(departmentRetrieved, updatedDepartment1); + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "redis"], + dependsOn: [redisDepartmentReadOneTest, redisDepartmentReadManyTest, redisDepartmentReadManyTestDependent] +} +function redisDepartmentUpdateTestNegative1() returns error? { + RedisRainierClient rainierClient = check new (); + + Department|error department = rainierClient->/departments/["invalid-department-id"].put({ + deptName: "Human Resources" + }); + + if department is persist:NotFoundError { + test:assertEquals(department.message(), "A record with the key 'invalid-department-id' does not exist for the entity 'Department'."); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "redis"], + dependsOn: [redisDepartmentReadOneTest, redisDepartmentReadManyTest, redisDepartmentReadManyTestDependent] +} +function redisDepartmentUpdateTestNegative2() returns error? { + RedisRainierClient rainierClient = check new (); + + Department|error department = rainierClient->/departments/[department1.deptNo].put({ + deptName: "unncessarily-long-department-name-to-force-error-on-update" + }); + + if department is persist:Error { + test:assertTrue(department.message().includes("value too long for type character varying")); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "redis"], + dependsOn: [redisDepartmentUpdateTest, redisDepartmentUpdateTestNegative2] +} +function redisDepartmentDeleteTest() returns error? { + RedisRainierClient rainierClient = check new (); + + Department department = check rainierClient->/departments/[department1.deptNo].delete(); + test:assertEquals(department, updatedDepartment1); + + stream departmentStream = rainierClient->/departments.get(); + Department[] departments = check from Department department2 in departmentStream + select department2; + + test:assertEquals(departments, [department2, department3]); + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "redis"], + dependsOn: [redisDepartmentDeleteTest] +} +function redisDepartmentDeleteTestNegative() returns error? { + RedisRainierClient rainierClient = check new (); + + Department|error department = rainierClient->/departments/[department1.deptNo].delete(); + + if department is persist:NotFoundError { + test:assertEquals(department.message(), string `A record with the key '${department1.deptNo}' does not exist for the entity 'Department'.`); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} diff --git a/ballerina/tests/redis-employee-tests.bal b/ballerina/tests/redis-employee-tests.bal new file mode 100644 index 0000000..3e694b6 --- /dev/null +++ b/ballerina/tests/redis-employee-tests.bal @@ -0,0 +1,260 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/persist; + +@test:Config { + groups: ["employee", "redis"], + dependsOn: [redisWorkspaceDeleteTestNegative, redisDepartmentDeleteTestNegative] +} +function redisEmployeeCreateTest() returns error? { + RedisRainierClient rainierClient = check new (); + + string[] empNos = check rainierClient->/employees.post([employee1]); + test:assertEquals(empNos, [employee1.empNo]); + + Employee employeeRetrieved = check rainierClient->/employees/[employee1.empNo].get(); + test:assertEquals(employeeRetrieved, employee1); + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "redis"], + dependsOn: [redisWorkspaceDeleteTestNegative, redisDepartmentDeleteTestNegative] +} +function redisEmployeeCreateTest2() returns error? { + RedisRainierClient rainierClient = check new (); + + string[] empNos = check rainierClient->/employees.post([employee2, employee3]); + + test:assertEquals(empNos, [employee2.empNo, employee3.empNo]); + + Employee employeeRetrieved = check rainierClient->/employees/[employee2.empNo].get(); + test:assertEquals(employeeRetrieved, employee2); + + employeeRetrieved = check rainierClient->/employees/[employee3.empNo].get(); + test:assertEquals(employeeRetrieved, employee3); + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "redis"] +} +function redisEmployeeCreateTestNegative() returns error? { + RedisRainierClient rainierClient = check new (); + + string[]|error employee = rainierClient->/employees.post([invalidEmployee]); + if employee is persist:Error { + test:assertTrue(employee.message().includes("value too long for type character varying")); + } else { + test:assertFail("Error expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "redis"], + dependsOn: [redisEmployeeCreateTest] +} +function redisEmployeeReadOneTest() returns error? { + RedisRainierClient rainierClient = check new (); + + Employee employeeRetrieved = check rainierClient->/employees/[employee1.empNo].get(); + test:assertEquals(employeeRetrieved, employee1); + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "redis"], + dependsOn: [redisEmployeeCreateTest] +} +function redisEmployeeReadOneTestNegative() returns error? { + RedisRainierClient rainierClient = check new (); + + Employee|error employeeRetrieved = rainierClient->/employees/["invalid-employee-id"].get(); + if employeeRetrieved is persist:NotFoundError { + test:assertEquals(employeeRetrieved.message(), "A record with the key 'invalid-employee-id' does not exist for the entity 'Employee'."); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "redis"], + dependsOn: [redisEmployeeCreateTest, redisEmployeeCreateTest2] +} +function redisEmployeeReadManyTest() returns error? { + RedisRainierClient rainierClient = check new (); + + stream employeeStream = rainierClient->/employees.get(); + Employee[] employees = check from Employee employee in employeeStream + select employee; + + test:assertEquals(employees, [employee1, employee2, employee3]); + check rainierClient.close(); +} + +@test:Config { + groups: ["dependent", "employee"], + dependsOn: [redisEmployeeCreateTest, redisEmployeeCreateTest2] +} +function redisEmployeeReadManyDependentTest1() returns error? { + RedisRainierClient rainierClient = check new (); + + stream employeeStream = rainierClient->/employees.get(); + EmployeeName[] employees = check from EmployeeName employee in employeeStream + select employee; + + test:assertEquals(employees, [ + {firstName: employee1.firstName, lastName: employee1.lastName}, + {firstName: employee2.firstName, lastName: employee2.lastName}, + {firstName: employee3.firstName, lastName: employee3.lastName} + ]); + check rainierClient.close(); +} + +@test:Config { + groups: ["dependent", "employee"], + dependsOn: [redisEmployeeCreateTest, redisEmployeeCreateTest2] +} +function redisEmployeeReadManyDependentTest2() returns error? { + RedisRainierClient rainierClient = check new (); + + stream employeeStream = rainierClient->/employees.get(); + EmployeeInfo2[] employees = check from EmployeeInfo2 employee in employeeStream + select employee; + + test:assertEquals(employees, [ + {empNo: employee1.empNo, birthDate: employee1.birthDate, departmentDeptNo: employee1.departmentDeptNo, workspaceWorkspaceId: employee1.workspaceWorkspaceId}, + {empNo: employee2.empNo, birthDate: employee2.birthDate, departmentDeptNo: employee2.departmentDeptNo, workspaceWorkspaceId: employee2.workspaceWorkspaceId}, + {empNo: employee3.empNo, birthDate: employee3.birthDate, departmentDeptNo: employee3.departmentDeptNo, workspaceWorkspaceId: employee3.workspaceWorkspaceId} + ]); + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "redis"], + dependsOn: [redisEmployeeReadOneTest, redisEmployeeReadManyTest, redisEmployeeReadManyDependentTest1, redisEmployeeReadManyDependentTest2] +} +function redisEmployeeUpdateTest() returns error? { + RedisRainierClient rainierClient = check new (); + + Employee employee = check rainierClient->/employees/[employee1.empNo].put({ + lastName: "Jones", + departmentDeptNo: "department-3", + birthDate: {year: 1994, month: 11, day: 13} + }); + + test:assertEquals(employee, updatedEmployee1); + + Employee employeeRetrieved = check rainierClient->/employees/[employee1.empNo].get(); + test:assertEquals(employeeRetrieved, updatedEmployee1); + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "redis"], + dependsOn: [redisEmployeeReadOneTest, redisEmployeeReadManyTest, redisEmployeeReadManyDependentTest1, redisEmployeeReadManyDependentTest2] +} +function redisEmployeeUpdateTestNegative1() returns error? { + RedisRainierClient rainierClient = check new (); + + Employee|error employee = rainierClient->/employees/["invalid-employee-id"].put({ + lastName: "Jones" + }); + + if employee is persist:NotFoundError { + test:assertEquals(employee.message(), "A record with the key 'invalid-employee-id' does not exist for the entity 'Employee'."); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "redis"], + dependsOn: [redisEmployeeReadOneTest, redisEmployeeReadManyTest, redisEmployeeReadManyDependentTest1, redisEmployeeReadManyDependentTest2] +} +function redisEmployeeUpdateTestNegative2() returns error? { + RedisRainierClient rainierClient = check new (); + + Employee|error employee = rainierClient->/employees/[employee1.empNo].put({ + firstName: "unncessarily-long-employee-name-to-force-error-on-update" + }); + + if employee is persist:Error { + test:assertTrue(employee.message().includes("value too long for type character varying")); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "redis"], + dependsOn: [redisEmployeeReadOneTest, redisEmployeeReadManyTest, redisEmployeeReadManyDependentTest1, redisEmployeeReadManyDependentTest2] +} +function redisEmployeeUpdateTestNegative3() returns error? { + RedisRainierClient rainierClient = check new (); + + Employee|error employee = rainierClient->/employees/[employee1.empNo].put({ + workspaceWorkspaceId: "invalid-workspaceWorkspaceId" + }); + + if employee is persist:ConstraintViolationError { + test:assertTrue(employee.message().includes("Detail: Key (workspaceWorkspaceId)=(invalid-workspaceWorkspaceId) is not present in table \"Workspace\".")); + } else { + test:assertFail("persist:ConstraintViolationError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "redis"], + dependsOn: [redisEmployeeUpdateTest, redisEmployeeUpdateTestNegative2, redisEmployeeUpdateTestNegative3] +} +function redisEmployeeDeleteTest() returns error? { + RedisRainierClient rainierClient = check new (); + + Employee employee = check rainierClient->/employees/[employee1.empNo].delete(); + test:assertEquals(employee, updatedEmployee1); + + stream employeeStream = rainierClient->/employees.get(); + Employee[] employees = check from Employee employee2 in employeeStream + select employee2; + + test:assertEquals(employees, [employee2, employee3]); + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "redis"], + dependsOn: [redisEmployeeDeleteTest] +} +function redisEmployeeDeleteTestNegative() returns error? { + RedisRainierClient rainierClient = check new (); + + Employee|error employee = rainierClient->/employees/[employee1.empNo].delete(); + + if employee is persist:NotFoundError { + test:assertEquals(employee.message(), string `A record with the key '${employee1.empNo}' does not exist for the entity 'Employee'.`); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} diff --git a/ballerina/tests/redis-id-fields-tests.bal b/ballerina/tests/redis-id-fields-tests.bal new file mode 100644 index 0000000..5b23a8f --- /dev/null +++ b/ballerina/tests/redis-id-fields-tests.bal @@ -0,0 +1,477 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +@test:Config { + groups: ["id-fields", "redis"] +} +function redisIntIdFieldTest() returns error? { + RedisTestEntitiesClient testEntitiesClient = check new (); + IntIdRecord intIdRecord1 = { + id: 1, + randomField: "test1" + }; + IntIdRecord intIdRecord2 = { + id: 2, + randomField: "test2" + }; + IntIdRecord intIdRecord3 = { + id: 3, + randomField: "test3" + }; + IntIdRecord intIdRecord1Updated = { + id: 1, + randomField: "test1Updated" + }; + + // create + int[] ids = check testEntitiesClient->/intidrecords.post([intIdRecord1, intIdRecord2, intIdRecord3]); + test:assertEquals(ids, [intIdRecord1.id, intIdRecord2.id, intIdRecord3.id]); + + // read one + IntIdRecord retrievedRecord1 = check testEntitiesClient->/intidrecords/[intIdRecord1.id].get(); + test:assertEquals(intIdRecord1, retrievedRecord1); + + // read one dependent + IntIdRecordDependent retrievedRecord1Dependent = check testEntitiesClient->/intidrecords/[intIdRecord1.id].get(); + test:assertEquals({randomField: intIdRecord1.randomField}, retrievedRecord1Dependent); + + // read + IntIdRecord[] intIdRecords = check from IntIdRecord intIdRecord in testEntitiesClient->/intidrecords.get(IntIdRecord) + select intIdRecord; + test:assertEquals(intIdRecords, [intIdRecord1, intIdRecord2, intIdRecord3]); + + // read dependent + IntIdRecordDependent[] intIdRecordsDependent = check from IntIdRecordDependent intIdRecord in testEntitiesClient->/intidrecords.get(IntIdRecordDependent) + select intIdRecord; + test:assertEquals(intIdRecordsDependent, [{randomField: intIdRecord1.randomField}, {randomField: intIdRecord2.randomField}, {randomField: intIdRecord3.randomField}]); + + // update + retrievedRecord1 = check testEntitiesClient->/intidrecords/[intIdRecord1.id].put({randomField: intIdRecord1Updated.randomField}); + test:assertEquals(intIdRecord1Updated, retrievedRecord1); + retrievedRecord1 = check testEntitiesClient->/intidrecords/[intIdRecord1.id].get(); + test:assertEquals(intIdRecord1Updated, retrievedRecord1); + + // delete + IntIdRecord retrievedRecord2 = check testEntitiesClient->/intidrecords/[intIdRecord2.id].delete(); + test:assertEquals(intIdRecord2, retrievedRecord2); + intIdRecords = check from IntIdRecord intIdRecord in testEntitiesClient->/intidrecords.get(IntIdRecord) + select intIdRecord; + test:assertEquals(intIdRecords, [intIdRecord3, intIdRecord1Updated]); + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["id-fields", "redis"] +} +function redisStringIdFieldTest() returns error? { + RedisTestEntitiesClient testEntitiesClient = check new (); + StringIdRecord stringIdRecord1 = { + id: "id-1", + randomField: "test1" + }; + StringIdRecord stringIdRecord2 = { + id: "id-2", + randomField: "test2" + }; + StringIdRecord stringIdRecord3 = { + id: "id-3", + randomField: "test3" + }; + StringIdRecord stringIdRecord1Updated = { + id: "id-1", + randomField: "test1Updated" + }; + + // create + string[] ids = check testEntitiesClient->/stringidrecords.post([stringIdRecord1, stringIdRecord2, stringIdRecord3]); + test:assertEquals(ids, [stringIdRecord1.id, stringIdRecord2.id, stringIdRecord3.id]); + + // read one + StringIdRecord retrievedRecord1 = check testEntitiesClient->/stringidrecords/[stringIdRecord1.id].get(); + test:assertEquals(stringIdRecord1, retrievedRecord1); + + // read one dependent + StringIdRecordDependent retrievedRecord1Dependent = check testEntitiesClient->/stringidrecords/[stringIdRecord1.id].get(); + test:assertEquals({randomField: stringIdRecord1.randomField}, retrievedRecord1Dependent); + + // read + StringIdRecord[] stringIdRecords = check from StringIdRecord stringIdRecord in testEntitiesClient->/stringidrecords.get(StringIdRecord) + select stringIdRecord; + test:assertEquals(stringIdRecords, [stringIdRecord1, stringIdRecord2, stringIdRecord3]); + + // read dependent + StringIdRecordDependent[] stringIdRecordsDependent = check from StringIdRecordDependent stringIdRecord in testEntitiesClient->/stringidrecords.get(StringIdRecordDependent) + select stringIdRecord; + test:assertEquals(stringIdRecordsDependent, [{randomField: stringIdRecord1.randomField}, {randomField: stringIdRecord2.randomField}, {randomField: stringIdRecord3.randomField}]); + + // update + retrievedRecord1 = check testEntitiesClient->/stringidrecords/[stringIdRecord1.id].put({randomField: stringIdRecord1Updated.randomField}); + test:assertEquals(stringIdRecord1Updated, retrievedRecord1); + retrievedRecord1 = check testEntitiesClient->/stringidrecords/[stringIdRecord1.id].get(); + test:assertEquals(stringIdRecord1Updated, retrievedRecord1); + + // delete + StringIdRecord retrievedRecord2 = check testEntitiesClient->/stringidrecords/[stringIdRecord2.id].delete(); + test:assertEquals(stringIdRecord2, retrievedRecord2); + stringIdRecords = check from StringIdRecord stringIdRecord in testEntitiesClient->/stringidrecords.get(StringIdRecord) + select stringIdRecord; + test:assertEquals(stringIdRecords, [stringIdRecord3, stringIdRecord1Updated]); + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["id-fields", "redis"] +} +function redisFloatIdFieldTest() returns error? { + RedisTestEntitiesClient testEntitiesClient = check new (); + FloatIdRecord floatIdRecord1 = { + id: 1.0, + randomField: "test1" + }; + FloatIdRecord floatIdRecord2 = { + id: 2.0, + randomField: "test2" + }; + FloatIdRecord floatIdRecord3 = { + id: 3.0, + randomField: "test3" + }; + FloatIdRecord floatIdRecord1Updated = { + id: 1.0, + randomField: "test1Updated" + }; + + // create + float[] ids = check testEntitiesClient->/floatidrecords.post([floatIdRecord1, floatIdRecord2, floatIdRecord3]); + test:assertEquals(ids, [floatIdRecord1.id, floatIdRecord2.id, floatIdRecord3.id]); + + // read one + FloatIdRecord retrievedRecord1 = check testEntitiesClient->/floatidrecords/[floatIdRecord1.id].get(); + test:assertEquals(floatIdRecord1, retrievedRecord1); + + // read one dependent + FloatIdRecordDependent retrievedRecord1Dependent = check testEntitiesClient->/floatidrecords/[floatIdRecord1.id].get(); + test:assertEquals({randomField: floatIdRecord1.randomField}, retrievedRecord1Dependent); + + // read + FloatIdRecord[] floatIdRecords = check from FloatIdRecord floatIdRecord in testEntitiesClient->/floatidrecords.get(FloatIdRecord) + select floatIdRecord; + test:assertEquals(floatIdRecords, [floatIdRecord1, floatIdRecord2, floatIdRecord3]); + + // read dependent + FloatIdRecordDependent[] floatIdRecordsDependent = check from FloatIdRecordDependent floatIdRecord in testEntitiesClient->/floatidrecords.get(FloatIdRecordDependent) + select floatIdRecord; + test:assertEquals(floatIdRecordsDependent, [{randomField: floatIdRecord1.randomField}, {randomField: floatIdRecord2.randomField}, {randomField: floatIdRecord3.randomField}]); + + // update + retrievedRecord1 = check testEntitiesClient->/floatidrecords/[floatIdRecord1.id].put({randomField: floatIdRecord1Updated.randomField}); + test:assertEquals(floatIdRecord1Updated, retrievedRecord1); + retrievedRecord1 = check testEntitiesClient->/floatidrecords/[floatIdRecord1.id].get(); + test:assertEquals(floatIdRecord1Updated, retrievedRecord1); + + // delete + FloatIdRecord retrievedRecord2 = check testEntitiesClient->/floatidrecords/[floatIdRecord2.id].delete(); + test:assertEquals(floatIdRecord2, retrievedRecord2); + floatIdRecords = check from FloatIdRecord floatIdRecord in testEntitiesClient->/floatidrecords.get(FloatIdRecord) + select floatIdRecord; + test:assertEquals(floatIdRecords, [floatIdRecord3, floatIdRecord1Updated]); +} + +@test:Config { + groups: ["id-fields", "redis"] +} +function redisDecimalIdFieldTest() returns error? { + RedisTestEntitiesClient testEntitiesClient = check new (); + DecimalIdRecord decimalIdRecord1 = { + id: 1.1d, + randomField: "test1" + }; + DecimalIdRecord decimalIdRecord2 = { + id: 2.2d, + randomField: "test2" + }; + DecimalIdRecord decimalIdRecord3 = { + id: 3.3d, + randomField: "test3" + }; + DecimalIdRecord decimalIdRecord1Updated = { + id: 1.1d, + randomField: "test1Updated" + }; + + // create + decimal[] ids = check testEntitiesClient->/decimalidrecords.post([decimalIdRecord1, decimalIdRecord2, decimalIdRecord3]); + test:assertEquals(ids, [decimalIdRecord1.id, decimalIdRecord2.id, decimalIdRecord3.id]); + + // read one + DecimalIdRecord retrievedRecord1 = check testEntitiesClient->/decimalidrecords/[decimalIdRecord1.id].get(); + test:assertEquals(decimalIdRecord1, retrievedRecord1); + + // read one dependent + DecimalIdRecordDependent retrievedRecord1Dependent = check testEntitiesClient->/decimalidrecords/[decimalIdRecord1.id].get(); + test:assertEquals({randomField: decimalIdRecord1.randomField}, retrievedRecord1Dependent); + + // read + DecimalIdRecord[] decimalIdRecords = check from DecimalIdRecord decimalIdRecord in testEntitiesClient->/decimalidrecords.get(DecimalIdRecord) + select decimalIdRecord; + test:assertEquals(decimalIdRecords, [decimalIdRecord1, decimalIdRecord2, decimalIdRecord3]); + + // read dependent + DecimalIdRecordDependent[] decimalIdRecordsDependent = check from DecimalIdRecordDependent decimalIdRecord in testEntitiesClient->/decimalidrecords.get(DecimalIdRecordDependent) + select decimalIdRecord; + test:assertEquals(decimalIdRecordsDependent, [{randomField: decimalIdRecord1.randomField}, {randomField: decimalIdRecord2.randomField}, {randomField: decimalIdRecord3.randomField}]); + + // update + retrievedRecord1 = check testEntitiesClient->/decimalidrecords/[decimalIdRecord1.id].put({randomField: decimalIdRecord1Updated.randomField}); + test:assertEquals(decimalIdRecord1Updated, retrievedRecord1); + retrievedRecord1 = check testEntitiesClient->/decimalidrecords/[decimalIdRecord1.id].get(); + test:assertEquals(decimalIdRecord1Updated, retrievedRecord1); + + // delete + DecimalIdRecord retrievedRecord2 = check testEntitiesClient->/decimalidrecords/[decimalIdRecord2.id].delete(); + test:assertEquals(decimalIdRecord2, retrievedRecord2); + decimalIdRecords = check from DecimalIdRecord decimalIdRecord in testEntitiesClient->/decimalidrecords.get(DecimalIdRecord) + select decimalIdRecord; + test:assertEquals(decimalIdRecords, [decimalIdRecord3, decimalIdRecord1Updated]); + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["id-fields", "redis"], + enable: false +} +function redisBooleanIdFieldTest() returns error? { + RedisTestEntitiesClient testEntitiesClient = check new (); + BooleanIdRecord booleanIdRecord1 = { + id: true, + randomField: "test1" + }; + BooleanIdRecord booleanIdRecord2 = { + id: false, + randomField: "test2" + }; + BooleanIdRecord booleanIdRecord1Updated = { + id: true, + randomField: "test1Updated" + }; + + // create + boolean[] ids = check testEntitiesClient->/booleanidrecords.post([booleanIdRecord1, booleanIdRecord2]); + test:assertEquals(ids, [booleanIdRecord1.id, booleanIdRecord2.id]); + + // read one + BooleanIdRecord retrievedRecord1 = check testEntitiesClient->/booleanidrecords/[booleanIdRecord1.id].get(); + test:assertEquals(booleanIdRecord1, retrievedRecord1); + + // read one dependent + BooleanIdRecordDependent retrievedRecord1Dependent = check testEntitiesClient->/booleanidrecords/[booleanIdRecord1.id].get(); + test:assertEquals({randomField: booleanIdRecord1.randomField}, retrievedRecord1Dependent); + + // read + BooleanIdRecord[] booleanIdRecords = check from BooleanIdRecord booleanIdRecord in testEntitiesClient->/booleanidrecords.get(BooleanIdRecord) + select booleanIdRecord; + test:assertEquals(booleanIdRecords, [booleanIdRecord2, booleanIdRecord1]); + + // read dependent + BooleanIdRecordDependent[] booleanIdRecordsDependent = check from BooleanIdRecordDependent booleanIdRecord in testEntitiesClient->/booleanidrecords.get(BooleanIdRecordDependent) + select booleanIdRecord; + test:assertEquals(booleanIdRecordsDependent, [{randomField: booleanIdRecord2.randomField}, {randomField: booleanIdRecord1.randomField}]); + + // update + retrievedRecord1 = check testEntitiesClient->/booleanidrecords/[booleanIdRecord1.id].put({randomField: booleanIdRecord1Updated.randomField}); + test:assertEquals(booleanIdRecord1Updated, retrievedRecord1); + retrievedRecord1 = check testEntitiesClient->/booleanidrecords/[booleanIdRecord1.id].get(); + test:assertEquals(booleanIdRecord1Updated, retrievedRecord1); + + // delete + BooleanIdRecord retrievedRecord2 = check testEntitiesClient->/booleanidrecords/[booleanIdRecord2.id].delete(); + test:assertEquals(booleanIdRecord2, retrievedRecord2); + booleanIdRecords = check from BooleanIdRecord booleanIdRecord in testEntitiesClient->/booleanidrecords.get(BooleanIdRecord) + select booleanIdRecord; + test:assertEquals(booleanIdRecords, [booleanIdRecord1Updated]); + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["id-fields", "redis"], + enable: false +} +function redisAllTypesIdFieldTest() returns error? { + RedisTestEntitiesClient testEntitiesClient = check new (); + AllTypesIdRecord allTypesIdRecord1 = { + intType: 1, + stringType: "id-1", + floatType: 1.0, + booleanType: true, + decimalType: 1.1d, + randomField: "test1" + }; + AllTypesIdRecord allTypesIdRecord2 = { + intType: 2, + stringType: "id-2", + floatType: 2.0, + booleanType: false, + decimalType: 2.2d, + randomField: "test2" + }; + AllTypesIdRecord allTypesIdRecord1Updated = { + intType: 1, + stringType: "id-1", + floatType: 1.0, + booleanType: true, + decimalType: 1.1d, + randomField: "test1Updated" + }; + + // create + [boolean, int, float, decimal, string][] ids = check testEntitiesClient->/alltypesidrecords.post([allTypesIdRecord1, allTypesIdRecord2]); + test:assertEquals(ids, [ + [allTypesIdRecord1.booleanType, allTypesIdRecord1.intType, allTypesIdRecord1.floatType, allTypesIdRecord1.decimalType, allTypesIdRecord1.stringType], + [allTypesIdRecord2.booleanType, allTypesIdRecord2.intType, allTypesIdRecord2.floatType, allTypesIdRecord2.decimalType, allTypesIdRecord2.stringType] + ]); + + // read one + AllTypesIdRecord retrievedRecord1 = check testEntitiesClient->/alltypesidrecords/[allTypesIdRecord1.booleanType]/[allTypesIdRecord1.intType]/[allTypesIdRecord1.floatType]/[allTypesIdRecord1.decimalType]/[allTypesIdRecord1.stringType].get(); + test:assertEquals(allTypesIdRecord1, retrievedRecord1); + + // read one dependent + AllTypesIdRecordDependent retrievedRecord1Dependent = check testEntitiesClient->/alltypesidrecords/[allTypesIdRecord1.booleanType]/[allTypesIdRecord1.intType]/[allTypesIdRecord1.floatType]/[allTypesIdRecord1.decimalType]/[allTypesIdRecord1.stringType].get(); + test:assertEquals({randomField: allTypesIdRecord1.randomField}, retrievedRecord1Dependent); + + // read + AllTypesIdRecord[] allTypesIdRecords = check from AllTypesIdRecord allTypesIdRecord in testEntitiesClient->/alltypesidrecords.get(AllTypesIdRecord) + select allTypesIdRecord; + test:assertEquals(allTypesIdRecords, [allTypesIdRecord2, allTypesIdRecord1]); + + // read dependent + AllTypesIdRecordDependent[] allTypesIdRecordsDependent = check from AllTypesIdRecordDependent allTypesIdRecord in testEntitiesClient->/alltypesidrecords.get(AllTypesIdRecordDependent) + select allTypesIdRecord; + test:assertEquals(allTypesIdRecordsDependent, [{randomField: allTypesIdRecord2.randomField}, {randomField: allTypesIdRecord1.randomField}]); + + // update + retrievedRecord1 = check testEntitiesClient->/alltypesidrecords/[allTypesIdRecord1.booleanType]/[allTypesIdRecord1.intType]/[allTypesIdRecord1.floatType]/[allTypesIdRecord1.decimalType]/[allTypesIdRecord1.stringType].put({randomField: allTypesIdRecord1Updated.randomField}); + test:assertEquals(allTypesIdRecord1Updated, retrievedRecord1); + retrievedRecord1 = check testEntitiesClient->/alltypesidrecords/[allTypesIdRecord1.booleanType]/[allTypesIdRecord1.intType]/[allTypesIdRecord1.floatType]/[allTypesIdRecord1.decimalType]/[allTypesIdRecord1.stringType].get(); + test:assertEquals(allTypesIdRecord1Updated, retrievedRecord1); + + // delete + AllTypesIdRecord retrievedRecord2 = check testEntitiesClient->/alltypesidrecords/[allTypesIdRecord2.booleanType]/[allTypesIdRecord2.intType]/[allTypesIdRecord2.floatType]/[allTypesIdRecord2.decimalType]/[allTypesIdRecord2.stringType].delete(); + test:assertEquals(allTypesIdRecord2, retrievedRecord2); + allTypesIdRecords = check from AllTypesIdRecord allTypesIdRecord in testEntitiesClient->/alltypesidrecords.get(AllTypesIdRecord) + select allTypesIdRecord; + test:assertEquals(allTypesIdRecords, [allTypesIdRecord1Updated]); + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["id-fields", "redis", "associations"], + dependsOn: [redisAllTypesIdFieldTest], + enable: false +} +function redisCompositeAssociationsTest() returns error? { + RedisTestEntitiesClient testEntitiesClient = check new (); + + CompositeAssociationRecord compositeAssociationRecord1 = { + id: "id-1", + randomField: "test1", + alltypesidrecordIntType: 1, + alltypesidrecordStringType: "id-1", + alltypesidrecordFloatType: 1.0, + alltypesidrecordBooleanType: true, + alltypesidrecordDecimalType: 1.10 + }; + + CompositeAssociationRecord compositeAssociationRecord2 = { + id: "id-2", + randomField: "test2", + alltypesidrecordIntType: 1, + alltypesidrecordStringType: "id-1", + alltypesidrecordFloatType: 1.0, + alltypesidrecordBooleanType: true, + alltypesidrecordDecimalType: 1.10 + }; + + CompositeAssociationRecord compositeAssociationRecordUpdated1 = { + id: "id-1", + randomField: "test1Updated", + alltypesidrecordIntType: 1, + alltypesidrecordStringType: "id-1", + alltypesidrecordFloatType: 1.0, + alltypesidrecordBooleanType: true, + alltypesidrecordDecimalType: 1.10 + }; + + AllTypesIdRecordOptionalized allTypesIdRecord1 = { + intType: 1, + stringType: "id-1", + floatType: 1.0, + booleanType: true, + decimalType: 1.10, + randomField: "test1Updated" + }; + + // create + string[] ids = check testEntitiesClient->/compositeassociationrecords.post([compositeAssociationRecord1, compositeAssociationRecord2]); + test:assertEquals(ids, [compositeAssociationRecord1.id, compositeAssociationRecord2.id]); + + // read one + CompositeAssociationRecord retrievedRecord1 = check testEntitiesClient->/compositeassociationrecords/[compositeAssociationRecord1.id].get(); + test:assertEquals(compositeAssociationRecord1, retrievedRecord1); + + // read one dependent + CompositeAssociationRecordDependent retrievedRecord1Dependent = check testEntitiesClient->/compositeassociationrecords/[compositeAssociationRecord1.id].get(); + test:assertEquals({ + randomField: compositeAssociationRecord1.randomField, + alltypesidrecordIntType: compositeAssociationRecord1.alltypesidrecordIntType, + alltypesidrecordDecimalType: compositeAssociationRecord1.alltypesidrecordDecimalType, + allTypesIdRecord: {intType: allTypesIdRecord1.intType, stringType: allTypesIdRecord1.stringType, booleanType: allTypesIdRecord1.booleanType, randomField: allTypesIdRecord1.randomField} + }, retrievedRecord1Dependent); + + // read + CompositeAssociationRecord[] compositeAssociationRecords = check from CompositeAssociationRecord compositeAssociationRecord in testEntitiesClient->/compositeassociationrecords.get(CompositeAssociationRecord) + select compositeAssociationRecord; + test:assertEquals(compositeAssociationRecords, [compositeAssociationRecord1, compositeAssociationRecord2]); + + // read dependent + CompositeAssociationRecordDependent[] compositeAssociationRecordsDependent = check from CompositeAssociationRecordDependent compositeAssociationRecord in testEntitiesClient->/compositeassociationrecords.get(CompositeAssociationRecordDependent) + select compositeAssociationRecord; + test:assertEquals(compositeAssociationRecordsDependent, [ + {randomField: compositeAssociationRecord1.randomField, alltypesidrecordIntType: compositeAssociationRecord1.alltypesidrecordIntType, alltypesidrecordDecimalType: compositeAssociationRecord1.alltypesidrecordDecimalType, allTypesIdRecord: {intType: allTypesIdRecord1.intType, stringType: allTypesIdRecord1.stringType, booleanType: allTypesIdRecord1.booleanType, randomField: allTypesIdRecord1.randomField}}, + {randomField: compositeAssociationRecord2.randomField, alltypesidrecordIntType: compositeAssociationRecord2.alltypesidrecordIntType, alltypesidrecordDecimalType: compositeAssociationRecord2.alltypesidrecordDecimalType, allTypesIdRecord: {intType: allTypesIdRecord1.intType, stringType: allTypesIdRecord1.stringType, booleanType: allTypesIdRecord1.booleanType, randomField: allTypesIdRecord1.randomField}} + ]); + + // update + retrievedRecord1 = check testEntitiesClient->/compositeassociationrecords/[compositeAssociationRecord1.id].put({randomField: "test1Updated"}); + test:assertEquals(compositeAssociationRecordUpdated1, retrievedRecord1); + retrievedRecord1 = check testEntitiesClient->/compositeassociationrecords/[compositeAssociationRecord1.id].get(); + test:assertEquals(compositeAssociationRecordUpdated1, retrievedRecord1); + + // delete + CompositeAssociationRecord retrievedRecord2 = check testEntitiesClient->/compositeassociationrecords/[compositeAssociationRecord2.id].delete(); + test:assertEquals(compositeAssociationRecord2, retrievedRecord2); + compositeAssociationRecords = check from CompositeAssociationRecord compositeAssociationRecord in testEntitiesClient->/compositeassociationrecords.get(CompositeAssociationRecord) + select compositeAssociationRecord; + test:assertEquals(compositeAssociationRecords, [compositeAssociationRecordUpdated1]); + + check testEntitiesClient.close(); +} diff --git a/ballerina/tests/redis-transaction-tests.bal b/ballerina/tests/redis-transaction-tests.bal new file mode 100644 index 0000000..0ab7b48 --- /dev/null +++ b/ballerina/tests/redis-transaction-tests.bal @@ -0,0 +1,75 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/persist; + +@test:Config { + groups: ["transactions", "redis"] +} +function redisTransactionTest() returns error? { + RedisRainierClient rainierClient = check new (); + + transaction { + string[] buildingCodes = check rainierClient->/buildings.post([building31, building32]); + test:assertEquals(buildingCodes, [building31.buildingCode, building32.buildingCode]); + + buildingCodes = check rainierClient->/buildings.post([building31]); + check commit; + } on fail error e { + test:assertTrue(e is persist:AlreadyExistsError, "AlreadyExistsError expected"); + } + + Building|persist:Error buildingRetrieved = rainierClient->/buildings/[building31.buildingCode].get(); + test:assertTrue(buildingRetrieved is persist:NotFoundError, "NotFoundError expected"); + + buildingRetrieved = rainierClient->/buildings/[building32.buildingCode].get(); + test:assertTrue(buildingRetrieved is persist:NotFoundError, "NotFoundError expected"); + + check rainierClient.close(); +} + +@test:Config { + groups: ["transactions", "redis"] +} +function redisTransactionTest2() returns error? { + RedisRainierClient rainierClient = check new (); + + _ = check rainierClient->/buildings.post([building33]); + Building buildingRetrieved = check rainierClient->/buildings/[building33.buildingCode].get(); + test:assertEquals(buildingRetrieved, building33); + + transaction { + Building building = check rainierClient->/buildings/[building33.buildingCode].put({ + city: "ColomboUpdated", + state: "Western ProvinceUpdated", + country: "Sri LankaUpdated" + }); + + test:assertEquals(building, building33Updated); + + // below should retrieve the updated building record + buildingRetrieved = check rainierClient->/buildings/[building33.buildingCode].get(); + test:assertEquals(buildingRetrieved, building33Updated); + + check commit; + } + + buildingRetrieved = check rainierClient->/buildings/[building33.buildingCode].get(); + test:assertEquals(buildingRetrieved, building33Updated); + + check rainierClient.close(); +} diff --git a/ballerina/tests/redis-workspace-tests.bal b/ballerina/tests/redis-workspace-tests.bal new file mode 100644 index 0000000..24d1b34 --- /dev/null +++ b/ballerina/tests/redis-workspace-tests.bal @@ -0,0 +1,251 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/persist; + +@test:Config { + groups: ["workspace", "redis"], + dependsOn: [redisBuildingDeleteTestNegative] +} +function redisWorkspaceCreateTest() returns error? { + RedisRainierClient rainierClient = check new (); + + string[] workspaceIds = check rainierClient->/workspaces.post([workspace1]); + test:assertEquals(workspaceIds, [workspace1.workspaceId]); + + Workspace workspaceRetrieved = check rainierClient->/workspaces/[workspace1.workspaceId].get(); + test:assertEquals(workspaceRetrieved, workspace1); +} + +@test:Config { + groups: ["workspace", "redis"] +} +function redisWorkspaceCreateTest2() returns error? { + RedisRainierClient rainierClient = check new (); + + string[] workspaceIds = check rainierClient->/workspaces.post([workspace2, workspace3]); + + test:assertEquals(workspaceIds, [workspace2.workspaceId, workspace3.workspaceId]); + + Workspace workspaceRetrieved = check rainierClient->/workspaces/[workspace2.workspaceId].get(); + test:assertEquals(workspaceRetrieved, workspace2); + + workspaceRetrieved = check rainierClient->/workspaces/[workspace3.workspaceId].get(); + test:assertEquals(workspaceRetrieved, workspace3); + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "redis"] +} +function redisWorkspaceCreateTestNegative() returns error? { + RedisRainierClient rainierClient = check new (); + + string[]|error workspace = rainierClient->/workspaces.post([invalidWorkspace]); + if workspace is persist:Error { + test:assertTrue(workspace.message().includes("value too long for type character varying")); + } else { + test:assertFail("Error expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "redis"], + dependsOn: [redisWorkspaceCreateTest] +} +function redisWorkspaceReadOneTest() returns error? { + RedisRainierClient rainierClient = check new (); + + Workspace workspaceRetrieved = check rainierClient->/workspaces/[workspace1.workspaceId].get(); + test:assertEquals(workspaceRetrieved, workspace1); + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "redis"], + dependsOn: [redisWorkspaceCreateTest] +} +function redisWorkspaceReadOneDependentTest() returns error? { + RedisRainierClient rainierClient = check new (); + + WorkspaceInfo2 workspaceRetrieved = check rainierClient->/workspaces/[workspace1.workspaceId].get(); + test:assertEquals(workspaceRetrieved, + { + workspaceType: workspace1.workspaceType, + locationBuildingCode: workspace1.locationBuildingCode + } + ); + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "redis"], + dependsOn: [redisWorkspaceCreateTest] +} +function redisWorkspaceReadOneTestNegative() returns error? { + RedisRainierClient rainierClient = check new (); + + Workspace|error workspaceRetrieved = rainierClient->/workspaces/["invalid-workspace-id"].get(); + if workspaceRetrieved is persist:NotFoundError { + test:assertEquals(workspaceRetrieved.message(), "A record with the key 'invalid-workspace-id' does not exist for the entity 'Workspace'."); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "redis"], + dependsOn: [redisWorkspaceCreateTest, redisWorkspaceCreateTest2] +} +function redisWorkspaceReadManyTest() returns error? { + RedisRainierClient rainierClient = check new (); + + stream workspaceStream = rainierClient->/workspaces.get(); + Workspace[] workspaces = check from Workspace workspace in workspaceStream + select workspace; + + test:assertEquals(workspaces, [workspace1, workspace2, workspace3]); + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "redis", "dependent"], + dependsOn: [redisWorkspaceCreateTest, redisWorkspaceCreateTest2] +} +function redisWorkspaceReadManyDependentTest() returns error? { + RedisRainierClient rainierClient = check new (); + + stream workspaceStream = rainierClient->/workspaces.get(); + WorkspaceInfo2[] workspaces = check from WorkspaceInfo2 workspace in workspaceStream + select workspace; + + test:assertEquals(workspaces, [ + {workspaceType: workspace1.workspaceType, locationBuildingCode: workspace1.locationBuildingCode}, + {workspaceType: workspace2.workspaceType, locationBuildingCode: workspace2.locationBuildingCode}, + {workspaceType: workspace3.workspaceType, locationBuildingCode: workspace3.locationBuildingCode} + ]); + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "redis"], + dependsOn: [redisWorkspaceReadOneTest, redisWorkspaceReadManyTest, redisWorkspaceReadManyDependentTest] +} +function redisWorkspaceUpdateTest() returns error? { + RedisRainierClient rainierClient = check new (); + + Workspace workspace = check rainierClient->/workspaces/[workspace1.workspaceId].put({ + workspaceType: "large" + }); + + test:assertEquals(workspace, updatedWorkspace1); + + Workspace workspaceRetrieved = check rainierClient->/workspaces/[workspace1.workspaceId].get(); + test:assertEquals(workspaceRetrieved, updatedWorkspace1); + check rainierClient.close(); +} + +// @test:Config { +// groups: ["workspace", "redis"], +// dependsOn: [redisWorkspaceCreateTest, redisWorkspaceCreateTest2] +// } +// function redisWorkspaceReadWithClauses() returns error? { +// RedisRainierClient rainierClient = check new (); +// string value = "small"; +// string id = "\"Workspace\".\"workspaceId\""; +// int count = 2; +// stream workspaceStream = rainierClient->/workspaces.get(whereClause = `"Workspace"."workspaceType" = ${value} OR "Workspace"."workspaceType" = 'medium'`, orderByClause = `"Workspace"."workspaceId" DESC `, limitClause = ` ${count}`, groupByClause = `${id}`); +// Workspace[] workspaces = check from Workspace workspace in workspaceStream +// select workspace; +// test:assertEquals(workspaces, [workspace3, workspace2]); +// check rainierClient.close(); +// } + +@test:Config { + groups: ["workspace", "redis"], + dependsOn: [redisWorkspaceReadOneTest, redisWorkspaceReadManyTest, redisWorkspaceReadManyDependentTest] +} +function redisWorkspaceUpdateTestNegative1() returns error? { + RedisRainierClient rainierClient = check new (); + + Workspace|error workspace = rainierClient->/workspaces/["invalid-workspace-id"].put({ + workspaceType: "large" + }); + + if workspace is persist:NotFoundError { + test:assertEquals(workspace.message(), "A record with the key 'invalid-workspace-id' does not exist for the entity 'Workspace'."); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "redis"], + dependsOn: [redisWorkspaceReadOneTest, redisWorkspaceReadManyTest, redisWorkspaceReadManyDependentTest] +} +function redisWorkspaceUpdateTestNegative2() returns error? { + RedisRainierClient rainierClient = check new (); + + Workspace|error workspace = rainierClient->/workspaces/[workspace1.workspaceId].put({ + workspaceType: "unncessarily-long-workspace-type-to-force-error-on-update" + }); + + if workspace is persist:Error { + test:assertTrue(workspace.message().includes("value too long for type character varying")); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "redis"], + dependsOn: [redisWorkspaceUpdateTest, redisWorkspaceUpdateTestNegative2] +} +function redisWorkspaceDeleteTest() returns error? { + RedisRainierClient rainierClient = check new (); + + Workspace workspace = check rainierClient->/workspaces/[workspace1.workspaceId].delete(); + test:assertEquals(workspace, updatedWorkspace1); + + stream workspaceStream = rainierClient->/workspaces.get(); + Workspace[] workspaces = check from Workspace workspace2 in workspaceStream + select workspace2; + + test:assertEquals(workspaces, [workspace2, workspace3]); + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "redis"], + dependsOn: [redisWorkspaceDeleteTest] +} +function redisWorkspaceDeleteTestNegative() returns error? { + RedisRainierClient rainierClient = check new (); + + Workspace|error workspace = rainierClient->/workspaces/[workspace1.workspaceId].delete(); + + if workspace is persist:NotFoundError { + test:assertEquals(workspace.message(), string `A record with the key '${workspace1.workspaceId}' does not exist for the entity 'Workspace'.`); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} diff --git a/ballerina/tests/redis_rainier_generated_client.bal b/ballerina/tests/redis_rainier_generated_client.bal new file mode 100644 index 0000000..31d83aa --- /dev/null +++ b/ballerina/tests/redis_rainier_generated_client.bal @@ -0,0 +1,360 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/persist; +import ballerina/jballerina.java; +import ballerinax/redis; + +const EMPLOYEE = "employees"; +const WORKSPACE = "workspaces"; +const BUILDING = "buildings"; +const DEPARTMENT = "departments"; +const ORDER_ITEM = "orderitems"; + +public isolated client class RedisRainierClient { + *persist:AbstractPersistClient; + + private final redis:Client dbClient; + + private final map persistClients; + + private final record {|RedisMetadata...;|} & readonly metadata = { + [EMPLOYEE] : { + entityName: "Employee", + collectionName: "Employee", + fieldMetadata: { + empNo: {fieldName: "empNo", fieldDataType: STRING}, + firstName: {fieldName: "firstName", fieldDataType: STRING}, + lastName: {fieldName: "lastName", fieldDataType: STRING}, + birthDate: {fieldName: "birthDate", fieldDataType: DATE}, + gender: {fieldName: "gender", fieldDataType: ENUM}, + hireDate: {fieldName: "hireDate", fieldDataType: DATE}, + departmentDeptNo: {fieldName: "departmentDeptNo", fieldDataType: STRING}, + workspaceWorkspaceId: {fieldName: "workspaceWorkspaceId", fieldDataType: STRING}, + "department.deptNo": {relation: {entityName: "department", refField: "deptNo", refFieldDataType: STRING}}, + "department.deptName": {relation: {entityName: "department", refField: "deptName", refFieldDataType: STRING}}, + "workspace.workspaceId": {relation: {entityName: "workspace", refField: "workspaceId", refFieldDataType: STRING}}, + "workspace.workspaceType": {relation: {entityName: "workspace", refField: "workspaceType", refFieldDataType: STRING}}, + "workspace.locationBuildingCode": {relation: {entityName: "workspace", refField: "locationBuildingCode", refFieldDataType: STRING}} + }, + keyFields: ["empNo"], + refMetadata: { + department: {entity: Department, fieldName: "department", refCollection: "Department", refFields: ["deptNo"], joinFields: ["departmentDeptNo"], 'type: ONE_TO_MANY}, + workspace: {entity: Workspace, fieldName: "workspace", refCollection: "Workspace", refFields: ["workspaceId"], joinFields: ["workspaceWorkspaceId"], 'type: ONE_TO_MANY} + } + }, + [WORKSPACE] : { + entityName: "Workspace", + collectionName: "Workspace", + fieldMetadata: { + workspaceId: {fieldName: "workspaceId", fieldDataType: STRING}, + workspaceType: {fieldName: "workspaceType", fieldDataType: STRING}, + locationBuildingCode: {fieldName: "locationBuildingCode", fieldDataType: STRING}, + "location.buildingCode": {relation: {entityName: "location", refField: "buildingCode", refFieldDataType: STRING}}, + "location.city": {relation: {entityName: "location", refField: "city", refFieldDataType: STRING}}, + "location.state": {relation: {entityName: "location", refField: "state", refFieldDataType: STRING}}, + "location.country": {relation: {entityName: "location", refField: "country", refFieldDataType: STRING}}, + "location.postalCode": {relation: {entityName: "location", refField: "postalCode", refFieldDataType: STRING}}, + "location.type": {relation: {entityName: "location", refField: "type", refFieldDataType: STRING}}, + "employees[].empNo": {relation: {entityName: "employees", refField: "empNo", refFieldDataType: STRING}}, + "employees[].firstName": {relation: {entityName: "employees", refField: "firstName", refFieldDataType: STRING}}, + "employees[].lastName": {relation: {entityName: "employees", refField: "lastName", refFieldDataType: STRING}}, + "employees[].birthDate": {relation: {entityName: "employees", refField: "birthDate", refFieldDataType: DATE}}, + "employees[].gender": {relation: {entityName: "employees", refField: "gender", refFieldDataType: ENUM}}, + "employees[].hireDate": {relation: {entityName: "employees", refField: "hireDate", refFieldDataType: DATE}}, + "employees[].departmentDeptNo": {relation: {entityName: "employees", refField: "departmentDeptNo", refFieldDataType: STRING}}, + "employees[].workspaceWorkspaceId": {relation: {entityName: "employees", refField: "workspaceWorkspaceId", refFieldDataType: STRING}} + }, + keyFields: ["workspaceId"], + refMetadata: { + location: {entity: Building, fieldName: "location", refCollection: "Building", refFields: ["buildingCode"], joinFields: ["locationBuildingCode"], 'type: ONE_TO_MANY}, + employees: {entity: Employee, fieldName: "employees", refCollection: "Employee", refFields: ["workspaceWorkspaceId"], joinFields: ["workspaceId"], 'type: MANY_TO_ONE} + } + }, + [BUILDING] : { + entityName: "Building", + collectionName: "Building", + fieldMetadata: { + buildingCode: {fieldName: "buildingCode", fieldDataType: STRING}, + city: {fieldName: "city", fieldDataType: STRING}, + state: {fieldName: "state", fieldDataType: STRING}, + country: {fieldName: "country", fieldDataType: STRING}, + postalCode: {fieldName: "postalCode", fieldDataType: STRING}, + 'type: {fieldName: "type", fieldDataType: STRING}, + "workspaces[].workspaceId": {relation: {entityName: "workspaces", refField: "workspaceId", refFieldDataType: STRING}}, + "workspaces[].workspaceType": {relation: {entityName: "workspaces", refField: "workspaceType", refFieldDataType: STRING}}, + "workspaces[].locationBuildingCode": {relation: {entityName: "workspaces", refField: "locationBuildingCode", refFieldDataType: STRING}} + }, + keyFields: ["buildingCode"], + refMetadata: {workspaces: {entity: Workspace, fieldName: "workspaces", refCollection: "Workspace", refFields: ["locationBuildingCode"], joinFields: ["buildingCode"], 'type: MANY_TO_ONE}} + }, + [DEPARTMENT] : { + entityName: "Department", + collectionName: "Department", + fieldMetadata: { + deptNo: {fieldName: "deptNo", fieldDataType: STRING}, + deptName: {fieldName: "deptName", fieldDataType: STRING}, + "employees[].empNo": {relation: {entityName: "employees", refField: "empNo", refFieldDataType: STRING}}, + "employees[].firstName": {relation: {entityName: "employees", refField: "firstName", refFieldDataType: STRING}}, + "employees[].lastName": {relation: {entityName: "employees", refField: "lastName", refFieldDataType: STRING}}, + "employees[].birthDate": {relation: {entityName: "employees", refField: "birthDate", refFieldDataType: DATE}}, + "employees[].gender": {relation: {entityName: "employees", refField: "gender", refFieldDataType: ENUM}}, + "employees[].hireDate": {relation: {entityName: "employees", refField: "hireDate", refFieldDataType: DATE}}, + "employees[].departmentDeptNo": {relation: {entityName: "employees", refField: "departmentDeptNo", refFieldDataType: STRING}}, + "employees[].workspaceWorkspaceId": {relation: {entityName: "employees", refField: "workspaceWorkspaceId", refFieldDataType: STRING}} + }, + keyFields: ["deptNo"], + refMetadata: {employees: {entity: Employee, fieldName: "employees", refCollection: "Employee", refFields: ["departmentDeptNo"], joinFields: ["deptNo"], 'type: MANY_TO_ONE}} + }, + [ORDER_ITEM] : { + entityName: "OrderItem", + collectionName: "OrderItem", + fieldMetadata: { + orderId: {fieldName: "orderId", fieldDataType: STRING}, + itemId: {fieldName: "itemId", fieldDataType: STRING}, + quantity: {fieldName: "quantity", fieldDataType: INT}, + notes: {fieldName: "notes", fieldDataType: STRING} + }, + keyFields: ["orderId", "itemId"] + } + }; + + public isolated function init() returns persist:Error? { + redis:Client|error dbClient = new (config = { host: redis.host+":"+redis.port.toString(), password: redis.password, options: redis.connectionOptions }); + if dbClient is error { + return error(dbClient.message()); + } + self.dbClient = dbClient; + self.persistClients = { + [EMPLOYEE] : check new (dbClient, self.metadata.get(EMPLOYEE)), + [WORKSPACE] : check new (dbClient, self.metadata.get(WORKSPACE)), + [BUILDING] : check new (dbClient, self.metadata.get(BUILDING)), + [DEPARTMENT] : check new (dbClient, self.metadata.get(DEPARTMENT)), + [ORDER_ITEM] : check new (dbClient, self.metadata.get(ORDER_ITEM)) + }; + } + + isolated resource function get employees(EmployeeTargetType targetType = <>) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "query" + } external; + + isolated resource function get employees/[string empNo](EmployeeTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "queryOne" + } external; + + isolated resource function post employees(EmployeeInsert[] data) returns string[]|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(EMPLOYEE); + } + _ = check redisClient.runBatchInsertQuery(data); + return from EmployeeInsert inserted in data + select inserted.empNo; + } + + isolated resource function put employees/[string empNo](EmployeeUpdate value) returns Employee|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(EMPLOYEE); + } + _ = check redisClient.runUpdateQuery(empNo, value); + return self->/employees/[empNo].get(); + } + + isolated resource function delete employees/[string empNo]() returns Employee|persist:Error { + Employee result = check self->/employees/[empNo].get(); + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(EMPLOYEE); + } + _ = check redisClient.runDeleteQuery(empNo); + return result; + } + + isolated resource function get workspaces(WorkspaceTargetType targetType = <>) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "query" + } external; + + isolated resource function get workspaces/[string workspaceId](WorkspaceTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "queryOne" + } external; + + isolated resource function post workspaces(WorkspaceInsert[] data) returns string[]|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(WORKSPACE); + } + _ = check redisClient.runBatchInsertQuery(data); + return from WorkspaceInsert inserted in data + select inserted.workspaceId; + } + + isolated resource function put workspaces/[string workspaceId](WorkspaceUpdate value) returns Workspace|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(WORKSPACE); + } + _ = check redisClient.runUpdateQuery(workspaceId, value); + return self->/workspaces/[workspaceId].get(); + } + + isolated resource function delete workspaces/[string workspaceId]() returns Workspace|persist:Error { + Workspace result = check self->/workspaces/[workspaceId].get(); + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(WORKSPACE); + } + _ = check redisClient.runDeleteQuery(workspaceId); + return result; + } + + isolated resource function get buildings(BuildingTargetType targetType = <>) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "query" + } external; + + isolated resource function get buildings/[string buildingCode](BuildingTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "queryOne" + } external; + + isolated resource function post buildings(BuildingInsert[] data) returns string[]|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(BUILDING); + } + _ = check redisClient.runBatchInsertQuery(data); + return from BuildingInsert inserted in data + select inserted.buildingCode; + } + + isolated resource function put buildings/[string buildingCode](BuildingUpdate value) returns Building|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(BUILDING); + } + _ = check redisClient.runUpdateQuery(buildingCode, value); + return self->/buildings/[buildingCode].get(); + } + + isolated resource function delete buildings/[string buildingCode]() returns Building|persist:Error { + Building result = check self->/buildings/[buildingCode].get(); + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(BUILDING); + } + _ = check redisClient.runDeleteQuery(buildingCode); + return result; + } + + isolated resource function get departments(DepartmentTargetType targetType = <>) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "query" + } external; + + isolated resource function get departments/[string deptNo](DepartmentTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "queryOne" + } external; + + isolated resource function post departments(DepartmentInsert[] data) returns string[]|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(DEPARTMENT); + } + _ = check redisClient.runBatchInsertQuery(data); + return from DepartmentInsert inserted in data + select inserted.deptNo; + } + + isolated resource function put departments/[string deptNo](DepartmentUpdate value) returns Department|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(DEPARTMENT); + } + _ = check redisClient.runUpdateQuery(deptNo, value); + return self->/departments/[deptNo].get(); + } + + isolated resource function delete departments/[string deptNo]() returns Department|persist:Error { + Department result = check self->/departments/[deptNo].get(); + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(DEPARTMENT); + } + _ = check redisClient.runDeleteQuery(deptNo); + return result; + } + + isolated resource function get orderitems(OrderItemTargetType targetType = <>) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "query" + } external; + + isolated resource function get orderitems/[string orderId]/[string itemId](OrderItemTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "queryOne" + } external; + + isolated resource function post orderitems(OrderItemInsert[] data) returns [string, string][]|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(ORDER_ITEM); + } + _ = check redisClient.runBatchInsertQuery(data); + return from OrderItemInsert inserted in data + select [inserted.orderId, inserted.itemId]; + } + + isolated resource function put orderitems/[string orderId]/[string itemId](OrderItemUpdate value) returns OrderItem|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(ORDER_ITEM); + } + _ = check redisClient.runUpdateQuery({"orderId": orderId, "itemId": itemId}, value); + return self->/orderitems/[orderId]/[itemId].get(); + } + + isolated resource function delete orderitems/[string orderId]/[string itemId]() returns OrderItem|persist:Error { + OrderItem result = check self->/orderitems/[orderId]/[itemId].get(); + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(ORDER_ITEM); + } + _ = check redisClient.runDeleteQuery({"orderId": orderId, "itemId": itemId}); + return result; + } + + // remote isolated function queryNativeSQL(redis:ParameterizedQuery redisQuery, typedesc rowType = <>) returns stream = @java:Method { + // 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor" + // } external; + + // remote isolated function executeNativeSQL(redis:ParameterizedQuery redisQuery) returns ExecutionResult|persist:Error = @java:Method { + // 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor" + // } external; + + public isolated function close() returns persist:Error? { + error? result = self.dbClient.stop(); + if result is error { + return error(result.message()); + } + return result; + } +} diff --git a/ballerina/tests/redis_test_entities_generated_client.bal b/ballerina/tests/redis_test_entities_generated_client.bal new file mode 100644 index 0000000..44e576e --- /dev/null +++ b/ballerina/tests/redis_test_entities_generated_client.bal @@ -0,0 +1,496 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/persist; +import ballerina/jballerina.java; +import ballerinax/redis; + +const ALL_TYPES = "alltypes"; +const STRING_ID_RECORD = "stringidrecords"; +const INT_ID_RECORD = "intidrecords"; +const FLOAT_ID_RECORD = "floatidrecords"; +const DECIMAL_ID_RECORD = "decimalidrecords"; +const BOOLEAN_ID_RECORD = "booleanidrecords"; +const COMPOSITE_ASSOCIATION_RECORD = "compositeassociationrecords"; +const ALL_TYPES_ID_RECORD = "alltypesidrecords"; + +public isolated client class RedisTestEntitiesClient { + *persist:AbstractPersistClient; + + private final redis:Client dbClient; + + private final map persistClients; + + private final record {|RedisMetadata...;|} & readonly metadata = { + [ALL_TYPES] : { + entityName: "AllTypes", + collectionName: "AllTypes", + fieldMetadata: { + id: {fieldName: "id", fieldDataType: INT}, + booleanType: {fieldName: "booleanType", fieldDataType: BOOLEAN}, + intType: {fieldName: "intType", fieldDataType: INT}, + floatType: {fieldName: "floatType", fieldDataType: FLOAT}, + decimalType: {fieldName: "decimalType", fieldDataType: DECIMAL}, + stringType: {fieldName: "stringType", fieldDataType: STRING}, + dateType: {fieldName: "dateType", fieldDataType: DATE}, + timeOfDayType: {fieldName: "timeOfDayType", fieldDataType: TIME_OF_DAY}, + booleanTypeOptional: {fieldName: "booleanTypeOptional", fieldDataType: BOOLEAN}, + intTypeOptional: {fieldName: "intTypeOptional", fieldDataType: INT}, + floatTypeOptional: {fieldName: "floatTypeOptional", fieldDataType: FLOAT}, + decimalTypeOptional: {fieldName: "decimalTypeOptional", fieldDataType: DECIMAL}, + stringTypeOptional: {fieldName: "stringTypeOptional", fieldDataType: STRING}, + dateTypeOptional: {fieldName: "dateTypeOptional", fieldDataType: DATE}, + timeOfDayTypeOptional: {fieldName: "timeOfDayTypeOptional", fieldDataType: TIME_OF_DAY}, + enumType: {fieldName: "enumType", fieldDataType: ENUM}, + enumTypeOptional: {fieldName: "enumTypeOptional", fieldDataType: ENUM} + }, + keyFields: ["id"] + }, + [STRING_ID_RECORD] : { + entityName: "StringIdRecord", + collectionName: "StringIdRecord", + fieldMetadata: { + id: {fieldName: "id", fieldDataType: STRING}, + randomField: {fieldName: "randomField", fieldDataType: STRING} + }, + keyFields: ["id"] + }, + [INT_ID_RECORD] : { + entityName: "IntIdRecord", + collectionName: "IntIdRecord", + fieldMetadata: { + id: {fieldName: "id", fieldDataType: INT}, + randomField: {fieldName: "randomField", fieldDataType: STRING} + }, + keyFields: ["id"] + }, + [FLOAT_ID_RECORD] : { + entityName: "FloatIdRecord", + collectionName: "FloatIdRecord", + fieldMetadata: { + id: {fieldName: "id", fieldDataType: FLOAT}, + randomField: {fieldName: "randomField", fieldDataType: STRING} + }, + keyFields: ["id"] + }, + [DECIMAL_ID_RECORD] : { + entityName: "DecimalIdRecord", + collectionName: "DecimalIdRecord", + fieldMetadata: { + id: {fieldName: "id", fieldDataType: DECIMAL}, + randomField: {fieldName: "randomField", fieldDataType: STRING} + }, + keyFields: ["id"] + }, + [BOOLEAN_ID_RECORD] : { + entityName: "BooleanIdRecord", + collectionName: "BooleanIdRecord", + fieldMetadata: { + id: {fieldName: "id", fieldDataType: BOOLEAN}, + randomField: {fieldName: "randomField", fieldDataType: STRING} + }, + keyFields: ["id"] + }, + [COMPOSITE_ASSOCIATION_RECORD] : { + entityName: "CompositeAssociationRecord", + collectionName: "CompositeAssociationRecord", + fieldMetadata: { + id: {fieldName: "id", fieldDataType: STRING}, + randomField: {fieldName: "randomField", fieldDataType: STRING}, + alltypesidrecordBooleanType: {fieldName: "alltypesidrecordBooleanType", fieldDataType: BOOLEAN}, + alltypesidrecordIntType: {fieldName: "alltypesidrecordIntType", fieldDataType: INT}, + alltypesidrecordFloatType: {fieldName: "alltypesidrecordFloatType", fieldDataType: FLOAT}, + alltypesidrecordDecimalType: {fieldName: "alltypesidrecordDecimalType", fieldDataType: DECIMAL}, + alltypesidrecordStringType: {fieldName: "alltypesidrecordStringType", fieldDataType: STRING}, + "allTypesIdRecord.booleanType": {relation: {entityName: "allTypesIdRecord", refField: "booleanType", refFieldDataType: BOOLEAN}}, + "allTypesIdRecord.intType": {relation: {entityName: "allTypesIdRecord", refField: "intType", refFieldDataType: INT}}, + "allTypesIdRecord.floatType": {relation: {entityName: "allTypesIdRecord", refField: "floatType", refFieldDataType: FLOAT}}, + "allTypesIdRecord.decimalType": {relation: {entityName: "allTypesIdRecord", refField: "decimalType", refFieldDataType: DECIMAL}}, + "allTypesIdRecord.stringType": {relation: {entityName: "allTypesIdRecord", refField: "stringType", refFieldDataType: STRING}}, + "allTypesIdRecord.randomField": {relation: {entityName: "allTypesIdRecord", refField: "randomField", refFieldDataType: STRING}} + }, + keyFields: ["id"], + refMetadata: {allTypesIdRecord: {entity: AllTypesIdRecord, fieldName: "allTypesIdRecord", refCollection: "AllTypesIdRecord", refFields: ["booleanType", "intType", "floatType", "decimalType", "stringType"], joinFields: ["alltypesidrecordBooleanType", "alltypesidrecordIntType", "alltypesidrecordFloatType", "alltypesidrecordDecimalType", "alltypesidrecordStringType"], 'type: ONE_TO_ONE}} + }, + [ALL_TYPES_ID_RECORD] : { + entityName: "AllTypesIdRecord", + collectionName: "AllTypesIdRecord", + fieldMetadata: { + booleanType: {fieldName: "booleanType", fieldDataType: BOOLEAN}, + intType: {fieldName: "intType", fieldDataType: INT}, + floatType: {fieldName: "floatType", fieldDataType: FLOAT}, + decimalType: {fieldName: "decimalType", fieldDataType: DECIMAL}, + stringType: {fieldName: "stringType", fieldDataType: STRING}, + randomField: {fieldName: "randomField", fieldDataType: STRING}, + "compositeAssociationRecord.id": {relation: {entityName: "compositeAssociationRecord", refField: "id", refFieldDataType: STRING}}, + "compositeAssociationRecord.randomField": {relation: {entityName: "compositeAssociationRecord", refField: "randomField", refFieldDataType: STRING}}, + "compositeAssociationRecord.alltypesidrecordBooleanType": {relation: {entityName: "compositeAssociationRecord", refField: "alltypesidrecordBooleanType", refFieldDataType: BOOLEAN}}, + "compositeAssociationRecord.alltypesidrecordIntType": {relation: {entityName: "compositeAssociationRecord", refField: "alltypesidrecordIntType", refFieldDataType: INT}}, + "compositeAssociationRecord.alltypesidrecordFloatType": {relation: {entityName: "compositeAssociationRecord", refField: "alltypesidrecordFloatType", refFieldDataType: FLOAT}}, + "compositeAssociationRecord.alltypesidrecordDecimalType": {relation: {entityName: "compositeAssociationRecord", refField: "alltypesidrecordDecimalType", refFieldDataType: DECIMAL}}, + "compositeAssociationRecord.alltypesidrecordStringType": {relation: {entityName: "compositeAssociationRecord", refField: "alltypesidrecordStringType", refFieldDataType: STRING}} + }, + keyFields: ["booleanType", "intType", "floatType", "decimalType", "stringType"], + refMetadata: {compositeAssociationRecord: {entity: CompositeAssociationRecord, fieldName: "compositeAssociationRecord", refCollection: "CompositeAssociationRecord", refFields: ["alltypesidrecordBooleanType", "alltypesidrecordIntType", "alltypesidrecordFloatType", "alltypesidrecordDecimalType", "alltypesidrecordStringType"], joinFields: ["booleanType", "intType", "floatType", "decimalType", "stringType"], 'type: ONE_TO_ONE}} + } + }; + + public isolated function init() returns persist:Error? { + redis:Client|error dbClient = new (config = { host: redis.host+":"+redis.port.toString(), password: redis.password, options: redis.connectionOptions }); + if dbClient is error { + return error(dbClient.message()); + } + self.dbClient = dbClient; + self.persistClients = { + [ALL_TYPES] : check new (dbClient, self.metadata.get(ALL_TYPES)), + [STRING_ID_RECORD] : check new (dbClient, self.metadata.get(STRING_ID_RECORD)), + [INT_ID_RECORD] : check new (dbClient, self.metadata.get(INT_ID_RECORD)), + [FLOAT_ID_RECORD] : check new (dbClient, self.metadata.get(FLOAT_ID_RECORD)), + [DECIMAL_ID_RECORD] : check new (dbClient, self.metadata.get(DECIMAL_ID_RECORD)), + [BOOLEAN_ID_RECORD] : check new (dbClient, self.metadata.get(BOOLEAN_ID_RECORD)), + [COMPOSITE_ASSOCIATION_RECORD] : check new (dbClient, self.metadata.get(COMPOSITE_ASSOCIATION_RECORD)), + [ALL_TYPES_ID_RECORD] : check new (dbClient, self.metadata.get(ALL_TYPES_ID_RECORD)) + }; + } + + isolated resource function get alltypes(AllTypesTargetType targetType = <>) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "query" + } external; + + isolated resource function get alltypes/[int id](AllTypesTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "queryOne" + } external; + + isolated resource function post alltypes(AllTypesInsert[] data) returns int[]|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(ALL_TYPES); + } + _ = check redisClient.runBatchInsertQuery(data); + return from AllTypesInsert inserted in data + select inserted.id; + } + + isolated resource function put alltypes/[int id](AllTypesUpdate value) returns AllTypes|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(ALL_TYPES); + } + _ = check redisClient.runUpdateQuery(id, value); + return self->/alltypes/[id].get(); + } + + isolated resource function delete alltypes/[int id]() returns AllTypes|persist:Error { + AllTypes result = check self->/alltypes/[id].get(); + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(ALL_TYPES); + } + _ = check redisClient.runDeleteQuery(id); + return result; + } + + isolated resource function get stringidrecords(StringIdRecordTargetType targetType = <>) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "query" + } external; + + isolated resource function get stringidrecords/[string id](StringIdRecordTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "queryOne" + } external; + + isolated resource function post stringidrecords(StringIdRecordInsert[] data) returns string[]|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(STRING_ID_RECORD); + } + _ = check redisClient.runBatchInsertQuery(data); + return from StringIdRecordInsert inserted in data + select inserted.id; + } + + isolated resource function put stringidrecords/[string id](StringIdRecordUpdate value) returns StringIdRecord|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(STRING_ID_RECORD); + } + _ = check redisClient.runUpdateQuery(id, value); + return self->/stringidrecords/[id].get(); + } + + isolated resource function delete stringidrecords/[string id]() returns StringIdRecord|persist:Error { + StringIdRecord result = check self->/stringidrecords/[id].get(); + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(STRING_ID_RECORD); + } + _ = check redisClient.runDeleteQuery(id); + return result; + } + + isolated resource function get intidrecords(IntIdRecordTargetType targetType = <>) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "query" + } external; + + isolated resource function get intidrecords/[int id](IntIdRecordTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "queryOne" + } external; + + isolated resource function post intidrecords(IntIdRecordInsert[] data) returns int[]|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(INT_ID_RECORD); + } + _ = check redisClient.runBatchInsertQuery(data); + return from IntIdRecordInsert inserted in data + select inserted.id; + } + + isolated resource function put intidrecords/[int id](IntIdRecordUpdate value) returns IntIdRecord|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(INT_ID_RECORD); + } + _ = check redisClient.runUpdateQuery(id, value); + return self->/intidrecords/[id].get(); + } + + isolated resource function delete intidrecords/[int id]() returns IntIdRecord|persist:Error { + IntIdRecord result = check self->/intidrecords/[id].get(); + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(INT_ID_RECORD); + } + _ = check redisClient.runDeleteQuery(id); + return result; + } + + isolated resource function get floatidrecords(FloatIdRecordTargetType targetType = <>) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "query" + } external; + + isolated resource function get floatidrecords/[float id](FloatIdRecordTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "queryOne" + } external; + + isolated resource function post floatidrecords(FloatIdRecordInsert[] data) returns float[]|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(FLOAT_ID_RECORD); + } + _ = check redisClient.runBatchInsertQuery(data); + return from FloatIdRecordInsert inserted in data + select inserted.id; + } + + isolated resource function put floatidrecords/[float id](FloatIdRecordUpdate value) returns FloatIdRecord|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(FLOAT_ID_RECORD); + } + _ = check redisClient.runUpdateQuery(id, value); + return self->/floatidrecords/[id].get(); + } + + isolated resource function delete floatidrecords/[float id]() returns FloatIdRecord|persist:Error { + FloatIdRecord result = check self->/floatidrecords/[id].get(); + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(FLOAT_ID_RECORD); + } + _ = check redisClient.runDeleteQuery(id); + return result; + } + + isolated resource function get decimalidrecords(DecimalIdRecordTargetType targetType = <>) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "query" + } external; + + isolated resource function get decimalidrecords/[decimal id](DecimalIdRecordTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "queryOne" + } external; + + isolated resource function post decimalidrecords(DecimalIdRecordInsert[] data) returns decimal[]|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(DECIMAL_ID_RECORD); + } + _ = check redisClient.runBatchInsertQuery(data); + return from DecimalIdRecordInsert inserted in data + select inserted.id; + } + + isolated resource function put decimalidrecords/[decimal id](DecimalIdRecordUpdate value) returns DecimalIdRecord|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(DECIMAL_ID_RECORD); + } + _ = check redisClient.runUpdateQuery(id, value); + return self->/decimalidrecords/[id].get(); + } + + isolated resource function delete decimalidrecords/[decimal id]() returns DecimalIdRecord|persist:Error { + DecimalIdRecord result = check self->/decimalidrecords/[id].get(); + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(DECIMAL_ID_RECORD); + } + _ = check redisClient.runDeleteQuery(id); + return result; + } + + isolated resource function get booleanidrecords(BooleanIdRecordTargetType targetType = <>) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "query" + } external; + + isolated resource function get booleanidrecords/[boolean id](BooleanIdRecordTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "queryOne" + } external; + + isolated resource function post booleanidrecords(BooleanIdRecordInsert[] data) returns boolean[]|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(BOOLEAN_ID_RECORD); + } + _ = check redisClient.runBatchInsertQuery(data); + return from BooleanIdRecordInsert inserted in data + select inserted.id; + } + + isolated resource function put booleanidrecords/[boolean id](BooleanIdRecordUpdate value) returns BooleanIdRecord|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(BOOLEAN_ID_RECORD); + } + _ = check redisClient.runUpdateQuery(id, value); + return self->/booleanidrecords/[id].get(); + } + + isolated resource function delete booleanidrecords/[boolean id]() returns BooleanIdRecord|persist:Error { + BooleanIdRecord result = check self->/booleanidrecords/[id].get(); + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(BOOLEAN_ID_RECORD); + } + _ = check redisClient.runDeleteQuery(id); + return result; + } + + isolated resource function get compositeassociationrecords(CompositeAssociationRecordTargetType targetType = <>) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "query" + } external; + + isolated resource function get compositeassociationrecords/[string id](CompositeAssociationRecordTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "queryOne" + } external; + + isolated resource function post compositeassociationrecords(CompositeAssociationRecordInsert[] data) returns string[]|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(COMPOSITE_ASSOCIATION_RECORD); + } + _ = check redisClient.runBatchInsertQuery(data); + return from CompositeAssociationRecordInsert inserted in data + select inserted.id; + } + + isolated resource function put compositeassociationrecords/[string id](CompositeAssociationRecordUpdate value) returns CompositeAssociationRecord|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(COMPOSITE_ASSOCIATION_RECORD); + } + _ = check redisClient.runUpdateQuery(id, value); + return self->/compositeassociationrecords/[id].get(); + } + + isolated resource function delete compositeassociationrecords/[string id]() returns CompositeAssociationRecord|persist:Error { + CompositeAssociationRecord result = check self->/compositeassociationrecords/[id].get(); + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(COMPOSITE_ASSOCIATION_RECORD); + } + _ = check redisClient.runDeleteQuery(id); + return result; + } + + isolated resource function get alltypesidrecords(AllTypesIdRecordTargetType targetType = <>) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "query" + } external; + + isolated resource function get alltypesidrecords/[boolean booleanType]/[int intType]/[float floatType]/[decimal decimalType]/[string stringType](AllTypesIdRecordTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor", + name: "queryOne" + } external; + + isolated resource function post alltypesidrecords(AllTypesIdRecordInsert[] data) returns [boolean, int, float, decimal, string][]|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(ALL_TYPES_ID_RECORD); + } + _ = check redisClient.runBatchInsertQuery(data); + return from AllTypesIdRecordInsert inserted in data + select [inserted.booleanType, inserted.intType, inserted.floatType, inserted.decimalType, inserted.stringType]; + } + + isolated resource function put alltypesidrecords/[boolean booleanType]/[int intType]/[float floatType]/[decimal decimalType]/[string stringType](AllTypesIdRecordUpdate value) returns AllTypesIdRecord|persist:Error { + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(ALL_TYPES_ID_RECORD); + } + _ = check redisClient.runUpdateQuery({"booleanType": booleanType, "intType": intType, "floatType": floatType, "decimalType": decimalType, "stringType": stringType}, value); + return self->/alltypesidrecords/[booleanType]/[intType]/[floatType]/[decimalType]/[stringType].get(); + } + + isolated resource function delete alltypesidrecords/[boolean booleanType]/[int intType]/[float floatType]/[decimal decimalType]/[string stringType]() returns AllTypesIdRecord|persist:Error { + AllTypesIdRecord result = check self->/alltypesidrecords/[booleanType]/[intType]/[floatType]/[decimalType]/[stringType].get(); + RedisClient redisClient; + lock { + redisClient = self.persistClients.get(ALL_TYPES_ID_RECORD); + } + _ = check redisClient.runDeleteQuery({"booleanType": booleanType, "intType": intType, "floatType": floatType, "decimalType": decimalType, "stringType": stringType}); + return result; + } + + // remote isolated function queryNativeSQL(redis:ParameterizedQuery redisQuery, typedesc rowType = <>) returns stream = @java:Method { + // 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor" + // } external; + + // remote isolated function executeNativeSQL(redis:ParameterizedQuery redisQuery) returns ExecutionResult|persist:Error = @java:Method { + // 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor" + // } external; + + public isolated function close() returns persist:Error? { + error? result = self.dbClient.stop(); + if result is error { + return error(result.message()); + } + return result; + } +} diff --git a/ballerina/tests/test_entities_generated_types.bal b/ballerina/tests/test_entities_generated_types.bal new file mode 100644 index 0000000..f967c5d --- /dev/null +++ b/ballerina/tests/test_entities_generated_types.bal @@ -0,0 +1,289 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/time; + +public enum EnumType { + TYPE_1, + TYPE_2, + TYPE_3, + TYPE_4 +} + +public enum OrderType { + ONLINE, + INSTORE +} + +public type AllTypes record {| + readonly int id; + boolean booleanType; + int intType; + float floatType; + decimal decimalType; + string stringType; + time:Date dateType; + time:TimeOfDay timeOfDayType; + boolean? booleanTypeOptional; + int? intTypeOptional; + float? floatTypeOptional; + decimal? decimalTypeOptional; + string? stringTypeOptional; + time:Date? dateTypeOptional; + time:TimeOfDay? timeOfDayTypeOptional; + EnumType enumType; + EnumType? enumTypeOptional; +|}; + +public type AllTypesOptionalized record {| + int id?; + boolean booleanType?; + int intType?; + float floatType?; + decimal decimalType?; + string stringType?; + time:Date dateType?; + time:TimeOfDay timeOfDayType?; + boolean? booleanTypeOptional?; + int? intTypeOptional?; + float? floatTypeOptional?; + decimal? decimalTypeOptional?; + string? stringTypeOptional?; + time:Date? dateTypeOptional?; + time:TimeOfDay? timeOfDayTypeOptional?; + EnumType? enumType?; + EnumType? enumTypeOptional?; +|}; + +public type AllTypesTargetType typedesc; + +public type AllTypesInsert AllTypes; + +public type AllTypesUpdate record {| + boolean booleanType?; + int intType?; + float floatType?; + decimal decimalType?; + string stringType?; + time:Date dateType?; + time:TimeOfDay timeOfDayType?; + boolean? booleanTypeOptional?; + int? intTypeOptional?; + float? floatTypeOptional?; + decimal? decimalTypeOptional?; + string? stringTypeOptional?; + time:Date? dateTypeOptional?; + time:TimeOfDay? timeOfDayTypeOptional?; + EnumType? enumType?; + EnumType? enumTypeOptional?; +|}; + +public type StringIdRecord record {| + readonly string id; + string randomField; +|}; + +public type StringIdRecordOptionalized record {| + string id?; + string randomField?; +|}; + +public type StringIdRecordTargetType typedesc; + +public type StringIdRecordInsert StringIdRecord; + +public type StringIdRecordUpdate record {| + string randomField?; +|}; + +public type IntIdRecord record {| + readonly int id; + string randomField; +|}; + +public type IntIdRecordOptionalized record {| + int id?; + string randomField?; +|}; + +public type IntIdRecordTargetType typedesc; + +public type IntIdRecordInsert IntIdRecord; + +public type IntIdRecordUpdate record {| + string randomField?; +|}; + +public type FloatIdRecord record {| + readonly float id; + string randomField; +|}; + +public type FloatIdRecordOptionalized record {| + float id?; + string randomField?; +|}; + +public type FloatIdRecordTargetType typedesc; + +public type FloatIdRecordInsert FloatIdRecord; + +public type FloatIdRecordUpdate record {| + string randomField?; +|}; + +public type DecimalIdRecord record {| + readonly decimal id; + string randomField; +|}; + +public type DecimalIdRecordOptionalized record {| + decimal id?; + string randomField?; +|}; + +public type DecimalIdRecordTargetType typedesc; + +public type DecimalIdRecordInsert DecimalIdRecord; + +public type DecimalIdRecordUpdate record {| + string randomField?; +|}; + +public type BooleanIdRecord record {| + readonly boolean id; + string randomField; +|}; + +public type BooleanIdRecordOptionalized record {| + boolean id?; + string randomField?; +|}; + +public type BooleanIdRecordTargetType typedesc; + +public type BooleanIdRecordInsert BooleanIdRecord; + +public type BooleanIdRecordUpdate record {| + string randomField?; +|}; + +public type CompositeAssociationRecord record {| + readonly string id; + string randomField; + boolean alltypesidrecordBooleanType; + int alltypesidrecordIntType; + float alltypesidrecordFloatType; + decimal alltypesidrecordDecimalType; + string alltypesidrecordStringType; +|}; + +public type CompositeAssociationRecordOptionalized record {| + string id?; + string randomField?; + boolean alltypesidrecordBooleanType?; + int alltypesidrecordIntType?; + float alltypesidrecordFloatType?; + decimal alltypesidrecordDecimalType?; + string alltypesidrecordStringType?; +|}; + +public type CompositeAssociationRecordWithRelations record {| + *CompositeAssociationRecordOptionalized; + AllTypesIdRecordOptionalized allTypesIdRecord?; +|}; + +public type CompositeAssociationRecordTargetType typedesc; + +public type CompositeAssociationRecordInsert CompositeAssociationRecord; + +public type CompositeAssociationRecordUpdate record {| + string randomField?; + boolean alltypesidrecordBooleanType?; + int alltypesidrecordIntType?; + float alltypesidrecordFloatType?; + decimal alltypesidrecordDecimalType?; + string alltypesidrecordStringType?; +|}; + +public type AllTypesIdRecord record {| + readonly boolean booleanType; + readonly int intType; + readonly float floatType; + readonly decimal decimalType; + readonly string stringType; + string randomField; +|}; + +public type AllTypesIdRecordOptionalized record {| + boolean booleanType?; + int intType?; + float floatType?; + decimal decimalType?; + string stringType?; + string randomField?; +|}; + +public type AllTypesIdRecordWithRelations record {| + *AllTypesIdRecordOptionalized; + CompositeAssociationRecordOptionalized compositeAssociationRecord?; +|}; + +public type AllTypesIdRecordTargetType typedesc; + +public type AllTypesIdRecordInsert AllTypesIdRecord; + +public type AllTypesIdRecordUpdate record {| + string randomField?; +|}; + +public type OrderItemExtended record {| + readonly string orderId; + readonly string itemId; + int CustomerId; + boolean paid; + float ammountPaid; + decimal ammountPaidDecimal; + time:Date arivalTimeDate; + time:TimeOfDay arivalTimeTimeOfDay; + OrderType orderType; +|}; + +public type OrderItemExtendedOptionalized record {| + string orderId?; + string itemId?; + int CustomerId?; + boolean paid?; + float ammountPaid?; + decimal ammountPaidDecimal?; + time:Date arivalTimeDate?; + time:TimeOfDay arivalTimeTimeOfDay?; + OrderType orderType?; +|}; + +public type OrderItemExtendedTargetType typedesc; + +public type OrderItemExtendedInsert OrderItemExtended; + +public type OrderItemExtendedUpdate record {| + int CustomerId?; + boolean paid?; + float ammountPaid?; + decimal ammountPaidDecimal?; + time:Date arivalTimeDate?; + time:TimeOfDay arivalTimeTimeOfDay?; + OrderType orderType?; +|}; diff --git a/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java index d3a281b..c91f3c1 100644 --- a/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java +++ b/native/src/main/java/io/ballerina/stdlib/persist/redis/datastore/RedisProcessor.java @@ -23,6 +23,7 @@ import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.async.Callback; import io.ballerina.runtime.api.creators.TypeCreator; +import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.types.ErrorType; import io.ballerina.runtime.api.types.RecordType; import io.ballerina.runtime.api.types.StreamType; @@ -67,6 +68,7 @@ public static BStream query(Environment env, BObject client, BTypedesc targetTyp RecordType recordType = (RecordType) targetType.getDescribingType(); RecordType recordTypeWithIdFields = getRecordTypeWithKeyFields(keyFields, recordType); + BTypedesc targetTypeWithIdFields = ValueCreator.createTypedescValue(recordTypeWithIdFields); StreamType streamTypeWithIdFields = TypeCreator.createStreamType(recordTypeWithIdFields, PredefinedTypes.TYPE_NULL); @@ -106,7 +108,7 @@ public void notifyFailure(BError bError) { typeDescriptions, persistClient, wrapError(bError))); } }, trxContextProperties, streamTypeWithIdFields, - targetType, true, typeMap, true, fields, true, includes, true); + targetTypeWithIdFields, true, typeMap, true, fields, true, includes, true); return null; } From 8d57aa55c40f5b8caa43f505816c7727aaa684bb Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 14 Feb 2024 12:11:06 +0530 Subject: [PATCH 39/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 404feeb..f59c660 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,18 +7,6 @@ dependencies-toml-version = "2" distribution-version = "2201.8.2" -[[package]] -org = "ballerina" -name = "io" -version = "1.4.1" -dependencies = [ - {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "lang.value"} -] -modules = [ - {org = "ballerina", packageName = "io", moduleName = "io"} -] - [[package]] org = "ballerina" name = "jballerina.java" @@ -36,14 +24,6 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] -[[package]] -org = "ballerina" -name = "lang.value" -version = "0.0.0" -dependencies = [ - {org = "ballerina", name = "jballerina.java"} -] - [[package]] org = "ballerina" name = "persist" @@ -84,7 +64,6 @@ org = "ballerinax" name = "persist.redis" version = "0.1.0" dependencies = [ - {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, {org = "ballerina", name = "test"}, From 96b3fd6466c96085212168d172d022c2399202e0 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 14 Feb 2024 14:28:53 +0530 Subject: [PATCH 40/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index f59c660..1781651 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,6 +7,18 @@ dependencies-toml-version = "2" distribution-version = "2201.8.2" +[[package]] +org = "ballerina" +name = "io" +version = "1.6.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + [[package]] org = "ballerina" name = "jballerina.java" @@ -24,6 +36,14 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "persist" @@ -64,6 +84,7 @@ org = "ballerinax" name = "persist.redis" version = "0.1.0" dependencies = [ + {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, {org = "ballerina", name = "test"}, From bfda1aa17d4ab89f76ef984587942653ca0b3a90 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 14 Feb 2024 16:07:52 +0530 Subject: [PATCH 41/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 1781651..f59c660 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,18 +7,6 @@ dependencies-toml-version = "2" distribution-version = "2201.8.2" -[[package]] -org = "ballerina" -name = "io" -version = "1.6.0" -dependencies = [ - {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "lang.value"} -] -modules = [ - {org = "ballerina", packageName = "io", moduleName = "io"} -] - [[package]] org = "ballerina" name = "jballerina.java" @@ -36,14 +24,6 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] -[[package]] -org = "ballerina" -name = "lang.value" -version = "0.0.0" -dependencies = [ - {org = "ballerina", name = "jballerina.java"} -] - [[package]] org = "ballerina" name = "persist" @@ -84,7 +64,6 @@ org = "ballerinax" name = "persist.redis" version = "0.1.0" dependencies = [ - {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, {org = "ballerina", name = "test"}, From 158930c0787ae30ddd014315fb569502a42583e1 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 14 Feb 2024 23:51:52 +0530 Subject: [PATCH 42/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index f59c660..1781651 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,6 +7,18 @@ dependencies-toml-version = "2" distribution-version = "2201.8.2" +[[package]] +org = "ballerina" +name = "io" +version = "1.6.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + [[package]] org = "ballerina" name = "jballerina.java" @@ -24,6 +36,14 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "persist" @@ -64,6 +84,7 @@ org = "ballerinax" name = "persist.redis" version = "0.1.0" dependencies = [ + {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, {org = "ballerina", name = "test"}, From 3f8d177068313642fea02d3ac814a096afc4460d Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Thu, 15 Feb 2024 00:11:54 +0530 Subject: [PATCH 43/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 1781651..f59c660 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,18 +7,6 @@ dependencies-toml-version = "2" distribution-version = "2201.8.2" -[[package]] -org = "ballerina" -name = "io" -version = "1.6.0" -dependencies = [ - {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "lang.value"} -] -modules = [ - {org = "ballerina", packageName = "io", moduleName = "io"} -] - [[package]] org = "ballerina" name = "jballerina.java" @@ -36,14 +24,6 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] -[[package]] -org = "ballerina" -name = "lang.value" -version = "0.0.0" -dependencies = [ - {org = "ballerina", name = "jballerina.java"} -] - [[package]] org = "ballerina" name = "persist" @@ -84,7 +64,6 @@ org = "ballerinax" name = "persist.redis" version = "0.1.0" dependencies = [ - {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, {org = "ballerina", name = "test"}, From bb5c31e07b8b73bdf70784c5f1f9e13d2cfb7d9d Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Thu, 15 Feb 2024 09:37:23 +0530 Subject: [PATCH 44/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index f59c660..1781651 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,6 +7,18 @@ dependencies-toml-version = "2" distribution-version = "2201.8.2" +[[package]] +org = "ballerina" +name = "io" +version = "1.6.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + [[package]] org = "ballerina" name = "jballerina.java" @@ -24,6 +36,14 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "persist" @@ -64,6 +84,7 @@ org = "ballerinax" name = "persist.redis" version = "0.1.0" dependencies = [ + {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, {org = "ballerina", name = "test"}, From 35e96976f6198d162b0c32247ea96f473089b0f4 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Thu, 15 Feb 2024 10:25:11 +0530 Subject: [PATCH 45/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 1781651..f59c660 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,18 +7,6 @@ dependencies-toml-version = "2" distribution-version = "2201.8.2" -[[package]] -org = "ballerina" -name = "io" -version = "1.6.0" -dependencies = [ - {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "lang.value"} -] -modules = [ - {org = "ballerina", packageName = "io", moduleName = "io"} -] - [[package]] org = "ballerina" name = "jballerina.java" @@ -36,14 +24,6 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] -[[package]] -org = "ballerina" -name = "lang.value" -version = "0.0.0" -dependencies = [ - {org = "ballerina", name = "jballerina.java"} -] - [[package]] org = "ballerina" name = "persist" @@ -84,7 +64,6 @@ org = "ballerinax" name = "persist.redis" version = "0.1.0" dependencies = [ - {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, {org = "ballerina", name = "test"}, From def18ebd9d6d8a988085b1c43c1de7f9958a9921 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Thu, 15 Feb 2024 11:34:07 +0530 Subject: [PATCH 46/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index f59c660..1781651 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,6 +7,18 @@ dependencies-toml-version = "2" distribution-version = "2201.8.2" +[[package]] +org = "ballerina" +name = "io" +version = "1.6.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + [[package]] org = "ballerina" name = "jballerina.java" @@ -24,6 +36,14 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "persist" @@ -64,6 +84,7 @@ org = "ballerinax" name = "persist.redis" version = "0.1.0" dependencies = [ + {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, {org = "ballerina", name = "test"}, From 18b5fcf11d5b1aed40bd971675fcafc0bdb44739 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Fri, 16 Feb 2024 10:58:21 +0530 Subject: [PATCH 47/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 1781651..f59c660 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,18 +7,6 @@ dependencies-toml-version = "2" distribution-version = "2201.8.2" -[[package]] -org = "ballerina" -name = "io" -version = "1.6.0" -dependencies = [ - {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "lang.value"} -] -modules = [ - {org = "ballerina", packageName = "io", moduleName = "io"} -] - [[package]] org = "ballerina" name = "jballerina.java" @@ -36,14 +24,6 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] -[[package]] -org = "ballerina" -name = "lang.value" -version = "0.0.0" -dependencies = [ - {org = "ballerina", name = "jballerina.java"} -] - [[package]] org = "ballerina" name = "persist" @@ -84,7 +64,6 @@ org = "ballerinax" name = "persist.redis" version = "0.1.0" dependencies = [ - {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, {org = "ballerina", name = "test"}, From 9856cb9d5445e084f27491166b699c550baa7287 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Sun, 18 Feb 2024 13:00:17 +0530 Subject: [PATCH 48/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index f59c660..1781651 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,6 +7,18 @@ dependencies-toml-version = "2" distribution-version = "2201.8.2" +[[package]] +org = "ballerina" +name = "io" +version = "1.6.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + [[package]] org = "ballerina" name = "jballerina.java" @@ -24,6 +36,14 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "persist" @@ -64,6 +84,7 @@ org = "ballerinax" name = "persist.redis" version = "0.1.0" dependencies = [ + {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, {org = "ballerina", name = "test"}, From bb72b13d9e5e34fd897b1cb65d56dd488bf40f70 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Sun, 18 Feb 2024 22:12:51 +0530 Subject: [PATCH 49/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 1781651..f59c660 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,18 +7,6 @@ dependencies-toml-version = "2" distribution-version = "2201.8.2" -[[package]] -org = "ballerina" -name = "io" -version = "1.6.0" -dependencies = [ - {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "lang.value"} -] -modules = [ - {org = "ballerina", packageName = "io", moduleName = "io"} -] - [[package]] org = "ballerina" name = "jballerina.java" @@ -36,14 +24,6 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] -[[package]] -org = "ballerina" -name = "lang.value" -version = "0.0.0" -dependencies = [ - {org = "ballerina", name = "jballerina.java"} -] - [[package]] org = "ballerina" name = "persist" @@ -84,7 +64,6 @@ org = "ballerinax" name = "persist.redis" version = "0.1.0" dependencies = [ - {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, {org = "ballerina", name = "test"}, From b83125e88cecc71cfa3fb4af91d03c4e19d69311 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Mon, 19 Feb 2024 00:43:32 +0530 Subject: [PATCH 50/65] completed integrating testing --- ballerina/build.gradle | 12 +- ballerina/redis_client.bal | 422 ++++++++++++------ ballerina/stream_types.bal | 7 +- ballerina/tests/init-test.bal | 36 +- ballerina/tests/persist/test_entities.bal | 16 +- ballerina/tests/redis-all-types-tests.bal | 11 +- ballerina/tests/redis-associations-tests.bal | 18 +- ballerina/tests/redis-building-tests.bal | 73 +-- ballerina/tests/redis-composite-key-tests.bal | 10 +- ballerina/tests/redis-department-tests.bal | 69 +-- ballerina/tests/redis-employee-tests.bal | 81 ++-- ballerina/tests/redis-id-fields-tests.bal | 8 +- ballerina/tests/redis-transaction-tests.bal | 75 ---- ballerina/tests/redis-workspace-tests.bal | 67 +-- .../tests/test_entities_generated_types.bal | 52 +-- native/build.gradle | 2 +- 16 files changed, 494 insertions(+), 465 deletions(-) delete mode 100644 ballerina/tests/redis-transaction-tests.bal diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 2e39528..72e7c21 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -138,8 +138,8 @@ task createRedisTestDockerImage(type: Exec) { } } else { def standardOutput = new ByteArrayOutputStream() - commandLine 'cmd', '/c', "docker build -f ${project.projectDir}/tests/resources/Dockerfile -t ballerina-persist-redis" + - " -q ${project.projectDir}/tests/resources" + commandLine 'cmd', '/c', "docker build -f ${project.projectDir}\\tests\\resources\\Dockerfile -t ballerina-persist-redis" + + " -q ${project.projectDir}\\tests\\resources\\" doLast { checkExecResult(executionResult, 'Error', standardOutput) sleep(10 * 1000) @@ -232,11 +232,11 @@ task stopRedisTestDockerContainer() { } updateTomlFiles.dependsOn copyStdlibs -// startRedisTestDockerContainer.dependsOn createRedisTestDockerImage +startRedisTestDockerContainer.dependsOn createRedisTestDockerImage -// build.dependsOn "generatePomFileForMavenPublication" +build.dependsOn "generatePomFileForMavenPublication" build.dependsOn ":${packageName}-native:build" -// build.finalizedBy stopRedisTestDockerContainer +build.finalizedBy stopRedisTestDockerContainer test.dependsOn ":${packageName}-native:build" -// test.dependsOn startRedisTestDockerContainer +test.dependsOn startRedisTestDockerContainer diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index af02106..4f114f6 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -16,7 +16,6 @@ import ballerina/persist; import ballerinax/redis; -import ballerina/io; import ballerina/time; # The client used by the generated persist clients to abstract and @@ -70,12 +69,11 @@ public isolated client class RedisClient { record {} 'object = check self.querySimpleFieldsByKey(typeMap, recordKey, fields); // Handling relation fields check self.getManyRelations(typeMap, 'object, fields, include); - self.removeUnwantedFields('object, fields); - self.removeNonExistOptionalFields('object); - io:println("cloneType in read by key"); - io:println(rowType); return check 'object.cloneWithType(rowType); } on fail error e { + if e is persist:NotFoundError { + return e; + } return error persist:Error(e.message()); } } @@ -90,25 +88,17 @@ public isolated client class RedisClient { # or a `persist:Error` if the operation fails public isolated function runReadQuery(typedesc rowType, map typeMap, string[] fields = [], string[] include = []) returns stream|persist:Error { // Get all the keys - string[]|error keys = self.dbClient->keys(string `${self.collectionName}${KEY_SEPERATOR}*`); + string[]|error keys = self.dbClient->lRange(self.collectionName, 0, -1); if keys is error { return error persist:Error(keys.message()); } - // Get data one by one using the key + // Get records one by one using the key record {}[] result = []; foreach string key in keys { - // Verifying the key belongs to a hash - string redisType = check self.dbClient->redisType(key); - if redisType != REDIS_HASH { - continue; - } - - // Handling simple fields only for batch read - record {} 'object = check self.querySimpleFieldsByKey(typeMap, key, fields); - // check self.getManyRelations(typeMap, 'object, fields, include); - self.removeUnwantedFields('object, fields); - self.removeNonExistOptionalFields('object); + // Handling simple fields + string recordKey = string `${self.collectionName}${key}`; + record {} 'object = check self.querySimpleFieldsByKey(typeMap, recordKey, fields); result.push('object); } on fail error e { return error persist:Error(e.message()); @@ -138,9 +128,9 @@ public isolated client class RedisClient { } // Check for duplicate keys withing the collection - int isKeyExists = check self.dbClient->exists([self.collectionName + key]); + int isKeyExists = check self.dbClient->exists([string `${self.collectionName}${key}`]); if isKeyExists != 0 { - return persist:getAlreadyExistsError(self.collectionName, self.collectionName + key); + return persist:getAlreadyExistsError(self.collectionName, string `${self.collectionName}${key}`); } // Check for any relation field constraints @@ -149,10 +139,15 @@ public isolated client class RedisClient { return checkConstraints; } - // Insert the object - result = self.dbClient->hMSet(self.collectionName + key, newInsertRecord); - if result is error { - return error persist:Error(result.message()); + // Insert the record + lock { + result = self.dbClient->hMSet(string `${self.collectionName}${key}`, newInsertRecord); + if result is error { + return error persist:Error(result.message()); + } + + // Insert the key to a list to preserve insertion order + _ = check self.dbClient->rPush(self.collectionName, [key]); } } on fail error e { return error persist:Error(e.message()); @@ -172,18 +167,40 @@ public isolated client class RedisClient { # + return - `()` if the operation is performed successfully # or a `persist:Error` if the operation fails public isolated function runDeleteQuery(anydata key) returns persist:Error? { - // Generate the key - string recordKey = string `${self.collectionName}${self.getKey(key)}`; + string recSuffix = string `${self.getKey(key)}`; + // Delete the record + lock { + do { + // Check for references + string[] allRefFields = []; + foreach RefMetadata refMedaData in self.refMetadata{ + allRefFields.push(...refMedaData.joinFields); + } - do { - // Delete the record - _ = check self.dbClient->del([recordKey]); - } on fail error e { - return error persist:Error(e.message()); + // Remove any references if exists + if allRefFields.length() > 0 { + map currentObject = check self.dbClient->hMGet( + string `${self.collectionName}${recSuffix}`,allRefFields); + foreach RefMetadata refMedaData in self.refMetadata{ + string refKey = refMedaData.refCollection; + foreach string refField in refMedaData.joinFields { + refKey += string `${KEY_SEPERATOR}${currentObject[refField].toString()}`; + } + _ = check self.dbClient->sRem(string `${refKey}${KEY_SEPERATOR}${self.collectionName}`, [recSuffix]); + } + } + + // Remove the record from the Collection list + _ = check self.dbClient->lRem(self.collectionName, 1, recSuffix); + // Remove the record + _ = check self.dbClient->del([string `${self.collectionName}${recSuffix}`]); + } on fail error e { + return error persist:Error(e.message()); + } } } - # Performs redis `HSET` operation to delete an entity record from the database. + # Performs redis `HSET` operation to update an entity record from the database. # # + key - The ordered keys used to update an entity record # + updateRecord - The new record to be updated @@ -192,23 +209,125 @@ public isolated client class RedisClient { // Generate the key string recordKey = string `${self.collectionName}${self.getKey(key)}`; - // Update only the given fields that is not nil - foreach [string, FieldMetadata & readonly] metaDataEntry in self.fieldMetadata.entries() { - FieldMetadata & readonly fieldMetadataValue = metaDataEntry[1]; - - // If the field is a simple field - if fieldMetadataValue is SimpleFieldMetadata { - if updateRecord.hasKey(fieldMetadataValue.fieldName) - && updateRecord[fieldMetadataValue.fieldName] != () { - // updating the object - _ = check self.dbClient->hSet(recordKey, fieldMetadataValue.fieldName, - updateRecord[fieldMetadataValue.fieldName].toString()); + // Verify the existence of the key + do { + int isKeyExists = check self.dbClient->exists([recordKey]); + if isKeyExists == 0 { + return persist:getNotFoundError(self.collectionName, recordKey); + } + } on fail var e { + return error persist:Error(e.message()); + } + + // Convert time:Date and time:TimeOfDay to string + record {} newUpdateRecord = check self.newRecordWithDateTime(updateRecord); + + // Get the original record before update + map<()> updatedEntities = {}; + map|error prevRecord = self.dbClient->hMGet(recordKey, newUpdateRecord.keys()); + if prevRecord is error { + return error persist:Error(prevRecord.message()); + } + + // Check the validity of new associations + foreach RefMetadata refMetaData in self.refMetadata{ + string[] joinFields = refMetaData.joinFields; + + // Recreate the key + string relatedRecordKey = refMetaData.refCollection; + foreach string joinField in joinFields{ + if newUpdateRecord.hasKey(joinField){ + updatedEntities[refMetaData.refCollection] = (); + relatedRecordKey += string `${KEY_SEPERATOR}${newUpdateRecord[joinField].toString()}`; + }else{ + relatedRecordKey += string `${KEY_SEPERATOR}${prevRecord[joinField].toString()}`; } - } else { - // If the field is a relation field + } + + // Verify the new associated entities does exists + if updatedEntities.hasKey(refMetaData.refCollection){ + int isKeyExists = check self.dbClient->exists([relatedRecordKey]); + if isKeyExists != 0 { + // Verify the key type as a HASH + string redisType = check self.dbClient->redisType(relatedRecordKey); + if redisType == REDIS_HASH { + continue; + } + } + // Return a constrain violation error if new associations does not exists + return getConstraintViolationError(self.collectionName, refMetaData.refCollection); } } on fail error e { - return error persist:Error(e.message()); + return error persist:Error(e.message()); + } + + // Verify the availablity of new associations + // Eg: Reffered record might already in a ONE-TO-ONE relationship + foreach RefMetadata refMetaData in self.refMetadata{ + if !updatedEntities.hasKey(refMetaData.refCollection) { + continue; + } + string[] joinFields = refMetaData.joinFields; + string newRelatedRecordKey = refMetaData.refCollection; + foreach string joinField in joinFields{ + if newUpdateRecord.hasKey(joinField){ + newRelatedRecordKey += string `${KEY_SEPERATOR}${newUpdateRecord[joinField].toString()}`; + }else{ + newRelatedRecordKey += string `${KEY_SEPERATOR}${prevRecord[joinField].toString()}`; + } + } + + // Get keys of existing associations + int isKeyExists = check self.dbClient->exists([ + string `${newRelatedRecordKey}${KEY_SEPERATOR}${self.collectionName}`]); + if isKeyExists != 0 { + // Verify the key type as a SET + string redisType = check self.dbClient->redisType( + string `${newRelatedRecordKey}${KEY_SEPERATOR}${self.collectionName}`); + if redisType == REDIS_SET { + // Check existing associations for ONE-TO-ONE + if refMetaData.'type == ONE_TO_ONE { + int cardinality = check self.dbClient->sCard( + string `${newRelatedRecordKey}${KEY_SEPERATOR}${self.collectionName}`); + if cardinality > 0 { + return getConstraintViolationError(self.collectionName, refMetaData.refCollection); + } + } + } + } + } on fail error e { + return error persist:Error(e.message()); + } + + // Update + lock { + foreach string updatedField in newUpdateRecord.keys(){ + _ = check self.dbClient->hSet(recordKey, updatedField, + newUpdateRecord[updatedField].toString()); + } on fail error e { + return error persist:Error(e.message()); + } + + // Add new association to the SET + foreach RefMetadata refMetaData in self.refMetadata{ + if !updatedEntities.hasKey(refMetaData.refCollection){ + continue; + } + string[] joinFields = refMetaData.joinFields; + string newRelatedRecordKey = refMetaData.refCollection; + foreach string joinField in joinFields{ + if newUpdateRecord.hasKey(joinField){ + newRelatedRecordKey += string `${KEY_SEPERATOR}${newUpdateRecord[joinField].toString()}`; + }else{ + newRelatedRecordKey += string `${KEY_SEPERATOR}${prevRecord[joinField].toString()}`; + } + } + int|error sAdd = self.dbClient->sAdd( + string `${newRelatedRecordKey}${KEY_SEPERATOR}${self.collectionName}`, [recordKey]); + if sAdd is error { + return error persist:Error(sAdd.message()); + } + } } } @@ -221,19 +340,20 @@ public isolated client class RedisClient { # + return - A `persist:Error` if the operation fails public isolated function getManyRelations(map typeMap, record {} 'object, string[] fields, string[] include) returns persist:Error? { - io:println(include); foreach int i in 0 ..< include.length() { string entity = include[i]; - CardinalityType cardinalityType = ONE_TO_MANY; + (RefMetadata & readonly)? refMetaData = self.refMetadata[entity]; + if refMetaData == () { + continue; + } - // checking for one to many relationships + // Check for one to many relationships + CardinalityType cardinalityType = ONE_TO_MANY; string[] relationFields = from string 'field in fields where 'field.startsWith(string `${entity}${MANY_ASSOCIATION_SEPERATOR}`) select 'field.substring(entity.length() + 3, 'field.length()); - io:println("one to many check"); - io:println(relationFields); - // checking for one to one relationships + // Check for one to one relationships if relationFields.length() == 0 { relationFields = from string 'field in fields where 'field.startsWith(string `${entity}${ASSOCIATION_SEPERATOR}`) @@ -243,20 +363,15 @@ public isolated client class RedisClient { cardinalityType = ONE_TO_ONE; } } - io:println("one to one check"); - io:println(relationFields); if relationFields.length() == 0 { continue; } - string[]|error keys = self.dbClient->keys( - string `${entity.substring(0, 1).toUpperAscii()}${entity.substring(1)}${KEY_SEPERATOR}*`); - io:println(keys); - if keys is error || keys.length() == 0 { - io:println("this should be printed"); + // Get key suffixes of asslociated records + string[]|error keySuffixes = self.getRelatedEntityKeySuffixes(entity,'object); + if keySuffixes is error || keySuffixes.length() == 0 { if cardinalityType == ONE_TO_MANY { - io:println("this should be printed too"); 'object[entity] = []; } else { 'object[entity] = {}; @@ -266,29 +381,17 @@ public isolated client class RedisClient { // Get data one by one using the key record {}[] associatedRecords = []; - foreach string key in keys { - // Handling simple fields - record {} valueToRecord = check self.queryRelationFieldsByKey(entity, cardinalityType, key, relationFields); - - // Check whether the record is associated with the current object - boolean isAssociated = true; - foreach string keyField in self.keyFields { - string refField = self.entityName.substring(0, 1).toLowerAscii() + self.entityName.substring(1) - + keyField.substring(0, 1).toUpperAscii() + keyField.substring(1); - boolean isSimilar = valueToRecord[refField] == 'object[keyField]; - if !isSimilar { - isAssociated = false; - } - } - - if isAssociated { - foreach string refField in valueToRecord.keys() { - if relationFields.indexOf(refField) is () { - _ = valueToRecord.remove(refField); - } + foreach string key in keySuffixes { + // Handling simple fields of the associated record + record {} valueToRecord = check self.queryRelationFieldsByKey(entity, cardinalityType, + string `${refMetaData.refCollection}${key}`, relationFields); + + foreach string refField in valueToRecord.keys() { + if relationFields.indexOf(refField) is () { + _ = valueToRecord.remove(refField); } - associatedRecords.push(valueToRecord); } + associatedRecords.push(valueToRecord); } if associatedRecords.length() > 0 { @@ -296,13 +399,14 @@ public isolated client class RedisClient { 'object[entity] = associatedRecords[0]; } else { 'object[entity] = associatedRecords; - io:println("Object with one to many associations"); - io:println('object); } } } on fail persist:Error e { return e; } + + self.removeUnwantedFields('object, fields); + self.removeNonExistOptionalFields('object); } public isolated function getKeyFields() returns string[] { @@ -312,30 +416,52 @@ public isolated client class RedisClient { // Private helper methods private isolated function getKey(anydata key) returns string { string keyValue = ""; - if key is map { - foreach string compositeKey in self.keyFields { - keyValue += string `${KEY_SEPERATOR}${key[compositeKey].toString()}`; - } + if key is map { + foreach string compositeKey in key.keys() { + keyValue += string `${KEY_SEPERATOR}${key[compositeKey].toString()}`; + } return keyValue; } return string `${KEY_SEPERATOR}${key.toString()}`; } - private isolated function querySimpleFieldsByKey(map typeMap, string key, string[] fields) - returns record {|anydata...;|}|error { - // hadling the simple fields - string[] simpleFields = self.getTargetSimpleFields(fields, typeMap); - // If no simpleFields given, then add all the fields by default - if simpleFields == [] { - foreach [string, FieldMetadata & readonly] metaDataEntry in self.fieldMetadata.entries() { - FieldMetadata & readonly fieldMetadataValue = metaDataEntry[1]; - - // If the field is a simple field - if fieldMetadataValue is SimpleFieldMetadata { - simpleFields.push(fieldMetadataValue.fieldName); + private isolated function getKeyFromObject(record {} 'object) returns string { + string key = self.collectionName; + foreach string keyField in self.keyFields{ + key += string `${KEY_SEPERATOR}${'object[keyField].toString()}`; + } + return key; + } + + private isolated function getRelatedEntityKeySuffixes(string entity, record {} 'object) returns string []|error { + (RefMetadata & readonly)? refMetaData = self.refMetadata[entity]; + if refMetaData == () { + return []; + } + + do { + if refMetaData.joinFields == self.keyFields { + // Non-owner have direct access to the association set + string[] keys = check self.dbClient->sMembers( + string `${self.getKeyFromObject('object)}${KEY_SEPERATOR}${refMetaData.refCollection}`); + return keys; + }else{ + map recordWithRefFields = check self.dbClient->hMGet(self.getKeyFromObject('object), + refMetaData.joinFields); + string key = ""; + foreach string joinField in refMetaData.joinFields{ + key += string `${KEY_SEPERATOR}${recordWithRefFields[joinField].toString()}`; } + return [key]; } + } on fail error e { + return e; } + } + + private isolated function querySimpleFieldsByKey(map typeMap, string key, string[] fields) + returns record {|anydata...;|}|persist:Error { + string[] simpleFields = self.getTargetSimpleFields(fields, typeMap); do { // Retrieve the record @@ -345,24 +471,27 @@ public isolated client class RedisClient { } record {} valueToRecord = {}; foreach string fieldKey in value.keys() { - // convert the data type from 'any' to required type + // Convert the data type from 'any' to relevent type valueToRecord[fieldKey] = check self.dataConverter( self.fieldMetadata[fieldKey], value[fieldKey]); } return valueToRecord; } on fail error e { - return error persist:Error(e.message()); + return e.cause(); } } private isolated function queryRelationFieldsByKey(string entity, CardinalityType cardinalityType, string key, string[] fields) returns record {|anydata...;|}|persist:Error { - // If the field doesn't containes reference fields, add them here string[] relationFields = fields.clone(); - foreach string keyField in self.keyFields { - string refField = self.entityName.substring(0, 1).toLowerAscii() + self.entityName.substring(1) - + keyField.substring(0, 1).toUpperAscii() + keyField.substring(1); - if relationFields.indexOf(refField) is () { - relationFields.push(refField); + (RefMetadata & readonly)? refMetaData = self.refMetadata[entity]; + if refMetaData == () { + return error persist:Error(string `Undefined relation between ${self.entityName} and ${entity}`); + } + + // Add required missing reference fields + foreach string refKeyField in refMetaData.refFields{ + if relationFields.indexOf(refKeyField) is () { + relationFields.push(refKeyField); } } @@ -370,7 +499,7 @@ public isolated client class RedisClient { // Retrieve related records map value = check self.dbClient->hMGet(key, relationFields); if self.isNoRecordFound(value) { - return error persist:Error(string `No '${self.entityName}' found for the given key`); + return error persist:Error(string `No '${self.entityName}' found for the given key '${key}'`); } record {} valueToRecord = {}; @@ -382,9 +511,9 @@ public isolated client class RedisClient { } foreach string fieldKey in value.keys() { - // convert the data type from 'any' to required type + // convert the data type from 'any' to relevant type valueToRecord[fieldKey] = check self.dataConverter( - self.fieldMetadata[fieldMetadataKeyPrefix + fieldKey], value[fieldKey]); + self.fieldMetadata[string `${fieldMetadataKeyPrefix}${fieldKey}`], value[fieldKey]); } return valueToRecord; } on fail error e { @@ -393,9 +522,15 @@ public isolated client class RedisClient { } private isolated function getTargetSimpleFields(string[] fields, map typeMap) returns string[] { - return from string 'field in fields + string[] requiredFields = from string 'field in fields where !'field.includes(".") && typeMap.hasKey('field) select 'field; + foreach string keyField in self.keyFields { + if requiredFields.indexOf(keyField) == () { + requiredFields.push(keyField); + } + } + return requiredFields; } private isolated function removeNonExistOptionalFields(record {} 'object) { @@ -418,7 +553,7 @@ public isolated client class RedisClient { private isolated function removeUnwantedFields(record {} 'object, string[] fields) { string[] keyFields = self.keyFields; foreach string keyField in keyFields { - if fields.indexOf(keyField) is () { + if fields.indexOf(keyField) is () && 'object.hasKey(keyField) { _ = 'object.remove(keyField); } } @@ -432,41 +567,30 @@ public isolated client class RedisClient { continue; } - boolean isRelationConstraintFalied = true; - // If the mandatory reference field is not exist or being null + // Generate the key to reference record + string refRecordKey = refMetadataValue.refCollection; foreach string joinField in refMetadataValue.joinFields { - if insertRecord.hasKey(joinField) && insertRecord[joinField] != () { - isRelationConstraintFalied = false; - break; - } + refRecordKey += string `${KEY_SEPERATOR}${insertRecord[joinField].toString()}`; } - if !isRelationConstraintFalied { - // Generate the key to reference record - string refRecordKey = refMetadataValue.refCollection; - foreach string joinField in refMetadataValue.joinFields { - refRecordKey += string `${KEY_SEPERATOR}${insertRecord[joinField].toString()}`; - } - - // Check the cardinality of refered entity object - int|error sCard = self.dbClient->sCard( - string `${refRecordKey}${KEY_SEPERATOR}${self.collectionName}`); - if sCard is int && sCard > 0 && refMetadataValue.'type == ONE_TO_ONE { - // If the refered object is already in a relationship - return getConstraintViolationError(self.entityName, refMetadataValue.refCollection); - } + // Check the cardinality of refered entity record + int|error sCard = self.dbClient->sCard( + string `${refRecordKey}${KEY_SEPERATOR}${self.collectionName}`); + if sCard is int && sCard > 0 && refMetadataValue.'type == ONE_TO_ONE { + // If the refered record is already in an association + return getConstraintViolationError(self.collectionName, refMetadataValue.refCollection); + } - // Relate current record with the refered record in the database - int|error sAdd = self.dbClient->sAdd( - string `${refRecordKey}${KEY_SEPERATOR}${self.collectionName}`, [key]); - if sAdd is error { - return error persist:Error(sAdd.message()); - } + // Associate current record with the refered record + int|error sAdd = self.dbClient->sAdd( + string `${refRecordKey}${KEY_SEPERATOR}${self.collectionName}`, [key]); + if sAdd is error { + return error persist:Error(sAdd.message()); + } - map value = check self.dbClient->hMGet(refRecordKey, refMetadataValue.refFields); - if self.isNoRecordFound(value) { - return getConstraintViolationError(self.entityName, refMetadataValue.refCollection); - } + map value = check self.dbClient->hMGet(refRecordKey, refMetadataValue.refFields); + if self.isNoRecordFound(value) { + return getConstraintViolationError(self.collectionName, refMetadataValue.refCollection); } } on fail error e { return error persist:Error(e.message()); @@ -482,7 +606,7 @@ public isolated client class RedisClient { DataType dataType = fieldMetaDataValue.fieldDataType; if dataType == DATE || dataType == TIME_OF_DAY { - RedisTimeType|error timeValue = insertRecord.get(recordfield).ensureType(); + RedisTimeType?|error timeValue = insertRecord.get(recordfield).ensureType(); if timeValue is error { return error persist:Error(timeValue.message()); } @@ -492,7 +616,6 @@ public isolated client class RedisClient { return timeValueInString; } - io:println(typeof insertRecord[recordfield]); newRecord[recordfield] = timeValueInString; }else{ newRecord[recordfield] = insertRecord[recordfield]; @@ -502,7 +625,11 @@ public isolated client class RedisClient { return newRecord; } - private isolated function timeToString(RedisTimeType timeValue) returns string|persist:Error? { + private isolated function timeToString(RedisTimeType? timeValue) returns string|persist:Error? { + if timeValue is () { + return (); + } + if timeValue is time:Date { return string `${timeValue.day}-${timeValue.month}-${timeValue.year}`; } @@ -517,7 +644,8 @@ public isolated client class RedisClient { private isolated function stringToTime(string timeValue, DataType dataType) returns RedisTimeType|error { if dataType == TIME_OF_DAY { string[] timeValues = re `-`.split(timeValue); - time:TimeOfDay output = {hour: check int:fromString(timeValues[0]), minute: check int:fromString(timeValues[1]), second: check decimal:fromString(timeValues[2])}; + time:TimeOfDay output = {hour: check int:fromString(timeValues[0]), minute: check int:fromString( + timeValues[1]), second: check decimal:fromString(timeValues[2])}; return output; } else if dataType == DATE { string[] timeValues = re `-`.split(timeValue); @@ -537,7 +665,7 @@ public isolated client class RedisClient { if (fieldMetaData is SimpleFieldMetadata && fieldMetaData[FIELD_DATA_TYPE] == INT) || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == INT) { - return check int:fromString(value); + return check int:fromString(value.toString()); } else if (fieldMetaData is SimpleFieldMetadata && (fieldMetaData[FIELD_DATA_TYPE] == STRING)) || (fieldMetaData is EntityFieldMetadata && fieldMetaData[RELATION][REF_FIELD_DATA_TYPE] == STRING) { diff --git a/ballerina/stream_types.bal b/ballerina/stream_types.bal index c081f2a..8f16a06 100644 --- a/ballerina/stream_types.bal +++ b/ballerina/stream_types.bal @@ -14,7 +14,6 @@ // specific language governing permissions and limitations // under the License. import ballerina/persist; -import ballerina/io; public class PersistRedisStream { @@ -63,14 +62,10 @@ public class PersistRedisStream { string[] keyFields = (self.persistClient).getKeyFields(); foreach string keyField in keyFields { - if self.fields.indexOf(keyField) is () { + if self.fields.indexOf(keyField) is () && value.hasKey(keyField) { _ = value.remove(keyField); } } - - io:println("value in the strea_types.bal"); - io:println(value); - io:println(self.targetType); // return {value: value}; record {|record {} value;|} nextRecord = {value: checkpanic value.cloneWithType(self.targetType)}; return nextRecord; diff --git a/ballerina/tests/init-test.bal b/ballerina/tests/init-test.bal index ab7457a..516075a 100644 --- a/ballerina/tests/init-test.bal +++ b/ballerina/tests/init-test.bal @@ -121,15 +121,7 @@ AllTypes allTypes3 = { stringType: "test2", dateType: {year: 1996, month: 11, day: 3}, timeOfDayType: {hour: 17, minute: 32, second: 34}, - booleanTypeOptional: (), - intTypeOptional: (), - floatTypeOptional: (), - decimalTypeOptional: (), - stringTypeOptional: (), - dateTypeOptional: (), - timeOfDayTypeOptional: (), - enumType: "TYPE_1", - enumTypeOptional: () + enumType: "TYPE_1" }; AllTypes allTypes3Expected = { @@ -141,15 +133,7 @@ AllTypes allTypes3Expected = { stringType: allTypes3.stringType, dateType: allTypes3.dateType, timeOfDayType: allTypes3.timeOfDayType, - booleanTypeOptional: allTypes3.booleanTypeOptional, - intTypeOptional: allTypes3.intTypeOptional, - floatTypeOptional: allTypes3.floatTypeOptional, - decimalTypeOptional: allTypes3.decimalTypeOptional, - stringTypeOptional: allTypes3.stringTypeOptional, - dateTypeOptional: allTypes3.dateTypeOptional, - timeOfDayTypeOptional: allTypes3.timeOfDayTypeOptional, - enumType: allTypes3.enumType, - enumTypeOptional: allTypes3.enumTypeOptional + enumType: allTypes3.enumType }; AllTypes allTypes1Updated = { @@ -200,13 +184,13 @@ public type AllTypesDependent record {| string stringType; time:Date dateType; time:TimeOfDay timeOfDayType; - boolean? booleanTypeOptional; - int? intTypeOptional; - float? floatTypeOptional; - decimal? decimalTypeOptional; - string? stringTypeOptional; - time:Date? dateTypeOptional; - time:TimeOfDay? timeOfDayTypeOptional; + boolean booleanTypeOptional?; + int intTypeOptional?; + float floatTypeOptional?; + decimal decimalTypeOptional?; + string stringTypeOptional?; + time:Date dateTypeOptional?; + time:TimeOfDay timeOfDayTypeOptional?; |}; OrderItemExtended orderItemExtended1 = { @@ -251,7 +235,7 @@ public type EmployeeInfo record {| record {| string deptName; |} department; - Workspace workspace; + Workspace workspace?; |}; OrderItemExtended orderItemExtended2Retrieved = { diff --git a/ballerina/tests/persist/test_entities.bal b/ballerina/tests/persist/test_entities.bal index 7ad161f..53600f9 100644 --- a/ballerina/tests/persist/test_entities.bal +++ b/ballerina/tests/persist/test_entities.bal @@ -32,15 +32,15 @@ type AllTypes record {| string stringType; time:Date dateType; time:TimeOfDay timeOfDayType; - boolean? booleanTypeOptional; - int? intTypeOptional; - float? floatTypeOptional; - decimal? decimalTypeOptional; - string? stringTypeOptional; - time:Date? dateTypeOptional; - time:TimeOfDay? timeOfDayTypeOptional; EnumType enumType; - EnumType? enumTypeOptional; + boolean booleanTypeOptional?; + int intTypeOptional?; + float floatTypeOptional?; + decimal decimalTypeOptional?; + string stringTypeOptional?; + time:Date dateTypeOptional?; + time:TimeOfDay timeOfDayTypeOptional?; + EnumType enumTypeOptional?; |}; type StringIdRecord record {| diff --git a/ballerina/tests/redis-all-types-tests.bal b/ballerina/tests/redis-all-types-tests.bal index e223232..99c12c3 100644 --- a/ballerina/tests/redis-all-types-tests.bal +++ b/ballerina/tests/redis-all-types-tests.bal @@ -84,14 +84,7 @@ function redisAllTypesReadDependentTest() returns error? { decimalType: allTypes3Expected.decimalType, stringType: allTypes3Expected.stringType, dateType: allTypes3Expected.dateType, - timeOfDayType: allTypes3Expected.timeOfDayType, - booleanTypeOptional: allTypes3Expected.booleanTypeOptional, - intTypeOptional: allTypes3Expected.intTypeOptional, - floatTypeOptional: allTypes3Expected.floatTypeOptional, - decimalTypeOptional: allTypes3Expected.decimalTypeOptional, - stringTypeOptional: allTypes3Expected.stringTypeOptional, - dateTypeOptional: allTypes3Expected.dateTypeOptional, - timeOfDayTypeOptional: allTypes3Expected.timeOfDayTypeOptional + timeOfDayType: allTypes3Expected.timeOfDayType }, { booleanType: allTypes1Expected.booleanType, @@ -156,7 +149,7 @@ function redisAllTypesReadOneTestNegative() returns error? { AllTypes|persist:Error allTypesRetrieved = testEntitiesClient->/alltypes/[4].get(); if allTypesRetrieved is persist:NotFoundError { - test:assertEquals(allTypesRetrieved.message(), "A record with the key '4' does not exist for the entity 'AllTypes'."); + test:assertEquals(allTypesRetrieved.message(), "A record with the key 'AllTypes:4' does not exist for the entity 'AllTypes'."); } else { test:assertFail("persist:NotFoundError expected."); diff --git a/ballerina/tests/redis-associations-tests.bal b/ballerina/tests/redis-associations-tests.bal index dbc8778..d32fea4 100644 --- a/ballerina/tests/redis-associations-tests.bal +++ b/ballerina/tests/redis-associations-tests.bal @@ -15,7 +15,7 @@ // under the License. import ballerina/test; -import ballerina/persist; +// import ballerina/persist; @test:Config { groups: ["associations", "redis"], @@ -60,9 +60,9 @@ function redisEmployeeRelationsTest() returns error? { _ = check rainierClient->/workspaces.post([workspace22]); _ = check rainierClient->/employees.post([employee21]); - stream employeeStream = rainierClient->/employees.get(); - EmployeeInfo[] employees = check from EmployeeInfo employee in employeeStream - select employee; + // stream employeeStream = rainierClient->/employees.get(); + // EmployeeInfo[] employees = check from EmployeeInfo employee in employeeStream + // select employee; EmployeeInfo retrieved = check rainierClient->/employees/["employee-21"].get(); @@ -79,8 +79,8 @@ function redisEmployeeRelationsTest() returns error? { } }; - test:assertTrue(employees.indexOf(expected) is int, "Expected EmployeeInfo not found."); test:assertEquals(retrieved, expected); + // test:assertTrue(employees.indexOf(expected) is int, "Expected EmployeeInfo not found."); check rainierClient.close(); } @@ -138,9 +138,9 @@ function redisDepartmentRelationsTest() returns error? { _ = check rainierClient->/workspaces.post([workspace12]); _ = check rainierClient->/employees.post([employee11, employee12]); - stream departmentStream = rainierClient->/departments.get(); - DepartmentInfo[] departments = check from DepartmentInfo department in departmentStream - select department; + // stream departmentStream = rainierClient->/departments.get(); + // DepartmentInfo[] departments = check from DepartmentInfo department in departmentStream + // select department; DepartmentInfo retrieved = check rainierClient->/departments/["department-12"].get(); @@ -159,8 +159,8 @@ function redisDepartmentRelationsTest() returns error? { ] }; - test:assertTrue(departments.indexOf(expected) is int, "Expected DepartmentInfo not found."); test:assertEquals(retrieved, expected); + // test:assertTrue(departments.indexOf(expected) is int, "Expected DepartmentInfo not found."); check rainierClient.close(); } diff --git a/ballerina/tests/redis-building-tests.bal b/ballerina/tests/redis-building-tests.bal index 2756383..75a867c 100644 --- a/ballerina/tests/redis-building-tests.bal +++ b/ballerina/tests/redis-building-tests.bal @@ -50,20 +50,20 @@ function redisBuildingCreateTest2() returns error? { check rainierClient.close(); } -@test:Config { - groups: ["building", "redis"] -} -function redisBuildingCreateTestNegative() returns error? { - RedisRainierClient rainierClient = check new (); - - string[]|error building = rainierClient->/buildings.post([invalidBuilding]); - if building is persist:Error { - test:assertTrue(building.message().includes("value too long for type character varying")); - } else { - test:assertFail("Error expected."); - } - check rainierClient.close(); -} +// @test:Config { +// groups: ["building", "redis"] +// } +// function redisBuildingCreateTestNegative() returns error? { +// RedisRainierClient rainierClient = check new (); + +// string[]|error building = rainierClient->/buildings.post([invalidBuilding]); +// if building is persist:Error { +// test:assertTrue(building.message().includes("value too long for type character varying")); +// } else { +// test:assertFail("Error expected."); +// } +// check rainierClient.close(); +// } @test:Config { groups: ["building", "redis"], @@ -86,7 +86,7 @@ function redisBuildingReadOneTestNegative() returns error? { Building|error buildingRetrieved = rainierClient->/buildings/["invalid-building-code"].get(); if buildingRetrieved is persist:NotFoundError { - test:assertEquals(buildingRetrieved.message(), "A record with the key 'invalid-building-code' does not exist for the entity 'Building'."); + test:assertEquals(buildingRetrieved.message(), "A record with the key 'Building:invalid-building-code' does not exist for the entity 'Building'."); } else { test:assertFail("persist:NotFoundError expected."); } @@ -162,37 +162,38 @@ function redisBuildingUpdateTestNegative1() returns error? { }); if building is persist:NotFoundError { - test:assertEquals(building.message(), "A record with the key 'invalid-building-code' does not exist for the entity 'Building'."); + test:assertEquals(building.message(), "A record with the key 'Building:invalid-building-code' does not exist for the entity 'Building'."); } else { test:assertFail("persist:NotFoundError expected."); } check rainierClient.close(); } -@test:Config { - groups: ["building", "redis"], - dependsOn: [redisBuildingReadOneTest, redisBuildingReadManyTest, redisBuildingReadManyDependentTest] -} -function redisBuildingUpdateTestNegative2() returns error? { - RedisRainierClient rainierClient = check new (); +// @test:Config { +// groups: ["building", "redis"], +// dependsOn: [redisBuildingReadOneTest, redisBuildingReadManyTest, redisBuildingReadManyDependentTest] +// } +// function redisBuildingUpdateTestNegative2() returns error? { +// RedisRainierClient rainierClient = check new (); - Building|error building = rainierClient->/buildings/[building1.buildingCode].put({ - city: "unncessarily-long-city-name-to-force-error-on-update", - state: "Southern Province", - postalCode: "10890" - }); +// Building|error building = rainierClient->/buildings/[building1.buildingCode].put({ +// city: "unncessarily-long-city-name-to-force-error-on-update", +// state: "Southern Province", +// postalCode: "10890" +// }); - if building is persist:Error { - test:assertTrue(building.message().includes("value too long for type character varying")); - } else { - test:assertFail("persist:NotFoundError expected."); - } - check rainierClient.close(); -} +// if building is persist:Error { +// test:assertTrue(building.message().includes("value too long for type character varying")); +// } else { +// test:assertFail("persist:NotFoundError expected."); +// } +// check rainierClient.close(); +// } @test:Config { groups: ["building", "redis"], - dependsOn: [redisBuildingUpdateTest, redisBuildingUpdateTestNegative2] + // dependsOn: [redisBuildingUpdateTest, redisBuildingUpdateTestNegative2] + dependsOn: [redisBuildingUpdateTest] } function redisBuildingDeleteTest() returns error? { RedisRainierClient rainierClient = check new (); @@ -218,7 +219,7 @@ function redisBuildingDeleteTestNegative() returns error? { Building|error building = rainierClient->/buildings/[building1.buildingCode].delete(); if building is error { - test:assertEquals(building.message(), string `A record with the key '${building1.buildingCode}' does not exist for the entity 'Building'.`); + test:assertEquals(building.message(), string `A record with the key 'Building:${building1.buildingCode}' does not exist for the entity 'Building'.`); } else { test:assertFail("persist:NotFoundError expected."); } diff --git a/ballerina/tests/redis-composite-key-tests.bal b/ballerina/tests/redis-composite-key-tests.bal index 3720c89..0b658db 100644 --- a/ballerina/tests/redis-composite-key-tests.bal +++ b/ballerina/tests/redis-composite-key-tests.bal @@ -44,7 +44,7 @@ function redisCompositeKeyCreateTestNegative() returns error? { [string, string][]|error ids = rainierClient->/orderitems.post([orderItem1]); if ids is persist:AlreadyExistsError { - test:assertEquals(ids.message(), "A record with the key '(\"orderId\", \"itemId\")=(order-1, item-1)' already exists for the entity 'OrderItem'."); + test:assertEquals(ids.message(), "A record with the key 'OrderItem:order-1:item-1' already exists for the entity 'OrderItem'."); } else { test:assertFail("persist:AlreadyExistsError expected"); } @@ -98,7 +98,7 @@ function redisCompositeKeyReadOneTestNegative1() returns error? { OrderItem|error orderItem = rainierClient->/orderitems/["invalid-order-id"]/[orderItem1.itemId].get(); if orderItem is persist:NotFoundError { - test:assertEquals(orderItem.message(), "A record with the key '{\"orderId\":\"invalid-order-id\",\"itemId\":\"item-1\"}' does not exist for the entity 'OrderItem'."); + test:assertEquals(orderItem.message(), "A record with the key 'OrderItem:invalid-order-id:item-1' does not exist for the entity 'OrderItem'."); } else { test:assertFail("Error expected."); } @@ -115,7 +115,7 @@ function redisCompositeKeyReadOneTestNegative2() returns error? { OrderItem|error orderItem = rainierClient->/orderitems/[orderItem1.orderId]/["invalid-item-id"].get(); if orderItem is persist:NotFoundError { - test:assertEquals(orderItem.message(), "A record with the key '{\"orderId\":\"order-1\",\"itemId\":\"invalid-item-id\"}' does not exist for the entity 'OrderItem'."); + test:assertEquals(orderItem.message(), "A record with the key 'OrderItem:order-1:invalid-item-id' does not exist for the entity 'OrderItem'."); } else { test:assertFail("Error expected."); } @@ -154,7 +154,7 @@ function redisCompositeKeyUpdateTestNegative() returns error? { notes: "updated notes" }); if orderItem is persist:NotFoundError { - test:assertEquals(orderItem.message(), "A record with the key '{\"orderId\":\"order-1\",\"itemId\":\"item-2\"}' does not exist for the entity 'OrderItem'."); + test:assertEquals(orderItem.message(), "A record with the key 'OrderItem:order-1:item-2' does not exist for the entity 'OrderItem'."); } else { test:assertFail("Error expected."); } @@ -187,7 +187,7 @@ function redisCompositeKeyDeleteTestNegative() returns error? { OrderItem|error orderItem = rainierClient->/orderitems/["invalid-order-id"]/[orderItem2.itemId].delete(); if orderItem is persist:NotFoundError { - test:assertEquals(orderItem.message(), "A record with the key '{\"orderId\":\"invalid-order-id\",\"itemId\":\"item-2\"}' does not exist for the entity 'OrderItem'."); + test:assertEquals(orderItem.message(), "A record with the key 'OrderItem:invalid-order-id:item-2' does not exist for the entity 'OrderItem'."); } else { test:assertFail("Error expected."); } diff --git a/ballerina/tests/redis-department-tests.bal b/ballerina/tests/redis-department-tests.bal index 9756dbb..4d1216e 100644 --- a/ballerina/tests/redis-department-tests.bal +++ b/ballerina/tests/redis-department-tests.bal @@ -49,20 +49,20 @@ function redisDepartmentCreateTest2() returns error? { check rainierClient.close(); } -@test:Config { - groups: ["department", "redis"] -} -function redisDepartmentCreateTestNegative() returns error? { - RedisRainierClient rainierClient = check new (); - - string[]|error department = rainierClient->/departments.post([invalidDepartment]); - if department is persist:Error { - test:assertTrue(department.message().includes("value too long for type character varying")); - } else { - test:assertFail("Error expected."); - } - check rainierClient.close(); -} +// @test:Config { +// groups: ["department", "redis"] +// } +// function redisDepartmentCreateTestNegative() returns error? { +// RedisRainierClient rainierClient = check new (); + +// string[]|error department = rainierClient->/departments.post([invalidDepartment]); +// if department is persist:Error { +// test:assertTrue(department.message().includes("value too long for type character varying")); +// } else { +// test:assertFail("Error expected."); +// } +// check rainierClient.close(); +// } @test:Config { groups: ["department", "redis"], @@ -85,7 +85,7 @@ function redisDepartmentReadOneTestNegative() returns error? { Department|error departmentRetrieved = rainierClient->/departments/["invalid-department-id"].get(); if departmentRetrieved is persist:NotFoundError { - test:assertEquals(departmentRetrieved.message(), "A record with the key 'invalid-department-id' does not exist for the entity 'Department'."); + test:assertEquals(departmentRetrieved.message(), "A record with the key 'Department:invalid-department-id' does not exist for the entity 'Department'."); } else { test:assertFail("NotFoundError expected."); } @@ -155,35 +155,36 @@ function redisDepartmentUpdateTestNegative1() returns error? { }); if department is persist:NotFoundError { - test:assertEquals(department.message(), "A record with the key 'invalid-department-id' does not exist for the entity 'Department'."); + test:assertEquals(department.message(), "A record with the key 'Department:invalid-department-id' does not exist for the entity 'Department'."); } else { test:assertFail("NotFoundError expected."); } check rainierClient.close(); } -@test:Config { - groups: ["department", "redis"], - dependsOn: [redisDepartmentReadOneTest, redisDepartmentReadManyTest, redisDepartmentReadManyTestDependent] -} -function redisDepartmentUpdateTestNegative2() returns error? { - RedisRainierClient rainierClient = check new (); +// @test:Config { +// groups: ["department", "redis"], +// dependsOn: [redisDepartmentReadOneTest, redisDepartmentReadManyTest, redisDepartmentReadManyTestDependent] +// } +// function redisDepartmentUpdateTestNegative2() returns error? { +// RedisRainierClient rainierClient = check new (); - Department|error department = rainierClient->/departments/[department1.deptNo].put({ - deptName: "unncessarily-long-department-name-to-force-error-on-update" - }); +// Department|error department = rainierClient->/departments/[department1.deptNo].put({ +// deptName: "unncessarily-long-department-name-to-force-error-on-update" +// }); - if department is persist:Error { - test:assertTrue(department.message().includes("value too long for type character varying")); - } else { - test:assertFail("NotFoundError expected."); - } - check rainierClient.close(); -} +// if department is persist:Error { +// test:assertTrue(department.message().includes("value too long for type character varying")); +// } else { +// test:assertFail("NotFoundError expected."); +// } +// check rainierClient.close(); +// } @test:Config { groups: ["department", "redis"], - dependsOn: [redisDepartmentUpdateTest, redisDepartmentUpdateTestNegative2] + // dependsOn: [redisDepartmentUpdateTest, redisDepartmentUpdateTestNegative2] + dependsOn: [redisDepartmentUpdateTest] } function redisDepartmentDeleteTest() returns error? { RedisRainierClient rainierClient = check new (); @@ -209,7 +210,7 @@ function redisDepartmentDeleteTestNegative() returns error? { Department|error department = rainierClient->/departments/[department1.deptNo].delete(); if department is persist:NotFoundError { - test:assertEquals(department.message(), string `A record with the key '${department1.deptNo}' does not exist for the entity 'Department'.`); + test:assertEquals(department.message(), string `A record with the key 'Department:${department1.deptNo}' does not exist for the entity 'Department'.`); } else { test:assertFail("NotFoundError expected."); } diff --git a/ballerina/tests/redis-employee-tests.bal b/ballerina/tests/redis-employee-tests.bal index 3e694b6..ec52dd3 100644 --- a/ballerina/tests/redis-employee-tests.bal +++ b/ballerina/tests/redis-employee-tests.bal @@ -51,20 +51,20 @@ function redisEmployeeCreateTest2() returns error? { check rainierClient.close(); } -@test:Config { - groups: ["employee", "redis"] -} -function redisEmployeeCreateTestNegative() returns error? { - RedisRainierClient rainierClient = check new (); - - string[]|error employee = rainierClient->/employees.post([invalidEmployee]); - if employee is persist:Error { - test:assertTrue(employee.message().includes("value too long for type character varying")); - } else { - test:assertFail("Error expected."); - } - check rainierClient.close(); -} +// @test:Config { +// groups: ["employee", "redis"] +// } +// function redisEmployeeCreateTestNegative() returns error? { +// RedisRainierClient rainierClient = check new (); + +// string[]|error employee = rainierClient->/employees.post([invalidEmployee]); +// if employee is persist:Error { +// test:assertTrue(employee.message().includes("value too long for type character varying")); +// } else { +// test:assertFail("Error expected."); +// } +// check rainierClient.close(); +// } @test:Config { groups: ["employee", "redis"], @@ -87,7 +87,7 @@ function redisEmployeeReadOneTestNegative() returns error? { Employee|error employeeRetrieved = rainierClient->/employees/["invalid-employee-id"].get(); if employeeRetrieved is persist:NotFoundError { - test:assertEquals(employeeRetrieved.message(), "A record with the key 'invalid-employee-id' does not exist for the entity 'Employee'."); + test:assertEquals(employeeRetrieved.message(), "A record with the key 'Employee:invalid-employee-id' does not exist for the entity 'Employee'."); } else { test:assertFail("NotFoundError expected."); } @@ -155,9 +155,9 @@ function redisEmployeeUpdateTest() returns error? { RedisRainierClient rainierClient = check new (); Employee employee = check rainierClient->/employees/[employee1.empNo].put({ - lastName: "Jones", - departmentDeptNo: "department-3", - birthDate: {year: 1994, month: 11, day: 13} + lastName: updatedEmployee1.lastName, + departmentDeptNo: updatedEmployee1.departmentDeptNo, + birthDate: updatedEmployee1.birthDate }); test:assertEquals(employee, updatedEmployee1); @@ -179,31 +179,31 @@ function redisEmployeeUpdateTestNegative1() returns error? { }); if employee is persist:NotFoundError { - test:assertEquals(employee.message(), "A record with the key 'invalid-employee-id' does not exist for the entity 'Employee'."); + test:assertEquals(employee.message(), "A record with the key 'Employee:invalid-employee-id' does not exist for the entity 'Employee'."); } else { test:assertFail("NotFoundError expected."); } check rainierClient.close(); } -@test:Config { - groups: ["employee", "redis"], - dependsOn: [redisEmployeeReadOneTest, redisEmployeeReadManyTest, redisEmployeeReadManyDependentTest1, redisEmployeeReadManyDependentTest2] -} -function redisEmployeeUpdateTestNegative2() returns error? { - RedisRainierClient rainierClient = check new (); - - Employee|error employee = rainierClient->/employees/[employee1.empNo].put({ - firstName: "unncessarily-long-employee-name-to-force-error-on-update" - }); - - if employee is persist:Error { - test:assertTrue(employee.message().includes("value too long for type character varying")); - } else { - test:assertFail("NotFoundError expected."); - } - check rainierClient.close(); -} +// @test:Config { +// groups: ["employee", "redis"], +// dependsOn: [redisEmployeeReadOneTest, redisEmployeeReadManyTest, redisEmployeeReadManyDependentTest1, redisEmployeeReadManyDependentTest2] +// } +// function redisEmployeeUpdateTestNegative2() returns error? { +// RedisRainierClient rainierClient = check new (); + +// Employee|error employee = rainierClient->/employees/[employee1.empNo].put({ +// firstName: "unncessarily-long-employee-name-to-force-error-on-update" +// }); + +// if employee is persist:Error { +// test:assertTrue(employee.message().includes("value too long for type character varying")); +// } else { +// test:assertFail("NotFoundError expected."); +// } +// check rainierClient.close(); +// } @test:Config { groups: ["employee", "redis"], @@ -217,7 +217,7 @@ function redisEmployeeUpdateTestNegative3() returns error? { }); if employee is persist:ConstraintViolationError { - test:assertTrue(employee.message().includes("Detail: Key (workspaceWorkspaceId)=(invalid-workspaceWorkspaceId) is not present in table \"Workspace\".")); + test:assertTrue(employee.message().includes(string `An association constraint failed between entities 'Employee' and 'Workspace'`)); } else { test:assertFail("persist:ConstraintViolationError expected."); } @@ -226,7 +226,8 @@ function redisEmployeeUpdateTestNegative3() returns error? { @test:Config { groups: ["employee", "redis"], - dependsOn: [redisEmployeeUpdateTest, redisEmployeeUpdateTestNegative2, redisEmployeeUpdateTestNegative3] + // dependsOn: [redisEmployeeUpdateTest, redisEmployeeUpdateTestNegative2, redisEmployeeUpdateTestNegative3] + dependsOn: [redisEmployeeUpdateTest, redisEmployeeUpdateTestNegative3] } function redisEmployeeDeleteTest() returns error? { RedisRainierClient rainierClient = check new (); @@ -252,7 +253,7 @@ function redisEmployeeDeleteTestNegative() returns error? { Employee|error employee = rainierClient->/employees/[employee1.empNo].delete(); if employee is persist:NotFoundError { - test:assertEquals(employee.message(), string `A record with the key '${employee1.empNo}' does not exist for the entity 'Employee'.`); + test:assertEquals(employee.message(), string `A record with the key 'Employee:${employee1.empNo}' does not exist for the entity 'Employee'.`); } else { test:assertFail("NotFoundError expected."); } diff --git a/ballerina/tests/redis-id-fields-tests.bal b/ballerina/tests/redis-id-fields-tests.bal index 5b23a8f..3eda8dc 100644 --- a/ballerina/tests/redis-id-fields-tests.bal +++ b/ballerina/tests/redis-id-fields-tests.bal @@ -71,7 +71,7 @@ function redisIntIdFieldTest() returns error? { test:assertEquals(intIdRecord2, retrievedRecord2); intIdRecords = check from IntIdRecord intIdRecord in testEntitiesClient->/intidrecords.get(IntIdRecord) select intIdRecord; - test:assertEquals(intIdRecords, [intIdRecord3, intIdRecord1Updated]); + test:assertEquals(intIdRecords, [intIdRecord1Updated, intIdRecord3]); check testEntitiesClient.close(); } @@ -131,7 +131,7 @@ function redisStringIdFieldTest() returns error? { test:assertEquals(stringIdRecord2, retrievedRecord2); stringIdRecords = check from StringIdRecord stringIdRecord in testEntitiesClient->/stringidrecords.get(StringIdRecord) select stringIdRecord; - test:assertEquals(stringIdRecords, [stringIdRecord3, stringIdRecord1Updated]); + test:assertEquals(stringIdRecords, [stringIdRecord1Updated, stringIdRecord3]); check testEntitiesClient.close(); } @@ -191,7 +191,7 @@ function redisFloatIdFieldTest() returns error? { test:assertEquals(floatIdRecord2, retrievedRecord2); floatIdRecords = check from FloatIdRecord floatIdRecord in testEntitiesClient->/floatidrecords.get(FloatIdRecord) select floatIdRecord; - test:assertEquals(floatIdRecords, [floatIdRecord3, floatIdRecord1Updated]); + test:assertEquals(floatIdRecords, [floatIdRecord1Updated, floatIdRecord3]); } @test:Config { @@ -249,7 +249,7 @@ function redisDecimalIdFieldTest() returns error? { test:assertEquals(decimalIdRecord2, retrievedRecord2); decimalIdRecords = check from DecimalIdRecord decimalIdRecord in testEntitiesClient->/decimalidrecords.get(DecimalIdRecord) select decimalIdRecord; - test:assertEquals(decimalIdRecords, [decimalIdRecord3, decimalIdRecord1Updated]); + test:assertEquals(decimalIdRecords, [decimalIdRecord1Updated, decimalIdRecord3]); check testEntitiesClient.close(); } diff --git a/ballerina/tests/redis-transaction-tests.bal b/ballerina/tests/redis-transaction-tests.bal deleted file mode 100644 index 0ab7b48..0000000 --- a/ballerina/tests/redis-transaction-tests.bal +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. -// -// WSO2 LLC. licenses this file to you under the Apache License, -// Version 2.0 (the "License"); you may not use this file except -// in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -import ballerina/test; -import ballerina/persist; - -@test:Config { - groups: ["transactions", "redis"] -} -function redisTransactionTest() returns error? { - RedisRainierClient rainierClient = check new (); - - transaction { - string[] buildingCodes = check rainierClient->/buildings.post([building31, building32]); - test:assertEquals(buildingCodes, [building31.buildingCode, building32.buildingCode]); - - buildingCodes = check rainierClient->/buildings.post([building31]); - check commit; - } on fail error e { - test:assertTrue(e is persist:AlreadyExistsError, "AlreadyExistsError expected"); - } - - Building|persist:Error buildingRetrieved = rainierClient->/buildings/[building31.buildingCode].get(); - test:assertTrue(buildingRetrieved is persist:NotFoundError, "NotFoundError expected"); - - buildingRetrieved = rainierClient->/buildings/[building32.buildingCode].get(); - test:assertTrue(buildingRetrieved is persist:NotFoundError, "NotFoundError expected"); - - check rainierClient.close(); -} - -@test:Config { - groups: ["transactions", "redis"] -} -function redisTransactionTest2() returns error? { - RedisRainierClient rainierClient = check new (); - - _ = check rainierClient->/buildings.post([building33]); - Building buildingRetrieved = check rainierClient->/buildings/[building33.buildingCode].get(); - test:assertEquals(buildingRetrieved, building33); - - transaction { - Building building = check rainierClient->/buildings/[building33.buildingCode].put({ - city: "ColomboUpdated", - state: "Western ProvinceUpdated", - country: "Sri LankaUpdated" - }); - - test:assertEquals(building, building33Updated); - - // below should retrieve the updated building record - buildingRetrieved = check rainierClient->/buildings/[building33.buildingCode].get(); - test:assertEquals(buildingRetrieved, building33Updated); - - check commit; - } - - buildingRetrieved = check rainierClient->/buildings/[building33.buildingCode].get(); - test:assertEquals(buildingRetrieved, building33Updated); - - check rainierClient.close(); -} diff --git a/ballerina/tests/redis-workspace-tests.bal b/ballerina/tests/redis-workspace-tests.bal index 24d1b34..c5f0e24 100644 --- a/ballerina/tests/redis-workspace-tests.bal +++ b/ballerina/tests/redis-workspace-tests.bal @@ -49,20 +49,20 @@ function redisWorkspaceCreateTest2() returns error? { check rainierClient.close(); } -@test:Config { - groups: ["workspace", "redis"] -} -function redisWorkspaceCreateTestNegative() returns error? { - RedisRainierClient rainierClient = check new (); +// @test:Config { +// groups: ["workspace", "redis"] +// } +// function redisWorkspaceCreateTestNegative() returns error? { +// RedisRainierClient rainierClient = check new (); - string[]|error workspace = rainierClient->/workspaces.post([invalidWorkspace]); - if workspace is persist:Error { - test:assertTrue(workspace.message().includes("value too long for type character varying")); - } else { - test:assertFail("Error expected."); - } - check rainierClient.close(); -} +// string[]|error workspace = rainierClient->/workspaces.post([invalidWorkspace]); +// if workspace is persist:Error { +// test:assertTrue(workspace.message().includes("value too long for type character varying")); +// } else { +// test:assertFail("Error expected."); +// } +// check rainierClient.close(); +// } @test:Config { groups: ["workspace", "redis"], @@ -102,7 +102,7 @@ function redisWorkspaceReadOneTestNegative() returns error? { Workspace|error workspaceRetrieved = rainierClient->/workspaces/["invalid-workspace-id"].get(); if workspaceRetrieved is persist:NotFoundError { - test:assertEquals(workspaceRetrieved.message(), "A record with the key 'invalid-workspace-id' does not exist for the entity 'Workspace'."); + test:assertEquals(workspaceRetrieved.message(), "A record with the key 'Workspace:invalid-workspace-id' does not exist for the entity 'Workspace'."); } else { test:assertFail("NotFoundError expected."); } @@ -189,35 +189,36 @@ function redisWorkspaceUpdateTestNegative1() returns error? { }); if workspace is persist:NotFoundError { - test:assertEquals(workspace.message(), "A record with the key 'invalid-workspace-id' does not exist for the entity 'Workspace'."); + test:assertEquals(workspace.message(), "A record with the key 'Workspace:invalid-workspace-id' does not exist for the entity 'Workspace'."); } else { test:assertFail("NotFoundError expected."); } check rainierClient.close(); } -@test:Config { - groups: ["workspace", "redis"], - dependsOn: [redisWorkspaceReadOneTest, redisWorkspaceReadManyTest, redisWorkspaceReadManyDependentTest] -} -function redisWorkspaceUpdateTestNegative2() returns error? { - RedisRainierClient rainierClient = check new (); +// @test:Config { +// groups: ["workspace", "redis"], +// dependsOn: [redisWorkspaceReadOneTest, redisWorkspaceReadManyTest, redisWorkspaceReadManyDependentTest] +// } +// function redisWorkspaceUpdateTestNegative2() returns error? { +// RedisRainierClient rainierClient = check new (); - Workspace|error workspace = rainierClient->/workspaces/[workspace1.workspaceId].put({ - workspaceType: "unncessarily-long-workspace-type-to-force-error-on-update" - }); +// Workspace|error workspace = rainierClient->/workspaces/[workspace1.workspaceId].put({ +// workspaceType: "unncessarily-long-workspace-type-to-force-error-on-update" +// }); - if workspace is persist:Error { - test:assertTrue(workspace.message().includes("value too long for type character varying")); - } else { - test:assertFail("NotFoundError expected."); - } - check rainierClient.close(); -} +// if workspace is persist:Error { +// test:assertTrue(workspace.message().includes("value too long for type character varying")); +// } else { +// test:assertFail("NotFoundError expected."); +// } +// check rainierClient.close(); +// } @test:Config { groups: ["workspace", "redis"], - dependsOn: [redisWorkspaceUpdateTest, redisWorkspaceUpdateTestNegative2] + // dependsOn: [redisWorkspaceUpdateTest, redisWorkspaceUpdateTestNegative2] + dependsOn: [redisWorkspaceUpdateTest] } function redisWorkspaceDeleteTest() returns error? { RedisRainierClient rainierClient = check new (); @@ -243,7 +244,7 @@ function redisWorkspaceDeleteTestNegative() returns error? { Workspace|error workspace = rainierClient->/workspaces/[workspace1.workspaceId].delete(); if workspace is persist:NotFoundError { - test:assertEquals(workspace.message(), string `A record with the key '${workspace1.workspaceId}' does not exist for the entity 'Workspace'.`); + test:assertEquals(workspace.message(), string `A record with the key 'Workspace:${workspace1.workspaceId}' does not exist for the entity 'Workspace'.`); } else { test:assertFail("NotFoundError expected."); } diff --git a/ballerina/tests/test_entities_generated_types.bal b/ballerina/tests/test_entities_generated_types.bal index f967c5d..e65165a 100644 --- a/ballerina/tests/test_entities_generated_types.bal +++ b/ballerina/tests/test_entities_generated_types.bal @@ -37,15 +37,15 @@ public type AllTypes record {| string stringType; time:Date dateType; time:TimeOfDay timeOfDayType; - boolean? booleanTypeOptional; - int? intTypeOptional; - float? floatTypeOptional; - decimal? decimalTypeOptional; - string? stringTypeOptional; - time:Date? dateTypeOptional; - time:TimeOfDay? timeOfDayTypeOptional; EnumType enumType; - EnumType? enumTypeOptional; + boolean booleanTypeOptional?; + int intTypeOptional?; + float floatTypeOptional?; + decimal decimalTypeOptional?; + string stringTypeOptional?; + time:Date dateTypeOptional?; + time:TimeOfDay timeOfDayTypeOptional?; + EnumType enumTypeOptional?; |}; public type AllTypesOptionalized record {| @@ -57,15 +57,15 @@ public type AllTypesOptionalized record {| string stringType?; time:Date dateType?; time:TimeOfDay timeOfDayType?; - boolean? booleanTypeOptional?; - int? intTypeOptional?; - float? floatTypeOptional?; - decimal? decimalTypeOptional?; - string? stringTypeOptional?; - time:Date? dateTypeOptional?; - time:TimeOfDay? timeOfDayTypeOptional?; - EnumType? enumType?; - EnumType? enumTypeOptional?; + EnumType enumType?; + boolean booleanTypeOptional?; + int intTypeOptional?; + float floatTypeOptional?; + decimal decimalTypeOptional?; + string stringTypeOptional?; + time:Date dateTypeOptional?; + time:TimeOfDay timeOfDayTypeOptional?; + EnumType enumTypeOptional?; |}; public type AllTypesTargetType typedesc; @@ -80,15 +80,15 @@ public type AllTypesUpdate record {| string stringType?; time:Date dateType?; time:TimeOfDay timeOfDayType?; - boolean? booleanTypeOptional?; - int? intTypeOptional?; - float? floatTypeOptional?; - decimal? decimalTypeOptional?; - string? stringTypeOptional?; - time:Date? dateTypeOptional?; - time:TimeOfDay? timeOfDayTypeOptional?; - EnumType? enumType?; - EnumType? enumTypeOptional?; + EnumType enumType?; + boolean booleanTypeOptional?; + int intTypeOptional?; + float floatTypeOptional?; + decimal decimalTypeOptional?; + string stringTypeOptional?; + time:Date dateTypeOptional?; + time:TimeOfDay timeOfDayTypeOptional?; + EnumType enumTypeOptional?; |}; public type StringIdRecord record {| diff --git a/native/build.gradle b/native/build.gradle index ff96f14..42ac8c3 100644 --- a/native/build.gradle +++ b/native/build.gradle @@ -45,7 +45,7 @@ tasks.withType(JavaCompile) { sourceCompatibility = JavaVersion.VERSION_17 jacoco { - toolVersion = "0.8.6" + toolVersion = "0.8.8" } test { From db16d1b01d4d0615179eb0ec9ad2766e50de4ea5 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Mon, 19 Feb 2024 08:36:10 +0530 Subject: [PATCH 51/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index f59c660..1781651 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,6 +7,18 @@ dependencies-toml-version = "2" distribution-version = "2201.8.2" +[[package]] +org = "ballerina" +name = "io" +version = "1.6.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + [[package]] org = "ballerina" name = "jballerina.java" @@ -24,6 +36,14 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "persist" @@ -64,6 +84,7 @@ org = "ballerinax" name = "persist.redis" version = "0.1.0" dependencies = [ + {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, {org = "ballerina", name = "test"}, From 9d7a36cbc282aebb551ddbcc65612d9a7ff2f94a Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Mon, 19 Feb 2024 09:24:10 +0530 Subject: [PATCH 52/65] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 1781651..f59c660 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,18 +7,6 @@ dependencies-toml-version = "2" distribution-version = "2201.8.2" -[[package]] -org = "ballerina" -name = "io" -version = "1.6.0" -dependencies = [ - {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "lang.value"} -] -modules = [ - {org = "ballerina", packageName = "io", moduleName = "io"} -] - [[package]] org = "ballerina" name = "jballerina.java" @@ -36,14 +24,6 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] -[[package]] -org = "ballerina" -name = "lang.value" -version = "0.0.0" -dependencies = [ - {org = "ballerina", name = "jballerina.java"} -] - [[package]] org = "ballerina" name = "persist" @@ -84,7 +64,6 @@ org = "ballerinax" name = "persist.redis" version = "0.1.0" dependencies = [ - {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "persist"}, {org = "ballerina", name = "test"}, From ac71fb0a4e5eb29d0160887390e42ccefe72c1b0 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Mon, 19 Feb 2024 09:25:28 +0530 Subject: [PATCH 53/65] fixed a bug in runUpdateQuery method --- ballerina/redis_client.bal | 12 ++++- ballerina/stream_types.bal | 1 - ballerina/tests/redis-associations-tests.bal | 18 +++---- ballerina/tests/redis-building-tests.bal | 37 -------------- ballerina/tests/redis-department-tests.bal | 35 ------------- ballerina/tests/redis-employee-tests.bal | 35 ------------- ballerina/tests/redis-workspace-tests.bal | 51 ------------------- .../tests/redis_rainier_generated_client.bal | 8 --- .../redis_test_entities_generated_client.bal | 8 --- 9 files changed, 20 insertions(+), 185 deletions(-) diff --git a/ballerina/redis_client.bal b/ballerina/redis_client.bal index 4f114f6..393a926 100644 --- a/ballerina/redis_client.bal +++ b/ballerina/redis_client.bal @@ -208,6 +208,7 @@ public isolated client class RedisClient { public isolated function runUpdateQuery(anydata key, record {} updateRecord) returns persist:Error? { // Generate the key string recordKey = string `${self.collectionName}${self.getKey(key)}`; + string recordKeySuffix = self.getKey(key); // Verify the existence of the key do { @@ -315,18 +316,27 @@ public isolated client class RedisClient { } string[] joinFields = refMetaData.joinFields; string newRelatedRecordKey = refMetaData.refCollection; + string prevRelatedRecordKey = refMetaData.refCollection; foreach string joinField in joinFields{ if newUpdateRecord.hasKey(joinField){ newRelatedRecordKey += string `${KEY_SEPERATOR}${newUpdateRecord[joinField].toString()}`; }else{ newRelatedRecordKey += string `${KEY_SEPERATOR}${prevRecord[joinField].toString()}`; } + prevRelatedRecordKey += string `${KEY_SEPERATOR}${prevRecord[joinField].toString()}`; } + // Attach to new association int|error sAdd = self.dbClient->sAdd( - string `${newRelatedRecordKey}${KEY_SEPERATOR}${self.collectionName}`, [recordKey]); + string `${newRelatedRecordKey}${KEY_SEPERATOR}${self.collectionName}`, [recordKeySuffix]); if sAdd is error { return error persist:Error(sAdd.message()); } + // Detach from previous association + int|error sRem = self.dbClient->sRem( + string `${prevRelatedRecordKey}${KEY_SEPERATOR}${self.collectionName}`, [recordKeySuffix]); + if sRem is error { + return error persist:Error(sRem.message()); + } } } } diff --git a/ballerina/stream_types.bal b/ballerina/stream_types.bal index 8f16a06..05e4122 100644 --- a/ballerina/stream_types.bal +++ b/ballerina/stream_types.bal @@ -66,7 +66,6 @@ public class PersistRedisStream { _ = value.remove(keyField); } } - // return {value: value}; record {|record {} value;|} nextRecord = {value: checkpanic value.cloneWithType(self.targetType)}; return nextRecord; } diff --git a/ballerina/tests/redis-associations-tests.bal b/ballerina/tests/redis-associations-tests.bal index d32fea4..55821fe 100644 --- a/ballerina/tests/redis-associations-tests.bal +++ b/ballerina/tests/redis-associations-tests.bal @@ -15,7 +15,7 @@ // under the License. import ballerina/test; -// import ballerina/persist; +import ballerina/persist; @test:Config { groups: ["associations", "redis"], @@ -60,9 +60,9 @@ function redisEmployeeRelationsTest() returns error? { _ = check rainierClient->/workspaces.post([workspace22]); _ = check rainierClient->/employees.post([employee21]); - // stream employeeStream = rainierClient->/employees.get(); - // EmployeeInfo[] employees = check from EmployeeInfo employee in employeeStream - // select employee; + stream employeeStream = rainierClient->/employees.get(); + EmployeeInfo[] employees = check from EmployeeInfo employee in employeeStream + select employee; EmployeeInfo retrieved = check rainierClient->/employees/["employee-21"].get(); @@ -80,7 +80,7 @@ function redisEmployeeRelationsTest() returns error? { }; test:assertEquals(retrieved, expected); - // test:assertTrue(employees.indexOf(expected) is int, "Expected EmployeeInfo not found."); + test:assertTrue(employees.indexOf(expected) is int, "Expected EmployeeInfo not found."); check rainierClient.close(); } @@ -138,9 +138,9 @@ function redisDepartmentRelationsTest() returns error? { _ = check rainierClient->/workspaces.post([workspace12]); _ = check rainierClient->/employees.post([employee11, employee12]); - // stream departmentStream = rainierClient->/departments.get(); - // DepartmentInfo[] departments = check from DepartmentInfo department in departmentStream - // select department; + stream departmentStream = rainierClient->/departments.get(); + DepartmentInfo[] departments = check from DepartmentInfo department in departmentStream + select department; DepartmentInfo retrieved = check rainierClient->/departments/["department-12"].get(); @@ -159,8 +159,8 @@ function redisDepartmentRelationsTest() returns error? { ] }; + test:assertTrue(departments.indexOf(expected) is int, "Expected DepartmentInfo not found."); test:assertEquals(retrieved, expected); - // test:assertTrue(departments.indexOf(expected) is int, "Expected DepartmentInfo not found."); check rainierClient.close(); } diff --git a/ballerina/tests/redis-building-tests.bal b/ballerina/tests/redis-building-tests.bal index 75a867c..1324506 100644 --- a/ballerina/tests/redis-building-tests.bal +++ b/ballerina/tests/redis-building-tests.bal @@ -50,21 +50,6 @@ function redisBuildingCreateTest2() returns error? { check rainierClient.close(); } -// @test:Config { -// groups: ["building", "redis"] -// } -// function redisBuildingCreateTestNegative() returns error? { -// RedisRainierClient rainierClient = check new (); - -// string[]|error building = rainierClient->/buildings.post([invalidBuilding]); -// if building is persist:Error { -// test:assertTrue(building.message().includes("value too long for type character varying")); -// } else { -// test:assertFail("Error expected."); -// } -// check rainierClient.close(); -// } - @test:Config { groups: ["building", "redis"], dependsOn: [redisBuildingCreateTest] @@ -169,30 +154,8 @@ function redisBuildingUpdateTestNegative1() returns error? { check rainierClient.close(); } -// @test:Config { -// groups: ["building", "redis"], -// dependsOn: [redisBuildingReadOneTest, redisBuildingReadManyTest, redisBuildingReadManyDependentTest] -// } -// function redisBuildingUpdateTestNegative2() returns error? { -// RedisRainierClient rainierClient = check new (); - -// Building|error building = rainierClient->/buildings/[building1.buildingCode].put({ -// city: "unncessarily-long-city-name-to-force-error-on-update", -// state: "Southern Province", -// postalCode: "10890" -// }); - -// if building is persist:Error { -// test:assertTrue(building.message().includes("value too long for type character varying")); -// } else { -// test:assertFail("persist:NotFoundError expected."); -// } -// check rainierClient.close(); -// } - @test:Config { groups: ["building", "redis"], - // dependsOn: [redisBuildingUpdateTest, redisBuildingUpdateTestNegative2] dependsOn: [redisBuildingUpdateTest] } function redisBuildingDeleteTest() returns error? { diff --git a/ballerina/tests/redis-department-tests.bal b/ballerina/tests/redis-department-tests.bal index 4d1216e..2231413 100644 --- a/ballerina/tests/redis-department-tests.bal +++ b/ballerina/tests/redis-department-tests.bal @@ -49,21 +49,6 @@ function redisDepartmentCreateTest2() returns error? { check rainierClient.close(); } -// @test:Config { -// groups: ["department", "redis"] -// } -// function redisDepartmentCreateTestNegative() returns error? { -// RedisRainierClient rainierClient = check new (); - -// string[]|error department = rainierClient->/departments.post([invalidDepartment]); -// if department is persist:Error { -// test:assertTrue(department.message().includes("value too long for type character varying")); -// } else { -// test:assertFail("Error expected."); -// } -// check rainierClient.close(); -// } - @test:Config { groups: ["department", "redis"], dependsOn: [redisDepartmentCreateTest] @@ -162,28 +147,8 @@ function redisDepartmentUpdateTestNegative1() returns error? { check rainierClient.close(); } -// @test:Config { -// groups: ["department", "redis"], -// dependsOn: [redisDepartmentReadOneTest, redisDepartmentReadManyTest, redisDepartmentReadManyTestDependent] -// } -// function redisDepartmentUpdateTestNegative2() returns error? { -// RedisRainierClient rainierClient = check new (); - -// Department|error department = rainierClient->/departments/[department1.deptNo].put({ -// deptName: "unncessarily-long-department-name-to-force-error-on-update" -// }); - -// if department is persist:Error { -// test:assertTrue(department.message().includes("value too long for type character varying")); -// } else { -// test:assertFail("NotFoundError expected."); -// } -// check rainierClient.close(); -// } - @test:Config { groups: ["department", "redis"], - // dependsOn: [redisDepartmentUpdateTest, redisDepartmentUpdateTestNegative2] dependsOn: [redisDepartmentUpdateTest] } function redisDepartmentDeleteTest() returns error? { diff --git a/ballerina/tests/redis-employee-tests.bal b/ballerina/tests/redis-employee-tests.bal index ec52dd3..375ea3c 100644 --- a/ballerina/tests/redis-employee-tests.bal +++ b/ballerina/tests/redis-employee-tests.bal @@ -51,21 +51,6 @@ function redisEmployeeCreateTest2() returns error? { check rainierClient.close(); } -// @test:Config { -// groups: ["employee", "redis"] -// } -// function redisEmployeeCreateTestNegative() returns error? { -// RedisRainierClient rainierClient = check new (); - -// string[]|error employee = rainierClient->/employees.post([invalidEmployee]); -// if employee is persist:Error { -// test:assertTrue(employee.message().includes("value too long for type character varying")); -// } else { -// test:assertFail("Error expected."); -// } -// check rainierClient.close(); -// } - @test:Config { groups: ["employee", "redis"], dependsOn: [redisEmployeeCreateTest] @@ -186,25 +171,6 @@ function redisEmployeeUpdateTestNegative1() returns error? { check rainierClient.close(); } -// @test:Config { -// groups: ["employee", "redis"], -// dependsOn: [redisEmployeeReadOneTest, redisEmployeeReadManyTest, redisEmployeeReadManyDependentTest1, redisEmployeeReadManyDependentTest2] -// } -// function redisEmployeeUpdateTestNegative2() returns error? { -// RedisRainierClient rainierClient = check new (); - -// Employee|error employee = rainierClient->/employees/[employee1.empNo].put({ -// firstName: "unncessarily-long-employee-name-to-force-error-on-update" -// }); - -// if employee is persist:Error { -// test:assertTrue(employee.message().includes("value too long for type character varying")); -// } else { -// test:assertFail("NotFoundError expected."); -// } -// check rainierClient.close(); -// } - @test:Config { groups: ["employee", "redis"], dependsOn: [redisEmployeeReadOneTest, redisEmployeeReadManyTest, redisEmployeeReadManyDependentTest1, redisEmployeeReadManyDependentTest2] @@ -226,7 +192,6 @@ function redisEmployeeUpdateTestNegative3() returns error? { @test:Config { groups: ["employee", "redis"], - // dependsOn: [redisEmployeeUpdateTest, redisEmployeeUpdateTestNegative2, redisEmployeeUpdateTestNegative3] dependsOn: [redisEmployeeUpdateTest, redisEmployeeUpdateTestNegative3] } function redisEmployeeDeleteTest() returns error? { diff --git a/ballerina/tests/redis-workspace-tests.bal b/ballerina/tests/redis-workspace-tests.bal index c5f0e24..6ef6729 100644 --- a/ballerina/tests/redis-workspace-tests.bal +++ b/ballerina/tests/redis-workspace-tests.bal @@ -49,21 +49,6 @@ function redisWorkspaceCreateTest2() returns error? { check rainierClient.close(); } -// @test:Config { -// groups: ["workspace", "redis"] -// } -// function redisWorkspaceCreateTestNegative() returns error? { -// RedisRainierClient rainierClient = check new (); - -// string[]|error workspace = rainierClient->/workspaces.post([invalidWorkspace]); -// if workspace is persist:Error { -// test:assertTrue(workspace.message().includes("value too long for type character varying")); -// } else { -// test:assertFail("Error expected."); -// } -// check rainierClient.close(); -// } - @test:Config { groups: ["workspace", "redis"], dependsOn: [redisWorkspaceCreateTest] @@ -161,22 +146,6 @@ function redisWorkspaceUpdateTest() returns error? { check rainierClient.close(); } -// @test:Config { -// groups: ["workspace", "redis"], -// dependsOn: [redisWorkspaceCreateTest, redisWorkspaceCreateTest2] -// } -// function redisWorkspaceReadWithClauses() returns error? { -// RedisRainierClient rainierClient = check new (); -// string value = "small"; -// string id = "\"Workspace\".\"workspaceId\""; -// int count = 2; -// stream workspaceStream = rainierClient->/workspaces.get(whereClause = `"Workspace"."workspaceType" = ${value} OR "Workspace"."workspaceType" = 'medium'`, orderByClause = `"Workspace"."workspaceId" DESC `, limitClause = ` ${count}`, groupByClause = `${id}`); -// Workspace[] workspaces = check from Workspace workspace in workspaceStream -// select workspace; -// test:assertEquals(workspaces, [workspace3, workspace2]); -// check rainierClient.close(); -// } - @test:Config { groups: ["workspace", "redis"], dependsOn: [redisWorkspaceReadOneTest, redisWorkspaceReadManyTest, redisWorkspaceReadManyDependentTest] @@ -196,28 +165,8 @@ function redisWorkspaceUpdateTestNegative1() returns error? { check rainierClient.close(); } -// @test:Config { -// groups: ["workspace", "redis"], -// dependsOn: [redisWorkspaceReadOneTest, redisWorkspaceReadManyTest, redisWorkspaceReadManyDependentTest] -// } -// function redisWorkspaceUpdateTestNegative2() returns error? { -// RedisRainierClient rainierClient = check new (); - -// Workspace|error workspace = rainierClient->/workspaces/[workspace1.workspaceId].put({ -// workspaceType: "unncessarily-long-workspace-type-to-force-error-on-update" -// }); - -// if workspace is persist:Error { -// test:assertTrue(workspace.message().includes("value too long for type character varying")); -// } else { -// test:assertFail("NotFoundError expected."); -// } -// check rainierClient.close(); -// } - @test:Config { groups: ["workspace", "redis"], - // dependsOn: [redisWorkspaceUpdateTest, redisWorkspaceUpdateTestNegative2] dependsOn: [redisWorkspaceUpdateTest] } function redisWorkspaceDeleteTest() returns error? { diff --git a/ballerina/tests/redis_rainier_generated_client.bal b/ballerina/tests/redis_rainier_generated_client.bal index 31d83aa..106e7a7 100644 --- a/ballerina/tests/redis_rainier_generated_client.bal +++ b/ballerina/tests/redis_rainier_generated_client.bal @@ -342,14 +342,6 @@ public isolated client class RedisRainierClient { return result; } - // remote isolated function queryNativeSQL(redis:ParameterizedQuery redisQuery, typedesc rowType = <>) returns stream = @java:Method { - // 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor" - // } external; - - // remote isolated function executeNativeSQL(redis:ParameterizedQuery redisQuery) returns ExecutionResult|persist:Error = @java:Method { - // 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor" - // } external; - public isolated function close() returns persist:Error? { error? result = self.dbClient.stop(); if result is error { diff --git a/ballerina/tests/redis_test_entities_generated_client.bal b/ballerina/tests/redis_test_entities_generated_client.bal index 44e576e..77a10ef 100644 --- a/ballerina/tests/redis_test_entities_generated_client.bal +++ b/ballerina/tests/redis_test_entities_generated_client.bal @@ -478,14 +478,6 @@ public isolated client class RedisTestEntitiesClient { return result; } - // remote isolated function queryNativeSQL(redis:ParameterizedQuery redisQuery, typedesc rowType = <>) returns stream = @java:Method { - // 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor" - // } external; - - // remote isolated function executeNativeSQL(redis:ParameterizedQuery redisQuery) returns ExecutionResult|persist:Error = @java:Method { - // 'class: "io.ballerina.stdlib.persist.redis.datastore.RedisProcessor" - // } external; - public isolated function close() returns persist:Error? { error? result = self.dbClient.stop(); if result is error { From 1d0ad5cc66958f177132220c11245dc973435e78 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Tue, 20 Feb 2024 21:23:42 +0530 Subject: [PATCH 54/65] added github workflow --- .github/pull_request_template.md | 12 +++ .../workflows/build-timestamped-master.yml | 64 ++++++++++++++ .../workflows/build-with-bal-test-graalvm.yml | 38 ++++++++ .github/workflows/central-publish.yml | 87 +++++++++++++++++++ .github/workflows/publish-release.yml | 76 ++++++++++++++++ .github/workflows/publish-snapshot-nexus.yml | 27 ++++++ .github/workflows/pull-request.yml | 48 ++++++++++ .github/workflows/stale_check.yml | 19 ++++ .github/workflows/trivy-scan.yml | 33 +++++++ docs/spec/spec.md | 1 + 10 files changed, 405 insertions(+) create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/build-timestamped-master.yml create mode 100644 .github/workflows/build-with-bal-test-graalvm.yml create mode 100644 .github/workflows/central-publish.yml create mode 100644 .github/workflows/publish-release.yml create mode 100644 .github/workflows/publish-snapshot-nexus.yml create mode 100644 .github/workflows/pull-request.yml create mode 100644 .github/workflows/stale_check.yml create mode 100644 .github/workflows/trivy-scan.yml create mode 100644 docs/spec/spec.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..40b67cc --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,12 @@ +## Purpose + +Fixes: + +## Examples + +## Checklist +- [ ] Linked to an issue +- [ ] Updated the specification +- [ ] Updated the changelog +- [ ] Added tests +- [ ] Checked native-image compatibility \ No newline at end of file diff --git a/.github/workflows/build-timestamped-master.yml b/.github/workflows/build-timestamped-master.yml new file mode 100644 index 0000000..d9dbd5b --- /dev/null +++ b/.github/workflows/build-timestamped-master.yml @@ -0,0 +1,64 @@ +name: Build + +on: + workflow_dispatch: + push: + branches: + - main + - 2201.[0-9]+.x + paths-ignore: + - 'load-tests/**' + - '*.md' + - 'docs/**' + +jobs: + build: + runs-on: ubuntu-latest + if: github.repository_owner == 'ballerina-platform' + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 17.0.7 + - name: Change to Timestamped Version + run: | + initialVersion=$((grep -w 'version' | cut -d= -f2) < gradle.properties ) + echo "Initial_Version=$initialVersion" >> $GITHUB_ENV + startTime=$(TZ="Asia/Kolkata" date +'%Y%m%d-%H%M00') + latestCommit=$(git log -n 1 --pretty=format:"%h") + VERSION=$((grep -w 'version' | cut -d= -f2) < gradle.properties | rev | cut --complement -d- -f1 | rev) + updatedVersion=$VERSION-$startTime-$latestCommit + echo $updatedVersion + sed -i "s/version=\(.*\)/version=$updatedVersion/g" gradle.properties + - name: Build with Gradle + env: + packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} + packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} + publishUser: ${{ secrets.BALLERINA_BOT_USERNAME }} + publishPAT: ${{ secrets.BALLERINA_BOT_TOKEN }} + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }} + run: | + ./gradlew clean build publishAllPublicationsToGitHubPackagesRepository --scan --no-daemon + - name: Generate CodeCov Report + uses: codecov/codecov-action@v2 + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + name: ballerina-runtime + path: target/ballerina-runtime/ + - name: Revert to SNAPSHOT Version + run: | + echo "version=${{ env.Initial_Version }}" + sed -i "s/version=\(.*\)/version=${{ env.Initial_Version }}/g" gradle.properties + - name: Publish to Nexus + env: + packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} + packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} + nexusUser: ${{ secrets.NEXUS_USERNAME }} + nexusPassword: ${{ secrets.NEXUS_PASSWORD }} + run: | + ./gradlew clean publishMavenJavaPublicationToWSO2NexusRepository -x test --scan --no-daemon diff --git a/.github/workflows/build-with-bal-test-graalvm.yml b/.github/workflows/build-with-bal-test-graalvm.yml new file mode 100644 index 0000000..41951cb --- /dev/null +++ b/.github/workflows/build-with-bal-test-graalvm.yml @@ -0,0 +1,38 @@ +name: GraalVM Check + +on: + workflow_dispatch: + inputs: + lang_tag: + description: Branch/Release Tag of the Ballerina Lang + required: true + default: master + lang_version: + description: Ballerina Lang Version (If given ballerina lang buid will be skipped) + required: false + default: '' + native_image_options: + description: Default native-image options + required: false + default: '' + schedule: + - cron: '30 18 * * *' + pull_request: + branches: + - main + types: [ opened, synchronize, reopened, labeled, unlabeled ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +jobs: + call_stdlib_workflow: + name: Run StdLib Workflow + if: ${{ github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository_owner == 'ballerina-platform') }} + uses: ballerina-platform/ballerina-standard-library/.github/workflows/build-with-bal-test-graalvm-template.yml@main + with: + lang_tag: ${{ inputs.lang_tag }} + lang_version: ${{ inputs.lang_version }} + native_image_options: '-J-Xmx7G ${{ inputs.native_image_options }}' + additional_windows_build_flags: '-x test' diff --git a/.github/workflows/central-publish.yml b/.github/workflows/central-publish.yml new file mode 100644 index 0000000..7825cb7 --- /dev/null +++ b/.github/workflows/central-publish.yml @@ -0,0 +1,87 @@ +name: Publish to the Ballerina central + +on: + workflow_dispatch: + inputs: + environment: + type: choice + description: Select environment + required: true + options: + - CENTRAL + - DEV CENTRAL + - STAGE CENTRAL + +jobs: + publish-release: + runs-on: ubuntu-latest + if: github.repository_owner == 'ballerina-platform' + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 17.0.7 + - name: Build with Gradle + env: + packageUser: ${{ github.actor }} + packagePAT: ${{ secrets.GITHUB_TOKEN }} + run: ./gradlew build -x check -x test + - name: Create lib directory if not exists + run: mkdir -p ballerina/lib + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + scan-type: 'rootfs' + scan-ref: '/github/workspace/ballerina/lib' + format: 'table' + timeout: '10m0s' + exit-code: '1' + + - name: Ballerina Central Push + if: ${{ github.event.inputs.environment == 'CENTRAL' }} + env: + BALLERINA_DEV_CENTRAL: false + BALLERINA_STAGE_CENTRAL: false + BALLERINA_CENTRAL_ACCESS_TOKEN: ${{ secrets.BALLERINA_CENTRAL_ACCESS_TOKEN }} + packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} + packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} + GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }} + run: | + ./gradlew clean build -PpublishToCentral=true + + - name: Ballerina Central Dev Push + if: ${{ github.event.inputs.environment == 'DEV CENTRAL' }} + env: + BALLERINA_DEV_CENTRAL: true + BALLERINA_STAGE_CENTRAL: false + BALLERINA_CENTRAL_ACCESS_TOKEN: ${{ secrets.BALLERINA_CENTRAL_DEV_ACCESS_TOKEN }} + packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} + packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} + GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }} + run: | + sed -i 's/version=\(.*\)-SNAPSHOT/version=\1/g' gradle.properties + ./gradlew clean build -PpublishToCentral=true + + - name: Ballerina Central Stage Push + if: ${{ github.event.inputs.environment == 'STAGE CENTRAL' }} + env: + BALLERINA_DEV_CENTRAL: false + BALLERINA_STAGE_CENTRAL: true + BALLERINA_CENTRAL_ACCESS_TOKEN: ${{ secrets.BALLERINA_CENTRAL_STAGE_ACCESS_TOKEN }} + packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} + packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} + GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }} + run: | + sed -i 's/version=\(.*\)-SNAPSHOT/version=\1/g' gradle.properties + ./gradlew clean build -PpublishToCentral=true diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..1681745 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,76 @@ +name: Publish release + +on: + workflow_dispatch: + repository_dispatch: + types: [ stdlib-release-pipeline ] + +jobs: + publish-release: + runs-on: ubuntu-latest + if: github.repository_owner == 'ballerina-platform' + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 17.0.7 + - name: Build with Gradle + env: + packageUser: ${{ github.actor }} + packagePAT: ${{ secrets.GITHUB_TOKEN }} + run: | + git config --global user.name ${{ secrets.BALLERINA_BOT_USERNAME }} + git config --global user.email ${{ secrets.BALLERINA_BOT_EMAIL }} + ./gradlew build -x check -x test + - name: Create lib directory if not exists + run: mkdir -p ballerina/lib + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + scan-type: 'rootfs' + scan-ref: '/github/workspace/ballerina/lib' + format: 'table' + timeout: '10m0s' + exit-code: '1' + - name: Set version env variable + run: echo "VERSION=$((grep -w 'version' | cut -d= -f2) < gradle.properties | rev | cut --complement -d- -f1 | rev)" >> $GITHUB_ENV + - name: Pre release dependency version update + env: + GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} + run: | + echo "Version: ${VERSION}" + git checkout -b release-${VERSION} + sed -i 's/ballerinaLangVersion=\(.*\)-SNAPSHOT/ballerinaLangVersion=\1/g' gradle.properties + sed -i 's/ballerinaLangVersion=\(.*\)-[0-9]\{8\}-[0-9]\{6\}-.*$/ballerinaLangVersion=\1/g' gradle.properties + sed -i 's/stdlib\(.*\)=\(.*\)-SNAPSHOT/stdlib\1=\2/g' gradle.properties + sed -i 's/stdlib\(.*\)=\(.*\)-[0-9]\{8\}-[0-9]\{6\}-.*$/stdlib\1=\2/g' gradle.properties + sed -i 's/observe\(.*\)=\(.*\)-SNAPSHOT/observe\1=\2/g' gradle.properties + sed -i 's/observe\(.*\)=\(.*\)-[0-9]\{8\}-[0-9]\{6\}-.*$/observe\1=\2/g' gradle.properties + git add gradle.properties + git commit -m "Move dependencies to stable version" || echo "No changes to commit" + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Publish artifact + env: + BALLERINA_CENTRAL_ACCESS_TOKEN: ${{ secrets.BALLERINA_CENTRAL_ACCESS_TOKEN }} + GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} + packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} + packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} + publishUser: ${{ secrets.BALLERINA_BOT_USERNAME }} + publishPAT: ${{ secrets.BALLERINA_BOT_TOKEN }} + nexusUser: ${{ secrets.NEXUS_USERNAME }} + nexusPassword: ${{ secrets.NEXUS_PASSWORD }} + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }} + run: | + ./gradlew clean release -Prelease.useAutomaticVersion=true + ./gradlew -Pversion=${VERSION} publish -x test -PpublishToCentral=true + - name: GitHub Release and Release Sync PR + env: + GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} + run: | + gh release create v$VERSION --title "module-ballerinax-persist.redis-v$VERSION" + gh pr create --title "[Automated] Sync main after $VERSION release" --body "Sync main after $VERSION release" diff --git a/.github/workflows/publish-snapshot-nexus.yml b/.github/workflows/publish-snapshot-nexus.yml new file mode 100644 index 0000000..b13aaea --- /dev/null +++ b/.github/workflows/publish-snapshot-nexus.yml @@ -0,0 +1,27 @@ +name: Publish Snapshot to Nexus + +on: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + if: github.repository_owner == 'ballerina-platform' + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 17.0.7 + - name: Build with Gradle + env: + packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} + packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} + nexusUser: ${{ secrets.NEXUS_USERNAME }} + nexusPassword: ${{ secrets.NEXUS_PASSWORD }} + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }} + run: | + ./gradlew build publishMavenJavaPublicationToWSO2NexusRepository --scan --no-daemon diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..23780cd --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,48 @@ +name: PR build + +on: + pull_request: + branches: + - main + - 2201.[0-9]+.x + +jobs: + ubuntu-build: + name: Build on Ubuntu + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 17.0.7 + - name: Build with Gradle + env: + packageUser: ${{ github.actor }} + packagePAT: ${{ secrets.GITHUB_TOKEN }} + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }} + run: ./gradlew build + - name: Generate Codecov Report + uses: codecov/codecov-action@v2 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + windows-build: + name: Build on Windows + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 17.0.7 + - name: Build with Gradle + env: + packageUser: ${{ github.actor }} + packagePAT: ${{ secrets.GITHUB_TOKEN }} + run: ./gradlew.bat build -x test + # Disabling tests because no docker in git-action windows diff --git a/.github/workflows/stale_check.yml b/.github/workflows/stale_check.yml new file mode 100644 index 0000000..8763360 --- /dev/null +++ b/.github/workflows/stale_check.yml @@ -0,0 +1,19 @@ +name: 'Close stale pull requests' + +on: + schedule: + - cron: '30 19 * * *' + workflow_dispatch: + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v3 + with: + stale-pr-message: 'This PR has been open for more than 15 days with no activity. This will be closed in 3 days unless the `stale` label is removed or commented.' + close-pr-message: 'Closed PR due to inactivity for more than 18 days.' + days-before-pr-stale: 15 + days-before-pr-close: 3 + days-before-issue-stale: -1 + days-before-issue-close: -1 diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml new file mode 100644 index 0000000..3e1f6fd --- /dev/null +++ b/.github/workflows/trivy-scan.yml @@ -0,0 +1,33 @@ +name: Trivy + +on: + workflow_dispatch: + schedule: + - cron: '30 20 * * *' + +jobs: + ubuntu-build: + name: Build on Ubuntu + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 17.0.7 + - name: Build with Gradle + env: + packageUser: ${{ github.actor }} + packagePAT: ${{ secrets.GITHUB_TOKEN }} + run: ./gradlew build -x check -x test + - name: Create lib directory if not exists + run: mkdir -p ballerina/lib + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + scan-type: 'rootfs' + scan-ref: '/github/workspace/ballerina/lib' + format: 'table' + timeout: '10m0s' + exit-code: '1' diff --git a/docs/spec/spec.md b/docs/spec/spec.md new file mode 100644 index 0000000..3d13494 --- /dev/null +++ b/docs/spec/spec.md @@ -0,0 +1 @@ +# Specification: Ballerina Persist Library \ No newline at end of file From 2c0ba0ff5e79c8e362c3e856fcc7bcc80038fa99 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Tue, 20 Feb 2024 21:41:42 +0530 Subject: [PATCH 55/65] un comment the release task in build.gradle --- build.gradle | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/build.gradle b/build.gradle index 746ceee..1c97a0d 100644 --- a/build.gradle +++ b/build.gradle @@ -80,19 +80,19 @@ subprojects { def moduleVersion = project.version.replace("-SNAPSHOT", "") -// release { -// failOnPublishNeeded = false -// failOnSnapshotDependencies = true - -// buildTasks = ['build'] -// versionPropertyFile = 'gradle.properties' -// tagTemplate = 'v$version' - -// git { -// requireBranch = "release-${moduleVersion}" -// pushToRemote = 'origin' -// } -// } +release { + failOnPublishNeeded = false + failOnSnapshotDependencies = true + + buildTasks = ['build'] + versionPropertyFile = 'gradle.properties' + tagTemplate = 'v$version' + + git { + requireBranch = "release-${moduleVersion}" + pushToRemote = 'origin' + } +} task build { dependsOn(":${packageName}-ballerina:build") From 3ed4a1b65f93edf863a8e29ca4a417032086b8c8 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 21 Feb 2024 07:33:25 +0530 Subject: [PATCH 56/65] changed end of line to lf for java files --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index 097f9f9..6584849 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,3 +7,5 @@ # These are Windows script files and should use crlf *.bat text eol=crlf +# These are Windows java files and should use lf +*.java text eol=lf From be8b87b6ef744db7887eda88df0884eeb09ff27b Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 21 Feb 2024 09:48:13 +0530 Subject: [PATCH 57/65] added dependencies in build.gradle --- build.gradle | 16 ++++++++++++++++ gradle.properties | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1c97a0d..d71dc62 100644 --- a/build.gradle +++ b/build.gradle @@ -71,10 +71,26 @@ subprojects { } dependencies { + ballerinaStdLibs "io.ballerina.stdlib:io-ballerina:${stdlibIoVersion}" ballerinaStdLibs "io.ballerina.stdlib:time-ballerina:${stdlibTimeVersion}" + ballerinaStdLibs "io.ballerina.stdlib:url-ballerina:${stdlibUrlVersion}" + ballerinaStdLibs "io.ballerina.stdlib:constraint-ballerina:${stdlibConstraintVersion}" + ballerinaStdLibs "io.ballerina.stdlib:task-ballerina:${stdlibTaskVersion}" + ballerinaStdLibs "io.ballerina.stdlib:crypto-ballerina:${stdlibCryptoVersion}" + ballerinaStdLibs "io.ballerina.stdlib:os-ballerina:${stdlibOsVersion}" + ballerinaStdLibs "io.ballerina.stdlib:log-ballerina:${stdlibLogVersion}" + ballerinaStdLibs "io.ballerina.stdlib:mime-ballerina:${stdlibMimeVersion}" + ballerinaStdLibs "io.ballerina.stdlib:file-ballerina:${stdlibFileVersion}" + ballerinaStdLibs "io.ballerina.stdlib:uuid-ballerina:${stdlibUuidVersion}" + ballerinaStdLibs "io.ballerina.stdlib:cache-ballerina:${stdlibCacheVersion}" + ballerinaStdLibs "io.ballerina.stdlib:oauth2-ballerina:${stdlibOAuth2Version}" + ballerinaStdLibs "io.ballerina.stdlib:auth-ballerina:${stdlibAuthVersion}" + ballerinaStdLibs "io.ballerina.stdlib:jwt-ballerina:${stdlibJwtVersion}" + ballerinaStdLibs "io.ballerina.stdlib:http-ballerina:${stdlibHttpVersion}" ballerinaStdLibs "io.ballerina.stdlib:persist-ballerina:${stdlibPersistVersion}" ballerinaStdLibs "io.ballerina.stdlib:observe-ballerina:${observeVersion}" ballerinaStdLibs "io.ballerina:observe-ballerina:${observeInternalVersion}" + ballerinaStdLibs "" } } diff --git a/gradle.properties b/gradle.properties index 97c2266..622c826 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,17 +14,49 @@ ballerinaGradlePluginVersion=2.0.1 ballerinaLangVersion=2201.8.2 # Direct Dependencies - # Level 01 +stdlibIoVersion=1.6.0 stdlibTimeVersion=2.4.0 +stdlibUrlVersion=2.4.0 + +# Level 02 +stdlibLogVersion=2.9.0 +stdlibOsVersion=1.8.0 + +# Level 03 +stdlibFileVersion=1.9.0 + +# Level 05 +stdlibHttpVersion=2.10.0 # Level 08 +stdlibRedisVersion=2.5.1 + +# Level 09 stdlibPersistVersion=1.2.0 # Ballerinax Observer observeVersion=1.2.0 observeInternalVersion=1.2.0 +# Transitive Dependencies +# Level 01 +stdlibConstraintVersion=1.4.0 + +#Level 02 +stdlibCryptoVersion=2.5.0 +stdlibTaskVersion=2.5.0 + +# Level 03 +stdlibCacheVersion=3.7.0 +stdlibMimeVersion=2.9.0 +stdlibUuidVersion=1.7.0 + +# Level 04 +stdlibAuthVersion=2.10.0 +stdlibJwtVersion=2.10.0 +stdlibOAuth2Version=2.10.0 + # Enabled publishing insecure checksums, due to fail to publish to maven central # Refer https://github.com/gradle/gradle/issues/11308 systemProp.org.gradle.internal.publish.checksums.insecure=true From b5af2d15f51e2769d3e9abada95325462f368a47 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 21 Feb 2024 09:55:43 +0530 Subject: [PATCH 58/65] added execution permission for gradle build in ubuntu --- .github/workflows/build-timestamped-master.yml | 2 ++ .github/workflows/central-publish.yml | 2 ++ .github/workflows/publish-release.yml | 2 ++ .github/workflows/publish-snapshot-nexus.yml | 2 ++ .github/workflows/pull-request.yml | 2 ++ .github/workflows/trivy-scan.yml | 2 ++ 6 files changed, 12 insertions(+) diff --git a/.github/workflows/build-timestamped-master.yml b/.github/workflows/build-timestamped-master.yml index d9dbd5b..c19f32e 100644 --- a/.github/workflows/build-timestamped-master.yml +++ b/.github/workflows/build-timestamped-master.yml @@ -32,6 +32,8 @@ jobs: updatedVersion=$VERSION-$startTime-$latestCommit echo $updatedVersion sed -i "s/version=\(.*\)/version=$updatedVersion/g" gradle.properties + - name: Give execute permission to gradlew + run: chmod +x ./gradlew - name: Build with Gradle env: packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} diff --git a/.github/workflows/central-publish.yml b/.github/workflows/central-publish.yml index 7825cb7..c8649d4 100644 --- a/.github/workflows/central-publish.yml +++ b/.github/workflows/central-publish.yml @@ -23,6 +23,8 @@ jobs: with: distribution: 'temurin' java-version: 17.0.7 + - name: Give execute permission to gradlew + run: chmod +x ./gradlew - name: Build with Gradle env: packageUser: ${{ github.actor }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 1681745..cc206bb 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -16,6 +16,8 @@ jobs: with: distribution: 'temurin' java-version: 17.0.7 + - name: Give execute permission to gradlew + run: chmod +x ./gradlew - name: Build with Gradle env: packageUser: ${{ github.actor }} diff --git a/.github/workflows/publish-snapshot-nexus.yml b/.github/workflows/publish-snapshot-nexus.yml index b13aaea..4672737 100644 --- a/.github/workflows/publish-snapshot-nexus.yml +++ b/.github/workflows/publish-snapshot-nexus.yml @@ -14,6 +14,8 @@ jobs: with: distribution: 'temurin' java-version: 17.0.7 + - name: Give execute permission to gradlew + run: chmod +x ./gradlew - name: Build with Gradle env: packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 23780cd..f750c12 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -17,6 +17,8 @@ jobs: with: distribution: 'temurin' java-version: 17.0.7 + - name: Give execute permission to gradlew + run: chmod +x ./gradlew - name: Build with Gradle env: packageUser: ${{ github.actor }} diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index 3e1f6fd..89e19bb 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -16,6 +16,8 @@ jobs: with: distribution: 'temurin' java-version: 17.0.7 + - name: Give execute permission to gradlew + run: chmod +x ./gradlew - name: Build with Gradle env: packageUser: ${{ github.actor }} From 230a521e7b6c69836220f7db96d32cbc6866ac38 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 21 Feb 2024 10:00:19 +0530 Subject: [PATCH 59/65] removed an unnecessary empty dependency --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index d71dc62..eb8ee25 100644 --- a/build.gradle +++ b/build.gradle @@ -90,7 +90,6 @@ subprojects { ballerinaStdLibs "io.ballerina.stdlib:persist-ballerina:${stdlibPersistVersion}" ballerinaStdLibs "io.ballerina.stdlib:observe-ballerina:${observeVersion}" ballerinaStdLibs "io.ballerina:observe-ballerina:${observeInternalVersion}" - ballerinaStdLibs "" } } From 4e69c076e419d0f39ebba88d9e66f5c9d5732a66 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 21 Feb 2024 10:30:51 +0530 Subject: [PATCH 60/65] pull redis dependency on gradle build --- ballerina/build.gradle | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 72e7c21..829dc36 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -231,6 +231,25 @@ task stopRedisTestDockerContainer() { } } +task pullRedisDependency(type: Exec) { + ignoreExitValue(true) + if (!Os.isFamily(Os.FAMILY_WINDOWS)) { + try { + String distributionBinPath = project.projectDir.absolutePath + "/build/jballerina-tools-${project.extensions.ballerina.langVersion}/bin" + commandLine 'sh', '-c', "$distributionBinPath/bal pull ballerinax/redis" + } catch (all) { + return 1 + } + } else { + try { + String distributionBinPath = project.projectDir.absolutePath + "/build/jballerina-tools-${project.extensions.ballerina.langVersion}/bin" + commandLine 'cmd', '/c', "$distributionBinPath/bal.bat pull ballerinax/redis" + } catch (all) { + return 1 + } + } +} + updateTomlFiles.dependsOn copyStdlibs startRedisTestDockerContainer.dependsOn createRedisTestDockerImage From 0b33d64221f3f4a560993d0801884f33bce8c2ed Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 21 Feb 2024 10:41:50 +0530 Subject: [PATCH 61/65] make build depend on pulling redis --- ballerina/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 829dc36..01eaab4 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -251,10 +251,12 @@ task pullRedisDependency(type: Exec) { } updateTomlFiles.dependsOn copyStdlibs +pullRedisDependency.dependsOn unpackJballerinaTools startRedisTestDockerContainer.dependsOn createRedisTestDockerImage build.dependsOn "generatePomFileForMavenPublication" build.dependsOn ":${packageName}-native:build" +build.dependsOn pullRedisDependency build.finalizedBy stopRedisTestDockerContainer test.dependsOn ":${packageName}-native:build" From ca0722c1c562a8a4b8434f67260b6db37596a001 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 21 Feb 2024 11:10:58 +0530 Subject: [PATCH 62/65] pull ballerinax/redis v.2.5.1 on build --- ballerina/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 01eaab4..bbfa14d 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -236,14 +236,14 @@ task pullRedisDependency(type: Exec) { if (!Os.isFamily(Os.FAMILY_WINDOWS)) { try { String distributionBinPath = project.projectDir.absolutePath + "/build/jballerina-tools-${project.extensions.ballerina.langVersion}/bin" - commandLine 'sh', '-c', "$distributionBinPath/bal pull ballerinax/redis" + commandLine 'sh', '-c', "$distributionBinPath/bal pull ballerinax/redis:2.5.1" } catch (all) { return 1 } } else { try { String distributionBinPath = project.projectDir.absolutePath + "/build/jballerina-tools-${project.extensions.ballerina.langVersion}/bin" - commandLine 'cmd', '/c', "$distributionBinPath/bal.bat pull ballerinax/redis" + commandLine 'cmd', '/c', "$distributionBinPath/bal.bat pull ballerinax/redis:2.5.1" } catch (all) { return 1 } From 74cdbd8012fe976f1f1bb133cfbbe4f59e32b776 Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 21 Feb 2024 11:53:56 +0530 Subject: [PATCH 63/65] added execution permission for graalVM --- .github/workflows/build-with-bal-test-graalvm.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-with-bal-test-graalvm.yml b/.github/workflows/build-with-bal-test-graalvm.yml index 41951cb..1342d63 100644 --- a/.github/workflows/build-with-bal-test-graalvm.yml +++ b/.github/workflows/build-with-bal-test-graalvm.yml @@ -36,3 +36,6 @@ jobs: lang_version: ${{ inputs.lang_version }} native_image_options: '-J-Xmx7G ${{ inputs.native_image_options }}' additional_windows_build_flags: '-x test' + steps: + - name: Give execute permission to gradlew + run: chmod +x ./gradlew From 42d97ff926f41461fe0a304c79e4c3cec0e3f20f Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 21 Feb 2024 12:07:32 +0530 Subject: [PATCH 64/65] added read permission to config.toml --- .github/workflows/pull-request.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index f750c12..bdafd9b 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -19,6 +19,8 @@ jobs: java-version: 17.0.7 - name: Give execute permission to gradlew run: chmod +x ./gradlew + - name: Give read and execute permission to config + run: chmod +rx ./ballerina/tests/Config.toml - name: Build with Gradle env: packageUser: ${{ github.actor }} From 8d36248980a48b18268701f15dba4f9af2950b5d Mon Sep 17 00:00:00 2001 From: Dinuka Amarasinghe Date: Wed, 21 Feb 2024 12:47:43 +0530 Subject: [PATCH 65/65] removed Config.toml file from gitignore --- .github/workflows/pull-request.yml | 2 -- ballerina/.gitignore | 1 - ballerina/tests/Config.toml | 4 ++++ 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 ballerina/tests/Config.toml diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index bdafd9b..f750c12 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -19,8 +19,6 @@ jobs: java-version: 17.0.7 - name: Give execute permission to gradlew run: chmod +x ./gradlew - - name: Give read and execute permission to config - run: chmod +rx ./ballerina/tests/Config.toml - name: Build with Gradle env: packageUser: ${{ github.actor }} diff --git a/ballerina/.gitignore b/ballerina/.gitignore index 39c9ccc..6a03f81 100644 --- a/ballerina/.gitignore +++ b/ballerina/.gitignore @@ -1,4 +1,3 @@ target generated -Config.toml .devcontainer.json diff --git a/ballerina/tests/Config.toml b/ballerina/tests/Config.toml new file mode 100644 index 0000000..7715977 --- /dev/null +++ b/ballerina/tests/Config.toml @@ -0,0 +1,4 @@ +[redis] +host="localhost" +port=6379 +password="" \ No newline at end of file