54 Commits

Author SHA1 Message Date
mainnika 23029ae710 Prepare for v3 2023-06-10 00:44:20 +02:00
mainnika 7c3e50e783 Keep tests in separated _test package 2022-01-17 00:35:06 +01:00
mainnika 42e484e47f Upgrade go mod packages 2022-01-16 23:28:48 +01:00
mainnika 6b34ad5a09 Override default ephemeral database uri by using env variable 2022-01-16 23:28:48 +01:00
mainnika eab00f99b5 Use hexed objectID as the name for ephemeral db name 2022-01-16 23:28:48 +01:00
mainnika 82e1de7216 Add dockerfile for testing 2022-01-16 23:28:48 +01:00
mainnika 5e8e65d03e Use the only field type elem to check the content 2021-03-08 09:28:39 +01:00
mainnika 8d6a318bb2 Bump go mod packages 2021-03-08 08:48:20 +01:00
mainnika e52b1bcc8a Retrieve the fields info from the type elem but not the value elem 2021-03-08 08:37:29 +01:00
mainnika 04382db4cd Remove usesd ref interface from the stream loader 2021-03-08 08:19:44 +01:00
mainnika 80a5c864c5 Fix comments 2020-12-06 01:06:20 +01:00
mainnika e087825026 Rework load array function 2020-12-06 01:06:07 +01:00
mainnika 8cc57cc82f Stream loader now consumes a provided interface instead of target 2020-12-06 01:05:36 +01:00
mainnika 18d9f9bed3 Call oncreate callback if new elem created while loading 2020-12-06 00:58:20 +01:00
mainnika 0bc4e62d80 Track if the target was created as new during reset 2020-12-06 00:51:20 +01:00
mainnika 1bfa6c3a21 Use clean validator interface name 2020-11-19 18:40:43 +01:00
mainnika 86397885e2 Function update one now considers the protection key using the update operator 2020-11-18 22:33:50 +01:00
mainnika c1ad7aea7d Let the protection key restate itself 2020-11-18 22:32:00 +01:00
mainnika fc2a867cbb Use update operator not the update pipeline 2020-11-18 22:30:16 +01:00
mainnika eeb1a8d598 Change preloader query function interface 2020-11-18 21:01:56 +01:00
mainnika 84ae518fe9 Now all query filters can validate themselves using Valider interface 2020-11-18 20:09:57 +01:00
mainnika 15bb53694f Refactor variables names 2020-11-18 20:06:54 +01:00
mainnika 024ea196f6 Allow to combine query filter purposes 2020-11-18 20:03:33 +01:00
mainnika 442320b31a Expose updateone to a service 2020-11-18 11:08:28 +01:00
mainnika 0b798fe818 Use primitive import instead of bson alias 2020-11-18 11:06:26 +01:00
mainnika f859828370 Implement UpdateOne query support 2020-11-18 02:32:38 +01:00
mainnika 1fdfa18740 Return result err in loadone if cant next 2020-11-18 01:47:39 +01:00
mainnika 24004ff910 Consistent onclose callback order and proper err handling 2020-11-18 01:46:46 +01:00
mainnika cd5b2b85d1 Bump packages 2020-11-18 00:56:26 +01:00
Nikita Tokarchuk 87708fee04 Add callback support to a saving 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk 06f6f48f11 Add context support to a saving 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk 28be1f46b9 Add filters support for a saving 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk 848703d56d Add callback support to a single deleting 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk 8fcd764a8c Add context query support to a single deleting 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk 50e947b203 Invoke onclose at the end of an array deleting 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk 73dc4974a2 Add query context support for an array deleting 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk 08397650c6 Add filters support for an array deleting 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk ee4bf46662 Add callback support for a counting 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk 9fb3094b87 Use reflect2 2020-07-23 00:00:53 +02:00
Nikita Tokarchuk b22b0f0919 Add query to the context 2020-07-23 00:00:53 +02:00
Nikita Tokarchuk 951e5f5bef Add onclose callback 2020-07-23 00:00:53 +02:00
Nikita Tokarchuk b796d5ac3b Make callback type 2020-07-23 00:00:53 +02:00
Nikita Tokarchuk 08c3c5b377 Add callback mechanism and implement on-decode callback 2020-07-13 16:32:48 +02:00
Nikita Tokarchuk 09fa64ab0e Return err if cannot decode array element 2020-07-13 04:09:04 +02:00
Nikita Tokarchuk c019a0ea4b Ignore unused arguments 2020-07-13 04:09:04 +02:00
Nikita Tokarchuk ee1b0e17d5 Improve panic messages 2020-07-13 04:09:04 +02:00
Nikita Tokarchuk 22a1d7033f Remove custom errors 2020-07-13 04:09:04 +02:00
Nikita Tokarchuk 1d3e29fe10 Use err type for panics 2020-07-13 04:09:04 +02:00
Nikita Tokarchuk 72e74a65b6 Use named returns for the code style consistency 2020-07-13 04:09:04 +02:00
Nikita Tokarchuk 9cf3551c20 MIT licensed 2020-06-04 21:30:19 +02:00
Nikita Tokarchuk 3035d8d571 Fix aggregation pipeline match step 2020-06-04 18:15:59 +02:00
Nikita Tokarchuk eac50d1770 Do not use unnecessary reflect 2020-06-04 18:15:35 +02:00
Nikita Tokarchuk 05ebb25e70 Use unsafe pointer in the interface struct header is more correct way 2020-06-04 18:15:08 +02:00
Nikita Tokarchuk fd53c66690 Use ordered document for index model 2020-03-25 17:40:26 +01:00
50 changed files with 1443 additions and 876 deletions
+21
View File
@@ -0,0 +1,21 @@
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.
+21 -1
View File
@@ -1 +1,21 @@
mongox-go-driver # mongox-go-driver
## testing locally
reqs:
- mongodb v4.0 or newer run on localhost
- golang v1.13 or newer
test it by calling go tests
```sh
$ go test ./...
```
## testing by using dockerfile
reqs:
- docker with buildkit
```sh
$ DOCKER_BUILDKIT=1 docker build -t mongox-testing -f testing.Dockerfile .
```
+7 -11
View File
@@ -1,14 +1,10 @@
module github.com/mainnika/mongox-go-driver/v2 module github.com/mainnika/mongox-go-driver/v2
require (
github.com/google/go-cmp v0.3.0 // indirect
github.com/klauspost/compress v1.10.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/stretchr/testify v1.5.1
github.com/xdg/stringprep v1.0.0 // indirect
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 go 1.13
require (
github.com/modern-go/reflect2 v1.0.2
github.com/stretchr/testify v1.8.4
github.com/valyala/bytebufferpool v1.0.0
go.mongodb.org/mongo-driver v1.11.6
)
+47 -100
View File
@@ -1,119 +1,66 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
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-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
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/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
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.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
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/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/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.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
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/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
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.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
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.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=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
go.mongodb.org/mongo-driver v1.3.0 h1:ew6uUIeJOo+qdUUv7LxFCUhtWmVv7ZV/Xuy4FAUsw2E= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= go.mongodb.org/mongo-driver v1.11.6 h1:XM7G6PjiGAO5betLF13BIa5TlLUUE3uJ/2Ox3Lz1K+o=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw= go.mongodb.org/mongo-driver v1.11.6/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
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/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+27 -7
View File
@@ -2,9 +2,9 @@ package database
import ( import (
"context" "context"
"math/rand" "os"
"strconv"
"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/options"
@@ -12,24 +12,44 @@ import (
"github.com/mainnika/mongox-go-driver/v2/mongox/database" "github.com/mainnika/mongox-go-driver/v2/mongox/database"
) )
// defaultURI is a mongodb uri that is being used by tests
var defaultURI = "mongodb://localhost"
// EphemeralDatabase is a temporary database connection that will be destroyed after close // EphemeralDatabase is a temporary database connection that will be destroyed after close
type EphemeralDatabase struct { type EphemeralDatabase struct {
mongox.Database mongox.Database
} }
func init() {
envURI := os.Getenv("MONGODB_URI")
if envURI != "" {
defaultURI = envURI
}
}
// NewEphemeral creates new mongo connection // NewEphemeral creates new mongo connection
func NewEphemeral(URI string) (db *EphemeralDatabase, err error) { func NewEphemeral(URI string) (db *EphemeralDatabase, err error) {
return NewEphemeralWithContext(context.Background(), URI)
}
name := strconv.Itoa(rand.Int()) func NewEphemeralWithContext(ctx context.Context, URI string) (db *EphemeralDatabase, err error) {
if URI == "" {
URI = defaultURI
}
name := primitive.NewObjectID().Hex()
opts := options.Client().ApplyURI(URI) opts := options.Client().ApplyURI(URI)
client, err := mongo.Connect(context.Background(), opts) client, err := mongo.Connect(ctx, opts)
if err != nil {
return nil, err
}
db = &EphemeralDatabase{Database: database.NewDatabase(client, name)} db = &EphemeralDatabase{Database: database.NewDatabase(ctx, client, name)}
return return db, nil
} }
// Close the connection and drop database // Close the connection and drop database
func (e *EphemeralDatabase) Close() error { func (e *EphemeralDatabase) Close() (err error) {
return e.Client().Database(e.Name()).Drop(e.Context()) return e.Client().Database(e.Name()).Drop(e.Context())
} }
+44
View File
@@ -0,0 +1,44 @@
package docbased
import (
"github.com/modern-go/reflect2"
"go.mongodb.org/mongo-driver/bson/primitive"
"github.com/mainnika/mongox-go-driver/v2/mongox"
)
var _ mongox.DocBased = (*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
}
// New creates a new Primary structure with a defined _id
func New(e primitive.E, ee ...primitive.E) Primary {
id := primitive.D{e}
if len(ee) > 0 {
id = append(id, ee...)
}
return Primary{ID: id}
}
func GetID(source mongox.DocBased) (id primitive.D, err error) {
id = source.GetID()
if !reflect2.IsNil(id) {
return id, nil
}
return nil, mongox.ErrUninitializedBase
}
@@ -1,4 +1,4 @@
package jsonbased package docbased_test
import ( import (
"encoding/json" "encoding/json"
@@ -8,26 +8,25 @@ import (
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
"github.com/mainnika/mongox-go-driver/v2/mongox-testing/database" "github.com/mainnika/mongox-go-driver/v2/mongox-testing/database"
"github.com/mainnika/mongox-go-driver/v2/mongox/base/docbased"
) )
func Test_GetID(t *testing.T) { func Test_GetID(t *testing.T) {
type DocWithObject struct { type DocWithObject struct {
Primary `bson:",inline" json:",inline" collection:"1"` docbased.Primary `bson:",inline" json:",inline" collection:"1"`
} }
doc := &DocWithObject{Primary{primitive.D{{"1", "one"}, {"2", "two"}}}} doc := &DocWithObject{Primary: docbased.New(primitive.E{"1", "one"}, primitive.E{"2", "two"})}
assert.Equal(t, primitive.D{{"1", "one"}, {"2", "two"}}, doc.GetID()) assert.Equal(t, primitive.D{{"1", "one"}, {"2", "two"}}, doc.GetID())
} }
func Test_SetID(t *testing.T) { func Test_SetID(t *testing.T) {
type DocWithObject struct { type DocWithObject struct {
Primary `bson:",inline" json:",inline" collection:"1"` docbased.Primary `bson:",inline" json:",inline" collection:"1"`
} }
doc := &DocWithObject{Primary{primitive.D{{"1", "one"}, {"2", "two"}}}} doc := &DocWithObject{Primary: docbased.New(primitive.E{"1", "one"}, primitive.E{"2", "two"})}
doc.SetID(primitive.D{{"3", "three"}, {"4", "you"}}) doc.SetID(primitive.D{{"3", "three"}, {"4", "you"}})
@@ -36,19 +35,18 @@ func Test_SetID(t *testing.T) {
} }
func Test_SaveLoad(t *testing.T) { func Test_SaveLoad(t *testing.T) {
type DocWithObjectID struct { type DocWithObjectID struct {
Primary `bson:",inline" json:",inline" collection:"1"` docbased.Primary `bson:",inline" json:",inline" collection:"1"`
} }
db, err := database.NewEphemeral("mongodb://localhost") db, err := database.NewEphemeral("")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer db.Close() defer func() { _ = db.Close() }()
doc1 := &DocWithObjectID{Primary{primitive.D{{"1", "one"}, {"2", "two"}}}} doc1 := &DocWithObjectID{Primary: docbased.New(primitive.E{"1", "one"}, primitive.E{"2", "two"})}
doc2 := &DocWithObjectID{} doc2 := &DocWithObjectID{}
err = db.SaveOne(doc1) err = db.SaveOne(doc1)
@@ -66,13 +64,11 @@ func Test_SaveLoad(t *testing.T) {
} }
func Test_Marshal(t *testing.T) { func Test_Marshal(t *testing.T) {
type DocWithObjectID struct { type DocWithObjectID struct {
Primary `bson:",inline" json:",inline" collection:"1"` docbased.Primary `bson:",inline" json:",inline" collection:"1"`
} }
id := primitive.D{{"1", "one"}, {"2", "two"}} doc := &DocWithObjectID{Primary: docbased.New(primitive.E{"1", "one"}, primitive.E{"2", "two"})}
doc := &DocWithObjectID{Primary{id}}
bytes, err := json.Marshal(doc) bytes, err := json.Marshal(doc)
assert.NoError(t, err) assert.NoError(t, err)
+11 -55
View File
@@ -2,70 +2,26 @@ package base
import ( import (
"fmt" "fmt"
"github.com/mainnika/mongox-go-driver/v2/mongox/base/docbased"
"go.mongodb.org/mongo-driver/bson/primitive" "github.com/mainnika/mongox-go-driver/v2/mongox/base/ifacebased"
"github.com/mainnika/mongox-go-driver/v2/mongox/base/oidbased"
"github.com/mainnika/mongox-go-driver/v2/mongox/base/stringbased"
"github.com/mainnika/mongox-go-driver/v2/mongox" "github.com/mainnika/mongox-go-driver/v2/mongox"
"github.com/mainnika/mongox-go-driver/v2/mongox/utils"
) )
// GetID returns source document id // GetID returns source document id
func GetID(source interface{}) (id interface{}) { func GetID(source interface{}) (id interface{}, err error) {
switch doc := source.(type) { switch doc := source.(type) {
case mongox.OIDBased: case mongox.OIDBased:
return getObjectIDOrGenerate(doc) return oidbased.GetID(doc)
case mongox.StringBased: case mongox.StringBased:
return getStringIDOrPanic(doc) return stringbased.GetID(doc)
case mongox.JSONBased: case mongox.DocBased:
return getObjectOrPanic(doc) return docbased.GetID(doc)
case mongox.InterfaceBased: case mongox.InterfaceBased:
return getInterfaceOrPanic(doc) return ifacebased.GetID(doc)
default: default:
panic(fmt.Errorf("source contains malformed document, %v", source)) return nil, fmt.Errorf("%w: unknown base type", mongox.ErrMalformedBase)
} }
} }
func getObjectIDOrGenerate(source mongox.OIDBased) (id primitive.ObjectID) {
id = source.GetID()
if id != primitive.NilObjectID {
return id
}
id = primitive.NewObjectID()
source.SetID(id)
return
}
func getStringIDOrPanic(source mongox.StringBased) (id string) {
id = source.GetID()
if id != "" {
return id
}
panic(fmt.Errorf("source contains malformed document, %v", source))
}
func getObjectOrPanic(source mongox.JSONBased) (id primitive.D) {
id = source.GetID()
if id != nil {
return id
}
panic(fmt.Errorf("source contains malformed document, %v", source))
}
func getInterfaceOrPanic(source mongox.InterfaceBased) (id interface{}) {
id = source.GetID()
if !utils.IsNil(id) {
return id
}
panic(fmt.Errorf("source contains malformed document, %v", source))
}
+18 -7
View File
@@ -1,12 +1,14 @@
package base package base_test
import ( import (
"github.com/mainnika/mongox-go-driver/v2/mongox/base/docbased"
"github.com/stretchr/testify/require"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson/primitive" "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"
"github.com/mainnika/mongox-go-driver/v2/mongox/base/oidbased" "github.com/mainnika/mongox-go-driver/v2/mongox/base/oidbased"
"github.com/mainnika/mongox-go-driver/v2/mongox/base/stringbased" "github.com/mainnika/mongox-go-driver/v2/mongox/base/stringbased"
) )
@@ -28,15 +30,24 @@ func TestGetID(t *testing.T) {
type DocWithObjectID struct { type DocWithObjectID struct {
oidbased.Primary `bson:",inline" json:",inline" collection:"1"` oidbased.Primary `bson:",inline" json:",inline" collection:"1"`
} }
id, err := base.GetID(&DocWithObjectID{Primary: oidbased.Primary{ID: [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2}}})
require.NoError(t, err)
assert.Equal(t, primitive.ObjectID([12]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2}), id)
type DocWithObject struct { type DocWithObject struct {
jsonbased.Primary `bson:",inline" json:",inline" collection:"2"` docbased.Primary `bson:",inline" json:",inline" collection:"2"`
} }
id, err = base.GetID(&DocWithObject{Primary: docbased.Primary{ID: primitive.D{{"1", "2"}}}})
require.NoError(t, err)
assert.Equal(t, primitive.D{{"1", "2"}}, id)
type DocWithString struct { type DocWithString struct {
stringbased.Primary `bson:",inline" json:",inline" collection:"3"` stringbased.Primary `bson:",inline" json:",inline" collection:"3"`
} }
id, err = base.GetID(&DocWithString{Primary: stringbased.Primary{ID: "foobar"}})
assert.Equal(t, "foobar", id)
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}}})) id, err = base.GetID(&DocWithCustomInterface{ID: 420})
assert.Equal(t, primitive.D{{"1", "2"}}, GetID(&DocWithObject{jsonbased.Primary{primitive.D{{"1", "2"}}}})) assert.NoError(t, err)
assert.Equal(t, "foobar", GetID(&DocWithString{stringbased.Primary{"foobar"}})) assert.Equal(t, 420, id)
assert.Equal(t, 420, GetID(&DocWithCustomInterface{ID: 420}))
} }
-38
View File
@@ -1,38 +0,0 @@
package base
import (
"reflect"
"github.com/mainnika/mongox-go-driver/v2/mongox/base/protection"
)
// GetProtection function finds protection field in the source document otherwise returns nil
func GetProtection(source interface{}) *protection.Key {
v := reflect.ValueOf(source)
if v.Kind() != reflect.Ptr || v.IsNil() {
return nil
}
el := v.Elem()
numField := el.NumField()
for i := 0; i < numField; i++ {
field := el.Field(i)
if !field.CanInterface() {
continue
}
switch field.Interface().(type) {
case *protection.Key:
return field.Interface().(*protection.Key)
case protection.Key:
ptr := field.Addr()
return ptr.Interface().(*protection.Key)
default:
continue
}
}
return nil
}
+16
View File
@@ -0,0 +1,16 @@
package ifacebased
import (
"github.com/mainnika/mongox-go-driver/v2/mongox"
"github.com/modern-go/reflect2"
)
// GetID returns an _id from the source document
func GetID(source mongox.InterfaceBased) (id interface{}, err error) {
id = source.GetID()
if !reflect2.IsNil(id) {
return id, nil
}
return nil, mongox.ErrUninitializedBase
}
-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() primitive.D {
return p.ID
}
// SetID sets an _id
func (p *Primary) SetID(id primitive.D) {
p.ID = id
}
+20 -1
View File
@@ -14,7 +14,7 @@ type Primary struct {
} }
// GetID returns an _id // GetID returns an _id
func (p *Primary) GetID() primitive.ObjectID { func (p *Primary) GetID() (id primitive.ObjectID) {
return p.ID return p.ID
} }
@@ -22,3 +22,22 @@ func (p *Primary) GetID() primitive.ObjectID {
func (p *Primary) SetID(id primitive.ObjectID) { func (p *Primary) SetID(id primitive.ObjectID) {
p.ID = id p.ID = id
} }
// Generate creates a new Primary structure with a new objectId
func Generate() Primary {
return Primary{ID: primitive.NewObjectID()}
}
// New creates a new Primary structure with a defined objectId
func New(id primitive.ObjectID) Primary {
return Primary{ID: id}
}
func GetID(source mongox.OIDBased) (id primitive.ObjectID, err error) {
id = source.GetID()
if id != primitive.NilObjectID {
return id, nil
}
return primitive.NilObjectID, mongox.ErrUninitializedBase
}
+14 -17
View File
@@ -1,4 +1,4 @@
package oidbased package oidbased_test
import ( import (
"encoding/json" "encoding/json"
@@ -8,27 +8,26 @@ import (
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
"github.com/mainnika/mongox-go-driver/v2/mongox-testing/database" "github.com/mainnika/mongox-go-driver/v2/mongox-testing/database"
"github.com/mainnika/mongox-go-driver/v2/mongox/base/oidbased"
) )
func Test_GetID(t *testing.T) { func Test_GetID(t *testing.T) {
type DocWithObjectID struct { type DocWithObjectID struct {
Primary `bson:",inline" json:",inline" collection:"1"` oidbased.Primary `bson:",inline" json:",inline" collection:"1"`
} }
doc := &DocWithObjectID{Primary{[12]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2}}} doc := &DocWithObjectID{Primary: oidbased.New([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()) 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) { func Test_SetID(t *testing.T) {
type DocWithObjectID struct { type DocWithObjectID struct {
Primary `bson:",inline" json:",inline" collection:"1"` oidbased.Primary `bson:",inline" json:",inline" collection:"1"`
} }
doc := &DocWithObjectID{} doc := &DocWithObjectID{Primary: oidbased.Generate()}
doc.SetID([12]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2}) 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.Primary.ID)
@@ -36,20 +35,19 @@ func Test_SetID(t *testing.T) {
} }
func Test_SaveLoad(t *testing.T) { func Test_SaveLoad(t *testing.T) {
type DocWithObjectID struct { type DocWithObjectID struct {
Primary `bson:",inline" json:",inline" collection:"1"` oidbased.Primary `bson:",inline" json:",inline" collection:"1"`
} }
db, err := database.NewEphemeral("mongodb://localhost") db, err := database.NewEphemeral("")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer db.Close() defer func() { _ = db.Close() }()
doc1 := &DocWithObjectID{} doc1 := &DocWithObjectID{Primary: oidbased.Generate()}
doc2 := &DocWithObjectID{} doc2 := &DocWithObjectID{Primary: oidbased.Generate()}
err = db.SaveOne(doc1) err = db.SaveOne(doc1)
assert.NoError(t, err) assert.NoError(t, err)
@@ -66,13 +64,12 @@ func Test_SaveLoad(t *testing.T) {
} }
func Test_Marshal(t *testing.T) { func Test_Marshal(t *testing.T) {
type DocWithObjectID struct { type DocWithObjectID struct {
Primary `bson:",inline" json:",inline" collection:"1"` oidbased.Primary `bson:",inline" json:",inline" collection:"1"`
} }
id, _ := primitive.ObjectIDFromHex("feadbeeffeadbeeffeadbeef") id, _ := primitive.ObjectIDFromHex("feadbeeffeadbeeffeadbeef")
doc := &DocWithObjectID{Primary{id}} doc := &DocWithObjectID{Primary: oidbased.New(id)}
bytes, err := json.Marshal(doc) bytes, err := json.Marshal(doc)
assert.NoError(t, err) assert.NoError(t, err)
+57
View File
@@ -1,6 +1,10 @@
package protection package protection
import ( import (
"reflect"
"time"
"github.com/modern-go/reflect2"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
) )
@@ -9,3 +13,56 @@ type Key struct {
X primitive.ObjectID `bson:"_x" json:"_x" index:",hashed"` X primitive.ObjectID `bson:"_x" json:"_x" index:",hashed"`
V int64 `bson:"_v" json:"_v"` V int64 `bson:"_v" json:"_v"`
} }
// Inject extends the doc with protection key values
func (k *Key) Inject(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()
}
// Get finds protection field in the source document otherwise returns nil
func Get(source interface{}) (key *Key) {
v := reflect.ValueOf(source)
if v.Kind() != reflect.Ptr || v.IsNil() {
return nil
}
el := v.Elem()
numField := el.NumField()
for i := 0; i < numField; i++ {
field := el.Field(i)
if !field.CanInterface() {
continue
}
switch field.Interface().(type) {
case *Key:
key = field.Interface().(*Key)
case Key:
ptr := field.Addr()
key = ptr.Interface().(*Key)
default:
continue
}
return key
}
return nil
}
+3
View File
@@ -0,0 +1,3 @@
package protection_test
// TODO:
+6 -4
View File
@@ -1,12 +1,12 @@
package base package base
import ( import (
"fmt"
"reflect" "reflect"
) )
// Reset function creates new zero object for the target pointer // Reset function creates new zero object for the target pointer
func Reset(target interface{}) { func Reset(target interface{}) (created bool) {
type resetter interface { type resetter interface {
Reset() Reset()
} }
@@ -14,16 +14,18 @@ func Reset(target interface{}) {
resettable, canReset := target.(resetter) resettable, canReset := target.(resetter)
if canReset { if canReset {
resettable.Reset() resettable.Reset()
return return false
} }
v := reflect.ValueOf(target) v := reflect.ValueOf(target)
if v.Kind() != reflect.Ptr { if v.Kind() != reflect.Ptr {
panic("reset target should be a pointer") panic(fmt.Errorf("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
} }
+15 -1
View File
@@ -12,7 +12,7 @@ type Primary struct {
} }
// GetID returns an _id // GetID returns an _id
func (p *Primary) GetID() string { func (p *Primary) GetID() (id string) {
return p.ID return p.ID
} }
@@ -20,3 +20,17 @@ func (p *Primary) GetID() string {
func (p *Primary) SetID(id string) { func (p *Primary) SetID(id string) {
p.ID = id p.ID = id
} }
// New creates a new Primary structure with a defined _id
func New(id string) Primary {
return Primary{ID: id}
}
func GetID(source mongox.StringBased) (id string, err error) {
id = source.GetID()
if id != "" {
return id, nil
}
return "", mongox.ErrUninitializedBase
}
+12 -15
View File
@@ -1,4 +1,4 @@
package stringbased package stringbased_test
import ( import (
"encoding/json" "encoding/json"
@@ -7,26 +7,25 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/mainnika/mongox-go-driver/v2/mongox-testing/database" "github.com/mainnika/mongox-go-driver/v2/mongox-testing/database"
"github.com/mainnika/mongox-go-driver/v2/mongox/base/stringbased"
) )
func Test_GetID(t *testing.T) { func Test_GetID(t *testing.T) {
type DocWithString struct { type DocWithString struct {
Primary `bson:",inline" json:",inline" collection:"1"` stringbased.Primary `bson:",inline" json:",inline" collection:"1"`
} }
doc := &DocWithString{Primary{"foobar"}} doc := &DocWithString{Primary: stringbased.New("foobar")}
assert.Equal(t, "foobar", doc.GetID()) assert.Equal(t, "foobar", doc.GetID())
} }
func Test_SetID(t *testing.T) { func Test_SetID(t *testing.T) {
type DocWithString struct { type DocWithString struct {
Primary `bson:",inline" json:",inline" collection:"1"` stringbased.Primary `bson:",inline" json:",inline" collection:"1"`
} }
doc := &DocWithString{Primary{"foobar"}} doc := &DocWithString{Primary: stringbased.New("foobar")}
doc.SetID("rockrockrock") doc.SetID("rockrockrock")
@@ -35,19 +34,18 @@ func Test_SetID(t *testing.T) {
} }
func Test_SaveLoad(t *testing.T) { func Test_SaveLoad(t *testing.T) {
type DocWithObjectID struct { type DocWithObjectID struct {
Primary `bson:",inline" json:",inline" collection:"1"` stringbased.Primary `bson:",inline" json:",inline" collection:"1"`
} }
db, err := database.NewEphemeral("mongodb://localhost") db, err := database.NewEphemeral("")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer db.Close() defer func() { _ = db.Close() }()
doc1 := &DocWithObjectID{Primary{"foobar"}} doc1 := &DocWithObjectID{Primary: stringbased.New("foobar")}
doc2 := &DocWithObjectID{} doc2 := &DocWithObjectID{}
err = db.SaveOne(doc1) err = db.SaveOne(doc1)
@@ -65,12 +63,11 @@ func Test_SaveLoad(t *testing.T) {
} }
func Test_Marshal(t *testing.T) { func Test_Marshal(t *testing.T) {
type DocWithObjectID struct { type DocWithObjectID struct {
Primary `bson:",inline" json:",inline" collection:"1"` stringbased.Primary `bson:",inline" json:",inline" collection:"1"`
} }
doc := &DocWithObjectID{Primary{"foobar"}} doc := &DocWithObjectID{Primary: stringbased.New("foobar")}
bytes, err := json.Marshal(doc) bytes, err := json.Marshal(doc)
assert.NoError(t, err) assert.NoError(t, err)
+23
View File
@@ -0,0 +1,23 @@
package database
import (
"context"
)
type ctxDatabaseKey struct{}
// GetFromContext function extracts the request data from context
func GetFromContext(ctx context.Context) (q *Database, ok bool) {
q, ok = ctx.Value(ctxDatabaseKey{}).(*Database)
if !ok {
return nil, false
}
return q, true
}
// WithContext creates the new context with a database attached
func WithContext(ctx context.Context, q *Database) (withQuery context.Context) {
db := NewDatabase(ctx, q.Client(), q.Name())
return context.WithValue(ctx, ctxDatabaseKey{}, db)
}
+17 -12
View File
@@ -1,31 +1,36 @@
package database package database
import ( import (
"fmt"
"go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/options"
"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/query"
) )
// Count function counts documents in the database by 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 // 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{}) (int64, error) { func (d *Database) Count(target interface{}, filters ...interface{}) (result int64, err error) {
composed, err := query.Compose(filters...)
if err != nil {
return -1, err
}
collection, err := d.GetCollectionOf(target)
if err != nil {
return -1, err
}
ctx := query.WithContext(d.Context(), composed)
m := composed.M()
collection := d.GetCollectionOf(target)
opts := options.Count() opts := options.Count()
composed := query.Compose(filters...)
opts.Limit = composed.Limiter() opts.Limit = composed.Limiter()
opts.Skip = composed.Skipper() opts.Skip = composed.Skipper()
result, err := collection.CountDocuments(d.Context(), composed.M(), opts) defer func() { _ = composed.OnClose().Invoke(ctx, target) }()
if err == mongox.ErrNoDocuments {
return 0, err result, err = collection.CountDocuments(ctx, m, opts)
}
if err != nil { if err != nil {
return 0, fmt.Errorf("can't decode desult: %w", err) return -1, err
} }
return result, nil return result, nil
+188
View File
@@ -0,0 +1,188 @@
package database
import (
"fmt"
"reflect"
"strconv"
"strings"
"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/query"
)
func (d *Database) createCursor(target interface{}, composed *query.Query) (cursor *mongox.Cursor, err error) {
_, hasPreloader := composed.Preloader()
if hasPreloader {
return d.createAggregateCursor(target, composed)
}
return d.createSimpleCursor(target, composed)
}
func (d *Database) createSimpleCursor(target interface{}, composed *query.Query) (cursor *mongox.Cursor, err error) {
collection, err := d.GetCollectionOf(target)
if err != nil {
return nil, err
}
opts := options.Find()
opts.Sort = composed.Sorter()
opts.Limit = composed.Limiter()
opts.Skip = composed.Skipper()
ctx := d.Context()
m := composed.M()
return collection.Find(ctx, m, opts)
}
func (d *Database) createAggregateCursor(target interface{}, composed *query.Query) (cursor *mongox.Cursor, err error) {
collection, err := d.GetCollectionOf(target)
if err != nil {
return nil, err
}
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)
elType := el.Type()
if elType.Kind() == reflect.Ptr {
elType = elType.Elem()
}
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 == "-" {
return nil, fmt.Errorf("%w: private field is not preloadable", mongox.ErrMalformedBase)
}
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 {
return nil, fmt.Errorf("%w: foreign field is not specified", mongox.ErrMalformedBase)
}
foreignField := strings.TrimSpace(preloadData[1])
if len(foreignField) == 0 {
return nil, fmt.Errorf("%w: foreign field is empty", mongox.ErrMalformedBase)
}
localField := strings.TrimSpace(preloadData[0])
if len(localField) == 0 {
localField = "_id"
}
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
} else {
return nil, fmt.Errorf("%w: preload limit should be an integer", mongox.ErrMalformedBase)
}
}
for _, preload := range preloads {
if preload != jsonName {
continue
}
field := elType.Field(i)
fieldType := field.Type
isSlice := fieldType.Kind() == reflect.Slice
if isSlice {
fieldType = fieldType.Elem()
}
isPtr := fieldType.Kind() != reflect.Ptr
if isPtr {
return nil, fmt.Errorf("%w: preload field should have ptr type", mongox.ErrMalformedBase)
}
lookupCollection, err := d.GetCollectionOf(reflect.Zero(fieldType).Interface())
if err != nil {
return nil, err
}
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,
},
})
}
}
ctx := d.Context()
opts := options.Aggregate()
return collection.Aggregate(ctx, pipeline, opts)
}
+25 -193
View File
@@ -2,238 +2,70 @@ package database
import ( import (
"context" "context"
"fmt"
"reflect" "reflect"
"strconv"
"strings"
"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"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
) )
// Database handler // Database handler
type Database struct { type Database struct {
client *mongox.Client client *mongox.Client
dbname string name 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) mongox.Database { func NewDatabase(ctx context.Context, client *mongox.Client, name string) (db mongox.Database) {
db = &Database{
db := &Database{} client: client,
db.client = client name: name,
db.dbname = dbname ctx: ctx,
}
return db return db
} }
// Client function returns a mongo client // Client function returns a mongo client
func (d *Database) Client() *mongox.Client { func (d *Database) Client() (client *mongox.Client) {
return d.client return d.client
} }
// Name function returns a database name
func (d *Database) Name() (name string) {
return d.name
}
// Context function returns a context // Context function returns a context
func (d *Database) Context() (ctx context.Context) { func (d *Database) Context() (ctx context.Context) {
ctx = d.ctx ctx = d.ctx
if ctx == nil { if ctx == nil {
ctx = context.Background() ctx = context.Background()
} }
return return ctx
}
// Name function returns a database name
func (d *Database) Name() string {
return d.dbname
}
// New function creates new database context with same client
func (d *Database) New(ctx context.Context) mongox.Database {
if ctx == nil {
ctx = context.Background()
}
return &Database{
client: d.client,
dbname: d.dbname,
ctx: ctx,
}
} }
// 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;
// the «collection» tag should exists, e.g.: //
// type Foobar struct { // example:
// base.ObjectID `bson:",inline" json:",inline" collection:"foobars"` // type Foobar struct {
// ... // base.ObjectID `bson:",inline" json:",inline" collection:"foobars"`
// Will panic if there is no «collection» tag // ...
func (d *Database) GetCollectionOf(document interface{}) *mongox.Collection { func (d *Database) GetCollectionOf(document interface{}) (collection *mongox.Collection, err error) {
el := reflect.TypeOf(document).Elem() el := reflect.TypeOf(document).Elem()
numField := el.NumField() numField := el.NumField()
databaseName := d.name
for i := 0; i < numField; i++ { for i := 0; i < numField; i++ {
field := el.Field(i) field := el.Field(i)
tag := field.Tag tag := field.Tag
found, ok := tag.Lookup("collection") collectionName, found := tag.Lookup("collection")
if !ok { if !found {
continue continue
} }
return d.client.Database(d.dbname).Collection(found) return d.client.Database(databaseName).Collection(collectionName), nil
} }
panic(fmt.Errorf("document %v does not have a collection tag", document)) return nil, mongox.ErrNoCollection
}
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": 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, 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("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 := 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)
} }
+35 -17
View File
@@ -8,11 +8,11 @@ import (
"go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/options"
"github.com/mainnika/mongox-go-driver/v2/mongox/base" "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 // DeleteArray removes documents list from a database by their ids
func (d *Database) DeleteArray(target interface{}) error { func (d *Database) DeleteArray(target interface{}, filters ...interface{}) (err error) {
targetV := reflect.ValueOf(target) targetV := reflect.ValueOf(target)
targetT := targetV.Type() targetT := targetV.Type()
@@ -34,25 +34,43 @@ func (d *Database) DeleteArray(target interface{}) error {
zeroElem := reflect.Zero(targetSliceElemT) zeroElem := reflect.Zero(targetSliceElemT)
targetLen := targetSliceV.Len() targetLen := targetSliceV.Len()
collection := d.GetCollectionOf(zeroElem.Interface())
opts := options.Delete()
ids := primitive.A{}
for i := 0; i < targetLen; i++ { collection, err := d.GetCollectionOf(zeroElem.Interface())
elem := targetSliceV.Index(i)
ids = append(ids, base.GetID(elem.Interface()))
}
if len(ids) == 0 {
return fmt.Errorf("can't delete zero elements")
}
result, err := collection.DeleteMany(d.Context(), primitive.M{"_id": primitive.M{"$in": ids}}, opts)
if err != nil { if err != nil {
return fmt.Errorf("can't create find and delete result: %w", err) return err
}
composed, err := query.Compose(filters...)
if err != nil {
return err
}
if targetLen > 0 {
var ids primitive.A
for i := 0; i < targetLen; i++ {
elem := targetSliceV.Index(i)
elemID, err := base.GetID(elem.Interface())
if err != nil {
return err
}
ids = append(ids, elemID)
}
composed.And(primitive.M{"_id": primitive.M{"$in": ids}})
}
ctx := query.WithContext(d.Context(), composed)
m := composed.M()
opts := options.Delete()
defer func() { _ = composed.OnClose().Invoke(ctx, target) }()
result, err := collection.DeleteMany(ctx, m, opts)
if err != nil {
return fmt.Errorf("while deleting array: %w", err)
} }
if result.DeletedCount != int64(targetLen) { if result.DeletedCount != int64(targetLen) {
return fmt.Errorf("can't verify delete result: removed count mismatch %d != %d", result.DeletedCount, targetLen) return fmt.Errorf("deleted count mismatch %d != %d", result.DeletedCount, targetLen)
} }
return nil return nil
+39 -23
View File
@@ -2,49 +2,65 @@ package database
import ( import (
"fmt" "fmt"
"time" "github.com/mainnika/mongox-go-driver/v2/mongox/base/protection"
"github.com/modern-go/reflect2"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options" "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/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/query" "github.com/mainnika/mongox-go-driver/v2/mongox/query"
"github.com/mainnika/mongox-go-driver/v2/mongox/utils"
) )
// DeleteOne removes a document from a database and then returns it into target // DeleteOne removes a document from a database and then returns it into target
func (d *Database) DeleteOne(target interface{}, filters ...interface{}) error { func (d *Database) DeleteOne(target interface{}, filters ...interface{}) (err error) {
composed, err := query.Compose(filters...)
if err != nil {
return err
}
collection := d.GetCollectionOf(target) collection, err := d.GetCollectionOf(target)
opts := &options.FindOneAndDeleteOptions{} if err != nil {
composed := query.Compose(filters...) return err
protected := base.GetProtection(target) }
if !reflect2.IsNil(target) {
targetID, err := base.GetID(target)
if err != nil {
return err
}
composed.And(primitive.M{"_id": targetID})
}
protected := protection.Get(target)
if protected != nil {
_, err := query.Push(composed, protected)
if err != nil {
return err
}
protected.Restate()
}
ctx := query.WithContext(d.Context(), composed)
m := composed.M()
opts := options.FindOneAndDelete()
opts.Sort = composed.Sorter() opts.Sort = composed.Sorter()
if !utils.IsNil(target) { defer func() { _ = composed.OnClose().Invoke(ctx, target) }()
composed.And(primitive.M{"_id": base.GetID(target)})
}
if protected != nil { result := collection.FindOneAndDelete(ctx, m, opts)
query.Push(composed, protected)
protected.X = primitive.NewObjectID()
protected.V = time.Now().Unix()
}
result := collection.FindOneAndDelete(d.Context(), composed.M(), opts)
if result.Err() != nil { if result.Err() != nil {
return fmt.Errorf("can't create find one and delete result: %w", result.Err()) return fmt.Errorf("can't create find one and delete result: %w", result.Err())
} }
err := result.Decode(target) err = result.Decode(target)
if err == mongox.ErrNoDocuments {
return err
}
if err != nil { if err != nil {
return fmt.Errorf("can't decode result: %w", err) return fmt.Errorf("can't decode find one and delete result: %w", err)
} }
_ = composed.OnDecode().Invoke(ctx, target)
return nil return nil
} }
+26 -21
View File
@@ -14,18 +14,21 @@ import (
) )
// IndexEnsure function ensures index in mongo collection of document // IndexEnsure function ensures index in mongo collection of document
// `index:""` -- https://docs.mongodb.com/manual/indexes/#create-an-index //
// `index:"-"` -- (descending) // `index:""` -- https://docs.mongodb.com/manual/indexes/#create-an-index
// `index:"-,+foo,+-bar"` -- https://docs.mongodb.com/manual/core/index-compound // `index:"-"` -- (descending)
// `index:"-,+foo,+-bar,unique"` -- https://docs.mongodb.com/manual/core/index-unique // `index:"-,+foo,+-bar"` -- https://docs.mongodb.com/manual/core/index-compound
// `index:"-,+foo,+-bar,unique,allowNull"` -- https://docs.mongodb.com/manual/core/index-partial // `index:"-,+foo,+-bar,unique"` -- https://docs.mongodb.com/manual/core/index-unique
// `index:"-,unique,allowNull,expireAfter=86400"` -- https://docs.mongodb.com/manual/core/index-ttl // `index:"-,+foo,+-bar,unique,allowNull"` -- https://docs.mongodb.com/manual/core/index-partial
// `index:"-,unique,allowNull,expireAfter={{.Expire}}"` -- evaluate index as a golang template with `cfg` arguments // `index:"-,unique,allowNull,expireAfter=86400"` -- https://docs.mongodb.com/manual/core/index-ttl
func (d *Database) IndexEnsure(cfg interface{}, document interface{}) error { // `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() el := reflect.ValueOf(document).Elem().Type()
numField := el.NumField() numField := el.NumField()
documents := d.GetCollectionOf(document) collection, err := d.GetCollectionOf(document)
if err != nil {
return err
}
for i := 0; i < numField; i++ { for i := 0; i < numField; i++ {
@@ -41,14 +44,16 @@ func (d *Database) IndexEnsure(cfg interface{}, document interface{}) error {
return fmt.Errorf("bson tag is not defined for field:%v document:%v", field, document) return fmt.Errorf("bson tag is not defined for field:%v document:%v", field, document)
} }
tmpBuffer := &bytes.Buffer{} var tmpBuffer = &bytes.Buffer{}
tpl, err := template.New("").Parse(indexTag) var tpl *template.Template
tpl, err = template.New("").Parse(indexTag)
if err != nil { if err != nil {
panic(fmt.Errorf("invalid prop template, %v", indexTag)) panic(fmt.Errorf("invalid prop template %v, err:%w", indexTag, err))
} }
err = tpl.Execute(tmpBuffer, cfg) err = tpl.Execute(tmpBuffer, cfg)
if err != nil { if err != nil {
panic(fmt.Errorf("failed to evaluate prop template, %v", indexTag)) panic(fmt.Errorf("failed to evaluate prop template %v, err:%w", indexTag, err))
} }
indexString := tmpBuffer.String() indexString := tmpBuffer.String()
@@ -64,15 +69,15 @@ func (d *Database) IndexEnsure(cfg interface{}, document interface{}) error {
panic(fmt.Errorf("cannot evaluate index key")) panic(fmt.Errorf("cannot evaluate index key"))
} }
index := primitive.M{key: 1}
opts := &options.IndexOptions{ opts := &options.IndexOptions{
Background: &f, Background: &f,
Unique: &f, Unique: &f,
Name: &name, Name: &name,
} }
index := primitive.D{{Key: key, Value: 1}}
if indexValues[0] == "-" { if indexValues[0] == "-" {
index[key] = -1 index = primitive.D{{Key: key, Value: -1}}
} }
for _, prop := range indexValues[1:] { for _, prop := range indexValues[1:] {
@@ -114,9 +119,9 @@ func (d *Database) IndexEnsure(cfg interface{}, document interface{}) error {
} }
if compoundValue[0] == '-' { if compoundValue[0] == '-' {
index[compoundValue[1:]] = -1 index = append(index, primitive.E{compoundValue[1:], -1})
} else { } else {
index[compoundValue] = 1 index = append(index, primitive.E{compoundValue, 1})
} }
default: default:
@@ -124,11 +129,11 @@ func (d *Database) IndexEnsure(cfg interface{}, document interface{}) error {
} }
} }
_, err = documents.Indexes().CreateOne(d.Context(), mongo.IndexModel{Keys: index, Options: opts}) _, err = collection.Indexes().CreateOne(d.Context(), mongo.IndexModel{Keys: index, Options: opts})
if err != nil { if err != nil {
return err return
} }
} }
return nil return
} }
+6 -2
View File
@@ -1,6 +1,7 @@
package database_test package database_test
import ( import (
"github.com/stretchr/testify/require"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -11,7 +12,7 @@ import (
func TestDatabase_Ensure(t *testing.T) { func TestDatabase_Ensure(t *testing.T) {
db, err := database.NewEphemeral("mongodb://localhost") db, err := database.NewEphemeral("")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -142,7 +143,10 @@ func TestDatabase_Ensure(t *testing.T) {
err = db.IndexEnsure(tt.settings, tt.doc) err = db.IndexEnsure(tt.settings, tt.doc)
assert.NoError(t, err) assert.NoError(t, err)
indexes, _ := db.GetCollectionOf(tt.doc).Indexes().List(db.Context()) collection, err := db.GetCollectionOf(tt.doc)
require.NoError(t, err)
indexes, _ := collection.Indexes().List(db.Context())
index := new(map[string]interface{}) index := new(map[string]interface{})
indexes.Next(db.Context()) // skip _id_ indexes.Next(db.Context()) // skip _id_
+40 -26
View File
@@ -4,14 +4,12 @@ import (
"fmt" "fmt"
"reflect" "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/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/query" "github.com/mainnika/mongox-go-driver/v2/mongox/query"
) )
// LoadArray loads an array of documents from the database by query // LoadArray loads an array of documents from the database by query
func (d *Database) LoadArray(target interface{}, filters ...interface{}) error { func (d *Database) LoadArray(target interface{}, filters ...interface{}) (err error) {
targetV := reflect.ValueOf(target) targetV := reflect.ValueOf(target)
targetT := targetV.Type() targetT := targetV.Type()
@@ -31,45 +29,61 @@ func (d *Database) LoadArray(target interface{}, filters ...interface{}) error {
panic(fmt.Errorf("target slice should contain ptrs")) panic(fmt.Errorf("target slice should contain ptrs"))
} }
composed := query.Compose(filters...)
zeroElem := reflect.Zero(targetSliceElemT) zeroElem := reflect.Zero(targetSliceElemT)
hasPreloader, _ := composed.Preloader()
var result *mongox.Cursor composed, err := query.Compose(filters...)
var err error if err != nil {
return err
if hasPreloader {
result, err = d.createAggregateLoad(zeroElem.Interface(), composed)
} else {
result, err = d.createSimpleLoad(zeroElem.Interface(), composed)
} }
ctx := query.WithContext(d.Context(), composed)
defer func() { _ = composed.OnClose().Invoke(ctx, target) }()
cur, err := d.createCursor(zeroElem.Interface(), composed)
if err != nil { if err != nil {
return fmt.Errorf("can't create find result: %w", err) return fmt.Errorf("can't create find result: %w", err)
} }
var i int defer func() { _ = cur.Close(ctx) }()
for i = 0; result.Next(d.Context()); { var i int
if targetSliceV.Len() == i { for i = 0; cur.Next(ctx); i++ {
elem := reflect.New(targetSliceElemT.Elem()) var elem interface{}
if err = result.Decode(elem.Interface()); err == nil { if i == targetSliceV.Len() {
targetSliceV = reflect.Append(targetSliceV, elem) value := reflect.New(targetSliceElemT.Elem())
} else { elem = value.Interface()
continue
_ = composed.OnCreate().Invoke(ctx, elem)
err = cur.Decode(elem)
if err != nil {
return err
} }
targetSliceV = reflect.Append(targetSliceV, value)
} else { } else {
elem := targetSliceV.Index(i).Interface() elem = targetSliceV.Index(i).Interface()
base.Reset(elem)
if err = result.Decode(elem); err != nil { if created := base.Reset(elem); created {
continue _ = composed.OnCreate().Invoke(ctx, elem)
}
err = cur.Decode(elem)
if err != nil {
return err
} }
} }
i++ _ = composed.OnDecode().Invoke(ctx, elem)
}
err = cur.Err()
if err != nil {
return err
} }
targetSliceV = targetSliceV.Slice(0, i) targetSliceV = targetSliceV.Slice(0, i)
targetV.Elem().Set(targetSliceV) targetV.Elem().Set(targetSliceV)
return result.Close(d.Context()) return nil
} }
+26 -20
View File
@@ -1,40 +1,46 @@
package database package database
import ( import (
"fmt"
"github.com/mainnika/mongox-go-driver/v2/mongox" "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/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/query" "github.com/mainnika/mongox-go-driver/v2/mongox/query"
) )
// LoadOne function loads a first single target document by a query // LoadOne function loads a first single target document by a query
func (d *Database) LoadOne(target interface{}, filters ...interface{}) error { func (d *Database) LoadOne(target interface{}, filters ...interface{}) (err error) {
composed, err := query.Compose(append(filters, query.Limit(1))...)
composed := query.Compose(append(filters, query.Limit(1))...)
hasPreloader, _ := composed.Preloader()
var result *mongox.Cursor
var err error
if hasPreloader {
result, err = d.createAggregateLoad(target, composed)
} else {
result, err = d.createSimpleLoad(target, composed)
}
if err != nil { if err != nil {
return fmt.Errorf("can't create find result: %w", err) return err
} }
hasNext := result.Next(d.Context()) ctx := query.WithContext(d.Context(), composed)
if result.Err() != nil {
defer func() { _ = composed.OnClose().Invoke(ctx, target) }()
cur, err := d.createCursor(target, composed)
if err != nil {
return err return err
} }
defer func() { _ = cur.Close(ctx) }()
hasNext := cur.Next(ctx)
if cur.Err() != nil {
return cur.Err()
}
if !hasNext { if !hasNext {
return mongox.ErrNoDocuments return mongox.ErrNoDocuments
} }
base.Reset(target) if created := base.Reset(target); created {
_ = composed.OnCreate().Invoke(ctx, target)
}
return result.Decode(target) err = cur.Decode(target)
if err != nil {
return err
}
_ = composed.OnDecode().Invoke(ctx, target)
return nil
} }
+12 -18
View File
@@ -1,31 +1,25 @@
package database package database
import ( import (
"fmt"
"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/query"
) )
// LoadStream function loads documents one by one into a target channel // LoadStream function loads documents one by one into a target channel
func (d *Database) LoadStream(target interface{}, filters ...interface{}) (mongox.StreamLoader, error) { func (d *Database) LoadStream(target interface{}, filters ...interface{}) (loader mongox.StreamLoader, err error) {
composed, err := query.Compose(filters...)
var cursor *mongox.Cursor
var err error
composed := query.Compose(filters...)
hasPreloader, _ := composed.Preloader()
if hasPreloader {
cursor, err = d.createAggregateLoad(target, composed)
} else {
cursor, err = d.createSimpleLoad(target, composed)
}
if err != nil { if err != nil {
return nil, fmt.Errorf("can't create find result: %w", err) return
} }
l := &StreamLoader{cur: cursor, ctx: d.Context(), target: target} ctx := query.WithContext(d.Context(), composed)
return l, nil cur, err := d.createCursor(target, composed)
if err != nil {
return nil, err
}
loader = &StreamLoader{cur: cur, ctx: ctx, query: composed}
return loader, nil
} }
+38 -16
View File
@@ -1,9 +1,7 @@
package database package database
import ( import (
"time" "github.com/mainnika/mongox-go-driver/v2/mongox/base/protection"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/options"
@@ -12,27 +10,51 @@ import (
) )
// SaveOne saves a single source document to the database // SaveOne saves a single source document to the database
func (d *Database) SaveOne(source interface{}) error { func (d *Database) SaveOne(source interface{}, filters ...interface{}) (err error) {
collection, err := d.GetCollectionOf(source)
if err != nil {
return err
}
collection := d.GetCollectionOf(source) composed, err := query.Compose(filters...)
if err != nil {
return err
}
id, err := base.GetID(source)
if err != nil {
return err
}
composed.And(primitive.M{"_id": id})
protected := protection.Get(source)
if protected != nil {
query.Push(composed, protected)
protected.Restate()
}
ctx := query.WithContext(d.Context(), composed)
m := composed.M()
opts := options.FindOneAndReplace() opts := options.FindOneAndReplace()
id := base.GetID(source)
protected := base.GetProtection(source)
composed := query.Compose(bson.M{"_id": id})
opts.SetUpsert(true) opts.SetUpsert(true)
opts.SetReturnDocument(options.After) opts.SetReturnDocument(options.After)
if protected != nil { defer func() { _ = composed.OnClose().Invoke(ctx, source) }()
query.Push(composed, protected)
protected.X = primitive.NewObjectID()
protected.V = time.Now().Unix()
}
result := collection.FindOneAndReplace(d.Context(), composed.M(), source, opts) result := collection.FindOneAndReplace(ctx, m, source, opts)
if result.Err() != nil { if result.Err() != nil {
return result.Err() return result.Err()
} }
return result.Decode(source) err = result.Decode(source)
if err != nil {
return
}
err = composed.OnDecode().Invoke(ctx, source)
if err != nil {
return
}
return
} }
+50 -39
View File
@@ -2,61 +2,59 @@ package database
import ( import (
"context" "context"
"fmt"
"github.com/mainnika/mongox-go-driver/v2/mongox" "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/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
) )
// StreamLoader is a controller for a database cursor // StreamLoader is a controller for a database cursor
type StreamLoader struct { type StreamLoader struct {
cur *mongox.Cursor cur *mongox.Cursor
ctx context.Context query *query.Query
target interface{} ctx context.Context
} }
// DecodeNext loads next documents to a target or returns an error // DecodeNextMsg decodes the next document to an interface or returns an error
func (l *StreamLoader) DecodeNext() error { func (l *StreamLoader) DecodeNextMsg(i interface{}) (err error) {
err = l.Next()
hasNext := l.cur.Next(l.ctx)
if l.cur.Err() != nil {
return l.cur.Err()
}
if !hasNext {
return mongox.ErrNoDocuments
}
base.Reset(l.target)
err := l.cur.Decode(l.target)
if err != nil { if err != nil {
return fmt.Errorf("can't decode desult: %w", err) return
} }
return nil err = l.DecodeMsg(i)
if err != nil {
return
}
return
} }
// Decode function decodes the current cursor document into the target // DecodeMsg decodes the current cursor document into an interface
func (l *StreamLoader) Decode() error { func (l *StreamLoader) DecodeMsg(i interface{}) (err error) {
if created := base.Reset(i); created {
base.Reset(l.target) _ = l.query.OnDecode().Invoke(l.ctx, i)
err := l.cur.Decode(l.target)
if err != nil {
return fmt.Errorf("can't decode desult: %w", err)
} }
if err != nil {
return err
}
err = l.cur.Decode(i)
if err != nil {
return err
}
_ = l.query.OnDecode().Invoke(l.ctx, i)
return nil return nil
} }
// Next loads next documents but doesn't perform decoding // Next loads next documents but doesn't perform decoding
func (l *StreamLoader) Next() error { func (l *StreamLoader) Next() (err error) {
hasNext := l.cur.Next(l.ctx) hasNext := l.cur.Next(l.ctx)
err = l.cur.Err()
if l.cur.Err() != nil { if err != nil {
return l.cur.Err() return err
} }
if !hasNext { if !hasNext {
return mongox.ErrNoDocuments return mongox.ErrNoDocuments
@@ -65,15 +63,28 @@ func (l *StreamLoader) Next() error {
return nil return nil
} }
func (l *StreamLoader) Cursor() *mongox.Cursor { // Cursor returns the underlying cursor
func (l *StreamLoader) Cursor() (cursor *mongox.Cursor) {
return l.cur return l.cur
} }
// Close cursor // Close stream loader and the underlying cursor
func (l *StreamLoader) Close() error { func (l *StreamLoader) Close() (err error) {
return l.cur.Close(l.ctx) defer func() { _ = l.query.OnClose().Invoke(l.ctx, nil) }()
err = l.cur.Close(l.ctx)
if err != nil {
return err
}
return nil
} }
func (l *StreamLoader) Err() error { // Err returns the last error
func (l *StreamLoader) Err() (err error) {
return l.cur.Err() return l.cur.Err()
} }
func (l *StreamLoader) Context() (ctx context.Context) {
return l.ctx
}
+64
View File
@@ -0,0 +1,64 @@
package database
import (
"github.com/mainnika/mongox-go-driver/v2/mongox/base/protection"
"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/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 err
}
update, err := composed.Updater()
if err != nil {
return err
}
protected := protection.Get(target)
if protected != nil {
if !protected.X.IsZero() {
query.Push(composed, protected)
}
protected.Restate()
setCmd, _ := update["$set"].(primitive.M)
if reflect2.IsNil(setCmd) {
setCmd = primitive.M{}
}
protected.Inject(setCmd)
update["$set"] = setCmd
}
collection, err := d.GetCollectionOf(target)
if err != nil {
return err
}
ctx := query.WithContext(d.Context(), composed)
m := composed.M()
opts := options.FindOneAndUpdate()
opts.SetReturnDocument(options.After)
defer func() { _ = composed.OnClose().Invoke(ctx, target) }()
result := collection.FindOneAndUpdate(ctx, m, update, opts)
if result.Err() != nil {
return result.Err()
}
err = result.Decode(target)
if err != nil {
return err
}
_ = composed.OnDecode().Invoke(ctx, target)
return nil
}
+8
View File
@@ -1,6 +1,8 @@
package mongox package mongox
import ( import (
"errors"
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
) )
@@ -18,3 +20,9 @@ var (
ErrWrongClient = mongo.ErrWrongClient ErrWrongClient = mongo.ErrWrongClient
ErrNoDocuments = mongo.ErrNoDocuments ErrNoDocuments = mongo.ErrNoDocuments
) )
var (
ErrMalformedBase = errors.New("source contains malformed document base")
ErrUninitializedBase = errors.New("uninitialized document")
ErrNoCollection = errors.New("no collection found")
)
+25 -25
View File
@@ -16,51 +16,51 @@ type (
// Database is the mongox database interface // Database is the mongox database interface
type Database interface { type Database interface {
Client() *Client Client() (client *Client)
Context() context.Context Context() (context context.Context)
Name() string Name() (name string)
New(ctx context.Context) Database GetCollectionOf(document interface{}) (collection *Collection, err error)
GetCollectionOf(document interface{}) *Collection Count(target interface{}, filters ...interface{}) (count int64, err error)
Count(target interface{}, filters ...interface{}) (int64, error) DeleteArray(target interface{}, filters ...interface{}) (err error)
DeleteArray(target interface{}) error DeleteOne(target interface{}, filters ...interface{}) (err error)
DeleteOne(target interface{}, filters ...interface{}) error LoadArray(target interface{}, filters ...interface{}) (err error)
LoadArray(target interface{}, filters ...interface{}) error LoadOne(target interface{}, filters ...interface{}) (err error)
LoadOne(target interface{}, filters ...interface{}) error LoadStream(target interface{}, filters ...interface{}) (loader StreamLoader, err error)
LoadStream(target interface{}, filters ...interface{}) (StreamLoader, error) SaveOne(source interface{}, filters ...interface{}) (err error)
SaveOne(source interface{}) error UpdateOne(target interface{}, filters ...interface{}) (err error)
IndexEnsure(cfg interface{}, document interface{}) error IndexEnsure(cfg interface{}, document interface{}) (err error)
} }
// StreamLoader is a interface to control database cursor // StreamLoader is a interface to control database cursor
type StreamLoader interface { type StreamLoader interface {
Cursor() *Cursor Cursor() (cursor *Cursor)
DecodeNext() error DecodeNextMsg(i interface{}) (err error)
Decode() error DecodeMsg(i interface{}) (err error)
Next() error Next() (err error)
Close() error Close() (err error)
Err() error Err() (err error)
} }
// OIDBased is an interface for documents that have objectId type for the _id field // OIDBased is an interface for documents that have objectId type for the _id field
type OIDBased interface { type OIDBased interface {
GetID() primitive.ObjectID GetID() (id primitive.ObjectID)
SetID(id primitive.ObjectID) SetID(id primitive.ObjectID)
} }
// StringBased is an interface for documents that have string type for the _id field // StringBased is an interface for documents that have string type for the _id field
type StringBased interface { type StringBased interface {
GetID() string GetID() (id string)
SetID(id string) SetID(id string)
} }
// JSONBased is an interface for documents that have object type for the _id field // DocBased is an interface for documents that have object type for the _id field
type JSONBased interface { type DocBased interface {
GetID() primitive.D GetID() (id 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 // InterfaceBased is an interface for documents that have custom declated type for the _id field
type InterfaceBased interface { type InterfaceBased interface {
GetID() interface{} GetID() (id interface{})
SetID(id interface{}) SetID(id interface{})
} }
+26
View File
@@ -0,0 +1,26 @@
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 err
}
}
return nil
}
+58
View File
@@ -0,0 +1,58 @@
package query_test
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
func TestCallbacks_InvokeOk(t *testing.T) {
mocked := mock.Mock{}
var callbacks = query.Callbacks{
func(ctx context.Context, iter interface{}) (err error) {
return mocked.Called(ctx, iter).Error(0)
},
func(ctx context.Context, iter interface{}) (err error) {
return mocked.Called(ctx, iter).Error(0)
},
}
ctx := context.Background()
iter := int64(42)
mocked.On("func1", ctx, iter).Return(nil).Once()
mocked.On("func2", ctx, iter).Return(nil).Once()
assert.NoError(t, callbacks.Invoke(ctx, iter))
assert.True(t, mocked.AssertExpectations(t))
}
func TestCallbacks_InvokeStopIfError(t *testing.T) {
mocked := mock.Mock{}
var callbacks = query.Callbacks{
func(ctx context.Context, iter interface{}) (err error) {
return mocked.Called(ctx, iter).Error(0)
},
func(ctx context.Context, iter interface{}) (err error) {
t.FailNow()
return
},
}
ctx := context.Background()
iter := int(42)
mocked.On("func1", ctx, iter).Return(fmt.Errorf("wat"))
assert.EqualError(t, callbacks.Invoke(ctx, iter), "wat")
assert.True(t, mocked.AssertExpectations(t))
}
+83 -61
View File
@@ -3,50 +3,66 @@ package query
import ( import (
"fmt" "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/protection"
"github.com/mainnika/mongox-go-driver/v2/mongox/utils"
) )
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 { func Compose(filters ...interface{}) (query *Query, err error) {
query = &Query{}
q := &Query{} for _, filter := range filters {
ok, err := Push(query, filter)
for _, f := range filters { if err != nil {
if !Push(q, f) { return nil, fmt.Errorf("invalid filter %v, %w", filter, err)
panic(fmt.Errorf("unknown filter %v", f)) }
if !ok {
panic(fmt.Errorf("unknown filter %v", filter))
} }
} }
return q return query, nil
} }
// Push applies single filter to a query // Push applies single filter to a query
func Push(q *Query, f interface{}) bool { func Push(query *Query, filter interface{}) (ok bool, err error) {
emptyFilter := reflect2.IsNil(filter)
if utils.IsNil(f) { if emptyFilter {
return true return true, nil
} }
ok := false validator, hasValidator := filter.(Validator)
ok = ok || applyBson(q, f) if hasValidator {
ok = ok || applyLimit(q, f) err := validator.Validate()
ok = ok || applySort(q, f) if err != nil {
ok = ok || applySkip(q, f) return false, fmt.Errorf("while validating filter %v, %w", filter, err)
ok = ok || applyProtection(q, f) }
ok = ok || applyPreloader(q, f) }
return ok ok = false // true if at least one filter was applied
for _, applier := range []applyFilterFunc{
applyBson,
applyLimit,
applySort,
applySkip,
applyProtection,
applyPreloader,
applyUpdater,
applyCallbacks,
} {
ok = applier(query, filter) || ok
}
return ok, nil
} }
// applyBson is a fallback for a custom bson.M // applyBson is a fallback for a custom primitive.M
func applyBson(q *Query, f interface{}) bool { func applyBson(query *Query, filter interface{}) (ok 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
} }
@@ -54,10 +70,9 @@ func applyBson(q *Query, f interface{}) bool {
} }
// applyLimits extends query with a limiter // applyLimits extends query with a limiter
func applyLimit(q *Query, f interface{}) bool { func applyLimit(query *Query, filter interface{}) (ok bool) {
if filter, ok := filter.(Limiter); ok {
if f, ok := f.(Limiter); ok { query.limiter = filter
q.limiter = f
return true return true
} }
@@ -65,10 +80,9 @@ func applyLimit(q *Query, f interface{}) bool {
} }
// applySort extends query with a sort rule // applySort extends query with a sort rule
func applySort(q *Query, f interface{}) bool { func applySort(query *Query, filter interface{}) (ok bool) {
if filter, ok := filter.(Sorter); ok {
if f, ok := f.(Sorter); ok { query.sorter = filter
q.sorter = f
return true return true
} }
@@ -76,50 +90,58 @@ func applySort(q *Query, f interface{}) bool {
} }
// applySkip extends query with a skip number // applySkip extends query with a skip number
func applySkip(q *Query, f interface{}) bool { func applySkip(query *Query, filter interface{}) (ok 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(q *Query, f interface{}) bool { func applyProtection(query *Query, filter interface{}) (ok bool) {
keyDoc := primitive.M{}
var x *primitive.ObjectID switch filter := filter.(type) {
var v *int64
switch f := f.(type) {
case protection.Key: case protection.Key:
x = &f.X filter.Inject(keyDoc)
v = &f.V
case *protection.Key: case *protection.Key:
x = &f.X filter.Inject(keyDoc)
v = &f.V
default: default:
return false return false
} }
if x.IsZero() { query.And(keyDoc)
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(q *Query, f interface{}) bool { func applyPreloader(query *Query, filter interface{}) (ok 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
}
+128
View File
@@ -0,0 +1,128 @@
package query_test
import (
"testing"
"github.com/stretchr/testify/assert"
"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/query"
)
func TestPushBSON(t *testing.T) {
q := &query.Query{}
ok, err := query.Push(q, primitive.M{"foo": "bar"})
assert.True(t, ok)
assert.NoError(t, err)
assert.NotEmpty(t, q.M())
assert.Len(t, q.M()["$and"], 1)
assert.Contains(t, q.M()["$and"], primitive.M{"foo": "bar"})
ok, err = query.Push(q, primitive.M{"bar": "foo"})
assert.True(t, ok)
assert.NoError(t, err)
assert.NotEmpty(t, q.M())
assert.Len(t, q.M()["$and"], 2)
assert.Contains(t, q.M()["$and"], primitive.M{"foo": "bar"})
assert.Contains(t, q.M()["$and"], primitive.M{"bar": "foo"})
}
func TestPushLimiter(t *testing.T) {
q := &query.Query{}
lim := query.Limit(2)
ok, err := query.Push(q, lim)
assert.True(t, ok)
assert.NoError(t, err)
assert.NotNil(t, q.Limiter())
assert.EqualValues(t, q.Limiter(), query.Limit(2).Limit())
}
func TestPushSorter(t *testing.T) {
q := &query.Query{}
sort := query.Sort{"foo": 1}
ok, err := query.Push(q, sort)
assert.True(t, ok)
assert.NoError(t, err)
assert.NotNil(t, q.Sorter())
assert.EqualValues(t, q.Sorter(), primitive.M{"foo": 1})
}
func TestPushSkipper(t *testing.T) {
q := &query.Query{}
skip := query.Skip(66)
ok, err := query.Push(q, skip)
assert.True(t, ok)
assert.NoError(t, err)
assert.NotNil(t, q.Skipper())
assert.EqualValues(t, q.Skipper(), query.Skip(66).Skip())
}
func TestPushProtection(t *testing.T) {
t.Run("push protection key pointer", func(t *testing.T) {
q := &query.Query{}
protected := &protection.Key{V: 1, X: primitive.ObjectID{2}}
ok, err := query.Push(q, protected)
assert.True(t, ok)
assert.NoError(t, err)
assert.NotEmpty(t, q.M()["$and"])
assert.Contains(t, q.M()["$and"], primitive.M{"_x": primitive.ObjectID{2}, "_v": int64(1)})
})
t.Run("push protection key struct", func(t *testing.T) {
q := &query.Query{}
protected := protection.Key{V: 1, X: primitive.ObjectID{2}}
ok, err := query.Push(q, protected)
assert.True(t, ok)
assert.NoError(t, err)
assert.NotEmpty(t, q.M()["$and"])
assert.Contains(t, q.M()["$and"], primitive.M{"_x": primitive.ObjectID{2}, "_v": int64(1)})
})
t.Run("protection key is empty", func(t *testing.T) {
q := &query.Query{}
protected := &protection.Key{}
ok, err := query.Push(q, protected)
assert.True(t, ok)
assert.NoError(t, err)
assert.NotEmpty(t, q.M()["$and"])
assert.Contains(t, q.M()["$and"], primitive.M{"_x": primitive.M{"$exists": false}, "_v": primitive.M{"$exists": false}})
})
}
func TestPushPreloader(t *testing.T) {
q := &query.Query{}
preloader := query.Preload{"a", "b"}
ok, err := query.Push(q, preloader)
assert.True(t, ok)
assert.NoError(t, err)
p, hasPreloader := q.Preloader()
assert.NotNil(t, p)
assert.True(t, hasPreloader)
assert.EqualValues(t, p, query.Preload{"a", "b"})
}
+22
View File
@@ -0,0 +1,22 @@
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)
if !ok {
return nil, false
}
return q, true
}
// WithContext function creates the new context with request data
func WithContext(ctx context.Context, q *Query) (withQuery context.Context) {
return context.WithValue(ctx, ctxQueryKey{}, q)
}
+7 -6
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() *int64 Limit() (limit *int64)
} }
// Limit is a simple implementation of the Limiter filter // Limit is a simple implementation of the Limiter filter
@@ -11,12 +11,13 @@ type Limit int64
var _ Limiter = Limit(0) var _ Limiter = Limit(0)
// Limit returns a limit // Limit returns a limit
func (l Limit) Limit() *int64 { func (l Limit) Limit() (limit *int64) {
if l <= 0 {
lim := int64(l)
if lim <= 0 {
return nil return nil
} }
return &lim limit = new(int64)
*limit = int64(l)
return limit
} }
+4 -4
View File
@@ -1,16 +1,16 @@
package query package query
// Preloader is a filter to skip the result // Preloader is a filter to preload the result
type Preloader interface { type Preloader interface {
Preload() []string Preload() (preloads []string)
} }
// Preload is a simple implementation of the Skipper filter // Preload is a simple implementation of the Preloader filter
type Preload []string type Preload []string
var _ Preloader = Preload{} var _ Preloader = Preload{}
// Preload returns a preload list // Preload returns a preload list
func (l Preload) Preload() []string { func (l Preload) Preload() (preloads []string) {
return l return l
} }
+71 -32
View File
@@ -1,42 +1,43 @@
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 bson.M map // Query is an enchanched primitive.M map
type Query struct { type Query struct {
m bson.M m primitive.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 bson.M) *Query { func (q *Query) And(elem primitive.M) (query *Query) {
if q.m == nil { if q.m == nil {
q.m = bson.M{} q.m = primitive.M{}
} }
queries, exists := q.m["$and"].(bson.A) queries, exists := q.m["$and"].(primitive.A)
if !exists { if !exists {
q.m["$and"] = bson.A{elem} q.m["$and"] = primitive.A{elem}
return q return q
} }
q.m["$and"] = append(queries, elem) q.m["$and"] = append(queries, elem)
return q return q
} }
// Limiter returns limiter value or nil // Limiter returns limiter value or nil
func (q *Query) Limiter() *int64 { func (q *Query) Limiter() (limit *int64) {
if q.limiter == nil { if q.limiter == nil {
return nil return nil
} }
@@ -45,8 +46,7 @@ func (q *Query) Limiter() *int64 {
} }
// Sorter is a sort rule for a query // Sorter is a sort rule for a query
func (q *Query) Sorter() interface{} { func (q *Query) Sorter() (sort interface{}) {
if q.sorter == nil { if q.sorter == nil {
return nil return nil
} }
@@ -55,8 +55,7 @@ func (q *Query) Sorter() interface{} {
} }
// Skipper is a skipper for a query // Skipper is a skipper for a query
func (q *Query) Skipper() *int64 { func (q *Query) Skipper() (skip *int64) {
if q.skipper == nil { if q.skipper == nil {
return nil return nil
} }
@@ -64,32 +63,72 @@ func (q *Query) Skipper() *int64 {
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 {
return primitive.M{}, nil
}
update = q.updater.Update()
if reflect2.IsNil(update) {
return primitive.M{}, nil
}
buffer := bytebufferpool.Get()
defer bytebufferpool.Put(buffer)
// convert update document to bson map values
buffer.Reset()
bsonBytes, err := bson.MarshalAppend(buffer.B, update)
if err != nil {
return primitive.M{}, err
}
update = primitive.M{} // reset update map and unmarshal bson bytes to it again
err = bson.Unmarshal(bsonBytes, update)
if err != nil {
return primitive.M{}, err
}
return update, nil
}
// Preloader is a preloader list for a query // Preloader is a preloader list for a query
func (q *Query) Preloader() (empty bool, preloader []string) { func (q *Query) Preloader() (preloads []string, ok bool) {
if q.preloader == nil { if q.preloader == nil {
return false, nil return nil, false
} }
preloader = q.preloader.Preload() preloads = q.preloader.Preload()
if len(preloader) == 0 { return preloads, len(preloads) > 0
return false, nil }
}
return true, preloader // OnDecode callback is called after the mongo decode function
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() bool { func (q *Query) Empty() (isEmpty 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() bson.M { func (q *Query) M() (m primitive.M) {
return q.m return q.m
} }
// New creates a new query
func New() (query *Query) {
return &Query{}
}
+7 -6
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() *int64 Skip() (skip *int64)
} }
// Skip is a simple implementation of the Skipper filter // Skip is a simple implementation of the Skipper filter
@@ -11,12 +11,13 @@ 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() *int64 { func (l Skip) Skip() (skip *int64) {
if l <= 0 {
lim := int64(l)
if lim <= 0 {
return nil return nil
} }
return &lim skip = new(int64)
*skip = int64(l)
return skip
} }
+5 -5
View File
@@ -1,20 +1,20 @@
package query package query
import ( import (
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive"
) )
// 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() bson.M Sort() (sort primitive.M)
} }
// Sort is a simple implementations of the Sorter filter // Sort is a simple implementations of the Sorter filter
type Sort bson.M type Sort primitive.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() bson.M { func (f Sort) Sort() (sort primitive.M) {
return bson.M(f) return primitive.M(f)
} }
+20
View File
@@ -0,0 +1,20 @@
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
@@ -0,0 +1,6 @@
package query
// Validator is a filter to validate the filter
type Validator interface {
Validate() (err error)
}
-21
View File
@@ -1,21 +0,0 @@
package utils
import (
"unsafe"
)
// IsNil function evaluates the interface value to nil
func IsNil(i interface{}) bool {
type iface struct {
_ *interface{}
ptr unsafe.Pointer
}
unpacked := (*iface)(unsafe.Pointer(&i))
if unpacked.ptr == nil {
return true
}
return *(*unsafe.Pointer)(unpacked.ptr) == nil
}
-32
View File
@@ -1,32 +0,0 @@
package utils
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsNil(t *testing.T) {
testvalues := []struct {
i interface{}
isnil bool
}{
{nil, true},
{(*string)(nil), true},
{([]string)(nil), true},
{(map[string]string)(nil), true},
{(func() bool)(nil), true},
{(chan func() bool)(nil), true},
{"", true},
{0, true},
{append(([]string)(nil), ""), false},
{[]string{}, false},
{1, false},
{"1", false},
}
for _, tt := range testvalues {
assert.Equal(t, tt.isnil, IsNil(tt.i))
}
}
+33
View File
@@ -0,0 +1,33 @@
# syntax = docker/dockerfile:1.3-labs
FROM registry.access.redhat.com/ubi8/ubi
RUN <<EOF cat >> /etc/yum.repos.d/mongo.repo
[mongodb-org]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/8/mongodb-org/4.4/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-4.4.asc
EOF
RUN set -eux \
&& dnf makecache \
&& dnf install -yq mongodb-org-server golang \
&& dnf clean all
WORKDIR /root/mongox
ENV GOPATH=/root/go
COPY go.mod .
COPY go.sum .
RUN set -eux \
&& go mod download
COPY mongox-testing mongox-testing
COPY mongox mongox
CMD set -eux \
&& nohup mongod --dbpath $(mktemp -d) \
& go test -timeout 30s -v ./...