1 Commits

Author SHA1 Message Date
Nikita Tokarchuk ebe1934792 v2 2020-03-04 22:50:42 +01:00
54 changed files with 923 additions and 1933 deletions
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright © 2020 Nikita Tokarchuk
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+9 -7
View File
@@ -1,11 +1,13 @@
module github.com/mainnika/mongox-go-driver/v2 module github.com/mainnika/mongox-go-driver/v2
go 1.13
require ( require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/google/go-cmp v0.3.0 // indirect
github.com/modern-go/reflect2 v1.0.1 github.com/klauspost/compress v1.10.1 // indirect
github.com/stretchr/testify v1.6.1 github.com/pkg/errors v0.9.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 github.com/xdg/stringprep v1.0.0 // indirect
go.mongodb.org/mongo-driver v1.4.3 go.mongodb.org/mongo-driver v1.3.0
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d // indirect
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
) )
go 1.13
+20 -35
View File
@@ -1,10 +1,7 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk=
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
@@ -33,34 +30,29 @@ github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/V
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M=
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.10.1 h1:a/QY0o9S6wCi0XhxaMX/QmusicNUqCqFugR6WKPOSoQ=
github.com/klauspost/compress v1.10.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -77,30 +69,28 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo=
github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
go.mongodb.org/mongo-driver v1.4.3 h1:moga+uhicpVshTyaqY9L23E6QqwcHRUv1sqyOsoyOO8= github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0=
go.mongodb.org/mongo-driver v1.4.3/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
go.mongodb.org/mongo-driver v1.3.0 h1:ew6uUIeJOo+qdUUv7LxFCUhtWmVv7ZV/Xuy4FAUsw2E=
go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -112,20 +102,15 @@ golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+10 -25
View File
@@ -1,33 +1,28 @@
package base package base
import ( import (
"fmt"
"github.com/modern-go/reflect2"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
"github.com/mainnika/mongox-go-driver/v2/mongox" "github.com/mainnika/mongox-go-driver/v2/mongox"
"github.com/mainnika/mongox-go-driver/v2/mongox/errors"
) )
// GetID returns source document id // GetID returns source document id
func GetID(source interface{}) (id interface{}) { func GetID(source interface{}) (id interface{}) {
switch doc := source.(type) { switch doc := source.(type) {
case mongox.OIDBased: case mongox.BaseObjectID:
return getObjectIDOrGenerate(doc) return getObjectIDOrGenerate(doc)
case mongox.StringBased: case mongox.BaseString:
return getStringIDOrPanic(doc) return getStringIDOrPanic(doc)
case mongox.JSONBased: case mongox.BaseObject:
return getObjectOrPanic(doc) return getObjectOrPanic(doc)
case mongox.InterfaceBased:
return getInterfaceOrPanic(doc)
default: default:
panic(fmt.Errorf("source contains malformed document, %v", source)) panic(errors.Malformedf("source contains malformed document, %v", source))
} }
} }
func getObjectIDOrGenerate(source mongox.OIDBased) (id primitive.ObjectID) { func getObjectIDOrGenerate(source mongox.BaseObjectID) (id primitive.ObjectID) {
id = source.GetID() id = source.GetID()
if id != primitive.NilObjectID { if id != primitive.NilObjectID {
@@ -40,32 +35,22 @@ func getObjectIDOrGenerate(source mongox.OIDBased) (id primitive.ObjectID) {
return return
} }
func getStringIDOrPanic(source mongox.StringBased) (id string) { func getStringIDOrPanic(source mongox.BaseString) (id string) {
id = source.GetID() id = source.GetID()
if id != "" { if id != "" {
return id return id
} }
panic(fmt.Errorf("source contains malformed document, %v", source)) panic(errors.Malformedf("victim contains malformed document, %v", source))
} }
func getObjectOrPanic(source mongox.JSONBased) (id primitive.D) { func getObjectOrPanic(source mongox.BaseObject) (id primitive.D) {
id = source.GetID() id = source.GetID()
if id != nil { if id != nil {
return id return id
} }
panic(fmt.Errorf("source contains malformed document, %v", source)) panic(errors.Malformedf("victim contains malformed document, %v", source))
}
func getInterfaceOrPanic(source mongox.InterfaceBased) (id interface{}) {
id = source.GetID()
if !reflect2.IsNil(id) {
return id
}
panic(fmt.Errorf("source contains malformed document, %v", source))
} }
-42
View File
@@ -1,42 +0,0 @@
package base
import (
"testing"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson/primitive"
"github.com/mainnika/mongox-go-driver/v2/mongox/base/jsonbased"
"github.com/mainnika/mongox-go-driver/v2/mongox/base/oidbased"
"github.com/mainnika/mongox-go-driver/v2/mongox/base/stringbased"
)
type DocWithCustomInterface struct {
ID int `bson:"_id" json:"_id" collection:"4"`
}
func (d *DocWithCustomInterface) GetID() interface{} {
return d.ID
}
func (d *DocWithCustomInterface) SetID(interface{}) {
panic("not implemented")
}
func TestGetID(t *testing.T) {
type DocWithObjectID struct {
oidbased.Primary `bson:",inline" json:",inline" collection:"1"`
}
type DocWithObject struct {
jsonbased.Primary `bson:",inline" json:",inline" collection:"2"`
}
type DocWithString struct {
stringbased.Primary `bson:",inline" json:",inline" collection:"3"`
}
assert.Equal(t, primitive.ObjectID([12]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2}), GetID(&DocWithObjectID{oidbased.Primary{[12]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2}}}))
assert.Equal(t, primitive.D{{"1", "2"}}, GetID(&DocWithObject{jsonbased.Primary{primitive.D{{"1", "2"}}}}))
assert.Equal(t, "foobar", GetID(&DocWithString{stringbased.Primary{"foobar"}}))
assert.Equal(t, 420, GetID(&DocWithCustomInterface{ID: 420}))
}
+7 -14
View File
@@ -2,16 +2,14 @@ package base
import ( import (
"reflect" "reflect"
"github.com/mainnika/mongox-go-driver/v2/mongox/base/protection"
) )
// GetProtection function finds protection field in the source document otherwise returns nil // GetProtection function finds protection field in the source document otherwise returns nil
func GetProtection(source interface{}) (key *protection.Key) { func GetProtection(source interface{}) *Protection {
v := reflect.ValueOf(source) v := reflect.ValueOf(source)
if v.Kind() != reflect.Ptr || v.IsNil() { if v.Kind() != reflect.Ptr || v.IsNil() {
return return nil
} }
el := v.Elem() el := v.Elem()
@@ -19,22 +17,17 @@ func GetProtection(source interface{}) (key *protection.Key) {
for i := 0; i < numField; i++ { for i := 0; i < numField; i++ {
field := el.Field(i) field := el.Field(i)
if !field.CanInterface() {
continue
}
switch field.Interface().(type) { switch field.Interface().(type) {
case *protection.Key: case *Protection:
key = field.Interface().(*protection.Key) return field.Interface().(*Protection)
case protection.Key: case Protection:
ptr := field.Addr() ptr := field.Addr()
key = ptr.Interface().(*protection.Key) return ptr.Interface().(*Protection)
default: default:
continue continue
} }
return
} }
return return nil
} }
-24
View File
@@ -1,24 +0,0 @@
package jsonbased
import (
"go.mongodb.org/mongo-driver/bson/primitive"
"github.com/mainnika/mongox-go-driver/v2/mongox"
)
var _ mongox.JSONBased = (*Primary)(nil)
// Primary is a structure with object as an _id field
type Primary struct {
ID primitive.D `bson:"_id" json:"_id"`
}
// GetID returns an _id
func (p *Primary) GetID() (id primitive.D) {
return p.ID
}
// SetID sets an _id
func (p *Primary) SetID(id primitive.D) {
p.ID = id
}
-80
View File
@@ -1,80 +0,0 @@
package jsonbased
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson/primitive"
"github.com/mainnika/mongox-go-driver/v2/mongox-testing/database"
)
func Test_GetID(t *testing.T) {
type DocWithObject struct {
Primary `bson:",inline" json:",inline" collection:"1"`
}
doc := &DocWithObject{Primary{primitive.D{{"1", "one"}, {"2", "two"}}}}
assert.Equal(t, primitive.D{{"1", "one"}, {"2", "two"}}, doc.GetID())
}
func Test_SetID(t *testing.T) {
type DocWithObject struct {
Primary `bson:",inline" json:",inline" collection:"1"`
}
doc := &DocWithObject{Primary{primitive.D{{"1", "one"}, {"2", "two"}}}}
doc.SetID(primitive.D{{"3", "three"}, {"4", "you"}})
assert.Equal(t, primitive.D{{"3", "three"}, {"4", "you"}}, doc.Primary.ID)
assert.Equal(t, primitive.D{{"3", "three"}, {"4", "you"}}, doc.GetID())
}
func Test_SaveLoad(t *testing.T) {
type DocWithObjectID struct {
Primary `bson:",inline" json:",inline" collection:"1"`
}
db, err := database.NewEphemeral("mongodb://localhost")
if err != nil {
t.Fatal(err)
}
defer db.Close()
doc1 := &DocWithObjectID{Primary{primitive.D{{"1", "one"}, {"2", "two"}}}}
doc2 := &DocWithObjectID{}
err = db.SaveOne(doc1)
assert.NoError(t, err)
err = db.LoadOne(doc2)
assert.NoError(t, err)
assert.Equal(t, doc1, doc2)
bytes1, _ := json.Marshal(doc1)
bytes2, _ := json.Marshal(doc2)
assert.Equal(t, bytes1, bytes2)
}
func Test_Marshal(t *testing.T) {
type DocWithObjectID struct {
Primary `bson:",inline" json:",inline" collection:"1"`
}
id := primitive.D{{"1", "one"}, {"2", "two"}}
doc := &DocWithObjectID{Primary{id}}
bytes, err := json.Marshal(doc)
assert.NoError(t, err)
assert.Equal(t, `{"_id":[{"Key":"1","Value":"one"},{"Key":"2","Value":"two"}]}`, string(bytes))
}
+24
View File
@@ -0,0 +1,24 @@
package base
import (
"go.mongodb.org/mongo-driver/bson/primitive"
"github.com/mainnika/mongox-go-driver/v2/mongox"
)
var _ mongox.BaseObject = &Object{}
// Object is a structure with object as an _id field
type Object struct {
ID primitive.D `bson:"_id,omitempty" json:"_id,omitempty"`
}
// GetID returns an _id
func (db *Object) GetID() primitive.D {
return db.ID
}
// SetID sets an _id
func (db *Object) SetID(id primitive.D) {
db.ID = id
}
+24
View File
@@ -0,0 +1,24 @@
package base
import (
"go.mongodb.org/mongo-driver/bson/primitive"
"github.com/mainnika/mongox-go-driver/v2/mongox"
)
var _ mongox.BaseObjectID = &ObjectID{}
// ObjectID is a structure with objectId as an _id field
type ObjectID struct {
ID primitive.ObjectID `bson:"_id,omitempty" json:"_id,omitempty"`
}
// GetID returns an _id
func (db *ObjectID) GetID() primitive.ObjectID {
return db.ID
}
// SetID sets an _id
func (db *ObjectID) SetID(id primitive.ObjectID) {
db.ID = id
}
-24
View File
@@ -1,24 +0,0 @@
package oidbased
import (
"go.mongodb.org/mongo-driver/bson/primitive"
"github.com/mainnika/mongox-go-driver/v2/mongox"
)
var _ mongox.OIDBased = (*Primary)(nil)
// Primary is a structure with objectId as the primary key
type Primary struct {
ID primitive.ObjectID `bson:"_id" json:"_id"`
}
// GetID returns an _id
func (p *Primary) GetID() (id primitive.ObjectID) {
return p.ID
}
// SetID sets an _id
func (p *Primary) SetID(id primitive.ObjectID) {
p.ID = id
}
-80
View File
@@ -1,80 +0,0 @@
package oidbased
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson/primitive"
"github.com/mainnika/mongox-go-driver/v2/mongox-testing/database"
)
func Test_GetID(t *testing.T) {
type DocWithObjectID struct {
Primary `bson:",inline" json:",inline" collection:"1"`
}
doc := &DocWithObjectID{Primary{[12]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2}}}
assert.Equal(t, primitive.ObjectID([12]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2}), doc.GetID())
}
func Test_SetID(t *testing.T) {
type DocWithObjectID struct {
Primary `bson:",inline" json:",inline" collection:"1"`
}
doc := &DocWithObjectID{}
doc.SetID([12]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2})
assert.Equal(t, primitive.ObjectID([12]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2}), doc.Primary.ID)
assert.Equal(t, primitive.ObjectID([12]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2}), doc.GetID())
}
func Test_SaveLoad(t *testing.T) {
type DocWithObjectID struct {
Primary `bson:",inline" json:",inline" collection:"1"`
}
db, err := database.NewEphemeral("mongodb://localhost")
if err != nil {
t.Fatal(err)
}
defer db.Close()
doc1 := &DocWithObjectID{}
doc2 := &DocWithObjectID{}
err = db.SaveOne(doc1)
assert.NoError(t, err)
err = db.LoadOne(doc2)
assert.NoError(t, err)
assert.Equal(t, doc1, doc2)
bytes1, _ := json.Marshal(doc1)
bytes2, _ := json.Marshal(doc2)
assert.Equal(t, bytes1, bytes2)
}
func Test_Marshal(t *testing.T) {
type DocWithObjectID struct {
Primary `bson:",inline" json:",inline" collection:"1"`
}
id, _ := primitive.ObjectIDFromHex("feadbeeffeadbeeffeadbeef")
doc := &DocWithObjectID{Primary{id}}
bytes, err := json.Marshal(doc)
assert.NoError(t, err)
assert.Equal(t, `{"_id":"feadbeeffeadbeeffeadbeef"}`, string(bytes))
}
+11
View File
@@ -0,0 +1,11 @@
package base
import (
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Protection field stores unique document id and version
type Protection struct {
X primitive.ObjectID `bson:"_x" json:"_x" index:",hashed"`
V int64 `bson:"_v" json:"_v"`
}
-36
View File
@@ -1,36 +0,0 @@
package protection
import (
"time"
"github.com/modern-go/reflect2"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Key field stores unique document id and version
type Key struct {
X primitive.ObjectID `bson:"_x" json:"_x" index:",hashed"`
V int64 `bson:"_v" json:"_v"`
}
// PutToDocument extends the doc with protection key values
func (k *Key) PutToDocument(doc primitive.M) {
if reflect2.IsNil(doc) {
return
}
if k.X.IsZero() {
doc["_x"] = primitive.M{"$exists": false}
doc["_v"] = primitive.M{"$exists": false}
} else {
doc["_x"] = k.X
doc["_v"] = k.V
}
}
// Restate creates a new protection key
func (k *Key) Restate() {
k.X = primitive.NewObjectID()
k.V = time.Now().Unix()
}
+6 -11
View File
@@ -1,32 +1,27 @@
package base package base
import ( import (
"fmt"
"reflect" "reflect"
"github.com/mainnika/mongox-go-driver/v2/mongox"
) )
// Reset function creates new zero object for the target pointer // Reset function creates new zero object for the target pointer
func Reset(target interface{}) (created bool) { func Reset(target interface{}) {
type resetter interface { resettable, canReset := target.(mongox.Resetter)
Reset()
}
resettable, canReset := target.(resetter)
if canReset { if canReset {
resettable.Reset() resettable.Reset()
return false return
} }
v := reflect.ValueOf(target) v := reflect.ValueOf(target)
if v.Kind() != reflect.Ptr { if v.Kind() != reflect.Ptr {
panic(fmt.Errorf("reset target should be a pointer")) panic("reset target should be a pointer")
} }
t := v.Elem().Type() t := v.Elem().Type()
zero := reflect.Zero(t) zero := reflect.Zero(t)
v.Elem().Set(zero) v.Elem().Set(zero)
return true
} }
+22
View File
@@ -0,0 +1,22 @@
package base
import (
"github.com/mainnika/mongox-go-driver/v2/mongox"
)
var _ mongox.BaseString = &String{}
// String is a structure with string as an _id field
type String struct {
ID string `bson:"_id,omitempty" json:"_id,omitempty"`
}
// GetID returns an _id
func (db *String) GetID() string {
return db.ID
}
// SetID sets an _id
func (db *String) SetID(id string) {
db.ID = id
}
-22
View File
@@ -1,22 +0,0 @@
package stringbased
import (
"github.com/mainnika/mongox-go-driver/v2/mongox"
)
var _ mongox.StringBased = (*Primary)(nil)
// Primary is a structure with string as an _id field
type Primary struct {
ID string `bson:"_id" json:"_id"`
}
// GetID returns an _id
func (p *Primary) GetID() (id string) {
return p.ID
}
// SetID sets an _id
func (p *Primary) SetID(id string) {
p.ID = id
}
-78
View File
@@ -1,78 +0,0 @@
package stringbased
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/mainnika/mongox-go-driver/v2/mongox-testing/database"
)
func Test_GetID(t *testing.T) {
type DocWithString struct {
Primary `bson:",inline" json:",inline" collection:"1"`
}
doc := &DocWithString{Primary{"foobar"}}
assert.Equal(t, "foobar", doc.GetID())
}
func Test_SetID(t *testing.T) {
type DocWithString struct {
Primary `bson:",inline" json:",inline" collection:"1"`
}
doc := &DocWithString{Primary{"foobar"}}
doc.SetID("rockrockrock")
assert.Equal(t, "rockrockrock", doc.Primary.ID)
assert.Equal(t, "rockrockrock", doc.GetID())
}
func Test_SaveLoad(t *testing.T) {
type DocWithObjectID struct {
Primary `bson:",inline" json:",inline" collection:"1"`
}
db, err := database.NewEphemeral("mongodb://localhost")
if err != nil {
t.Fatal(err)
}
defer db.Close()
doc1 := &DocWithObjectID{Primary{"foobar"}}
doc2 := &DocWithObjectID{}
err = db.SaveOne(doc1)
assert.NoError(t, err)
err = db.LoadOne(doc2)
assert.NoError(t, err)
assert.Equal(t, doc1, doc2)
bytes1, _ := json.Marshal(doc1)
bytes2, _ := json.Marshal(doc2)
assert.Equal(t, bytes1, bytes2)
}
func Test_Marshal(t *testing.T) {
type DocWithObjectID struct {
Primary `bson:",inline" json:",inline" collection:"1"`
}
doc := &DocWithObjectID{Primary{"foobar"}}
bytes, err := json.Marshal(doc)
assert.NoError(t, err)
assert.Equal(t, `{"_id":"foobar"}`, string(bytes))
}
+162
View File
@@ -0,0 +1,162 @@
package common
import (
"reflect"
"strconv"
"strings"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/mainnika/mongox-go-driver/v2/mongox"
"github.com/mainnika/mongox-go-driver/v2/mongox/errors"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
func createSimpleLoad(db mongox.Database, target interface{}, composed *query.Query) (cursor *mongo.Cursor, err error) {
collection := db.GetCollectionOf(target)
opts := options.Find()
opts.Sort = composed.Sorter()
opts.Limit = composed.Limiter()
opts.Skip = composed.Skipper()
return collection.Find(db.Context(), composed.M(), opts)
}
func createAggregateLoad(db mongox.Database, target interface{}, composed *query.Query) (cursor *mongo.Cursor, err error) {
collection := db.GetCollectionOf(target)
opts := options.Aggregate()
pipeline := primitive.A{}
if !composed.Empty() {
pipeline = append(pipeline, primitive.M{"$match": primitive.M{"$expr": composed.M()}})
}
if composed.Sorter() != nil {
pipeline = append(pipeline, primitive.M{"$sort": composed.Sorter()})
}
if composed.Skipper() != nil {
pipeline = append(pipeline, primitive.M{"$skip": *composed.Skipper()})
}
if composed.Limiter() != nil {
pipeline = append(pipeline, primitive.M{"$limit": *composed.Limiter()})
}
el := reflect.ValueOf(target).Elem()
elType := el.Type()
numField := elType.NumField()
_, preloads := composed.Preloader()
for i := 0; i < numField; i++ {
field := elType.Field(i)
tag := field.Tag
preloadTag, ok := tag.Lookup("preload")
if !ok {
continue
}
jsonTag, ok := tag.Lookup("json")
if jsonTag == "-" {
return nil, errors.Malformedf("preload private field is impossible")
}
jsonData := strings.SplitN(jsonTag, ",", 2)
jsonName := field.Name
if len(jsonData) > 0 {
jsonName = strings.TrimSpace(jsonData[0])
}
preloadData := strings.Split(preloadTag, ",")
if len(preloadData) == 0 {
continue
}
if len(preloadData) == 1 {
panic("there is no foreign field")
}
localField := strings.TrimSpace(preloadData[0])
if len(localField) == 0 {
localField = "_id"
}
foreignField := strings.TrimSpace(preloadData[1])
if len(foreignField) == 0 {
panic("there is no foreign field")
}
preloadLimiter := 100
preloadReversed := false
if len(preloadData) > 2 {
stringLimit := strings.TrimSpace(preloadData[2])
intLimit := preloadLimiter
preloadReversed = strings.HasPrefix(stringLimit, "-")
if preloadReversed {
stringLimit = stringLimit[1:]
}
intLimit, err = strconv.Atoi(stringLimit)
if err == nil {
preloadLimiter = intLimit
}
}
for _, preload := range preloads {
if preload != jsonName {
continue
}
isSlice := el.Field(i).Kind() == reflect.Slice
typ := el.Field(i).Type()
if typ.Kind() == reflect.Slice {
typ = typ.Elem()
}
if typ.Kind() != reflect.Ptr {
panic("preload field should have ptr type")
}
lookupCollection := db.GetCollectionOf(reflect.Zero(typ).Interface())
lookupVars := primitive.M{"selector": "$" + localField}
lookupPipeline := primitive.A{
primitive.M{"$match": primitive.M{"$expr": primitive.M{"$eq": primitive.A{"$" + foreignField, "$$selector"}}}},
}
if preloadReversed {
lookupPipeline = append(lookupPipeline, primitive.M{"$sort": primitive.M{"_id": -1}})
}
if isSlice && preloadLimiter > 0 {
lookupPipeline = append(lookupPipeline, primitive.M{"$limit": preloadLimiter})
} else if !isSlice {
lookupPipeline = append(lookupPipeline, primitive.M{"$limit": 1})
}
pipeline = append(pipeline, primitive.M{
"$lookup": primitive.M{
"from": lookupCollection.Name(),
"let": lookupVars,
"pipeline": lookupPipeline,
"as": jsonName,
},
})
if isSlice {
continue
}
pipeline = append(pipeline, primitive.M{
"$unwind": primitive.M{
"preserveNullAndEmptyArrays": true,
"path": "$" + jsonName,
},
})
}
}
return collection.Aggregate(db.Context(), pipeline, opts)
}
+32
View File
@@ -0,0 +1,32 @@
package common
import (
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/mainnika/mongox-go-driver/v2/mongox"
"github.com/mainnika/mongox-go-driver/v2/mongox/errors"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
// Count function counts documents in the database by query
// target is used only to get collection by tag so it'd be better to use nil ptr here
func Count(db mongox.Database, target interface{}, filters ...interface{}) (int64, error) {
collection := db.GetCollectionOf(target)
opts := options.Count()
composed := query.Compose(filters...)
opts.Limit = composed.Limiter()
opts.Skip = composed.Skipper()
result, err := collection.CountDocuments(db.Context(), composed.M(), opts)
if err == mongo.ErrNoDocuments {
return 0, errors.NotFoundErrorf("%s", err)
}
if err != nil {
return 0, errors.InternalErrorf("can't decode desult: %s", err)
}
return result, nil
}
+60
View File
@@ -0,0 +1,60 @@
package common
import (
"reflect"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/mainnika/mongox-go-driver/v2/mongox"
"github.com/mainnika/mongox-go-driver/v2/mongox/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/errors"
)
// DeleteArray removes documents list from a database by their ids
func DeleteArray(db mongox.Database, target interface{}) error {
targetV := reflect.ValueOf(target)
targetT := targetV.Type()
targetK := targetV.Kind()
if targetK != reflect.Ptr {
panic(errors.Malformedf("target is not a ptr"))
}
targetSliceV := targetV.Elem()
targetSliceT := targetT.Elem()
if targetSliceT.Kind() != reflect.Slice {
panic(errors.Malformedf("target should be a ptr to a slice"))
}
targetSliceElemT := targetSliceT.Elem()
if targetSliceElemT.Kind() != reflect.Ptr {
panic(errors.Malformedf("target slice should contain ptrs"))
}
zeroElem := reflect.Zero(targetSliceElemT)
targetLen := targetSliceV.Len()
collection := db.GetCollectionOf(zeroElem.Interface())
opts := options.Delete()
ids := primitive.A{}
for i := 0; i < targetLen; i++ {
elem := targetSliceV.Index(i)
ids = append(ids, base.GetID(elem.Interface()))
}
if len(ids) == 0 {
return errors.Malformedf("can't delete zero elements")
}
result, err := collection.DeleteMany(db.Context(), primitive.M{"_id": primitive.M{"$in": ids}}, opts)
if err != nil {
return errors.NotFoundErrorf("can't create find and delete result: %s", err)
}
if result.DeletedCount != int64(targetLen) {
return errors.InternalErrorf("can't verify delete result: removed count mismatch %d != %d", result.DeletedCount, targetLen)
}
return nil
}
+50
View File
@@ -0,0 +1,50 @@
package common
import (
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/mainnika/mongox-go-driver/v2/mongox"
"github.com/mainnika/mongox-go-driver/v2/mongox/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/errors"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
// DeleteOne removes a document from a database and then returns it into target
func DeleteOne(db mongox.Database, target interface{}, filters ...interface{}) error {
collection := db.GetCollectionOf(target)
opts := &options.FindOneAndDeleteOptions{}
composed := query.Compose(filters...)
protected := base.GetProtection(target)
opts.Sort = composed.Sorter()
if target != nil {
composed.And(primitive.M{"_id": base.GetID(target)})
}
if protected != nil {
query.Push(composed, protected)
protected.X = primitive.NewObjectID()
protected.V = time.Now().Unix()
}
result := collection.FindOneAndDelete(db.Context(), composed.M(), opts)
if result.Err() != nil {
return errors.InternalErrorf("can't create find one and delete result: %s", result.Err())
}
err := result.Decode(target)
if err == mongo.ErrNoDocuments {
return errors.NotFoundErrorf("%s", err)
}
if err != nil {
return errors.InternalErrorf("can't decode result: %s", err)
}
return nil
}
+78
View File
@@ -0,0 +1,78 @@
package common
import (
"reflect"
"go.mongodb.org/mongo-driver/mongo"
"github.com/mainnika/mongox-go-driver/v2/mongox"
"github.com/mainnika/mongox-go-driver/v2/mongox/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/errors"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
// LoadArray loads an array of documents from the database by query
func LoadArray(db mongox.Database, target interface{}, filters ...interface{}) error {
targetV := reflect.ValueOf(target)
targetT := targetV.Type()
targetK := targetV.Kind()
if targetK != reflect.Ptr {
panic(errors.InternalErrorf("target is not a ptr"))
}
targetSliceV := targetV.Elem()
targetSliceT := targetT.Elem()
if targetSliceT.Kind() != reflect.Slice {
panic(errors.InternalErrorf("target should be a ptr to a slice"))
}
targetSliceElemT := targetSliceT.Elem()
if targetSliceElemT.Kind() != reflect.Ptr {
panic(errors.InternalErrorf("target slice should contain ptrs"))
}
composed := query.Compose(filters...)
zeroElem := reflect.Zero(targetSliceElemT)
hasPreloader, _ := composed.Preloader()
var result *mongo.Cursor
var err error
if hasPreloader {
result, err = createAggregateLoad(db, zeroElem.Interface(), composed)
} else {
result, err = createSimpleLoad(db, zeroElem.Interface(), composed)
}
if err != nil {
return errors.InternalErrorf("can't create find result: %s", err)
}
defer result.Close(db.Context())
var i int
for i = 0; result.Next(db.Context()); {
if targetSliceV.Len() == i {
elem := reflect.New(targetSliceElemT.Elem())
if err = result.Decode(elem.Interface()); err == nil {
targetSliceV = reflect.Append(targetSliceV, elem)
} else {
continue
}
} else {
elem := targetSliceV.Index(i).Interface()
base.Reset(elem)
if err = result.Decode(elem); err != nil {
continue
}
}
i++
}
targetSliceV = targetSliceV.Slice(0, i)
targetV.Elem().Set(targetSliceV)
return nil
}
+38
View File
@@ -0,0 +1,38 @@
package common
import (
"go.mongodb.org/mongo-driver/mongo"
"github.com/mainnika/mongox-go-driver/v2/mongox"
"github.com/mainnika/mongox-go-driver/v2/mongox/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/errors"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
// LoadOne function loads a first single target document by a query
func LoadOne(db mongox.Database, target interface{}, filters ...interface{}) error {
composed := query.Compose(append(filters, query.Limit(1))...)
hasPreloader, _ := composed.Preloader()
var result *mongo.Cursor
var err error
if hasPreloader {
result, err = createAggregateLoad(db, target, composed)
} else {
result, err = createSimpleLoad(db, target, composed)
}
if err != nil {
return errors.InternalErrorf("can't create find result: %s", err)
}
hasNext := result.Next(db.Context())
if !hasNext {
return errors.NotFoundErrorf("can't find result: %s", result.Err())
}
base.Reset(target)
return result.Decode(target)
}
+78
View File
@@ -0,0 +1,78 @@
package common
import (
"context"
"go.mongodb.org/mongo-driver/mongo"
"github.com/mainnika/mongox-go-driver/v2/mongox"
"github.com/mainnika/mongox-go-driver/v2/mongox/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/errors"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
// StreamLoader is a controller for a database cursor
type StreamLoader struct {
*mongo.Cursor
ctx context.Context
target interface{}
}
// DecodeNext loads next documents to a target or returns an error
func (l *StreamLoader) DecodeNext() error {
hasNext := l.Cursor.Next(l.ctx)
if !hasNext {
return errors.NotFoundErrorf("%s", mongo.ErrNoDocuments)
}
base.Reset(l.target)
err := l.Decode(l.target)
if err != nil {
return errors.InternalErrorf("can't decode desult: %s", err)
}
return nil
}
// Next loads next documents but doesn't perform decoding
func (l *StreamLoader) Next() error {
hasNext := l.Cursor.Next(l.ctx)
if !hasNext {
return errors.NotFoundErrorf("%s", mongo.ErrNoDocuments)
}
return nil
}
// Close cursor
func (l *StreamLoader) Close() error {
return l.Cursor.Close(l.ctx)
}
// LoadStream function loads documents one by one into a target channel
func LoadStream(db mongox.Database, target interface{}, filters ...interface{}) (*StreamLoader, error) {
var cursor *mongo.Cursor
var err error
composed := query.Compose(filters...)
hasPreloader, _ := composed.Preloader()
if hasPreloader {
cursor, err = createAggregateLoad(db, target, composed)
} else {
cursor, err = createSimpleLoad(db, target, composed)
}
if err != nil {
return nil, errors.InternalErrorf("can't create find result: %s", err)
}
l := &StreamLoader{Cursor: cursor, ctx: db.Context(), target: target}
return l, nil
}
+40
View File
@@ -0,0 +1,40 @@
package common
import (
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/mainnika/mongox-go-driver/v2/mongox"
"github.com/mainnika/mongox-go-driver/v2/mongox/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/errors"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
// SaveOne saves a single source document to the database
func SaveOne(db mongox.Database, source interface{}) error {
collection := db.GetCollectionOf(source)
opts := options.FindOneAndReplace()
id := base.GetID(source)
protected := base.GetProtection(source)
composed := query.Compose(bson.M{"_id": id})
opts.SetUpsert(true)
opts.SetReturnDocument(options.After)
if protected != nil {
query.Push(composed, protected)
protected.X = primitive.NewObjectID()
protected.V = time.Now().Unix()
}
result := collection.FindOneAndReplace(db.Context(), composed.M(), source, opts)
if result.Err() != nil {
return errors.NotFoundErrorf("%s", result.Err())
}
return result.Decode(source)
}
-30
View File
@@ -1,30 +0,0 @@
package database
import (
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
// Count function counts documents in the database by query
// target is used only to get collection by tag so it'd be better to use nil ptr here
func (d *Database) Count(target interface{}, filters ...interface{}) (result int64, err error) {
composed, err := query.Compose(filters...)
if err != nil {
return
}
collection := d.GetCollectionOf(target)
ctx := query.WithContext(d.Context(), composed)
opts := options.Count()
opts.Limit = composed.Limiter()
opts.Skip = composed.Skipper()
result, err = collection.CountDocuments(ctx, composed.M(), opts)
_ = composed.OnClose().Invoke(ctx, target)
return
}
+17 -177
View File
@@ -2,71 +2,58 @@ package database
import ( import (
"context" "context"
"fmt"
"reflect" "reflect"
"strconv"
"strings"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/mainnika/mongox-go-driver/v2/mongox" "github.com/mainnika/mongox-go-driver/v2/mongox"
"github.com/mainnika/mongox-go-driver/v2/mongox/query" "github.com/mainnika/mongox-go-driver/v2/mongox/errors"
) )
// Database handler // Database handler
type Database struct { type Database struct {
client *mongox.Client client *mongo.Client
dbname string dbname string
ctx context.Context ctx context.Context
} }
// NewDatabase function creates new database instance with mongo client and empty context // NewDatabase function creates new database instance with mongo client and empty context
func NewDatabase(client *mongox.Client, dbname string) (db mongox.Database) { func NewDatabase(client *mongo.Client, dbname string) mongox.Database {
db = &Database{ db := &Database{}
client: client, db.client = client
dbname: dbname, db.dbname = dbname
}
return return db
} }
// Client function returns a mongo client // Client function returns a mongo client
func (d *Database) Client() (client *mongox.Client) { func (d *Database) Client() mongox.MongoClient {
return d.client return d.client
} }
// Context function returns a context // Context function returns a context
func (d *Database) Context() (ctx context.Context) { func (d *Database) Context() context.Context {
return d.ctx
ctx = d.ctx
if ctx == nil {
ctx = context.Background()
}
return
} }
// Name function returns a database name // Name function returns a database name
func (d *Database) Name() (name string) { func (d *Database) Name() string {
return d.dbname return d.dbname
} }
// New function creates new database context with same client // New function creates new database context with same client
func (d *Database) New(ctx context.Context) (db mongox.Database) { func (d *Database) New(ctx context.Context) mongox.Database {
if ctx == nil { if ctx != nil {
ctx = context.Background() ctx = context.Background()
} }
db = &Database{ return &Database{
client: d.client, client: d.client,
dbname: d.dbname, dbname: d.dbname,
ctx: ctx, ctx: ctx,
} }
return
} }
// GetCollectionOf returns the collection object by the «collection» tag of the given document; // GetCollectionOf returns the collection object by the «collection» tag of the given document;
@@ -75,7 +62,7 @@ func (d *Database) New(ctx context.Context) (db mongox.Database) {
// base.ObjectID `bson:",inline" json:",inline" collection:"foobars"` // base.ObjectID `bson:",inline" json:",inline" collection:"foobars"`
// ... // ...
// Will panic if there is no «collection» tag // Will panic if there is no «collection» tag
func (d *Database) GetCollectionOf(document interface{}) (collection *mongox.Collection) { func (d *Database) GetCollectionOf(document interface{}) mongox.MongoCollection {
el := reflect.TypeOf(document).Elem() el := reflect.TypeOf(document).Elem()
numField := el.NumField() numField := el.NumField()
@@ -91,152 +78,5 @@ func (d *Database) GetCollectionOf(document interface{}) (collection *mongox.Col
return d.client.Database(d.dbname).Collection(found) return d.client.Database(d.dbname).Collection(found)
} }
panic(fmt.Errorf("document %v does not have a collection tag", document)) panic(errors.InternalErrorf("document %v does not have a collection tag", document))
}
func (d *Database) createSimpleLoad(target interface{}, composed *query.Query) (cursor *mongox.Cursor, err error) {
collection := d.GetCollectionOf(target)
opts := options.Find()
opts.Sort = composed.Sorter()
opts.Limit = composed.Limiter()
opts.Skip = composed.Skipper()
return collection.Find(d.Context(), composed.M(), opts)
}
func (d *Database) createAggregateLoad(target interface{}, composed *query.Query) (cursor *mongox.Cursor, err error) {
collection := d.GetCollectionOf(target)
opts := options.Aggregate()
pipeline := primitive.A{}
if !composed.Empty() {
pipeline = append(pipeline, primitive.M{"$match": composed.M()})
}
if composed.Sorter() != nil {
pipeline = append(pipeline, primitive.M{"$sort": composed.Sorter()})
}
if composed.Skipper() != nil {
pipeline = append(pipeline, primitive.M{"$skip": *composed.Skipper()})
}
if composed.Limiter() != nil {
pipeline = append(pipeline, primitive.M{"$limit": *composed.Limiter()})
}
el := reflect.ValueOf(target).Elem()
elType := el.Type()
numField := elType.NumField()
preloads, _ := composed.Preloader()
for i := 0; i < numField; i++ {
field := elType.Field(i)
tag := field.Tag
preloadTag, ok := tag.Lookup("preload")
if !ok {
continue
}
jsonTag, _ := tag.Lookup("json")
if jsonTag == "-" {
panic(fmt.Errorf("preload private field is impossible"))
}
jsonData := strings.SplitN(jsonTag, ",", 2)
jsonName := field.Name
if len(jsonData) > 0 {
jsonName = strings.TrimSpace(jsonData[0])
}
preloadData := strings.Split(preloadTag, ",")
if len(preloadData) == 0 {
continue
}
if len(preloadData) == 1 {
panic(fmt.Errorf("there is no foreign field"))
}
localField := strings.TrimSpace(preloadData[0])
if len(localField) == 0 {
localField = "_id"
}
foreignField := strings.TrimSpace(preloadData[1])
if len(foreignField) == 0 {
panic(fmt.Errorf("there is no foreign field"))
}
preloadLimiter := 100
preloadReversed := false
if len(preloadData) > 2 {
stringLimit := strings.TrimSpace(preloadData[2])
intLimit := preloadLimiter
preloadReversed = strings.HasPrefix(stringLimit, "-")
if preloadReversed {
stringLimit = stringLimit[1:]
}
intLimit, err = strconv.Atoi(stringLimit)
if err == nil {
preloadLimiter = intLimit
}
}
for _, preload := range preloads {
if preload != jsonName {
continue
}
isSlice := el.Field(i).Kind() == reflect.Slice
typ := el.Field(i).Type()
if typ.Kind() == reflect.Slice {
typ = typ.Elem()
}
if typ.Kind() != reflect.Ptr {
panic(fmt.Errorf("preload field should have ptr type"))
}
lookupCollection := d.GetCollectionOf(reflect.Zero(typ).Interface())
lookupVars := primitive.M{"selector": "$" + localField}
lookupPipeline := primitive.A{
primitive.M{"$match": primitive.M{"$expr": primitive.M{"$eq": primitive.A{"$" + foreignField, "$$selector"}}}},
}
if preloadReversed {
lookupPipeline = append(lookupPipeline, primitive.M{"$sort": primitive.M{"_id": -1}})
}
if isSlice && preloadLimiter > 0 {
lookupPipeline = append(lookupPipeline, primitive.M{"$limit": preloadLimiter})
} else if !isSlice {
lookupPipeline = append(lookupPipeline, primitive.M{"$limit": 1})
}
pipeline = append(pipeline, primitive.M{
"$lookup": primitive.M{
"from": lookupCollection.Name(),
"let": lookupVars,
"pipeline": lookupPipeline,
"as": jsonName,
},
})
if isSlice {
continue
}
pipeline = append(pipeline, primitive.M{
"$unwind": primitive.M{
"preserveNullAndEmptyArrays": true,
"path": "$" + jsonName,
},
})
}
}
return collection.Aggregate(d.Context(), pipeline, opts)
} }
-77
View File
@@ -1,77 +0,0 @@
package database
import (
"fmt"
"reflect"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/mainnika/mongox-go-driver/v2/mongox/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
// DeleteArray removes documents list from a database by their ids
func (d *Database) DeleteArray(target interface{}, filters ...interface{}) (err error) {
targetV := reflect.ValueOf(target)
targetT := targetV.Type()
targetK := targetV.Kind()
if targetK != reflect.Ptr {
panic(fmt.Errorf("target is not a ptr"))
}
targetSliceV := targetV.Elem()
targetSliceT := targetT.Elem()
if targetSliceT.Kind() != reflect.Slice {
panic(fmt.Errorf("target should be a ptr to a slice"))
}
targetSliceElemT := targetSliceT.Elem()
if targetSliceElemT.Kind() != reflect.Ptr {
panic(fmt.Errorf("target slice should contain ptrs"))
}
zeroElem := reflect.Zero(targetSliceElemT)
targetLen := targetSliceV.Len()
composed, err := query.Compose(filters...)
if err != nil {
return
}
collection := d.GetCollectionOf(zeroElem.Interface())
ids := primitive.A{}
ctx := query.WithContext(d.Context(), composed)
for i := 0; i < targetLen; i++ {
elem := targetSliceV.Index(i)
ids = append(ids, base.GetID(elem.Interface()))
}
defer func() {
invokerr := composed.OnClose().Invoke(ctx, target)
if err == nil {
err = invokerr
}
return
}()
if len(ids) == 0 {
return fmt.Errorf("can't delete zero elements")
}
composed.And(primitive.M{"_id": primitive.M{"$in": ids}})
result, err := collection.DeleteMany(ctx, composed.M(), options.Delete())
if err != nil {
return
}
if result.DeletedCount != int64(targetLen) {
err = fmt.Errorf("can't verify delete result: removed count mismatch %d != %d", result.DeletedCount, targetLen)
}
return
}
-63
View File
@@ -1,63 +0,0 @@
package database
import (
"fmt"
"github.com/modern-go/reflect2"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/mainnika/mongox-go-driver/v2/mongox/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
// DeleteOne removes a document from a database and then returns it into target
func (d *Database) DeleteOne(target interface{}, filters ...interface{}) (err error) {
composed, err := query.Compose(filters...)
if err != nil {
return
}
collection := d.GetCollectionOf(target)
protected := base.GetProtection(target)
ctx := query.WithContext(d.Context(), composed)
opts := options.FindOneAndDelete()
opts.Sort = composed.Sorter()
if !reflect2.IsNil(target) {
composed.And(primitive.M{"_id": base.GetID(target)})
}
if protected != nil {
query.Push(composed, protected)
protected.Restate()
}
defer func() {
invokerr := composed.OnClose().Invoke(ctx, target)
if err == nil {
err = invokerr
}
return
}()
result := collection.FindOneAndDelete(ctx, composed.M(), opts)
if result.Err() != nil {
return fmt.Errorf("can't create find one and delete result: %w", result.Err())
}
err = result.Decode(target)
if err != nil {
return
}
err = composed.OnDecode().Invoke(ctx, target)
if err != nil {
return
}
return
}
-136
View File
@@ -1,136 +0,0 @@
package database
import (
"bytes"
"fmt"
"reflect"
"strconv"
"strings"
"text/template"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// IndexEnsure function ensures index in mongo collection of document
// `index:""` -- https://docs.mongodb.com/manual/indexes/#create-an-index
// `index:"-"` -- (descending)
// `index:"-,+foo,+-bar"` -- https://docs.mongodb.com/manual/core/index-compound
// `index:"-,+foo,+-bar,unique"` -- https://docs.mongodb.com/manual/core/index-unique
// `index:"-,+foo,+-bar,unique,allowNull"` -- https://docs.mongodb.com/manual/core/index-partial
// `index:"-,unique,allowNull,expireAfter=86400"` -- https://docs.mongodb.com/manual/core/index-ttl
// `index:"-,unique,allowNull,expireAfter={{.Expire}}"` -- evaluate index as a golang template with `cfg` arguments
func (d *Database) IndexEnsure(cfg interface{}, document interface{}) (err error) {
el := reflect.ValueOf(document).Elem().Type()
numField := el.NumField()
documents := d.GetCollectionOf(document)
for i := 0; i < numField; i++ {
field := el.Field(i)
tag := field.Tag
indexTag, ok := tag.Lookup("index")
if !ok {
continue
}
bsonTag, ok := tag.Lookup("bson")
if !ok {
return fmt.Errorf("bson tag is not defined for field:%v document:%v", field, document)
}
var tmpBuffer = &bytes.Buffer{}
var tpl *template.Template
tpl, err = template.New("").Parse(indexTag)
if err != nil {
panic(fmt.Errorf("invalid prop template %v, err:%w", indexTag, err))
}
err = tpl.Execute(tmpBuffer, cfg)
if err != nil {
panic(fmt.Errorf("failed to evaluate prop template %v, err:%w", indexTag, err))
}
indexString := tmpBuffer.String()
indexValues := strings.Split(indexString, ",")
bsonValues := strings.Split(bsonTag, ",")
var f = false
var t = true
var key = bsonValues[0]
var name = fmt.Sprintf("%s_%s_", indexString, key)
if len(key) == 0 {
panic(fmt.Errorf("cannot evaluate index key"))
}
opts := &options.IndexOptions{
Background: &f,
Unique: &f,
Name: &name,
}
index := primitive.D{{Key: key, Value: 1}}
if indexValues[0] == "-" {
index = primitive.D{{Key: key, Value: -1}}
}
for _, prop := range indexValues[1:] {
var left string
var right string
pair := strings.SplitN(prop, "=", 2)
left = pair[0]
if len(pair) > 1 {
right = pair[1]
}
switch {
case left == "unique":
opts.Unique = &t
case left == "allowNull":
expression, isMap := opts.PartialFilterExpression.(primitive.M)
if !isMap || expression == nil {
expression = primitive.M{}
}
expression[key] = primitive.M{"$exists": true}
opts.PartialFilterExpression = expression
case left == "expireAfter":
expireAfter, err := strconv.Atoi(right)
if err != nil || expireAfter < 1 {
panic(fmt.Errorf("invalid expireAfter value, err: %w", err))
}
expireAfterInt32 := int32(expireAfter)
opts.ExpireAfterSeconds = &expireAfterInt32
case len(left) > 0 && left[0] == '+':
compoundValue := left[1:]
if len(compoundValue) == 0 {
panic(fmt.Errorf("invalid compound value"))
}
if compoundValue[0] == '-' {
index = append(index, primitive.E{compoundValue[1:], -1})
} else {
index = append(index, primitive.E{compoundValue, 1})
}
default:
panic(fmt.Errorf("unsupported flag:%q in tag:%q of type:%s", prop, tag, el))
}
}
_, err = documents.Indexes().CreateOne(d.Context(), mongo.IndexModel{Keys: index, Options: opts})
if err != nil {
return
}
}
return
}
-156
View File
@@ -1,156 +0,0 @@
package database_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/mainnika/mongox-go-driver/v2/mongox-testing/database"
"github.com/mainnika/mongox-go-driver/v2/mongox/base/oidbased"
)
func TestDatabase_Ensure(t *testing.T) {
db, err := database.NewEphemeral("mongodb://localhost")
if err != nil {
t.Fatal(err)
}
defer db.Close()
testvalues := []struct {
doc interface{}
settings map[string]interface{}
index map[string]interface{}
}{
{
doc: &struct {
oidbased.Primary `bson:",inline" json:",inline" collection:"1"`
Foobar int `bson:"foobar" json:"foobar" index:"-,unique,allowNull,expireAfter=86400"`
Foo int `bson:"foo" json:"foo"`
Bar int `bson:"bar" json:"bar"`
}{},
index: map[string]interface{}{
"background": false,
"expireAfterSeconds": int32(86400),
"key": map[string]interface{}{
"foobar": int32(-1),
},
"name": "-,unique,allowNull,expireAfter=86400_foobar_",
"partialFilterExpression": map[string]interface{}{
"foobar": map[string]interface{}{"$exists": true},
},
"unique": true,
},
},
{
doc: &struct {
oidbased.Primary `bson:",inline" json:",inline" collection:"2"`
Foobar int `bson:"foobar" json:"foobar" index:",unique"`
}{},
index: map[string]interface{}{
"background": false,
"key": map[string]interface{}{
"foobar": int32(1),
},
"name": ",unique_foobar_",
"unique": true,
},
},
{
doc: &struct {
oidbased.Primary `bson:",inline" json:",inline" collection:"3"`
Foobar int `bson:"foobar" json:"foobar" index:"-,+foo,+-bar,unique,allowNull"`
Foo int `bson:"foo" json:"foo"`
Bar int `bson:"bar" json:"bar"`
}{},
index: map[string]interface{}{
"background": false,
"key": map[string]interface{}{
"foobar": int32(-1),
"foo": int32(1),
"bar": int32(-1),
},
"name": "-,+foo,+-bar,unique,allowNull_foobar_",
"partialFilterExpression": map[string]interface{}{
"foobar": map[string]interface{}{"$exists": true},
},
"unique": true,
},
},
{
doc: &struct {
oidbased.Primary `bson:",inline" json:",inline" collection:"4"`
Foobar int `bson:"foobar" json:"foobar" index:""`
Foo int `bson:"foo" json:"foo"`
Bar int `bson:"bar" json:"bar"`
}{},
index: map[string]interface{}{
"background": false,
"key": map[string]interface{}{
"foobar": int32(1),
},
"name": "_foobar_",
},
},
{
doc: &struct {
oidbased.Primary `bson:",inline" json:",inline" collection:"5"`
Foobar int `bson:"foobar" json:"foobar" index:"-"`
Foo int `bson:"foo" json:"foo"`
Bar int `bson:"bar" json:"bar"`
}{},
index: map[string]interface{}{
"background": false,
"key": map[string]interface{}{
"foobar": int32(-1),
},
"name": "-_foobar_",
},
},
{
doc: &struct {
oidbased.Primary `bson:",inline" json:",inline" collection:"1"`
Foobar int `bson:"foobar" json:"foobar" index:"-,unique,allowNull,expireAfter={{.Expire}}"`
Foo int `bson:"foo" json:"foo"`
Bar int `bson:"bar" json:"bar"`
}{},
settings: map[string]interface{}{
"Expire": 86400,
},
index: map[string]interface{}{
"background": false,
"expireAfterSeconds": int32(86400),
"key": map[string]interface{}{
"foobar": int32(-1),
},
"name": "-,unique,allowNull,expireAfter=86400_foobar_",
"partialFilterExpression": map[string]interface{}{
"foobar": map[string]interface{}{"$exists": true},
},
"unique": true,
},
},
}
for _, tt := range testvalues {
err = db.IndexEnsure(tt.settings, tt.doc)
assert.NoError(t, err)
indexes, _ := db.GetCollectionOf(tt.doc).Indexes().List(db.Context())
index := new(map[string]interface{})
indexes.Next(db.Context()) // skip _id_
indexes.Next(db.Context())
indexes.Decode(&index)
for k, v := range tt.index {
assert.Equal(t, v, (*index)[k])
}
}
}
-118
View File
@@ -1,118 +0,0 @@
package database
import (
"fmt"
"reflect"
"github.com/mainnika/mongox-go-driver/v2/mongox"
"github.com/mainnika/mongox-go-driver/v2/mongox/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
// LoadArray loads an array of documents from the database by query
func (d *Database) LoadArray(target interface{}, filters ...interface{}) (err error) {
targetV := reflect.ValueOf(target)
targetT := targetV.Type()
targetK := targetV.Kind()
if targetK != reflect.Ptr {
panic(fmt.Errorf("target is not a ptr"))
}
targetSliceV := targetV.Elem()
targetSliceT := targetT.Elem()
if targetSliceT.Kind() != reflect.Slice {
panic(fmt.Errorf("target should be a ptr to a slice"))
}
targetSliceElemT := targetSliceT.Elem()
if targetSliceElemT.Kind() != reflect.Ptr {
panic(fmt.Errorf("target slice should contain ptrs"))
}
composed, err := query.Compose(filters...)
if err != nil {
return
}
zeroElem := reflect.Zero(targetSliceElemT)
_, hasPreloader := composed.Preloader()
ctx := query.WithContext(d.Context(), composed)
var result *mongox.Cursor
var i int
defer func() {
if result != nil {
closerr := result.Close(ctx)
if err == nil {
err = closerr
}
}
invokerr := composed.OnClose().Invoke(ctx, target)
if err == nil {
err = invokerr
}
return
}()
if hasPreloader {
result, err = d.createAggregateLoad(zeroElem.Interface(), composed)
} else {
result, err = d.createSimpleLoad(zeroElem.Interface(), composed)
}
if err != nil {
err = fmt.Errorf("can't create find result: %w", err)
return
}
for i = 0; result.Next(ctx); i++ {
var elem interface{}
if i == targetSliceV.Len() {
value := reflect.New(targetSliceElemT.Elem())
elem = value.Interface()
err = composed.OnCreate().Invoke(ctx, elem)
if err != nil {
return
}
err = result.Decode(elem)
if err != nil {
return
}
targetSliceV = reflect.Append(targetSliceV, value)
} else {
elem = targetSliceV.Index(i).Interface()
if created := base.Reset(elem); created {
err = composed.OnCreate().Invoke(ctx, elem)
}
if err != nil {
return
}
err = result.Decode(elem)
if err != nil {
return
}
}
err = composed.OnDecode().Invoke(ctx, elem)
if err != nil {
return
}
}
targetSliceV = targetSliceV.Slice(0, i)
targetV.Elem().Set(targetSliceV)
return
}
-77
View File
@@ -1,77 +0,0 @@
package database
import (
"fmt"
"github.com/mainnika/mongox-go-driver/v2/mongox"
"github.com/mainnika/mongox-go-driver/v2/mongox/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
// LoadOne function loads a first single target document by a query
func (d *Database) LoadOne(target interface{}, filters ...interface{}) (err error) {
composed, err := query.Compose(append(filters, query.Limit(1))...)
if err != nil {
return
}
_, hasPreloader := composed.Preloader()
ctx := query.WithContext(d.Context(), composed)
var result *mongox.Cursor
defer func() {
if result != nil {
closerr := result.Close(ctx)
if err == nil {
err = closerr
}
}
invokerr := composed.OnClose().Invoke(ctx, target)
if err == nil {
err = invokerr
}
return
}()
if hasPreloader {
result, err = d.createAggregateLoad(target, composed)
} else {
result, err = d.createSimpleLoad(target, composed)
}
if err != nil {
return fmt.Errorf("can't create find result: %w", err)
}
hasNext := result.Next(ctx)
if result.Err() != nil {
err = result.Err()
return
}
if !hasNext {
return mongox.ErrNoDocuments
}
if created := base.Reset(target); created {
err = composed.OnCreate().Invoke(ctx, target)
}
if err != nil {
return
}
err = result.Decode(target)
if err != nil {
return
}
err = composed.OnDecode().Invoke(ctx, target)
if err != nil {
return
}
return
}
-36
View File
@@ -1,36 +0,0 @@
package database
import (
"fmt"
"github.com/mainnika/mongox-go-driver/v2/mongox"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
// LoadStream function loads documents one by one into a target channel
func (d *Database) LoadStream(target interface{}, filters ...interface{}) (loader mongox.StreamLoader, err error) {
composed, err := query.Compose(filters...)
if err != nil {
return
}
_, hasPreloader := composed.Preloader()
ctx := query.WithContext(d.Context(), composed)
var cursor *mongox.Cursor
if hasPreloader {
cursor, err = d.createAggregateLoad(target, composed)
} else {
cursor, err = d.createSimpleLoad(target, composed)
}
if err != nil {
err = fmt.Errorf("can't create find result: %w", err)
return
}
loader = &StreamLoader{cur: cursor, ctx: ctx, ref: target, query: composed}
return
}
-60
View File
@@ -1,60 +0,0 @@
package database
import (
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/mainnika/mongox-go-driver/v2/mongox/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
// SaveOne saves a single source document to the database
func (d *Database) SaveOne(source interface{}, filters ...interface{}) (err error) {
composed, err := query.Compose(filters...)
if err != nil {
return
}
collection := d.GetCollectionOf(source)
id := base.GetID(source)
protected := base.GetProtection(source)
ctx := query.WithContext(d.Context(), composed)
composed.And(primitive.M{"_id": id})
opts := options.FindOneAndReplace()
opts.SetUpsert(true)
opts.SetReturnDocument(options.After)
if protected != nil {
query.Push(composed, protected)
protected.Restate()
}
defer func() {
invokerr := composed.OnClose().Invoke(ctx, source)
if err == nil {
err = invokerr
}
return
}()
result := collection.FindOneAndReplace(ctx, composed.M(), source, opts)
if result.Err() != nil {
return result.Err()
}
err = result.Decode(source)
if err != nil {
return
}
err = composed.OnDecode().Invoke(ctx, source)
if err != nil {
return
}
return
}
-101
View File
@@ -1,101 +0,0 @@
package database
import (
"context"
"github.com/mainnika/mongox-go-driver/v2/mongox"
"github.com/mainnika/mongox-go-driver/v2/mongox/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
// StreamLoader is a controller for a database cursor
type StreamLoader struct {
cur *mongox.Cursor
query *query.Query
ctx context.Context
ref interface{}
}
// DecodeNextMsg decodes the next document to an interface or returns an error
func (l *StreamLoader) DecodeNextMsg(i interface{}) (err error) {
err = l.Next()
if err != nil {
return
}
err = l.DecodeMsg(i)
if err != nil {
return
}
return
}
// DecodeMsg decodes the current cursor document into an interface
func (l *StreamLoader) DecodeMsg(i interface{}) (err error) {
if created := base.Reset(i); created {
err = l.query.OnDecode().Invoke(l.ctx, i)
}
if err != nil {
return
}
err = l.cur.Decode(i)
if err != nil {
return
}
err = l.query.OnDecode().Invoke(l.ctx, i)
if err != nil {
return
}
return
}
// Next loads next documents but doesn't perform decoding
func (l *StreamLoader) Next() (err error) {
hasNext := l.cur.Next(l.ctx)
err = l.cur.Err()
if err != nil {
return
}
if !hasNext {
err = mongox.ErrNoDocuments
}
return
}
// Cursor returns the underlying cursor
func (l *StreamLoader) Cursor() (cursor *mongox.Cursor) {
return l.cur
}
// Close stream loader and the underlying cursor
func (l *StreamLoader) Close() (err error) {
closerr := l.cur.Close(l.ctx)
invokerr := l.query.OnClose().Invoke(l.ctx, l.ref)
if closerr != nil {
err = closerr
return
}
if invokerr != nil {
err = invokerr
return
}
return
}
// Err returns the last error
func (l *StreamLoader) Err() (err error) {
return l.cur.Err()
}
-72
View File
@@ -1,72 +0,0 @@
package database
import (
"github.com/modern-go/reflect2"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/mainnika/mongox-go-driver/v2/mongox/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
// UpdateOne updates a single document in the database and loads it into target
func (d *Database) UpdateOne(target interface{}, filters ...interface{}) (err error) {
composed, err := query.Compose(filters...)
if err != nil {
return
}
updaterDoc, err := composed.Updater()
if err != nil {
return
}
collection := d.GetCollectionOf(target)
protected := base.GetProtection(target)
ctx := query.WithContext(d.Context(), composed)
opts := options.FindOneAndUpdate()
opts.SetReturnDocument(options.After)
if protected != nil {
if !protected.X.IsZero() {
query.Push(composed, protected)
}
protected.Restate()
setCmd, _ := updaterDoc["$set"].(primitive.M)
if reflect2.IsNil(setCmd) {
setCmd = primitive.M{}
}
protected.PutToDocument(setCmd)
updaterDoc["$set"] = setCmd
}
defer func() {
invokerr := composed.OnClose().Invoke(ctx, target)
if err == nil {
err = invokerr
}
return
}()
result := collection.FindOneAndUpdate(ctx, composed.M(), updaterDoc, opts)
if result.Err() != nil {
return result.Err()
}
err = result.Decode(target)
if err != nil {
return
}
err = composed.OnDecode().Invoke(ctx, target)
if err != nil {
return
}
return
}
-20
View File
@@ -1,20 +0,0 @@
package mongox
import (
"go.mongodb.org/mongo-driver/mongo"
)
// Reexported mongo errors
var (
ErrMissingResumeToken = mongo.ErrMissingResumeToken
ErrNilCursor = mongo.ErrNilCursor
ErrUnacknowledgedWrite = mongo.ErrUnacknowledgedWrite
ErrClientDisconnected = mongo.ErrClientDisconnected
ErrNilDocument = mongo.ErrNilDocument
ErrEmptySlice = mongo.ErrEmptySlice
ErrInvalidIndexValue = mongo.ErrInvalidIndexValue
ErrNonStringIndexName = mongo.ErrNonStringIndexName
ErrMultipleIndexDrop = mongo.ErrMultipleIndexDrop
ErrWrongClient = mongo.ErrWrongClient
ErrNoDocuments = mongo.ErrNoDocuments
)
+16
View File
@@ -0,0 +1,16 @@
package errors
import "fmt"
// InternalError error
type InternalError string
// Error message
func (ie InternalError) Error() string {
return fmt.Sprintf("internal error, %s", string(ie))
}
// InternalErrorf function creates an instance of InternalError
func InternalErrorf(format string, params ...interface{}) error {
return InternalError(fmt.Sprintf(format, params...))
}
+16
View File
@@ -0,0 +1,16 @@
package errors
import "fmt"
// Malformed error
type Malformed string
// Error message
func (m Malformed) Error() string {
return fmt.Sprintf("Malformed, %s", string(m))
}
// Malformedf creates an instance of Malformed
func Malformedf(format string, params ...interface{}) error {
return Malformed(fmt.Sprintf(format, params...))
}
+16
View File
@@ -0,0 +1,16 @@
package errors
import "fmt"
// NotFound error
type NotFound string
// Error message
func (nf NotFound) Error() string {
return fmt.Sprintf("can not find, %s", string(nf))
}
// NotFoundErrorf function creates an instance of BadRequestError
func NotFoundErrorf(format string, params ...interface{}) error {
return NotFound(fmt.Sprintf(format, params...))
}
+76 -44
View File
@@ -5,63 +5,95 @@ import (
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
) "go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
// Reexport basic mongo structs
type (
Cursor = mongo.Cursor
Client = mongo.Client
Collection = mongo.Collection
) )
// Database is the mongox database interface // Database is the mongox database interface
type Database interface { type Database interface {
Client() (client *Client) Client() MongoClient
Context() (context context.Context) Context() context.Context
Name() (name string) Name() string
New(ctx context.Context) (db Database) New(ctx context.Context) Database
GetCollectionOf(document interface{}) (collection *Collection) GetCollectionOf(document interface{}) MongoCollection
Count(target interface{}, filters ...interface{}) (count int64, err error)
DeleteArray(target interface{}, filters ...interface{}) (err error)
DeleteOne(target interface{}, filters ...interface{}) (err error)
LoadArray(target interface{}, filters ...interface{}) (err error)
LoadOne(target interface{}, filters ...interface{}) (err error)
LoadStream(target interface{}, filters ...interface{}) (loader StreamLoader, err error)
SaveOne(source interface{}, filters ...interface{}) (err error)
UpdateOne(target interface{}, filters ...interface{}) (err error)
IndexEnsure(cfg interface{}, document interface{}) (err error)
} }
// StreamLoader is a interface to control database cursor // MongoClient is the mongo client interface
type StreamLoader interface { type MongoClient interface {
Cursor() (cursor *Cursor) Connect(ctx context.Context) error
DecodeNextMsg(i interface{}) (err error) Disconnect(ctx context.Context) error
DecodeMsg(i interface{}) (err error) Ping(ctx context.Context, rp *readpref.ReadPref) error
Next() (err error) StartSession(opts ...*options.SessionOptions) (mongo.Session, error)
Close() (err error) Database(name string, opts ...*options.DatabaseOptions) *mongo.Database
Err() (err error) ListDatabases(ctx context.Context, filter interface{}, opts ...*options.ListDatabasesOptions) (mongo.ListDatabasesResult, error)
ListDatabaseNames(ctx context.Context, filter interface{}, opts ...*options.ListDatabasesOptions) ([]string, error)
UseSession(ctx context.Context, fn func(mongo.SessionContext) error) error
UseSessionWithOptions(ctx context.Context, opts *options.SessionOptions, fn func(mongo.SessionContext) error) error
Watch(ctx context.Context, pipeline interface{}, opts ...*options.ChangeStreamOptions) (*mongo.ChangeStream, error)
NumberSessionsInProgress() int
} }
// OIDBased is an interface for documents that have objectId type for the _id field // MongoCollection is the mongo collection interface
type OIDBased interface { type MongoCollection interface {
GetID() (id primitive.ObjectID) Clone(opts ...*options.CollectionOptions) (*mongo.Collection, error)
Name() string
Database() *mongo.Database
BulkWrite(ctx context.Context, models []mongo.WriteModel, opts ...*options.BulkWriteOptions) (*mongo.BulkWriteResult, error)
InsertOne(ctx context.Context, document interface{}, opts ...*options.InsertOneOptions) (*mongo.InsertOneResult, error)
InsertMany(ctx context.Context, documents []interface{}, opts ...*options.InsertManyOptions) (*mongo.InsertManyResult, error)
DeleteOne(ctx context.Context, filter interface{}, opts ...*options.DeleteOptions) (*mongo.DeleteResult, error)
DeleteMany(ctx context.Context, filter interface{}, opts ...*options.DeleteOptions) (*mongo.DeleteResult, error)
UpdateOne(ctx context.Context, filter interface{}, update interface{}, opts ...*options.UpdateOptions) (*mongo.UpdateResult, error)
UpdateMany(ctx context.Context, filter interface{}, update interface{}, opts ...*options.UpdateOptions) (*mongo.UpdateResult, error)
ReplaceOne(ctx context.Context, filter interface{}, replacement interface{}, opts ...*options.ReplaceOptions) (*mongo.UpdateResult, error)
Aggregate(ctx context.Context, pipeline interface{}, opts ...*options.AggregateOptions) (*mongo.Cursor, error)
CountDocuments(ctx context.Context, filter interface{}, opts ...*options.CountOptions) (int64, error)
EstimatedDocumentCount(ctx context.Context, opts ...*options.EstimatedDocumentCountOptions) (int64, error)
Distinct(ctx context.Context, fieldName string, filter interface{}, opts ...*options.DistinctOptions) ([]interface{}, error)
Find(ctx context.Context, filter interface{}, opts ...*options.FindOptions) (*mongo.Cursor, error)
FindOne(ctx context.Context, filter interface{}, opts ...*options.FindOneOptions) *mongo.SingleResult
FindOneAndDelete(ctx context.Context, filter interface{}, opts ...*options.FindOneAndDeleteOptions) *mongo.SingleResult
FindOneAndReplace(ctx context.Context, filter interface{}, replacement interface{}, opts ...*options.FindOneAndReplaceOptions) *mongo.SingleResult
FindOneAndUpdate(ctx context.Context, filter interface{}, update interface{}, opts ...*options.FindOneAndUpdateOptions) *mongo.SingleResult
Watch(ctx context.Context, pipeline interface{}, opts ...*options.ChangeStreamOptions) (*mongo.ChangeStream, error)
Indexes() mongo.IndexView
Drop(ctx context.Context) error
}
// Saver is an interface for documents that can be saved
type Saver interface {
Save(db Database) error
}
// Deleter is an interface for documents that can be deleted
type Deleter interface {
Delete(db Database) error
}
// Loader is an interface for documents that can be loaded
type Loader interface {
Load(db Database, filters ...interface{}) error
}
// Resetter is an interface for documenta that can be resetted
type Resetter interface {
Reset()
}
// BaseObjectID is an interface for documents that have objectId type for the _id field
type BaseObjectID interface {
GetID() primitive.ObjectID
SetID(id primitive.ObjectID) SetID(id primitive.ObjectID)
} }
// StringBased is an interface for documents that have string type for the _id field // BaseString is an interface for documents that have string type for the _id field
type StringBased interface { type BaseString interface {
GetID() (id string) GetID() string
SetID(id string) SetID(id string)
} }
// JSONBased is an interface for documents that have object type for the _id field // BaseObject is an interface for documents that have object type for the _id field
type JSONBased interface { type BaseObject interface {
GetID() (id primitive.D) GetID() primitive.D
SetID(id primitive.D) SetID(id primitive.D)
} }
// InterfaceBased is an interface for documents that have custom declated type for the _id field
type InterfaceBased interface {
GetID() (id interface{})
SetID(id interface{})
}
-26
View File
@@ -1,26 +0,0 @@
package query
import (
"context"
)
type Callback func(ctx context.Context, iter interface{}) (err error)
type Callbacks []Callback
type (
OnDecode Callback
OnClose Callback
)
// Invoke callbacks sequence
func (c Callbacks) Invoke(ctx context.Context, iter interface{}) (err error) {
for _, cb := range c {
err = cb(ctx, iter)
if err != nil {
return
}
}
return
}
+54 -91
View File
@@ -1,71 +1,46 @@
package query package query
import ( import (
"fmt" "go.mongodb.org/mongo-driver/bson"
"github.com/modern-go/reflect2"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
"github.com/mainnika/mongox-go-driver/v2/mongox/base/protection" "github.com/mainnika/mongox-go-driver/v2/mongox/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/errors"
) )
type applyFilterFunc = func(query *Query, filter interface{}) (ok bool)
// Compose is a function to compose filters into a single query // Compose is a function to compose filters into a single query
func Compose(filters ...interface{}) (query *Query, err error) { func Compose(filters ...interface{}) *Query {
query = &Query{} q := &Query{}
for _, filter := range filters { for _, f := range filters {
ok, err := Push(query, filter) if !Push(q, f) {
if err != nil { panic(errors.InternalErrorf("unknown filter %v", f))
return nil, fmt.Errorf("invalid filter %v, %w", filter, err)
}
if !ok {
panic(fmt.Errorf("unknown filter %v", filter))
} }
} }
return return q
} }
// Push applies single filter to a query // Push applies single filter to a query
func Push(query *Query, filter interface{}) (ok bool, err error) { func Push(q *Query, f interface{}) bool {
ok = reflect2.IsNil(filter) ok := false
if ok { ok = ok || applyBson(q, f)
return ok = ok || applyLimit(q, f)
} ok = ok || applySort(q, f)
ok = ok || applySkip(q, f)
ok = ok || applyProtection(q, f)
ok = ok || applyPreloader(q, f)
validator, hasValidator := filter.(Validator) return ok
if hasValidator {
err = validator.Validate()
}
if err != nil {
return
}
for _, applier := range []applyFilterFunc{
applyBson,
applyLimit,
applySort,
applySkip,
applyProtection,
applyPreloader,
applyUpdater,
applyCallbacks,
} {
ok = applier(query, filter) || ok
}
return
} }
// applyBson is a fallback for a custom primitive.M // applyBson is a fallback for a custom bson.M
func applyBson(query *Query, filter interface{}) (ok bool) { func applyBson(q *Query, f interface{}) bool {
if filter, ok := filter.(primitive.M); ok { if f, ok := f.(bson.M); ok {
query.And(filter) q.And(f)
return true return true
} }
@@ -73,10 +48,10 @@ func applyBson(query *Query, filter interface{}) (ok bool) {
} }
// applyLimits extends query with a limiter // applyLimits extends query with a limiter
func applyLimit(query *Query, filter interface{}) (ok bool) { func applyLimit(q *Query, f interface{}) bool {
if filter, ok := filter.(Limiter); ok { if f, ok := f.(Limiter); ok {
query.limiter = filter q.limiter = f
return true return true
} }
@@ -84,10 +59,10 @@ func applyLimit(query *Query, filter interface{}) (ok bool) {
} }
// applySort extends query with a sort rule // applySort extends query with a sort rule
func applySort(query *Query, filter interface{}) (ok bool) { func applySort(q *Query, f interface{}) bool {
if filter, ok := filter.(Sorter); ok { if f, ok := f.(Sorter); ok {
query.sorter = filter q.sorter = f
return true return true
} }
@@ -95,65 +70,53 @@ func applySort(query *Query, filter interface{}) (ok bool) {
} }
// applySkip extends query with a skip number // applySkip extends query with a skip number
func applySkip(query *Query, filter interface{}) (ok bool) { func applySkip(q *Query, f interface{}) bool {
if filter, ok := filter.(Skipper); ok { if f, ok := f.(Skipper); ok {
query.skipper = filter q.skipper = f
return true return true
} }
return false return false
} }
func applyProtection(query *Query, filter interface{}) (ok bool) { func applyProtection(q *Query, f interface{}) bool {
var keyDoc = primitive.M{} var x *primitive.ObjectID
var v *int64
switch filter := filter.(type) { switch f := f.(type) {
case protection.Key: case base.Protection:
filter.PutToDocument(keyDoc) x = &f.X
case *protection.Key: v = &f.V
filter.PutToDocument(keyDoc) case *base.Protection:
if f == nil {
return false
}
x = &f.X
v = &f.V
default: default:
return false return false
} }
query.And(keyDoc) if x.IsZero() {
q.And(primitive.M{"_x": primitive.M{"$exists": false}})
q.And(primitive.M{"_v": primitive.M{"$exists": false}})
} else {
q.And(primitive.M{"_x": *x})
q.And(primitive.M{"_v": *v})
}
return true return true
} }
func applyPreloader(query *Query, filter interface{}) (ok bool) { func applyPreloader(q *Query, f interface{}) bool {
if filter, ok := filter.(Preloader); ok { if f, ok := f.(Preloader); ok {
query.preloader = filter q.preloader = f
return true return true
} }
return false return false
} }
func applyUpdater(query *Query, filter interface{}) (ok bool) {
if filter, ok := filter.(Updater); ok {
query.updater = filter
return true
}
return false
}
func applyCallbacks(query *Query, filter interface{}) (ok bool) {
switch callback := filter.(type) {
case OnDecode:
query.ondecode = append(query.ondecode, Callback(callback))
case OnClose:
query.onclose = append(query.onclose, Callback(callback))
default:
return false
}
return true
}
-19
View File
@@ -1,19 +0,0 @@
package query
import (
"context"
)
type ctxQueryKey struct{}
// GetFromContext function extracts the request data from context
func GetFromContext(ctx context.Context) (q *Query, ok bool) {
q, ok = ctx.Value(ctxQueryKey{}).(*Query)
return
}
// WithContext function creates the new context with request data
func WithContext(ctx context.Context, q *Query) (withQuery context.Context) {
withQuery = context.WithValue(ctx, ctxQueryKey{}, q)
return
}
+6 -8
View File
@@ -2,7 +2,7 @@ package query
// Limiter is a filter to limit the result // Limiter is a filter to limit the result
type Limiter interface { type Limiter interface {
Limit() (limit *int64) Limit() *int64
} }
// Limit is a simple implementation of the Limiter filter // Limit is a simple implementation of the Limiter filter
@@ -11,14 +11,12 @@ type Limit int64
var _ Limiter = Limit(0) var _ Limiter = Limit(0)
// Limit returns a limit // Limit returns a limit
func (l Limit) Limit() (limit *int64) { func (l Limit) Limit() *int64 {
if l <= 0 { lim := int64(l)
return if lim <= 0 {
return nil
} }
limit = new(int64) return &lim
*limit = int64(l)
return
} }
+4 -3
View File
@@ -2,7 +2,7 @@ package query
// Preloader is a filter to skip the result // Preloader is a filter to skip the result
type Preloader interface { type Preloader interface {
Preload() (preloads []string) Preload() []string
} }
// Preload is a simple implementation of the Skipper filter // Preload is a simple implementation of the Skipper filter
@@ -11,6 +11,7 @@ type Preload []string
var _ Preloader = Preload{} var _ Preloader = Preload{}
// Preload returns a preload list // Preload returns a preload list
func (l Preload) Preload() (preloads []string) { func (l Preload) Preload() []string {
return l
return Preload(l)
} }
+28 -73
View File
@@ -1,36 +1,31 @@
package query package query
import ( import (
"github.com/modern-go/reflect2"
"github.com/valyala/bytebufferpool"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"reflect"
) )
// Query is an enchanched primitive.M map // Query is an enchanched bson.M map
type Query struct { type Query struct {
m primitive.M m bson.M
limiter Limiter limiter Limiter
sorter Sorter sorter Sorter
skipper Skipper skipper Skipper
preloader Preloader preloader Preloader
updater Updater
ondecode Callbacks
onclose Callbacks
oncreate Callbacks
} }
// And function pushes the elem query to the $and array of the query // And function pushes the elem query to the $and array of the query
func (q *Query) And(elem primitive.M) (query *Query) { func (q *Query) And(elem bson.M) *Query {
if q.m == nil { if q.m == nil {
q.m = primitive.M{} q.m = bson.M{}
} }
queries, exists := q.m["$and"].(primitive.A) queries, exists := q.m["$and"].(bson.A)
if !exists { if !exists {
q.m["$and"] = primitive.A{elem} q.m["$and"] = bson.A{elem}
return q return q
} }
@@ -40,101 +35,61 @@ func (q *Query) And(elem primitive.M) (query *Query) {
} }
// Limiter returns limiter value or nil // Limiter returns limiter value or nil
func (q *Query) Limiter() (limit *int64) { func (q *Query) Limiter() *int64 {
if q.limiter == nil { if q.limiter == nil {
return return nil
} }
return q.limiter.Limit() return q.limiter.Limit()
} }
// Sorter is a sort rule for a query // Sorter is a sort rule for a query
func (q *Query) Sorter() (sort interface{}) { func (q *Query) Sorter() interface{} {
if q.sorter == nil { if q.sorter == nil {
return return nil
} }
return q.sorter.Sort() return q.sorter.Sort()
} }
// Skipper is a skipper for a query // Skipper is a skipper for a query
func (q *Query) Skipper() (skip *int64) { func (q *Query) Skipper() *int64 {
if q.skipper == nil { if q.skipper == nil {
return return nil
} }
return q.skipper.Skip() return q.skipper.Skip()
} }
// Updater is an update command for a query
func (q *Query) Updater() (update primitive.M, err error) {
if q.updater == nil {
update = primitive.M{}
return
}
update = q.updater.Update()
if reflect2.IsNil(update) {
update = primitive.M{}
return
}
buffer := bytebufferpool.Get()
defer bytebufferpool.Put(buffer)
// convert update document to bson map values
bsonBytes, err := bson.MarshalAppend(buffer.B, update)
if err != nil {
return
}
update = primitive.M{}
err = bson.Unmarshal(bsonBytes, update)
if err != nil {
return
}
return
}
// Preloader is a preloader list for a query // Preloader is a preloader list for a query
func (q *Query) Preloader() (preloads []string, ok bool) { func (q *Query) Preloader() (empty bool, preloader []string) {
if q.preloader == nil { if q.preloader == nil {
return nil, false return false, nil
} }
preloads = q.preloader.Preload() preloader = q.preloader.Preload()
ok = len(preloads) > 0
return if len(preloader) == 0 {
} return false, nil
}
// OnDecode callback is called after the mongo decode function return true, preloader
func (q *Query) OnDecode() (callbacks Callbacks) {
return q.ondecode
}
// OnClose callback is called after the mongox ends a loading procedure
func (q *Query) OnClose() (callbacks Callbacks) {
return q.onclose
}
// OnCreate callback is called if the mongox creates a new document instance during loading
func (q *Query) OnCreate() (callbacks Callbacks) {
return q.onclose
} }
// Empty checks the query for any content // Empty checks the query for any content
func (q *Query) Empty() (isEmpty bool) { func (q *Query) Empty() bool {
return len(q.m) == 0
qv := reflect.ValueOf(q.m)
keys := qv.MapKeys()
return len(keys) == 0
} }
// M returns underlying query map // M returns underlying query map
func (q *Query) M() (m primitive.M) { func (q *Query) M() bson.M {
return q.m return q.m
} }
+6 -8
View File
@@ -2,7 +2,7 @@ package query
// Skipper is a filter to skip the result // Skipper is a filter to skip the result
type Skipper interface { type Skipper interface {
Skip() (skip *int64) Skip() *int64
} }
// Skip is a simple implementation of the Skipper filter // Skip is a simple implementation of the Skipper filter
@@ -11,14 +11,12 @@ type Skip int64
var _ Skipper = Skip(0) var _ Skipper = Skip(0)
// Skip returns a skip number // Skip returns a skip number
func (l Skip) Skip() (skip *int64) { func (l Skip) Skip() *int64 {
if l <= 0 { lim := int64(l)
return if lim <= 0 {
return nil
} }
skip = new(int64) return &lim
*skip = int64(l)
return
} }
+5 -5
View File
@@ -1,20 +1,20 @@
package query package query
import ( import (
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson"
) )
// Sorter is a filter to sort the data before query // Sorter is a filter to sort the data before query
type Sorter interface { type Sorter interface {
Sort() (sort primitive.M) Sort() bson.M
} }
// Sort is a simple implementations of the Sorter filter // Sort is a simple implementations of the Sorter filter
type Sort primitive.M type Sort bson.M
var _ Sorter = &Sort{} var _ Sorter = &Sort{}
// Sort returns a slice of fields which have to be sorted // Sort returns a slice of fields which have to be sorted
func (f Sort) Sort() (sort primitive.M) { func (f Sort) Sort() bson.M {
return primitive.M(f) return bson.M(f)
} }
-20
View File
@@ -1,20 +0,0 @@
package query
import (
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Updater is a filter to update the data
type Updater interface {
Update() (update primitive.M)
}
// Update is a simple implementations of the Updater filter
type Update primitive.M
var _ Updater = &Update{}
// Update returns an update command
func (u Update) Update() (update primitive.M) {
return primitive.M(u)
}
-6
View File
@@ -1,6 +0,0 @@
package query
// Validator is a filter to validate the filter
type Validator interface {
Validate() (err error)
}
@@ -1,4 +1,4 @@
package database package tempdb
import ( import (
"context" "context"
@@ -12,24 +12,24 @@ import (
"github.com/mainnika/mongox-go-driver/v2/mongox/database" "github.com/mainnika/mongox-go-driver/v2/mongox/database"
) )
// EphemeralDatabase is a temporary database connection that will be destroyed after close // TempDB is a temporary database connection that will be destroyed after close
type EphemeralDatabase struct { type TempDB struct {
mongox.Database mongox.Database
} }
// NewEphemeral creates new mongo connection // NewTempDB creates new mongo connection
func NewEphemeral(URI string) (db *EphemeralDatabase, err error) { func NewTempDB(URI string) (tempdb *TempDB, err error) {
name := strconv.Itoa(rand.Int()) name := strconv.Itoa(rand.Int())
opts := options.Client().ApplyURI(URI) opts := options.Client().ApplyURI(URI)
client, err := mongo.Connect(context.Background(), opts) client, err := mongo.Connect(context.Background(), opts)
db = &EphemeralDatabase{Database: database.NewDatabase(client, name)} tempdb = &TempDB{Database: database.NewDatabase(client, name)}
return return
} }
// Close the connection and drop database // Close the connection and drop database
func (e *EphemeralDatabase) Close() (err error) { func (tdb *TempDB) Close() {
return e.Client().Database(e.Name()).Drop(e.Context()) _ = tdb.Client().Database(tdb.Name()).Drop(tdb.Context())
} }