- update dorm

master
李光春 2 years ago
parent f0bd973011
commit 2dc991f552

@ -3,6 +3,7 @@ module go.dtapp.net/library
go 1.18
require (
entgo.io/ent v0.11.1
gitee.com/chunanyong/dm v1.8.5
gitee.com/chunanyong/zorm v1.5.6
github.com/aliyun/aliyun-oss-go-sdk v2.2.4+incompatible
@ -24,10 +25,11 @@ require (
github.com/godror/godror v0.33.3
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.12+incompatible
github.com/jasonlvhit/gocron v0.0.1
github.com/kamva/mgm/v3 v3.4.1
github.com/ks3sdklib/aws-sdk-go v1.1.6
github.com/lib/pq v1.10.5
github.com/mailru/go-clickhouse/v2 v2.0.0
github.com/mattn/go-sqlite3 v1.14.12
github.com/mattn/go-sqlite3 v1.14.13
github.com/mitchellh/mapstructure v1.5.0
github.com/mvdan/xurls v1.1.0
github.com/natefinch/lumberjack v2.0.0+incompatible
@ -68,6 +70,7 @@ require (
github.com/clbanning/mxj v1.8.4 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deckarep/golang-set v1.8.0 // indirect
github.com/denisenkom/go-mssqldb v0.12.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
@ -108,10 +111,12 @@ require (
github.com/mozillazg/go-httpheader v0.3.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/saracen/go7z-fixtures v0.0.0-20190623165746-aa6b8fba1d2f // indirect
github.com/saracen/solidblock v0.0.0-20190426153529-45df20abab6f // indirect
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
github.com/stretchr/testify v1.7.2 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
@ -130,6 +135,7 @@ require (
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.16.8 // indirect
modernc.org/mathutil v1.4.1 // indirect
modernc.org/memory v1.1.1 // indirect

@ -1,5 +1,7 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
entgo.io/ent v0.11.1 h1:im67R+2W3Nee2bNS2YnoYz8oAF0Qz4AOlIvKRIAEISY=
entgo.io/ent v0.11.1/go.mod h1:X5b1YfMayrRTgKGO//8IqpL7XJx0uqdeReEkxNpXROA=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
gitee.com/chunanyong/dm v1.8.5 h1:7RbtxRqZSzMxMT/6vhjdV/Gym2lV2S0PLUUW0ht4ZC4=
@ -173,6 +175,30 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
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/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.9.8 h1:DxXB6MLd6yyel7CLph8EwNIonUtVZd3Ue5iRcL4DQCE=
github.com/goccy/go-json v0.9.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
@ -363,6 +389,7 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@ -375,11 +402,16 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kamva/mgm/v3 v3.4.1 h1:KdK60ijRkwMjbky3Fg7wk4iHKe1nzwpeUt1VXDf+muE=
github.com/kamva/mgm/v3 v3.4.1/go.mod h1:DzYf1/ZcxUUp90O/GGkj4r9MbgppvNqNtVgE6NDNcLk=
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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.8 h1:JahtItbkWjf2jzm/T+qgMxkP9EMHsqEUA6vCMGmXvhA=
github.com/klauspost/compress v1.15.8/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
@ -413,6 +445,8 @@ github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0U
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mailru/go-clickhouse/v2 v2.0.0 h1:O+ZGJDwp/E5W19ooeouEqaOlg+qxA+4Zsfjt63QcnVU=
github.com/mailru/go-clickhouse/v2 v2.0.0/go.mod h1:TwxN829KnFZ7jAka9l9EoCV+U0CBFq83SFev4oLbnNU=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@ -428,8 +462,9 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.13 h1:1tj15ngiFfcZzii7yd82foL+ks+ouQcj8j/TPq3fk1I=
github.com/mattn/go-sqlite3 v1.14.13/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
@ -497,6 +532,7 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw=
github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
@ -548,6 +584,8 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
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/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
@ -578,6 +616,7 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
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/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
@ -589,6 +628,7 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
@ -647,6 +687,7 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.4 h1:lrneYvz923dvC14R54XcA7FXoZ3mlGZAgmwhfm7H
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.4 h1:p83BUL3tAYS0OT/r0qglgc3M1JjhM0diV8DSWAhVXv4=
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
go.mongodb.org/mongo-driver v1.7.0/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8=
go.mongodb.org/mongo-driver v1.9.0/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
go.mongodb.org/mongo-driver v1.9.1 h1:m078y9v7sBItkt1aaoe2YlvWEXcD263e1a4E1fBrJ1c=
go.mongodb.org/mongo-driver v1.9.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
@ -681,6 +722,7 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -711,8 +753,8 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -748,6 +790,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -768,8 +811,10 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -825,6 +870,9 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
@ -839,8 +887,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.9-0.20211216111533-8d383106f7e7 h1:M1gcVrIb2lSn2FIL19DG0+/b8nNVKJ7W7b4WcAGZAYM=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

@ -0,0 +1,16 @@
package dorm
import (
"github.com/beego/beego/v2/client/orm"
)
type ConfigBeegoClient struct {
Dns string // 地址
}
// BeegoClient
// https://beego.vip/
type BeegoClient struct {
Db *orm.Ormer // 驱动
config *ConfigBeegoClient // 配置
}

@ -0,0 +1,29 @@
package dorm
import (
"database/sql"
"errors"
"fmt"
"github.com/beego/beego/v2/client/orm"
_ "github.com/go-sql-driver/mysql"
)
func NewBeegoMysqlClient(config *ConfigBeegoClient) (*BeegoClient, error) {
var err error
c := &BeegoClient{config: config}
err = orm.RegisterDriver("mysql", orm.DRMySQL)
if err != nil {
return nil, errors.New(fmt.Sprintf("加载驱动失败:%v", err))
}
var db *sql.DB
o, err := orm.NewOrmWithDB("mysql", "default", db)
if err != nil {
return nil, err
}
c.Db = &o
return c, nil
}

@ -0,0 +1,29 @@
package dorm
import (
"database/sql"
"errors"
"fmt"
"github.com/beego/beego/v2/client/orm"
_ "github.com/lib/pq"
)
func NewBeegoOracleClient(config *ConfigBeegoClient) (*BeegoClient, error) {
var err error
c := &BeegoClient{config: config}
err = orm.RegisterDriver("oracle", orm.DROracle)
if err != nil {
return nil, errors.New(fmt.Sprintf("加载驱动失败:%v", err))
}
var db *sql.DB
o, err := orm.NewOrmWithDB("oracle", "default", db)
if err != nil {
return nil, err
}
c.Db = &o
return c, nil
}

@ -0,0 +1,29 @@
package dorm
import (
"database/sql"
"errors"
"fmt"
"github.com/beego/beego/v2/client/orm"
_ "github.com/lib/pq"
)
func NewBeegoPgsqlClient(config *ConfigBeegoClient) (*BeegoClient, error) {
var err error
c := &BeegoClient{config: config}
err = orm.RegisterDriver("pgsql", orm.DRPostgres)
if err != nil {
return nil, errors.New(fmt.Sprintf("加载驱动失败:%v", err))
}
var db *sql.DB
o, err := orm.NewOrmWithDB("pgsql", "default", db)
if err != nil {
return nil, err
}
c.Db = &o
return c, nil
}

@ -0,0 +1,29 @@
package dorm
import (
"database/sql"
"errors"
"fmt"
"github.com/beego/beego/v2/client/orm"
_ "github.com/mattn/go-sqlite3"
)
func NewBeegoSqliteClient(config *ConfigBeegoClient) (*BeegoClient, error) {
var err error
c := &BeegoClient{config: config}
err = orm.RegisterDriver("sqlite", orm.DRSqlite)
if err != nil {
return nil, errors.New(fmt.Sprintf("加载驱动失败:%v", err))
}
var db *sql.DB
o, err := orm.NewOrmWithDB("sqlite", "default", db)
if err != nil {
return nil, err
}
c.Db = &o
return c, nil
}

@ -0,0 +1,29 @@
package dorm
import (
"database/sql"
"errors"
"fmt"
"github.com/beego/beego/v2/client/orm"
_ "github.com/lib/pq"
)
func NewBeegoTidbClient(config *ConfigBeegoClient) (*BeegoClient, error) {
var err error
c := &BeegoClient{config: config}
err = orm.RegisterDriver("TiDB", orm.DRTiDB)
if err != nil {
return nil, errors.New(fmt.Sprintf("加载驱动失败:%v", err))
}
var db *sql.DB
o, err := orm.NewOrmWithDB("TiDB", "default", db)
if err != nil {
return nil, err
}
c.Db = &o
return c, nil
}

@ -0,0 +1,16 @@
package dorm
import (
"entgo.io/ent"
)
type ConfigEntClient struct {
Dns string // 地址
}
// EntClient
// https://entgo.io/
type EntClient struct {
Db *ent.Edge // 驱动
config *ConfigEntClient // 配置
}

@ -0,0 +1,19 @@
package dorm
import (
_ "github.com/go-sql-driver/mysql"
)
func NewEntMysqlClient(config *ConfigEntClient) (*EntClient, error) {
//var err error
//c := &EntClient{config: config}
//client, err := ent.Open("mysql", c.config.Dns)
//if err != nil {
// return nil, errors.New(fmt.Sprintf("连接失败:%v", err))
//}
//defer client.Close()
return nil, nil
}

@ -0,0 +1,24 @@
package dorm
import (
"github.com/kamva/mgm/v3"
"go.mongodb.org/mongo-driver/mongo/options"
)
type ConfigMgmClient struct {
Opts *options.ClientOptions
DatabaseName string // 库名
}
type MgmClient struct {
config *ConfigMgmClient // 配置
}
func NewMgmClient(config *ConfigMgmClient) (*MgmClient, error) {
c := &MgmClient{config: config}
_ = mgm.SetDefaultConfig(nil, c.config.DatabaseName, c.config.Opts)
return c, nil
}

@ -0,0 +1,828 @@
{
"files": [
"doc/md/contributors.md"
],
"imageSize": 100,
"commit": false,
"contributors": [
{
"login": "a8m",
"name": "Ariel Mashraki",
"avatar_url": "https://avatars.githubusercontent.com/u/7413593?v=4",
"profile": "https://github.com/a8m",
"contributions": [
"maintenance",
"doc",
"code"
]
},
{
"login": "alexsn",
"name": "Alex Snast",
"avatar_url": "https://avatars.githubusercontent.com/u/987019?v=4",
"profile": "https://github.com/alexsn",
"contributions": [
"code"
]
},
{
"login": "rotemtam",
"name": "Rotem Tamir",
"avatar_url": "https://avatars.githubusercontent.com/u/1522681?v=4",
"profile": "https://rotemtam.com/",
"contributions": [
"maintenance",
"doc",
"code"
]
},
{
"login": "cliedeman",
"name": "Ciaran Liedeman",
"avatar_url": "https://avatars.githubusercontent.com/u/3578740?v=4",
"profile": "https://github.com/cliedeman",
"contributions": [
"code"
]
},
{
"login": "marwan-at-work",
"name": "Marwan Sulaiman",
"avatar_url": "https://avatars.githubusercontent.com/u/16294261?v=4",
"profile": "https://www.marwan.io/",
"contributions": [
"code"
]
},
{
"login": "napei",
"name": "Nathaniel Peiffer",
"avatar_url": "https://avatars.githubusercontent.com/u/8946502?v=4",
"profile": "https://nathaniel.peiffer.com.au/",
"contributions": [
"code"
]
},
{
"login": "tmc",
"name": "Travis Cline",
"avatar_url": "https://avatars.githubusercontent.com/u/3977?v=4",
"profile": "https://github.com/tmc",
"contributions": [
"code"
]
},
{
"login": "hantmac",
"name": "Jeremy",
"avatar_url": "https://avatars.githubusercontent.com/u/7600925?v=4",
"profile": "https://cloudsjhan.github.io/",
"contributions": [
"code"
]
},
{
"login": "aca",
"name": "aca",
"avatar_url": "https://avatars.githubusercontent.com/u/50316549?v=4",
"profile": "https://github.com/aca",
"contributions": [
"code"
]
},
{
"login": "BrentChesny",
"name": "BrentChesny",
"avatar_url": "https://avatars.githubusercontent.com/u/1449435?v=4",
"profile": "https://github.com/BrentChesny",
"contributions": [
"code",
"doc"
]
},
{
"login": "giautm",
"name": "Giau. Tran Minh",
"avatar_url": "https://avatars.githubusercontent.com/u/12751435?v=4",
"profile": "https://github.com/giautm",
"contributions": [
"code",
"review"
]
},
{
"login": "htdvisser",
"name": "Hylke Visser",
"avatar_url": "https://avatars.githubusercontent.com/u/181308?v=4",
"profile": "https://htdvisser.dev/",
"contributions": [
"code"
]
},
{
"login": "kerbelp",
"name": "Pavel Kerbel",
"avatar_url": "https://avatars.githubusercontent.com/u/3934990?v=4",
"profile": "https://github.com/kerbelp",
"contributions": [
"code"
]
},
{
"login": "day-dreams",
"name": "zhangnan",
"avatar_url": "https://avatars.githubusercontent.com/u/24593904?v=4",
"profile": "https://github.com/day-dreams",
"contributions": [
"code"
]
},
{
"login": "uta-mori",
"name": "mori yuta",
"avatar_url": "https://avatars.githubusercontent.com/u/59682979?v=4",
"profile": "https://github.com/uta-mori",
"contributions": [
"code",
"translation",
"review"
]
},
{
"login": "chris-rock",
"name": "Christoph Hartmann",
"avatar_url": "https://avatars.githubusercontent.com/u/1178413?v=4",
"profile": "http://lollyrock.com/",
"contributions": [
"code"
]
},
{
"login": "rubensayshi",
"name": "Ruben de Vries",
"avatar_url": "https://avatars.githubusercontent.com/u/649160?v=4",
"profile": "https://github.com/rubensayshi",
"contributions": [
"code"
]
},
{
"login": "ernado",
"name": "Aleksandr Razumov",
"avatar_url": "https://avatars.githubusercontent.com/u/866677?v=4",
"profile": "https://keybase.io/ernado",
"contributions": [
"code"
]
},
{
"login": "apbuteau",
"name": "apbuteau",
"avatar_url": "https://avatars.githubusercontent.com/u/6796073?v=4",
"profile": "https://github.com/apbuteau",
"contributions": [
"code"
]
},
{
"login": "ichord",
"name": "Harold.Luo",
"avatar_url": "https://avatars.githubusercontent.com/u/1324791?v=4",
"profile": "https://github.com/ichord",
"contributions": [
"code"
]
},
{
"login": "idoshveki",
"name": "ido shveki",
"avatar_url": "https://avatars.githubusercontent.com/u/11615669?v=4",
"profile": "https://github.com/idoshveki",
"contributions": [
"code"
]
},
{
"login": "masseelch",
"name": "MasseElch",
"avatar_url": "https://avatars.githubusercontent.com/u/12862103?v=4",
"profile": "https://github.com/masseelch",
"contributions": [
"code"
]
},
{
"login": "kidlj",
"name": "Jian Li",
"avatar_url": "https://avatars.githubusercontent.com/u/300616?v=4",
"profile": "https://github.com/kidlj",
"contributions": [
"code"
]
},
{
"login": "nolotz",
"name": "Noah-Jerome Lotzer",
"avatar_url": "https://avatars.githubusercontent.com/u/5778728?v=4",
"profile": "https://noah.je/",
"contributions": [
"code"
]
},
{
"login": "danf0rth",
"name": "danforth",
"avatar_url": "https://avatars.githubusercontent.com/u/14220891?v=4",
"profile": "https://github.com/danf0rth",
"contributions": [
"code"
]
},
{
"login": "maxiloEmmmm",
"name": "maxilozoz",
"avatar_url": "https://avatars.githubusercontent.com/u/16779121?v=4",
"profile": "https://github.com/maxiloEmmmm",
"contributions": [
"code"
]
},
{
"login": "zzwx",
"name": "zzwx",
"avatar_url": "https://avatars.githubusercontent.com/u/8169082?v=4",
"profile": "https://gist.github.com/zzwx",
"contributions": [
"code"
]
},
{
"login": "ix64",
"name": "MengYX",
"avatar_url": "https://avatars.githubusercontent.com/u/13902388?v=4",
"profile": "https://github.com/ix64",
"contributions": [
"translation"
]
},
{
"login": "mattn",
"name": "mattn",
"avatar_url": "https://avatars.githubusercontent.com/u/10111?v=4",
"profile": "https://mattn.kaoriya.net/",
"contributions": [
"translation"
]
},
{
"login": "Bladrak",
"name": "Hugo Briand",
"avatar_url": "https://avatars.githubusercontent.com/u/1321977?v=4",
"profile": "https://github.com/Bladrak",
"contributions": [
"code"
]
},
{
"login": "enmand",
"name": "Dan Enman",
"avatar_url": "https://avatars.githubusercontent.com/u/432487?v=4",
"profile": "https://danielenman.com/",
"contributions": [
"code"
]
},
{
"login": "UnAfraid",
"name": "Rumen Nikiforov",
"avatar_url": "https://avatars.githubusercontent.com/u/2185291?v=4",
"profile": "http://www.l2junity.org/",
"contributions": [
"code"
]
},
{
"login": "wenerme",
"name": "陈杨文",
"avatar_url": "https://avatars.githubusercontent.com/u/1777211?v=4",
"profile": "https://wener.me",
"contributions": [
"code"
]
},
{
"login": "joesonw",
"name": "Qiaosen (Joeson) Huang",
"avatar_url": "https://avatars.githubusercontent.com/u/1635441?v=4",
"profile": "https://djwong.net",
"contributions": [
"bug"
]
},
{
"login": "davebehr1",
"name": "AlonDavidBehr",
"avatar_url": "https://avatars.githubusercontent.com/u/16716239?v=4",
"profile": "https://github.com/davebehr1",
"contributions": [
"code",
"review"
]
},
{
"login": "DuGlaser",
"name": "DuGlaser",
"avatar_url": "https://avatars.githubusercontent.com/u/50506482?v=4",
"profile": "http://duglaser.dev",
"contributions": [
"doc"
]
},
{
"login": "shanna",
"name": "Shane Hanna",
"avatar_url": "https://avatars.githubusercontent.com/u/28489?v=4",
"profile": "https://github.com/shanna",
"contributions": [
"doc"
]
},
{
"login": "mahmud2011",
"name": "Mahmudul Haque",
"avatar_url": "https://avatars.githubusercontent.com/u/5278142?v=4",
"profile": "https://www.linkedin.com/in/mahmud2011",
"contributions": [
"code"
]
},
{
"login": "sywesk",
"name": "Benjamin Bourgeais",
"avatar_url": "https://avatars.githubusercontent.com/u/862607?v=4",
"profile": "http://blog.scaleprocess.net",
"contributions": [
"code"
]
},
{
"login": "8ayac",
"name": "8ayac(Yoshinori Hayashi)",
"avatar_url": "https://avatars.githubusercontent.com/u/29266382?v=4",
"profile": "https://about.8ay.ac/",
"contributions": [
"doc"
]
},
{
"login": "y-yagi",
"name": "y-yagi",
"avatar_url": "https://avatars.githubusercontent.com/u/987638?v=4",
"profile": "https://github.com/y-yagi",
"contributions": [
"doc"
]
},
{
"login": "Sacro",
"name": "Ben Woodward",
"avatar_url": "https://avatars.githubusercontent.com/u/2659869?v=4",
"profile": "https://github.com/Sacro",
"contributions": [
"code"
]
},
{
"login": "wzyjerry",
"name": "WzyJerry",
"avatar_url": "https://avatars.githubusercontent.com/u/11435169?v=4",
"profile": "https://github.com/wzyjerry",
"contributions": [
"code"
]
},
{
"login": "tarrencev",
"name": "Tarrence van As",
"avatar_url": "https://avatars.githubusercontent.com/u/4740651?v=4",
"profile": "https://github.com/tarrencev",
"contributions": [
"doc",
"code"
]
},
{
"login": "MONAKA0721",
"name": "Yuya Sumie",
"avatar_url": "https://avatars.githubusercontent.com/u/32859963?v=4",
"profile": "https://mo7ka.com",
"contributions": [
"doc"
]
},
{
"login": "akfaew",
"name": "Michal Mazurek",
"avatar_url": "https://avatars.githubusercontent.com/u/7853732?v=4",
"profile": "http://jasminek.net",
"contributions": [
"code"
]
},
{
"login": "nmemoto",
"name": "Takafumi Umemoto",
"avatar_url": "https://avatars.githubusercontent.com/u/1522332?v=4",
"profile": "https://github.com/nmemoto",
"contributions": [
"doc"
]
},
{
"login": "squarebat",
"name": "Khadija Sidhpuri",
"avatar_url": "https://avatars.githubusercontent.com/u/59063821?v=4",
"profile": "http://www.linkedin.com/in/khadija-sidhpuri-87709316a",
"contributions": [
"code"
]
},
{
"login": "neel229",
"name": "Neel Modi",
"avatar_url": "https://avatars.githubusercontent.com/u/53475167?v=4",
"profile": "https://github.com/neel229",
"contributions": [
"code"
]
},
{
"login": "shomodj",
"name": "Boris Shomodjvarac",
"avatar_url": "https://avatars.githubusercontent.com/u/304768?v=4",
"profile": "https://ie.linkedin.com/in/boris-shomodjvarac-51970879",
"contributions": [
"doc"
]
},
{
"login": "sadmansakib",
"name": "Sadman Sakib",
"avatar_url": "https://avatars.githubusercontent.com/u/17023844?v=4",
"profile": "https://github.com/sadmansakib",
"contributions": [
"doc"
]
},
{
"login": "dakimura",
"name": "dakimura",
"avatar_url": "https://avatars.githubusercontent.com/u/34202807?v=4",
"profile": "https://github.com/dakimura",
"contributions": [
"code"
]
},
{
"login": "RiskyFeryansyahP",
"name": "Risky Feryansyah",
"avatar_url": "https://avatars.githubusercontent.com/u/36788585?v=4",
"profile": "https://github.com/RiskyFeryansyahP",
"contributions": [
"code"
]
},
{
"login": "seiichi1101",
"name": "seiichi ",
"avatar_url": "https://avatars.githubusercontent.com/u/20941952?v=4",
"profile": "https://github.com/seiichi1101",
"contributions": [
"code"
]
},
{
"login": "odeke-em",
"name": "Emmanuel T Odeke",
"avatar_url": "https://avatars.githubusercontent.com/u/4898263?v=4",
"profile": "https://orijtech.com/",
"contributions": [
"code"
]
},
{
"login": "isoppp",
"name": "Hiroki Isogai",
"avatar_url": "https://avatars.githubusercontent.com/u/16318727?v=4",
"profile": "https://isoppp.com",
"contributions": [
"doc"
]
},
{
"login": "tsingsun",
"name": "李清山",
"avatar_url": "https://avatars.githubusercontent.com/u/5848549?v=4",
"profile": "https://github.com/tsingsun",
"contributions": [
"code"
]
},
{
"login": "s-takehana",
"name": "s-takehana",
"avatar_url": "https://avatars.githubusercontent.com/u/3423547?v=4",
"profile": "https://github.com/s-takehana",
"contributions": [
"doc"
]
},
{
"login": "EndlessIdea",
"name": "Kuiba",
"avatar_url": "https://avatars.githubusercontent.com/u/1527796?v=4",
"profile": "https://github.com/EndlessIdea",
"contributions": [
"code"
]
},
{
"login": "storyicon",
"name": "storyicon",
"avatar_url": "https://avatars.githubusercontent.com/u/29772821?v=4",
"profile": "https://github.com/storyicon",
"contributions": [
"code"
]
},
{
"login": "evanlurvey",
"name": "Evan Lurvey",
"avatar_url": "https://avatars.githubusercontent.com/u/54965655?v=4",
"profile": "https://github.com/evanlurvey",
"contributions": [
"code"
]
},
{
"login": "attackordie",
"name": "Brian",
"avatar_url": "https://avatars.githubusercontent.com/u/20145334?v=4",
"profile": "https://github.com/attackordie",
"contributions": [
"doc"
]
},
{
"login": "ThinkontrolSY",
"name": "Shen Yang",
"avatar_url": "https://avatars.githubusercontent.com/u/11331554?v=4",
"profile": "http://www.thinkontrol.com",
"contributions": [
"code"
]
},
{
"login": "sivchari",
"name": "sivchari",
"avatar_url": "https://avatars.githubusercontent.com/u/55221074?v=4",
"profile": "https://twitter.com/sivchari",
"contributions": [
"code"
]
},
{
"login": "mookjp",
"name": "mook",
"avatar_url": "https://avatars.githubusercontent.com/u/1519309?v=4",
"profile": "https://blog.mookjp.io",
"contributions": [
"code"
]
},
{
"login": "heliumbrain",
"name": "heliumbrain",
"avatar_url": "https://avatars.githubusercontent.com/u/1607668?v=4",
"profile": "http://www.entiros.se",
"contributions": [
"doc"
]
},
{
"login": "JeremyV2014",
"name": "Jeremy Maxey-Vesperman",
"avatar_url": "https://avatars.githubusercontent.com/u/9276415?v=4",
"profile": "https://github.com/JeremyV2014",
"contributions": [
"code",
"doc"
]
},
{
"login": "tankbusta",
"name": "Christopher Schmitt",
"avatar_url": "https://avatars.githubusercontent.com/u/592749?v=4",
"profile": "https://github.com/tankbusta",
"contributions": [
"doc"
]
},
{
"login": "grevych",
"name": "Gerardo Reyes",
"avatar_url": "https://avatars.githubusercontent.com/u/3792003?v=4",
"profile": "https://github.com/grevych",
"contributions": [
"code"
]
},
{
"login": "naormatania",
"name": "Naor Matania",
"avatar_url": "https://avatars.githubusercontent.com/u/6978437?v=4",
"profile": "https://github.com/naormatania",
"contributions": [
"code"
]
},
{
"login": "idc77",
"name": "idc77",
"avatar_url": "https://avatars.githubusercontent.com/u/87644834?v=4",
"profile": "https://github.com/idc77",
"contributions": [
"doc"
]
},
{
"login": "HurSungYun",
"name": "Sungyun Hur",
"avatar_url": "https://avatars.githubusercontent.com/u/8033896?v=4",
"profile": "http://ethanhur.me",
"contributions": [
"doc"
]
},
{
"login": "peanut-cc",
"name": "peanut-pg",
"avatar_url": "https://avatars.githubusercontent.com/u/55480838?v=4",
"profile": "https://github.com/peanut-cc",
"contributions": [
"doc"
]
},
{
"login": "m3hm3t",
"name": "Mehmet Yılmaz",
"avatar_url": "https://avatars.githubusercontent.com/u/22320354?v=4",
"profile": "https://github.com/m3hm3t",
"contributions": [
"code"
]
},
{
"login": "Laconty",
"name": "Roman Maklakov",
"avatar_url": "https://avatars.githubusercontent.com/u/17760166?v=4",
"profile": "https://github.com/Laconty",
"contributions": [
"code"
]
},
{
"login": "genevieve",
"name": "Genevieve",
"avatar_url": "https://avatars.githubusercontent.com/u/12158641?v=4",
"profile": "https://github.com/genevieve",
"contributions": [
"code"
]
},
{
"login": "cjraa",
"name": "Clarence",
"avatar_url": "https://avatars.githubusercontent.com/u/62199269?v=4",
"profile": "https://github.com/cjraa",
"contributions": [
"code"
]
},
{
"login": "iamnande",
"name": "Nicholas Anderson",
"avatar_url": "https://avatars.githubusercontent.com/u/7806510?v=4",
"profile": "https://www.linkedin.com/in/iamnande/",
"contributions": [
"code"
]
},
{
"login": "hezhizhen",
"name": "Zhizhen He",
"avatar_url": "https://avatars.githubusercontent.com/u/7611700?v=4",
"profile": "https://github.com/hezhizhen",
"contributions": [
"code"
]
},
{
"login": "crossworth",
"name": "Pedro Henrique",
"avatar_url": "https://avatars.githubusercontent.com/u/1251151?v=4",
"profile": "https://pedro.dev.br",
"contributions": [
"code"
]
},
{
"login": "MrParano1d",
"name": "MrParano1d",
"avatar_url": "https://avatars.githubusercontent.com/u/7414374?v=4",
"profile": "https://2jp.de",
"contributions": [
"code"
]
},
{
"login": "tprebs",
"name": "Thomas Prebble",
"avatar_url": "https://avatars.githubusercontent.com/u/6523587?v=4",
"profile": "https://github.com/tprebs",
"contributions": [
"code"
]
},
{
"login": "imhuytq",
"name": "Huy TQ",
"avatar_url": "https://avatars.githubusercontent.com/u/5723282?v=4",
"profile": "https://huytq.com",
"contributions": [
"code"
]
},
{
"login": "maorlipchuk",
"name": "maorlipchuk",
"avatar_url": "https://avatars.githubusercontent.com/u/7034637?v=4",
"profile": "https://github.com/maorlipchuk",
"contributions": [
"code"
]
},
{
"login": "iwata",
"name": "Motonori Iwata",
"avatar_url": "https://avatars.githubusercontent.com/u/121048?v=4",
"profile": "https://mobcov.hatenadiary.org/",
"contributions": [
"doc"
]
},
{
"login": "CharlesGe129",
"name": "Charles Ge",
"avatar_url": "https://avatars.githubusercontent.com/u/20162173?v=4",
"profile": "https://github.com/CharlesGe129",
"contributions": [
"code"
]
},
{
"login": "thmeitz",
"name": "Thomas Meitz",
"avatar_url": "https://avatars.githubusercontent.com/u/92851940?v=4",
"profile": "https://github.com/thmeitz",
"contributions": [
"code",
"doc"
]
},
{
"login": "booleangate",
"name": "Justin Johnson",
"avatar_url": "https://avatars.githubusercontent.com/u/181567?v=4",
"profile": "http://justinjohnson.org",
"contributions": [
"code"
]
},
{
"login": "hax10",
"name": "hax10",
"avatar_url": "https://avatars.githubusercontent.com/u/85743468?v=4",
"profile": "https://github.com/hax10",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
"projectName": "ent",
"projectOwner": "ent",
"repoType": "github",
"repoHost": "https://github.com",
"skipCi": true
}

75
vendor/entgo.io/ent/.golangci.yml generated vendored

@ -0,0 +1,75 @@
run:
go: '1.17'
timeout: 5m
linters-settings:
errcheck:
ignore: fmt:.*,Read|Write|Close|Exec,io:Copy
dupl:
threshold: 100
funlen:
lines: 115
statements: 115
goheader:
template: |-
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
linters:
disable-all: true
enable:
- bodyclose
- deadcode
- depguard
- dogsled
- dupl
- errcheck
- funlen
- gocritic
- gofmt
- goheader
- gosec
- gosimple
- govet
- ineffassign
- misspell
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unused
- varcheck
- whitespace
issues:
exclude-rules:
- path: _test\.go
linters:
- dupl
- funlen
- gosec
- gocritic
- linters:
- unused
source: ent.Schema
- path: dialect/sql/schema
linters:
- dupl
- gosec
- text: "Expect WriteFile permissions to be 0600 or less"
linters:
- gosec
- path: privacy/privacy.go
linters:
- stylecheck
- path: entc/load/schema.go
linters:
- staticcheck
- path: entc/gen/graph.go
linters:
- gocritic
- path: entc/integration/multischema/multischema_test.go
linters:
- staticcheck
text: SA1019

@ -0,0 +1,77 @@
# Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies within all project spaces, and it also applies when
an individual is representing the project or its community in public spaces.
Examples of representing a project or community include using an official
project e-mail address, posting via an official social media account, or acting
as an appointed representative at an online or offline event. Representation of
a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at <opensource-conduct@fb.com>. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

@ -0,0 +1,69 @@
# Contributing to ent
We want to make contributing to this project as easy and transparent as
possible.
# Project structure
- `dialect` - Contains SQL and Gremlin code used by the generated code.
- `dialect/sql/schema` - Auto migration logic resides there.
- `schema` - User schema API.
- `schema/{field, edge, index, mixin}` - provides schema builders API.
- `schema/field/gen` - Templates and codegen for numeric builders.
- `entc` - Codegen of `ent`.
- `entc/load` - `entc` loader API for loading user schemas into a Go objects at runtime.
- `entc/gen` - The actual code generation logic resides in this package (and its `templates` package).
- `integration` - Integration tests for `entc`.
- `privacy` - Runtime code for [privacy layer](https://entgo.io/docs/privacy/).
- `doc` - Documentation code for `entgo.io` (uses [Docusaurus](https://docusaurus.io)).
- `doc/md` - Markdown files for documentation.
- `doc/website` - Website code and assets.
In order to test your documentation changes, run `npm start` from the `doc/website` directory, and open [localhost:3000](http://localhost:3000/).
# Run integration tests
If you touch any file in `entc`, run the following command in `entc`:
```
go generate ./...
```
Then, in `entc/integration` run `docker-compose` in order to spin-up all database containers:
```
docker-compose -f docker-compose.yaml up -d
```
Then, run `go test ./...` to run all integration tests.
## Pull Requests
We actively welcome your pull requests.
1. Fork the repo and create your branch from `master`.
2. If you've added code that should be tested, add tests.
3. If you've changed APIs, update the documentation.
4. Ensure the test suite passes.
5. Make sure your code lints.
6. If you haven't already, complete the Contributor License Agreement ("CLA").
## Contributor License Agreement ("CLA")
In order to accept your pull request, we need you to submit a CLA. You only need
to do this once to work on any of Facebook's open source projects.
Complete your CLA here: <https://code.facebook.com/cla>
## Issues
We use GitHub issues to track public bugs. Please ensure your description is
clear and has sufficient instructions to be able to reproduce the issue.
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
disclosure of security bugs. In those cases, please go through the process
outlined on that page and do not file a public issue.
## License
By contributing to ent, you agree that your contributions will be licensed
under the LICENSE file in the root directory of this source tree.

202
vendor/entgo.io/ent/LICENSE generated vendored

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

58
vendor/entgo.io/ent/README.md generated vendored

@ -0,0 +1,58 @@
## ent - An Entity Framework For Go
[![Twitter](https://img.shields.io/twitter/url/https/twitter.com/entgo_io.svg?style=social&label=Follow%20%40entgo_io)](https://twitter.com/entgo_io)
[![Discord](https://img.shields.io/discord/885059418646003782?label=discord&logo=discord&style=flat-square&logoColor=white)](https://discord.gg/qZmPgTE6RX)
[English](README.md) | [中文](README_zh.md) | [日本語](README_jp.md)
<img width="50%"
align="right"
style="display: block; margin:40px auto;"
src="https://s3.eu-central-1.amazonaws.com/entgo.io/assets/gopher_graph.png"/>
Simple, yet powerful entity framework for Go, that makes it easy to build and maintain applications
with large data-models.
- **Schema As Code** - model any database schema as Go objects.
- **Easily Traverse Any Graph** - run queries, aggregations and traverse any graph structure easily.
- **Statically Typed And Explicit API** - 100% statically typed and explicit API using code generation.
- **Multi Storage Driver** - supports MySQL, MariaDB, TiDB, PostgreSQL, CockroachDB, SQLite and Gremlin.
- **Extendable** - simple to extend and customize using Go templates.
## Quick Installation
```console
go install entgo.io/ent/cmd/ent@latest
```
For proper installation using [Go modules], visit [entgo.io website][entgo instal].
## Docs and Support
The documentation for developing and using ent is available at: https://entgo.io
For discussion and support, [open an issue](https://github.com/ent/ent/issues/new/choose) or join our [channel](https://gophers.slack.com/archives/C01FMSQDT53) in the gophers Slack.
## Join the ent Community
Building `ent` would not have been possible without the collective work of our entire community. We maintain a [contributors page](doc/md/contributors.md)
which lists the contributors to this `ent`.
In order to contribute to `ent`, see the [CONTRIBUTING](CONTRIBUTING.md) file for how to go get started.
If your company or your product is using `ent`, please let us know by adding yourself to the [ent users page](https://github.com/ent/ent/wiki/ent-users).
For updates, follow us on Twitter at https://twitter.com/entgo_io
## About the Project
The `ent` project was inspired by Ent, an entity framework we use internally. It is developed and maintained
by [a8m](https://github.com/a8m) and [alexsn](https://github.com/alexsn)
from the [Facebook Connectivity][fbc] team. It is used by multiple teams and projects in production,
and the roadmap for its v1 release is described [here](https://github.com/ent/ent/issues/46).
Read more about the motivation of the project [here](https://entgo.io/blog/2019/10/03/introducing-ent).
## License
ent is licensed under Apache 2.0 as found in the [LICENSE file](LICENSE).
[entgo instal]: https://entgo.io/docs/code-gen/#version-compatibility-between-entc-and-ent
[Go modules]: https://github.com/golang/go/wiki/Modules#quick-start
[fbc]: https://connectivity.fb.com

54
vendor/entgo.io/ent/README_jp.md generated vendored

@ -0,0 +1,54 @@
## ent - Goのエンティティーフレームワーク
[![Twitter](https://img.shields.io/twitter/url/https/twitter.com/entgo_io.svg?style=social&label=Follow%20%40entgo_io)](https://twitter.com/entgo_io)
[English](README.md) | [中文](README_zh.md) | [日本語](README_jp.md)
<img width="50%"
align="right"
style="display: block; margin:40px auto;"
src="https://s3.eu-central-1.amazonaws.com/entgo.io/assets/gopher_graph.png"/>
シンプルながらもパワフルなGoのエンティティフレームワークであり、大規模なデータモデルを持つアプリケーションを容易に構築・保守できるようにします。
- **Schema As Code(コードとしてのスキーマ)** - あらゆるデータベーススキーマをGoオブジェクトとしてモデル化します。
- **任意のグラフを簡単にトラバースできます** - クエリや集約の実行、任意のグラフ構造の走査を容易に実行できます。
- **100%静的に型付けされた明示的なAPI** - コード生成により、100%静的に型付けされた曖昧さのないAPIを提供します。
- **マルチストレージドライバ** - MySQL、PostgreSQL、SQLite、Gremlinをサポートしています。
- **拡張性** - Goテンプレートを使用して簡単に拡張、カスタマイズできます。
## クイックインストール
```console
go install entgo.io/ent/cmd/ent@latest
```
[Go modules]を使ったインストールについては、[entgo.io website][entgo instal]をご覧ください。
## ドキュメントとサポート
entを開発・使用するためのドキュメントは、こちら。: https://entgo.io
議論やサポートについては、[Issueを立てる](https://github.com/ent/ent/issues/new/choose)か、gophers Slackの[チャンネル](https://gophers.slack.com/archives/C01FMSQDT53)に参加してください。
## entコミュニティへの参加
`ent`の構築は、コミュニティ全体の協力なしには実現できませんでした。 私たちは、この`ent`の貢献者をリストアップした[contributorsページ](doc/md/contributors.md)を管理しています。
`ent`に貢献するときは、まず[CONTRIBUTING](CONTRIBUTING.md)を参照してください。
もし、あなたの会社や製品で`ent`を利用している場合は、[ent usersページ](https://github.com/ent/ent/wiki/ent-users)に追記する形で、そのことをぜひ教えて下さい。
最新情報については、Twitter(<https://twitter.com/entgo_io>)をフォローしてください。
## プロジェクトについて
`ent`プロジェクトは、私たちが社内で使用しているエンティティフレームワークである`Ent`からインスピレーションを得ています。
entは、[Facebook Connectivity][fbc]チームの[a8m](https://github.com/a8m)と[alexsn](https://github.com/alexsn)が開発・保守しています。
本番環境では複数のチームやプロジェクトで使用されており、v1リリースまでのロードマップは[こちら](https://github.com/ent/ent/issues/46)に記載されています。
このプロジェクトの動機については[こちら](https://entgo.io/blog/2019/10/03/introducing-ent)をご覧ください。
## ライセンス
entは、[LICENSEファイル](LICENSE)にもある通り、Apache 2.0でライセンスされています。
[entgo instal]: https://entgo.io/docs/code-gen/#version-compatibility-between-entc-and-ent
[Go modules]: https://github.com/golang/go/wiki/Modules#quick-start
[fbc]: https://connectivity.fb.com

44
vendor/entgo.io/ent/README_zh.md generated vendored

@ -0,0 +1,44 @@
## ent - 一个强大的Go语言实体框架
[English](README.md) | [中文](README_zh.md) | [日本語](README_jp.md)
<img width="50%"
align="right"
style="display: block; margin:40px auto;"
src="https://s3.eu-central-1.amazonaws.com/entgo.io/assets/gopher_graph.png"/>
ent是一个简单而又功能强大的Go语言实体框架ent易于构建和维护应用程序与大数据模型。
- **图就是代码** - 将任何数据库表建模为Go对象。
- **轻松地遍历任何图形** - 可以轻松地运行查询、聚合和遍历任何图形结构。
- **静态类型和显式API** - 使用代码生成静态类型和显式API查询数据更加便捷。
- **多存储驱动程序** - 支持MySQL, PostgreSQL, SQLite 和 Gremlin。
- **可扩展** - 简单地扩展和使用Go模板自定义。
## 快速安装
```console
go install entgo.io/ent/cmd/ent@latest
```
请访问[entgo.io website][entgo instal]以使用[Go modules]进行正确安装。
## 文档和支持
开发和使用ent的文档请参照 https://entgo.io
如要讨论问题和支持, [创建一个issue](https://github.com/ent/ent/issues/new/choose) 或者加入我们的Gopher Slack(Slack软件,类似于论坛)[讨论组](https://gophers.slack.com/archives/C01FMSQDT53)
## 加入 ent 社区
如果你想为`ent`做出贡献, [贡献代码](CONTRIBUTING.md) 中写了如何做出自己的贡献
如果你的公司或者产品在使用`ent`,请让我们知道你已经加入 [ent 用户](https://github.com/ent/ent/wiki/ent-users)
## 关于项目
`ent` 项目灵感来自于EntEnt是一个facebook内部使用的一个实体框架项目。 它由 [Facebook Connectivity][fbc] 团队通过 [a8m](https://github.com/a8m) 和 [alexsn](https://github.com/alexsn) 开发和维护
, 它被生产中的多个团队和项目使用。它的v1版本的路线图为 [版本的路线图](https://github.com/ent/ent/issues/46).
关于项目更多的信息 [ent介绍](https://entgo.io/blog/2019/10/03/introducing-ent)。
## 声明
ent使用Apache 2.0协议授权,可以在[LICENSE文件](LICENSE)中找到。
[entgo instal]: https://entgo.io/docs/code-gen/#version-compatibility-between-entc-and-ent
[Go modules]: https://github.com/golang/go/wiki/Modules#quick-start
[fbc]: https://connectivity.fb.com

365
vendor/entgo.io/ent/ent.go generated vendored

@ -0,0 +1,365 @@
// Copyright 2019-present Facebook Inc. All rights reserved.
// This source code is licensed under the Apache 2.0 license found
// in the LICENSE file in the root directory of this source tree.
// Package ent is the interface between end-user schemas and entc (ent codegen).
package ent
import (
"context"
"entgo.io/ent/schema"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
type (
// The Interface type describes the requirements for an exported type defined in the schema package.
// It functions as the interface between the user's schema types and codegen loader.
// Users should use the Schema type for embedding as follows:
//
// type T struct {
// ent.Schema
// }
//
Interface interface {
// Type is a dummy method, that is used in edge declaration.
//
// The Type method should be used as follows:
//
// type S struct { ent.Schema }
//
// type T struct { ent.Schema }
//
// func (T) Edges() []ent.Edge {
// return []ent.Edge{
// edge.To("S", S.Type),
// }
// }
//
Type()
// Fields returns the fields of the schema.
Fields() []Field
// Edges returns the edges of the schema.
Edges() []Edge
// Indexes returns the indexes of the schema.
Indexes() []Index
// Config returns an optional config for the schema.
//
// Deprecated: the Config method predates the Annotations method, and it
// is planned be removed in v0.5.0. New code should use Annotations instead.
//
// func (T) Annotations() []schema.Annotation {
// return []schema.Annotation{
// entsql.Annotation{Table: "Name"},
// }
// }
//
Config() Config
// Mixin returns an optional list of Mixin to extends
// the schema.
Mixin() []Mixin
// Hooks returns an optional list of Hook to apply on
// mutations.
Hooks() []Hook
// Policy returns the privacy policy of the schema.
Policy() Policy
// Annotations returns a list of schema annotations to be used by
// codegen extensions.
Annotations() []schema.Annotation
}
// A Field interface returns a field descriptor for vertex fields/properties.
// The usage for the interface is as follows:
//
// func (T) Fields() []ent.Field {
// return []ent.Field{
// field.Int("int"),
// }
// }
//
Field interface {
Descriptor() *field.Descriptor
}
// An Edge interface returns an edge descriptor for vertex edges.
// The usage for the interface is as follows:
//
// func (T) Edges() []ent.Edge {
// return []ent.Edge{
// edge.To("S", S.Type),
// }
// }
//
Edge interface {
Descriptor() *edge.Descriptor
}
// An Index interface returns an index descriptor for vertex indexes.
// The usage for the interface is as follows:
//
// func (T) Indexes() []ent.Index {
// return []ent.Index{
// index.Fields("f1", "f2").
// Unique(),
// }
// }
//
Index interface {
Descriptor() *index.Descriptor
}
// A Config structure is used to configure an entity schema.
// The usage of this structure is as follows:
//
// func (T) Config() ent.Config {
// return ent.Config{
// Table: "Name",
// }
// }
//
// Deprecated: the Config object predates the schema.Annotation method and it
// is planned be removed in v0.5.0. New code should use Annotations instead.
//
// func (T) Annotations() []schema.Annotation {
// return []schema.Annotation{
// entsql.Annotation{Table: "Name"},
// }
// }
//
Config struct {
// A Table is an optional table name defined for the schema.
Table string
}
// The Mixin type describes a set of methods that can extend
// other methods in the schema without calling them directly.
//
// type TimeMixin struct {}
//
// func (TimeMixin) Fields() []ent.Field {
// return []ent.Field{
// field.Time("created_at").
// Immutable().
// Default(time.Now),
// field.Time("updated_at").
// Default(time.Now).
// UpdateDefault(time.Now),
// }
// }
//
// type T struct {
// ent.Schema
// }
//
// func(T) Mixin() []ent.Mixin {
// return []ent.Mixin{
// TimeMixin{},
// }
// }
//
Mixin interface {
// Fields returns a slice of fields to add to the schema.
Fields() []Field
// Edges returns a slice of edges to add to the schema.
Edges() []Edge
// Indexes returns a slice of indexes to add to the schema.
Indexes() []Index
// Hooks returns a slice of hooks to add to the schema.
// Note that mixin hooks are executed before schema hooks.
Hooks() []Hook
// Policy returns a privacy policy to add to the schema.
// Note that mixin policy are executed before schema policy.
Policy() Policy
// Annotations returns a list of schema annotations to add
// to the schema annotations.
Annotations() []schema.Annotation
}
// The Policy type defines the write privacy policy of an entity.
// The usage for the interface is as follows:
//
// type T struct {
// ent.Schema
// }
//
// func(T) Policy() ent.Policy {
// return privacy.AlwaysAllowReadWrite()
// }
//
Policy interface {
EvalMutation(context.Context, Mutation) error
EvalQuery(context.Context, Query) error
}
// Schema is the default implementation for the schema Interface.
// It can be embedded in end-user schemas as follows:
//
// type T struct {
// ent.Schema
// }
//
Schema struct {
Interface
}
)
// Fields of the schema.
func (Schema) Fields() []Field { return nil }
// Edges of the schema.
func (Schema) Edges() []Edge { return nil }
// Indexes of the schema.
func (Schema) Indexes() []Index { return nil }
// Config of the schema.
func (Schema) Config() Config { return Config{} }
// Mixin of the schema.
func (Schema) Mixin() []Mixin { return nil }
// Hooks of the schema.
func (Schema) Hooks() []Hook { return nil }
// Policy of the schema.
func (Schema) Policy() Policy { return nil }
// Annotations of the schema.
func (Schema) Annotations() []schema.Annotation { return nil }
type (
// Value represents a value returned by ent.
Value interface{}
// Query represents an ent query builder.
Query interface{}
// Mutation represents an operation that mutate the graph.
// For example, adding a new node, updating many, or dropping
// data. The implementation is generated by entc (ent codegen).
Mutation interface {
// Op returns the operation name generated by entc.
Op() Op
// Type returns the schema type for this mutation.
Type() string
// Fields returns all fields that were changed during
// this mutation. Note that, in order to get all numeric
// fields that were in/decremented, call AddedFields().
Fields() []string
// Field returns the value of a field with the given name.
// The second boolean value indicates that this field was
// not set, or was not defined in the schema.
Field(name string) (Value, bool)
// SetField sets the value for the given name. It returns an
// error if the field is not defined in the schema, or if the
// type mismatch the field type.
SetField(name string, value Value) error
// AddedFields returns all numeric fields that were incremented
// or decremented during this mutation.
AddedFields() []string
// AddedField returns the numeric value that was in/decremented
// from a field with the given name. The second value indicates
// that this field was not set, or was not define in the schema.
AddedField(name string) (Value, bool)
// AddField adds the value for the given name. It returns an
// error if the field is not defined in the schema, or if the
// type mismatch the field type.
AddField(name string, value Value) error
// ClearedFields returns all nullable fields that were cleared
// during this mutation.
ClearedFields() []string
// FieldCleared returns a bool indicates if this field was
// cleared in this mutation.
FieldCleared(name string) bool
// ClearField clears the value for the given name. It returns an
// error if the field is not defined in the schema.
ClearField(name string) error
// ResetField resets all changes in the mutation regarding the
// given field name. It returns an error if the field is not
// defined in the schema.
ResetField(name string) error
// AddedEdges returns all edge names that were set/added in this
// mutation.
AddedEdges() []string
// AddedIDs returns all ids (to other nodes) that were added for
// the given edge name.
AddedIDs(name string) []Value
// RemovedEdges returns all edge names that were removed in this
// mutation.
RemovedEdges() []string
// RemovedIDs returns all ids (to other nodes) that were removed for
// the given edge name.
RemovedIDs(name string) []Value
// ClearedEdges returns all edge names that were cleared in this
// mutation.
ClearedEdges() []string
// EdgeCleared returns a bool indicates if this edge was
// cleared in this mutation.
EdgeCleared(name string) bool
// ClearEdge clears the value for the given name. It returns an
// error if the edge name is not defined in the schema.
ClearEdge(name string) error
// ResetEdge resets all changes in the mutation regarding the
// given edge name. It returns an error if the edge is not
// defined in the schema.
ResetEdge(name string) error
// OldField returns the old value of the field from the database.
// An error is returned if the mutation operation is not UpdateOne,
// or the query to the database was failed.
OldField(ctx context.Context, name string) (Value, error)
}
// Mutator is the interface that wraps the Mutate method.
Mutator interface {
// Mutate apply the given mutation on the graph.
Mutate(context.Context, Mutation) (Value, error)
}
// The MutateFunc type is an adapter to allow the use of ordinary
// function as mutator. If f is a function with the appropriate signature,
// MutateFunc(f) is a Mutator that calls f.
MutateFunc func(context.Context, Mutation) (Value, error)
// Hook defines the "mutation middleware". A function that gets a Mutator
// and returns a Mutator. For example:
//
// hook := func(next ent.Mutator) ent.Mutator {
// return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
// fmt.Printf("Type: %s, Operation: %s, ConcreteType: %T\n", m.Type(), m.Op(), m)
// return next.Mutate(ctx, m)
// })
// }
//
Hook func(Mutator) Mutator
)
// Mutate calls f(ctx, m).
func (f MutateFunc) Mutate(ctx context.Context, m Mutation) (Value, error) {
return f(ctx, m)
}
// An Op represents a mutation operation.
type Op uint
// Mutation operations.
const (
OpCreate Op = 1 << iota // node creation.
OpUpdate // update nodes by predicate (if any).
OpUpdateOne // update one node.
OpDelete // delete nodes by predicate (if any).
OpDeleteOne // delete one node.
)
// Is reports whether o is match the given operation.
func (i Op) Is(o Op) bool { return i&o != 0 }
//go:generate go run golang.org/x/tools/cmd/stringer -type Op

43
vendor/entgo.io/ent/op_string.go generated vendored

@ -0,0 +1,43 @@
// Code generated by "stringer -type Op"; DO NOT EDIT.
package ent
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[OpCreate-1]
_ = x[OpUpdate-2]
_ = x[OpUpdateOne-4]
_ = x[OpDelete-8]
_ = x[OpDeleteOne-16]
}
const (
_Op_name_0 = "OpCreateOpUpdate"
_Op_name_1 = "OpUpdateOne"
_Op_name_2 = "OpDelete"
_Op_name_3 = "OpDeleteOne"
)
var (
_Op_index_0 = [...]uint8{0, 8, 16}
)
func (i Op) String() string {
switch {
case 1 <= i && i <= 2:
i -= 1
return _Op_name_0[_Op_index_0[i]:_Op_index_0[i+1]]
case i == 4:
return _Op_name_1
case i == 8:
return _Op_name_2
case i == 16:
return _Op_name_3
default:
return "Op(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

@ -0,0 +1,49 @@
// Copyright 2019-present Facebook Inc. All rights reserved.
// This source code is licensed under the Apache 2.0 license found
// in the LICENSE file in the root directory of this source tree.
package edge
import "entgo.io/ent/schema"
// Annotation is a builtin schema annotation for
// configuring the edges' behavior in codegen.
type Annotation struct {
// The StructTag option allows overriding the struct-tag
// of the `Edges` field in the generated entity. For example:
//
// edge.Annotation{
// StructTag: `json: "pet_edges"`
// }
//
StructTag string
}
// Name describes the annotation name.
func (Annotation) Name() string {
return "Edges"
}
// Merge implements the schema.Merger interface.
func (a Annotation) Merge(other schema.Annotation) schema.Annotation {
var ant Annotation
switch other := other.(type) {
case Annotation:
ant = other
case *Annotation:
if other != nil {
ant = *other
}
default:
return a
}
if tag := ant.StructTag; tag != "" {
a.StructTag = tag
}
return a
}
var (
_ schema.Annotation = (*Annotation)(nil)
_ schema.Merger = (*Annotation)(nil)
)

@ -0,0 +1,270 @@
// Copyright 2019-present Facebook Inc. All rights reserved.
// This source code is licensed under the Apache 2.0 license found
// in the LICENSE file in the root directory of this source tree.
package edge
import (
"reflect"
"entgo.io/ent/schema"
)
// A Descriptor for edge configuration.
type Descriptor struct {
Tag string // struct tag.
Type string // edge type.
Name string // edge name.
Field string // edge field name (e.g. foreign-key).
RefName string // ref name; inverse only.
Ref *Descriptor // edge reference; to/from of the same type.
Through *struct{ N, T string } // through type and name.
Unique bool // unique edge.
Inverse bool // inverse edge.
Required bool // required on creation.
StorageKey *StorageKey // optional storage-key configuration.
Annotations []schema.Annotation // edge annotations.
Comment string // edge comment.
}
// To defines an association edge between two vertices.
func To(name string, t interface{}) *assocBuilder {
return &assocBuilder{desc: &Descriptor{Name: name, Type: typ(t)}}
}
// From represents a reversed-edge between two vertices that has a back-reference to its source edge.
func From(name string, t interface{}) *inverseBuilder {
return &inverseBuilder{desc: &Descriptor{Name: name, Type: typ(t), Inverse: true}}
}
func typ(t interface{}) string {
if rt := reflect.TypeOf(t); rt.NumIn() > 0 {
return rt.In(0).Name()
}
return ""
}
// assocBuilder is the builder for assoc edges.
type assocBuilder struct {
desc *Descriptor
}
// Unique sets the edge type to be unique. Basically, it limits the edge to be one of the two:
// one2one or one2many. one2one applied if the inverse-edge is also unique.
func (b *assocBuilder) Unique() *assocBuilder {
b.desc.Unique = true
return b
}
// Required indicates that this edge is a required field on creation.
// Unlike fields, edges are optional by default.
func (b *assocBuilder) Required() *assocBuilder {
b.desc.Required = true
return b
}
// StructTag sets the struct tag of the assoc edge.
func (b *assocBuilder) StructTag(s string) *assocBuilder {
b.desc.Tag = s
return b
}
// From creates an inverse-edge with the same type.
func (b *assocBuilder) From(name string) *inverseBuilder {
return &inverseBuilder{desc: &Descriptor{Name: name, Type: b.desc.Type, Inverse: true, Ref: b.desc}}
}
// Field is used to bind an edge (with a foreign-key) to a field in the schema.
//
// field.Int("owner_id").
// Optional()
//
// edge.To("owner", User.Type).
// Field("owner_id").
// Unique(),
//
func (b *assocBuilder) Field(f string) *assocBuilder {
b.desc.Field = f
return b
}
// Through allows setting an "edge schema" to interact explicitly with M2M edges.
//
// edge.To("friends", User.Type).
// Through("friendships", Friendship.Type)
//
func (b *assocBuilder) Through(name string, t interface{}) *assocBuilder {
b.desc.Through = &struct{ N, T string }{N: name, T: typ(t)}
return b
}
// Comment used to put annotations on the schema.
func (b *assocBuilder) Comment(c string) *assocBuilder {
b.desc.Comment = c
return b
}
// StorageKey sets the storage key of the edge.
//
// edge.To("groups", Group.Type).
// StorageKey(edge.Table("user_groups"), edge.Columns("user_id", "group_id"))
//
func (b *assocBuilder) StorageKey(opts ...StorageOption) *assocBuilder {
if b.desc.StorageKey == nil {
b.desc.StorageKey = &StorageKey{}
}
for i := range opts {
opts[i](b.desc.StorageKey)
}
return b
}
// Annotations adds a list of annotations to the edge object to be used by
// codegen extensions.
//
// edge.To("pets", Pet.Type).
// Annotations(entgql.Bind())
//
func (b *assocBuilder) Annotations(annotations ...schema.Annotation) *assocBuilder {
b.desc.Annotations = append(b.desc.Annotations, annotations...)
return b
}
// Descriptor implements the ent.Descriptor interface.
func (b *assocBuilder) Descriptor() *Descriptor {
return b.desc
}
// inverseBuilder is the builder for inverse edges.
type inverseBuilder struct {
desc *Descriptor
}
// Ref sets the referenced-edge of this inverse edge.
func (b *inverseBuilder) Ref(ref string) *inverseBuilder {
b.desc.RefName = ref
return b
}
// Unique sets the edge type to be unique. Basically, it limits the edge to be one of the two:
// one2one or one2many. one2one applied if the inverse-edge is also unique.
func (b *inverseBuilder) Unique() *inverseBuilder {
b.desc.Unique = true
return b
}
// Required indicates that this edge is a required field on creation.
// Unlike fields, edges are optional by default.
func (b *inverseBuilder) Required() *inverseBuilder {
b.desc.Required = true
return b
}
// StructTag sets the struct tag of the inverse edge.
func (b *inverseBuilder) StructTag(s string) *inverseBuilder {
b.desc.Tag = s
return b
}
// Comment used to put annotations on the schema.
func (b *inverseBuilder) Comment(c string) *inverseBuilder {
b.desc.Comment = c
return b
}
// Field is used to bind an edge (with a foreign-key) to a field in the schema.
//
// field.Int("owner_id").
// Optional()
//
// edge.From("owner", User.Type).
// Ref("pets").
// Field("owner_id").
// Unique(),
//
func (b *inverseBuilder) Field(f string) *inverseBuilder {
b.desc.Field = f
return b
}
// Through allows setting an "edge schema" to interact explicitly with M2M edges.
//
// edge.From("liked_users", User.Type).
// Ref("liked_tweets").
// Through("likes", TweetLike.Type)
//
func (b *inverseBuilder) Through(name string, t interface{}) *inverseBuilder {
b.desc.Through = &struct{ N, T string }{N: name, T: typ(t)}
return b
}
// Annotations adds a list of annotations to the edge object to be used by
// codegen extensions.
//
// edge.From("owner", User.Type).
// Ref("pets").
// Unique().
// Annotations(entgql.Bind())
//
func (b *inverseBuilder) Annotations(annotations ...schema.Annotation) *inverseBuilder {
b.desc.Annotations = append(b.desc.Annotations, annotations...)
return b
}
// Descriptor implements the ent.Descriptor interface.
func (b *inverseBuilder) Descriptor() *Descriptor {
return b.desc
}
// StorageKey holds the configuration for edge storage-key.
type StorageKey struct {
Table string // Table or label.
Symbols []string // Symbols/names of the foreign-key constraints.
Columns []string // Foreign-key columns.
}
// StorageOption allows for setting the storage configuration using functional options.
type StorageOption func(*StorageKey)
// Table sets the table name option for M2M edges.
func Table(name string) StorageOption {
return func(key *StorageKey) {
key.Table = name
}
}
// Symbol sets the symbol/name of the foreign-key constraint for O2O, O2M and M2O edges.
// Note that, for M2M edges (2 columns and 2 constraints), use the edge.Symbols option.
func Symbol(symbol string) StorageOption {
return func(key *StorageKey) {
key.Symbols = []string{symbol}
}
}
// Symbols sets the symbol/name of the foreign-key constraints for M2M edges.
// The 1st column defines the name of the "To" edge, and the 2nd defines
// the name of the "From" edge (inverse edge).
// Note that, for O2O, O2M and M2O edges, use the edge.Symbol option.
func Symbols(to, from string) StorageOption {
return func(key *StorageKey) {
key.Symbols = []string{to, from}
}
}
// Column sets the foreign-key column name option for O2O, O2M and M2O edges.
// Note that, for M2M edges (2 columns), use the edge.Columns option.
func Column(name string) StorageOption {
return func(key *StorageKey) {
key.Columns = []string{name}
}
}
// Columns sets the foreign-key column names option for M2M edges.
// The 1st column defines the name of the "To" edge, and the 2nd defines
// the name of the "From" edge (inverse edge).
// Note that, for O2O, O2M and M2O edges, use the edge.Column option.
func Columns(to, from string) StorageOption {
return func(key *StorageKey) {
key.Columns = []string{to, from}
}
}

@ -0,0 +1,81 @@
// Copyright 2019-present Facebook Inc. All rights reserved.
// This source code is licensed under the Apache 2.0 license found
// in the LICENSE file in the root directory of this source tree.
package field
import "entgo.io/ent/schema"
// Annotation is a builtin schema annotation for
// configuring the schema fields in codegen.
type Annotation struct {
// The StructTag option allows overriding the struct-tag
// of the fields in the generated entity. For example:
//
// field.Annotation{
// StructTag: map[string]string{
// "id": `json:"id,omitempty" yaml:"-"`,
// },
// }
//
StructTag map[string]string
// ID defines a multi-field schema identifier. Note,
// the annotation is valid only for edge schemas.
//
// func (TweetLike) Annotations() []schema.Annotation {
// return []schema.Annotation{
// field.ID("user_id", "tweet_id"),
// }
// }
//
ID []string
}
// ID defines a multi-field schema identifier. Note, the
// annotation is valid only for edge schemas.
//
// func (TweetLike) Annotations() []schema.Annotation {
// return []schema.Annotation{
// field.ID("user_id", "tweet_id"),
// }
// }
//
func ID(first, second string, fields ...string) *Annotation {
return &Annotation{ID: append([]string{first, second}, fields...)}
}
// Name describes the annotation name.
func (Annotation) Name() string {
return "Fields"
}
// Merge implements the schema.Merger interface.
func (a Annotation) Merge(other schema.Annotation) schema.Annotation {
var ant Annotation
switch other := other.(type) {
case Annotation:
ant = other
case *Annotation:
if other != nil {
ant = *other
}
default:
return a
}
for k, v := range ant.StructTag {
if a.StructTag == nil {
a.StructTag = make(map[string]string)
}
a.StructTag[k] = v
}
if len(ant.ID) > 0 {
a.ID = ant.ID
}
return a
}
var _ interface {
schema.Annotation
schema.Merger
} = (*Annotation)(nil)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,244 @@
// Copyright 2019-present Facebook Inc. All rights reserved.
// This source code is licensed under the Apache 2.0 license found
// in the LICENSE file in the root directory of this source tree.
package field
import (
"fmt"
"reflect"
"strings"
)
// A Type represents a field type.
type Type uint8
// List of field types.
const (
TypeInvalid Type = iota
TypeBool
TypeTime
TypeJSON
TypeUUID
TypeBytes
TypeEnum
TypeString
TypeOther
TypeInt8
TypeInt16
TypeInt32
TypeInt
TypeInt64
TypeUint8
TypeUint16
TypeUint32
TypeUint
TypeUint64
TypeFloat32
TypeFloat64
endTypes
)
// String returns the string representation of a type.
func (t Type) String() string {
if t < endTypes {
return typeNames[t]
}
return typeNames[TypeInvalid]
}
// Numeric reports if the given type is a numeric type.
func (t Type) Numeric() bool {
return t >= TypeInt8 && t < endTypes
}
// Float reports if the given type is a float type.
func (t Type) Float() bool {
return t == TypeFloat32 || t == TypeFloat64
}
// Integer reports if the given type is an integral type.
func (t Type) Integer() bool {
return t.Numeric() && !t.Float()
}
// Valid reports if the given type if known type.
func (t Type) Valid() bool {
return t > TypeInvalid && t < endTypes
}
// ConstName returns the constant name of an info type.
// It's used by entc for printing the constant name in templates.
func (t Type) ConstName() string {
switch {
case !t.Valid():
return typeNames[TypeInvalid]
case int(t) < len(constNames) && constNames[t] != "":
return constNames[t]
default:
return "Type" + strings.Title(typeNames[t])
}
}
// TypeInfo holds the information regarding field type.
// Used by complex types like JSON and Bytes.
type TypeInfo struct {
Type Type
Ident string
PkgPath string // import path.
PkgName string // local package name.
Nillable bool // slices or pointers.
RType *RType
}
// String returns the string representation of a type.
func (t TypeInfo) String() string {
switch {
case t.Ident != "":
return t.Ident
case t.Type < endTypes:
return typeNames[t.Type]
default:
return typeNames[TypeInvalid]
}
}
// Valid reports if the given type if known type.
func (t TypeInfo) Valid() bool {
return t.Type.Valid()
}
// Numeric reports if the given type is a numeric type.
func (t TypeInfo) Numeric() bool {
return t.Type.Numeric()
}
// ConstName returns the const name of the info type.
func (t TypeInfo) ConstName() string {
return t.Type.ConstName()
}
// ValueScanner indicates if this type implements the ValueScanner interface.
func (t TypeInfo) ValueScanner() bool {
return t.RType.Implements(valueScannerType)
}
// Valuer indicates if this type implements the driver.Valuer interface.
func (t TypeInfo) Valuer() bool {
return t.RType.Implements(valuerType)
}
// Comparable reports whether values of this type are comparable.
func (t TypeInfo) Comparable() bool {
switch t.Type {
case TypeBool, TypeTime, TypeUUID, TypeEnum, TypeString:
return true
case TypeOther:
// Always accept custom types as comparable on the database side.
// In the future, we should consider adding an interface to let
// custom types tell if they are comparable or not (see #1304).
return true
default:
return t.Numeric()
}
}
var stringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
// Stringer indicates if this type implements the Stringer interface.
func (t TypeInfo) Stringer() bool {
return t.RType.Implements(stringerType)
}
var (
typeNames = [...]string{
TypeInvalid: "invalid",
TypeBool: "bool",
TypeTime: "time.Time",
TypeJSON: "json.RawMessage",
TypeUUID: "[16]byte",
TypeBytes: "[]byte",
TypeEnum: "string",
TypeString: "string",
TypeOther: "other",
TypeInt: "int",
TypeInt8: "int8",
TypeInt16: "int16",
TypeInt32: "int32",
TypeInt64: "int64",
TypeUint: "uint",
TypeUint8: "uint8",
TypeUint16: "uint16",
TypeUint32: "uint32",
TypeUint64: "uint64",
TypeFloat32: "float32",
TypeFloat64: "float64",
}
constNames = [...]string{
TypeJSON: "TypeJSON",
TypeUUID: "TypeUUID",
TypeTime: "TypeTime",
TypeEnum: "TypeEnum",
TypeBytes: "TypeBytes",
TypeOther: "TypeOther",
}
)
// RType holds a serializable reflect.Type information of
// Go object. Used by the entc package.
type RType struct {
Name string // reflect.Type.Name
Ident string // reflect.Type.String
Kind reflect.Kind
PkgPath string
Methods map[string]struct{ In, Out []*RType }
// Used only for in-package checks.
rtype reflect.Type
}
// TypeEqual reports if the underlying type is equal to the RType (after pointer indirections).
func (r *RType) TypeEqual(t reflect.Type) bool {
tv := indirect(t)
return r.Name == tv.Name() && r.Kind == t.Kind() && r.PkgPath == tv.PkgPath()
}
// RType returns the string value of the indirect reflect.Type.
func (r *RType) String() string {
if r.rtype != nil {
return r.rtype.String()
}
return r.Ident
}
// IsPtr reports if the reflect-type is a pointer type.
func (r *RType) IsPtr() bool {
return r != nil && r.Kind == reflect.Ptr
}
// Implements reports whether the RType ~implements the given interface type.
func (r *RType) Implements(typ reflect.Type) bool {
if r == nil {
return false
}
n := typ.NumMethod()
for i := 0; i < n; i++ {
m0 := typ.Method(i)
m1, ok := r.Methods[m0.Name]
if !ok || len(m1.In) != m0.Type.NumIn() || len(m1.Out) != m0.Type.NumOut() {
return false
}
in := m0.Type.NumIn()
for j := 0; j < in; j++ {
if !m1.In[j].TypeEqual(m0.Type.In(j)) {
return false
}
}
out := m0.Type.NumOut()
for j := 0; j < out; j++ {
if !m1.Out[j].TypeEqual(m0.Type.Out(j)) {
return false
}
}
}
return true
}

@ -0,0 +1,121 @@
// Copyright 2019-present Facebook Inc. All rights reserved.
// This source code is licensed under the Apache 2.0 license found
// in the LICENSE file in the root directory of this source tree.
package index
import "entgo.io/ent/schema"
// A Descriptor for index configuration.
type Descriptor struct {
Unique bool // unique index.
Edges []string // edge columns.
Fields []string // field columns.
StorageKey string // custom index name.
Annotations []schema.Annotation // index annotations.
}
// Builder for indexes on vertex columns and edges in the graph.
type Builder struct {
desc *Descriptor
}
// Fields creates an index on the given vertex fields.
// Note that indexes are implemented only for SQL dialects, and does not support gremlin.
//
// func (T) Indexes() []ent.Index {
//
// // Unique index on 2 fields.
// index.Fields("first", "last").
// Unique(),
//
// // Unique index of field under specific edge.
// index.Fields("name").
// Edges("parent").
// Unique(),
//
// }
//
func Fields(fields ...string) *Builder {
return &Builder{desc: &Descriptor{Fields: fields}}
}
// Edges creates an index on the given vertex edge fields.
// Note that indexes are implemented only for SQL dialects, and does not support gremlin.
//
// func (T) Indexes() []ent.Index {
//
// // Unique index of field under 2 edges.
// index.Fields("name").
// Edges("parent", "type").
// Unique(),
//
// }
//
func Edges(edges ...string) *Builder {
return &Builder{desc: &Descriptor{Edges: edges}}
}
// Fields sets the fields of the index.
//
// func (T) Indexes() []ent.Index {
//
// // Unique "name" and "age" fields under the "parent" edge.
// index.Edges("parent").
// Fields("name", "age").
// Unique(),
//
// }
func (b *Builder) Fields(fields ...string) *Builder {
b.desc.Fields = fields
return b
}
// Edges sets the fields index to be unique under the set of edges (sub-graph). For example:
//
// func (T) Indexes() []ent.Index {
//
// // Unique "name" field under the "parent" edge.
// index.Fields("name").
// Edges("parent").
// Unique(),
// }
//
func (b *Builder) Edges(edges ...string) *Builder {
b.desc.Edges = edges
return b
}
// Unique sets the index to be a unique index.
// Note that defining a uniqueness on optional fields won't prevent
// duplicates if one of the column contains NULL values.
func (b *Builder) Unique() *Builder {
b.desc.Unique = true
return b
}
// StorageKey sets the storage key of the index. In SQL dialects, it's the index name.
func (b *Builder) StorageKey(key string) *Builder {
b.desc.StorageKey = key
return b
}
// Annotations adds a list of annotations to the index object to be used by codegen extensions.
//
// func (T) Indexes() []ent.Index {
//
// // Partial index on name where the entity is not deleted.
// index.Fields("name").
// Annotations(entsql.Prefix(100))
//
// }
//
func (b *Builder) Annotations(annotations ...schema.Annotation) *Builder {
b.desc.Annotations = append(b.desc.Annotations, annotations...)
return b
}
// Descriptor implements the ent.Descriptor interface.
func (b *Builder) Descriptor() *Descriptor {
return b.desc
}

@ -0,0 +1,24 @@
// Copyright 2019-present Facebook Inc. All rights reserved.
// This source code is licensed under the Apache 2.0 license found
// in the LICENSE file in the root directory of this source tree.
package schema
// Annotation is used to attach arbitrary metadata to the schema objects in codegen.
// The object must be serializable to JSON raw value (e.g. struct, map or slice).
//
// Template extensions can retrieve this metadata and use it inside their templates.
// Read more about it in ent website: https://entgo.io/docs/templates/#annotations.
type Annotation interface {
// Name defines the name of the annotation to be retrieved by the codegen.
Name() string
}
// Merger wraps the single Merge function allows custom annotation to provide
// an implementation for merging 2 or more annotations from the same type.
//
// A common use case is where the same Annotation type is defined both in
// mixin.Schema and ent.Schema.
type Merger interface {
Merge(Annotation) Annotation
}

@ -0,0 +1,15 @@
ISC License
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

@ -0,0 +1,145 @@
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is not running on Google App Engine, compiled by GopherJS, and
// "-tags safe" is not added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// Go versions prior to 1.4 are disabled because they use a different layout
// for interfaces which make the implementation of unsafeReflectValue more complex.
// +build !js,!appengine,!safe,!disableunsafe,go1.4
package spew
import (
"reflect"
"unsafe"
)
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = false
// ptrSize is the size of a pointer on the current arch.
ptrSize = unsafe.Sizeof((*byte)(nil))
)
type flag uintptr
var (
// flagRO indicates whether the value field of a reflect.Value
// is read-only.
flagRO flag
// flagAddr indicates whether the address of the reflect.Value's
// value may be taken.
flagAddr flag
)
// flagKindMask holds the bits that make up the kind
// part of the flags field. In all the supported versions,
// it is in the lower 5 bits.
const flagKindMask = flag(0x1f)
// Different versions of Go have used different
// bit layouts for the flags type. This table
// records the known combinations.
var okFlags = []struct {
ro, addr flag
}{{
// From Go 1.4 to 1.5
ro: 1 << 5,
addr: 1 << 7,
}, {
// Up to Go tip.
ro: 1<<5 | 1<<6,
addr: 1 << 8,
}}
var flagValOffset = func() uintptr {
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
if !ok {
panic("reflect.Value has no flag field")
}
return field.Offset
}()
// flagField returns a pointer to the flag field of a reflect.Value.
func flagField(v *reflect.Value) *flag {
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
}
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
// the typical safety restrictions preventing access to unaddressable and
// unexported data. It works by digging the raw pointer to the underlying
// value out of the protected value and generating a new unprotected (unsafe)
// reflect.Value to it.
//
// This allows us to check for implementations of the Stringer and error
// interfaces to be used for pretty printing ordinarily unaddressable and
// inaccessible values such as unexported struct fields.
func unsafeReflectValue(v reflect.Value) reflect.Value {
if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
return v
}
flagFieldPtr := flagField(&v)
*flagFieldPtr &^= flagRO
*flagFieldPtr |= flagAddr
return v
}
// Sanity checks against future reflect package changes
// to the type or semantics of the Value.flag field.
func init() {
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
if !ok {
panic("reflect.Value has no flag field")
}
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
panic("reflect.Value flag field has changed kind")
}
type t0 int
var t struct {
A t0
// t0 will have flagEmbedRO set.
t0
// a will have flagStickyRO set
a t0
}
vA := reflect.ValueOf(t).FieldByName("A")
va := reflect.ValueOf(t).FieldByName("a")
vt0 := reflect.ValueOf(t).FieldByName("t0")
// Infer flagRO from the difference between the flags
// for the (otherwise identical) fields in t.
flagPublic := *flagField(&vA)
flagWithRO := *flagField(&va) | *flagField(&vt0)
flagRO = flagPublic ^ flagWithRO
// Infer flagAddr from the difference between a value
// taken from a pointer and not.
vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
flagNoPtr := *flagField(&vA)
flagPtr := *flagField(&vPtrA)
flagAddr = flagNoPtr ^ flagPtr
// Check that the inferred flags tally with one of the known versions.
for _, f := range okFlags {
if flagRO == f.ro && flagAddr == f.addr {
return
}
}
panic("reflect.Value read-only flag has changed semantics")
}

@ -0,0 +1,38 @@
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is running on Google App Engine, compiled by GopherJS, or
// "-tags safe" is added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// +build js appengine safe disableunsafe !go1.4
package spew
import "reflect"
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = true
)
// unsafeReflectValue typically converts the passed reflect.Value into a one
// that bypasses the typical safety restrictions preventing access to
// unaddressable and unexported data. However, doing this relies on access to
// the unsafe package. This is a stub version which simply returns the passed
// reflect.Value when the unsafe package is not available.
func unsafeReflectValue(v reflect.Value) reflect.Value {
return v
}

@ -0,0 +1,341 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"reflect"
"sort"
"strconv"
)
// Some constants in the form of bytes to avoid string overhead. This mirrors
// the technique used in the fmt package.
var (
panicBytes = []byte("(PANIC=")
plusBytes = []byte("+")
iBytes = []byte("i")
trueBytes = []byte("true")
falseBytes = []byte("false")
interfaceBytes = []byte("(interface {})")
commaNewlineBytes = []byte(",\n")
newlineBytes = []byte("\n")
openBraceBytes = []byte("{")
openBraceNewlineBytes = []byte("{\n")
closeBraceBytes = []byte("}")
asteriskBytes = []byte("*")
colonBytes = []byte(":")
colonSpaceBytes = []byte(": ")
openParenBytes = []byte("(")
closeParenBytes = []byte(")")
spaceBytes = []byte(" ")
pointerChainBytes = []byte("->")
nilAngleBytes = []byte("<nil>")
maxNewlineBytes = []byte("<max depth reached>\n")
maxShortBytes = []byte("<max>")
circularBytes = []byte("<already shown>")
circularShortBytes = []byte("<shown>")
invalidAngleBytes = []byte("<invalid>")
openBracketBytes = []byte("[")
closeBracketBytes = []byte("]")
percentBytes = []byte("%")
precisionBytes = []byte(".")
openAngleBytes = []byte("<")
closeAngleBytes = []byte(">")
openMapBytes = []byte("map[")
closeMapBytes = []byte("]")
lenEqualsBytes = []byte("len=")
capEqualsBytes = []byte("cap=")
)
// hexDigits is used to map a decimal value to a hex digit.
var hexDigits = "0123456789abcdef"
// catchPanic handles any panics that might occur during the handleMethods
// calls.
func catchPanic(w io.Writer, v reflect.Value) {
if err := recover(); err != nil {
w.Write(panicBytes)
fmt.Fprintf(w, "%v", err)
w.Write(closeParenBytes)
}
}
// handleMethods attempts to call the Error and String methods on the underlying
// type the passed reflect.Value represents and outputes the result to Writer w.
//
// It handles panics in any called methods by catching and displaying the error
// as the formatted value.
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
// We need an interface to check if the type implements the error or
// Stringer interface. However, the reflect package won't give us an
// interface on certain things like unexported struct fields in order
// to enforce visibility rules. We use unsafe, when it's available,
// to bypass these restrictions since this package does not mutate the
// values.
if !v.CanInterface() {
if UnsafeDisabled {
return false
}
v = unsafeReflectValue(v)
}
// Choose whether or not to do error and Stringer interface lookups against
// the base type or a pointer to the base type depending on settings.
// Technically calling one of these methods with a pointer receiver can
// mutate the value, however, types which choose to satisify an error or
// Stringer interface with a pointer receiver should not be mutating their
// state inside these interface methods.
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
v = unsafeReflectValue(v)
}
if v.CanAddr() {
v = v.Addr()
}
// Is it an error or Stringer?
switch iface := v.Interface().(type) {
case error:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.Error()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.Error()))
return true
case fmt.Stringer:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.String()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.String()))
return true
}
return false
}
// printBool outputs a boolean value as true or false to Writer w.
func printBool(w io.Writer, val bool) {
if val {
w.Write(trueBytes)
} else {
w.Write(falseBytes)
}
}
// printInt outputs a signed integer value to Writer w.
func printInt(w io.Writer, val int64, base int) {
w.Write([]byte(strconv.FormatInt(val, base)))
}
// printUint outputs an unsigned integer value to Writer w.
func printUint(w io.Writer, val uint64, base int) {
w.Write([]byte(strconv.FormatUint(val, base)))
}
// printFloat outputs a floating point value using the specified precision,
// which is expected to be 32 or 64bit, to Writer w.
func printFloat(w io.Writer, val float64, precision int) {
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
}
// printComplex outputs a complex value using the specified float precision
// for the real and imaginary parts to Writer w.
func printComplex(w io.Writer, c complex128, floatPrecision int) {
r := real(c)
w.Write(openParenBytes)
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
i := imag(c)
if i >= 0 {
w.Write(plusBytes)
}
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
w.Write(iBytes)
w.Write(closeParenBytes)
}
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
// prefix to Writer w.
func printHexPtr(w io.Writer, p uintptr) {
// Null pointer.
num := uint64(p)
if num == 0 {
w.Write(nilAngleBytes)
return
}
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
buf := make([]byte, 18)
// It's simpler to construct the hex string right to left.
base := uint64(16)
i := len(buf) - 1
for num >= base {
buf[i] = hexDigits[num%base]
num /= base
i--
}
buf[i] = hexDigits[num]
// Add '0x' prefix.
i--
buf[i] = 'x'
i--
buf[i] = '0'
// Strip unused leading bytes.
buf = buf[i:]
w.Write(buf)
}
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
// elements to be sorted.
type valuesSorter struct {
values []reflect.Value
strings []string // either nil or same len and values
cs *ConfigState
}
// newValuesSorter initializes a valuesSorter instance, which holds a set of
// surrogate keys on which the data should be sorted. It uses flags in
// ConfigState to decide if and how to populate those surrogate keys.
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
vs := &valuesSorter{values: values, cs: cs}
if canSortSimply(vs.values[0].Kind()) {
return vs
}
if !cs.DisableMethods {
vs.strings = make([]string, len(values))
for i := range vs.values {
b := bytes.Buffer{}
if !handleMethods(cs, &b, vs.values[i]) {
vs.strings = nil
break
}
vs.strings[i] = b.String()
}
}
if vs.strings == nil && cs.SpewKeys {
vs.strings = make([]string, len(values))
for i := range vs.values {
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
}
}
return vs
}
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
// directly, or whether it should be considered for sorting by surrogate keys
// (if the ConfigState allows it).
func canSortSimply(kind reflect.Kind) bool {
// This switch parallels valueSortLess, except for the default case.
switch kind {
case reflect.Bool:
return true
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return true
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return true
case reflect.Float32, reflect.Float64:
return true
case reflect.String:
return true
case reflect.Uintptr:
return true
case reflect.Array:
return true
}
return false
}
// Len returns the number of values in the slice. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Len() int {
return len(s.values)
}
// Swap swaps the values at the passed indices. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Swap(i, j int) {
s.values[i], s.values[j] = s.values[j], s.values[i]
if s.strings != nil {
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
}
}
// valueSortLess returns whether the first value should sort before the second
// value. It is used by valueSorter.Less as part of the sort.Interface
// implementation.
func valueSortLess(a, b reflect.Value) bool {
switch a.Kind() {
case reflect.Bool:
return !a.Bool() && b.Bool()
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return a.Int() < b.Int()
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return a.Uint() < b.Uint()
case reflect.Float32, reflect.Float64:
return a.Float() < b.Float()
case reflect.String:
return a.String() < b.String()
case reflect.Uintptr:
return a.Uint() < b.Uint()
case reflect.Array:
// Compare the contents of both arrays.
l := a.Len()
for i := 0; i < l; i++ {
av := a.Index(i)
bv := b.Index(i)
if av.Interface() == bv.Interface() {
continue
}
return valueSortLess(av, bv)
}
}
return a.String() < b.String()
}
// Less returns whether the value at index i should sort before the
// value at index j. It is part of the sort.Interface implementation.
func (s *valuesSorter) Less(i, j int) bool {
if s.strings == nil {
return valueSortLess(s.values[i], s.values[j])
}
return s.strings[i] < s.strings[j]
}
// sortValues is a sort function that handles both native types and any type that
// can be converted to error or Stringer. Other inputs are sorted according to
// their Value.String() value to ensure display stability.
func sortValues(values []reflect.Value, cs *ConfigState) {
if len(values) == 0 {
return
}
sort.Sort(newValuesSorter(values, cs))
}

@ -0,0 +1,306 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"os"
)
// ConfigState houses the configuration options used by spew to format and
// display values. There is a global instance, Config, that is used to control
// all top-level Formatter and Dump functionality. Each ConfigState instance
// provides methods equivalent to the top-level functions.
//
// The zero value for ConfigState provides no indentation. You would typically
// want to set it to a space or a tab.
//
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
// with default settings. See the documentation of NewDefaultConfig for default
// values.
type ConfigState struct {
// Indent specifies the string to use for each indentation level. The
// global config instance that all top-level functions use set this to a
// single space by default. If you would like more indentation, you might
// set this to a tab with "\t" or perhaps two spaces with " ".
Indent string
// MaxDepth controls the maximum number of levels to descend into nested
// data structures. The default, 0, means there is no limit.
//
// NOTE: Circular data structures are properly detected, so it is not
// necessary to set this value unless you specifically want to limit deeply
// nested data structures.
MaxDepth int
// DisableMethods specifies whether or not error and Stringer interfaces are
// invoked for types that implement them.
DisableMethods bool
// DisablePointerMethods specifies whether or not to check for and invoke
// error and Stringer interfaces on types which only accept a pointer
// receiver when the current type is not a pointer.
//
// NOTE: This might be an unsafe action since calling one of these methods
// with a pointer receiver could technically mutate the value, however,
// in practice, types which choose to satisify an error or Stringer
// interface with a pointer receiver should not be mutating their state
// inside these interface methods. As a result, this option relies on
// access to the unsafe package, so it will not have any effect when
// running in environments without access to the unsafe package such as
// Google App Engine or with the "safe" build tag specified.
DisablePointerMethods bool
// DisablePointerAddresses specifies whether to disable the printing of
// pointer addresses. This is useful when diffing data structures in tests.
DisablePointerAddresses bool
// DisableCapacities specifies whether to disable the printing of capacities
// for arrays, slices, maps and channels. This is useful when diffing
// data structures in tests.
DisableCapacities bool
// ContinueOnMethod specifies whether or not recursion should continue once
// a custom error or Stringer interface is invoked. The default, false,
// means it will print the results of invoking the custom error or Stringer
// interface and return immediately instead of continuing to recurse into
// the internals of the data type.
//
// NOTE: This flag does not have any effect if method invocation is disabled
// via the DisableMethods or DisablePointerMethods options.
ContinueOnMethod bool
// SortKeys specifies map keys should be sorted before being printed. Use
// this to have a more deterministic, diffable output. Note that only
// native types (bool, int, uint, floats, uintptr and string) and types
// that support the error or Stringer interfaces (if methods are
// enabled) are supported, with other types sorted according to the
// reflect.Value.String() output which guarantees display stability.
SortKeys bool
// SpewKeys specifies that, as a last resort attempt, map keys should
// be spewed to strings and sorted by those strings. This is only
// considered if SortKeys is true.
SpewKeys bool
}
// Config is the active configuration of the top-level functions.
// The configuration can be changed by modifying the contents of spew.Config.
var Config = ConfigState{Indent: " "}
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the formatted string as a value that satisfies error. See NewFormatter
// for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, c.convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, c.convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, c.convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a Formatter interface returned by c.NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, c.convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
return fmt.Print(c.convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, c.convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
return fmt.Println(c.convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprint(a ...interface{}) string {
return fmt.Sprint(c.convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, c.convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a Formatter interface returned by c.NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintln(a ...interface{}) string {
return fmt.Sprintln(c.convertArgs(a)...)
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
c.Printf, c.Println, or c.Printf.
*/
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(c, v)
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
fdump(c, w, a...)
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by modifying the public members
of c. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func (c *ConfigState) Dump(a ...interface{}) {
fdump(c, os.Stdout, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func (c *ConfigState) Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(c, &buf, a...)
return buf.String()
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a spew Formatter interface using
// the ConfigState associated with s.
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = newFormatter(c, arg)
}
return formatters
}
// NewDefaultConfig returns a ConfigState with the following default settings.
//
// Indent: " "
// MaxDepth: 0
// DisableMethods: false
// DisablePointerMethods: false
// ContinueOnMethod: false
// SortKeys: false
func NewDefaultConfig() *ConfigState {
return &ConfigState{Indent: " "}
}

@ -0,0 +1,211 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
Package spew implements a deep pretty printer for Go data structures to aid in
debugging.
A quick overview of the additional features spew provides over the built-in
printing facilities for Go data types are as follows:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output (only when using
Dump style)
There are two different approaches spew allows for dumping Go data structures:
* Dump style which prints with newlines, customizable indentation,
and additional debug information such as types and all pointer addresses
used to indirect to the final value
* A custom Formatter interface that integrates cleanly with the standard fmt
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
similar to the default %v while providing the additional functionality
outlined above and passing unsupported format verbs such as %x and %q
along to fmt
Quick Start
This section demonstrates how to quickly get started with spew. See the
sections below for further details on formatting and configuration options.
To dump a variable with full newlines, indentation, type, and pointer
information use Dump, Fdump, or Sdump:
spew.Dump(myVar1, myVar2, ...)
spew.Fdump(someWriter, myVar1, myVar2, ...)
str := spew.Sdump(myVar1, myVar2, ...)
Alternatively, if you would prefer to use format strings with a compacted inline
printing style, use the convenience wrappers Printf, Fprintf, etc with
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
%#+v (adds types and pointer addresses):
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
Configuration Options
Configuration of spew is handled by fields in the ConfigState type. For
convenience, all of the top-level functions use a global state available
via the spew.Config global.
It is also possible to create a ConfigState instance that provides methods
equivalent to the top-level functions. This allows concurrent configuration
options. See the ConfigState documentation for more details.
The following configuration options are available:
* Indent
String to use for each indentation level for Dump functions.
It is a single space by default. A popular alternative is "\t".
* MaxDepth
Maximum number of levels to descend into nested data structures.
There is no limit by default.
* DisableMethods
Disables invocation of error and Stringer interface methods.
Method invocation is enabled by default.
* DisablePointerMethods
Disables invocation of error and Stringer interface methods on types
which only accept pointer receivers from non-pointer variables.
Pointer method invocation is enabled by default.
* DisablePointerAddresses
DisablePointerAddresses specifies whether to disable the printing of
pointer addresses. This is useful when diffing data structures in tests.
* DisableCapacities
DisableCapacities specifies whether to disable the printing of
capacities for arrays, slices, maps and channels. This is useful when
diffing data structures in tests.
* ContinueOnMethod
Enables recursion into types after invoking error and Stringer interface
methods. Recursion after method invocation is disabled by default.
* SortKeys
Specifies map keys should be sorted before being printed. Use
this to have a more deterministic, diffable output. Note that
only native types (bool, int, uint, floats, uintptr and string)
and types which implement error or Stringer interfaces are
supported with other types sorted according to the
reflect.Value.String() output which guarantees display
stability. Natural map order is used by default.
* SpewKeys
Specifies that, as a last resort attempt, map keys should be
spewed to strings and sorted by those strings. This is only
considered if SortKeys is true.
Dump Usage
Simply call spew.Dump with a list of variables you want to dump:
spew.Dump(myVar1, myVar2, ...)
You may also call spew.Fdump if you would prefer to output to an arbitrary
io.Writer. For example, to dump to standard error:
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
A third option is to call spew.Sdump to get the formatted output as a string:
str := spew.Sdump(myVar1, myVar2, ...)
Sample Dump Output
See the Dump example for details on the setup of the types and variables being
shown here.
(main.Foo) {
unexportedField: (*main.Bar)(0xf84002e210)({
flag: (main.Flag) flagTwo,
data: (uintptr) <nil>
}),
ExportedField: (map[interface {}]interface {}) (len=1) {
(string) (len=3) "one": (bool) true
}
}
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
command as shown.
([]uint8) (len=32 cap=32) {
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
00000020 31 32 |12|
}
Custom Formatter
Spew provides a custom formatter that implements the fmt.Formatter interface
so that it integrates cleanly with standard fmt package printing functions. The
formatter is useful for inline printing of smaller data types similar to the
standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Custom Formatter Usage
The simplest way to make use of the spew custom formatter is to call one of the
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
functions have syntax you are most likely already familiar with:
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Println(myVar, myVar2)
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
See the Index for the full list convenience functions.
Sample Formatter Output
Double pointer to a uint8:
%v: <**>5
%+v: <**>(0xf8400420d0->0xf8400420c8)5
%#v: (**uint8)5
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
Pointer to circular struct with a uint8 field and a pointer to itself:
%v: <*>{1 <*><shown>}
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
See the Printf example for details on the setup of variables being shown
here.
Errors
Since it is possible for custom Stringer/error interfaces to panic, spew
detects them and handles them internally by printing the panic information
inline with the output. Since spew is intended to provide deep pretty printing
capabilities on structures, it intentionally does not return any errors.
*/
package spew

@ -0,0 +1,509 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"os"
"reflect"
"regexp"
"strconv"
"strings"
)
var (
// uint8Type is a reflect.Type representing a uint8. It is used to
// convert cgo types to uint8 slices for hexdumping.
uint8Type = reflect.TypeOf(uint8(0))
// cCharRE is a regular expression that matches a cgo char.
// It is used to detect character arrays to hexdump them.
cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
// char. It is used to detect unsigned character arrays to hexdump
// them.
cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
// It is used to detect uint8_t arrays to hexdump them.
cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
)
// dumpState contains information about the state of a dump operation.
type dumpState struct {
w io.Writer
depth int
pointers map[uintptr]int
ignoreNextType bool
ignoreNextIndent bool
cs *ConfigState
}
// indent performs indentation according to the depth level and cs.Indent
// option.
func (d *dumpState) indent() {
if d.ignoreNextIndent {
d.ignoreNextIndent = false
return
}
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
}
// unpackValue returns values inside of non-nil interfaces when possible.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface && !v.IsNil() {
v = v.Elem()
}
return v
}
// dumpPtr handles formatting of pointers by indirecting them as necessary.
func (d *dumpState) dumpPtr(v reflect.Value) {
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range d.pointers {
if depth >= d.depth {
delete(d.pointers, k)
}
}
// Keep list of all dereferenced pointers to show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by dereferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
cycleFound = true
indirects--
break
}
d.pointers[addr] = d.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type information.
d.w.Write(openParenBytes)
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
d.w.Write([]byte(ve.Type().String()))
d.w.Write(closeParenBytes)
// Display pointer information.
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
d.w.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
d.w.Write(pointerChainBytes)
}
printHexPtr(d.w, addr)
}
d.w.Write(closeParenBytes)
}
// Display dereferenced value.
d.w.Write(openParenBytes)
switch {
case nilFound:
d.w.Write(nilAngleBytes)
case cycleFound:
d.w.Write(circularBytes)
default:
d.ignoreNextType = true
d.dump(ve)
}
d.w.Write(closeParenBytes)
}
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
// reflection) arrays and slices are dumped in hexdump -C fashion.
func (d *dumpState) dumpSlice(v reflect.Value) {
// Determine whether this type should be hex dumped or not. Also,
// for types which should be hexdumped, try to use the underlying data
// first, then fall back to trying to convert them to a uint8 slice.
var buf []uint8
doConvert := false
doHexDump := false
numEntries := v.Len()
if numEntries > 0 {
vt := v.Index(0).Type()
vts := vt.String()
switch {
// C types that need to be converted.
case cCharRE.MatchString(vts):
fallthrough
case cUnsignedCharRE.MatchString(vts):
fallthrough
case cUint8tCharRE.MatchString(vts):
doConvert = true
// Try to use existing uint8 slices and fall back to converting
// and copying if that fails.
case vt.Kind() == reflect.Uint8:
// We need an addressable interface to convert the type
// to a byte slice. However, the reflect package won't
// give us an interface on certain things like
// unexported struct fields in order to enforce
// visibility rules. We use unsafe, when available, to
// bypass these restrictions since this package does not
// mutate the values.
vs := v
if !vs.CanInterface() || !vs.CanAddr() {
vs = unsafeReflectValue(vs)
}
if !UnsafeDisabled {
vs = vs.Slice(0, numEntries)
// Use the existing uint8 slice if it can be
// type asserted.
iface := vs.Interface()
if slice, ok := iface.([]uint8); ok {
buf = slice
doHexDump = true
break
}
}
// The underlying data needs to be converted if it can't
// be type asserted to a uint8 slice.
doConvert = true
}
// Copy and convert the underlying type if needed.
if doConvert && vt.ConvertibleTo(uint8Type) {
// Convert and copy each element into a uint8 byte
// slice.
buf = make([]uint8, numEntries)
for i := 0; i < numEntries; i++ {
vv := v.Index(i)
buf[i] = uint8(vv.Convert(uint8Type).Uint())
}
doHexDump = true
}
}
// Hexdump the entire slice as needed.
if doHexDump {
indent := strings.Repeat(d.cs.Indent, d.depth)
str := indent + hex.Dump(buf)
str = strings.Replace(str, "\n", "\n"+indent, -1)
str = strings.TrimRight(str, d.cs.Indent)
d.w.Write([]byte(str))
return
}
// Recursively call dump for each item.
for i := 0; i < numEntries; i++ {
d.dump(d.unpackValue(v.Index(i)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
// dump is the main workhorse for dumping a value. It uses the passed reflect
// value to figure out what kind of object we are dealing with and formats it
// appropriately. It is a recursive function, however circular data structures
// are detected and handled properly.
func (d *dumpState) dump(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
d.w.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
d.indent()
d.dumpPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !d.ignoreNextType {
d.indent()
d.w.Write(openParenBytes)
d.w.Write([]byte(v.Type().String()))
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
d.ignoreNextType = false
// Display length and capacity if the built-in len and cap functions
// work with the value's kind and the len/cap itself is non-zero.
valueLen, valueCap := 0, 0
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.Chan:
valueLen, valueCap = v.Len(), v.Cap()
case reflect.Map, reflect.String:
valueLen = v.Len()
}
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
d.w.Write(openParenBytes)
if valueLen != 0 {
d.w.Write(lenEqualsBytes)
printInt(d.w, int64(valueLen), 10)
}
if !d.cs.DisableCapacities && valueCap != 0 {
if valueLen != 0 {
d.w.Write(spaceBytes)
}
d.w.Write(capEqualsBytes)
printInt(d.w, int64(valueCap), 10)
}
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
// Call Stringer/error interfaces if they exist and the handle methods flag
// is enabled
if !d.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(d.cs, d.w, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(d.w, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(d.w, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(d.w, v.Uint(), 10)
case reflect.Float32:
printFloat(d.w, v.Float(), 32)
case reflect.Float64:
printFloat(d.w, v.Float(), 64)
case reflect.Complex64:
printComplex(d.w, v.Complex(), 32)
case reflect.Complex128:
printComplex(d.w, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
d.dumpSlice(v)
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.String:
d.w.Write([]byte(strconv.Quote(v.String())))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
d.w.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
numEntries := v.Len()
keys := v.MapKeys()
if d.cs.SortKeys {
sortValues(keys, d.cs)
}
for i, key := range keys {
d.dump(d.unpackValue(key))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.MapIndex(key)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Struct:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
vt := v.Type()
numFields := v.NumField()
for i := 0; i < numFields; i++ {
d.indent()
vtf := vt.Field(i)
d.w.Write([]byte(vtf.Name))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.Field(i)))
if i < (numFields - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(d.w, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(d.w, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it in case any new
// types are added.
default:
if v.CanInterface() {
fmt.Fprintf(d.w, "%v", v.Interface())
} else {
fmt.Fprintf(d.w, "%v", v.String())
}
}
}
// fdump is a helper function to consolidate the logic from the various public
// methods which take varying writers and config states.
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
for _, arg := range a {
if arg == nil {
w.Write(interfaceBytes)
w.Write(spaceBytes)
w.Write(nilAngleBytes)
w.Write(newlineBytes)
continue
}
d := dumpState{w: w, cs: cs}
d.pointers = make(map[uintptr]int)
d.dump(reflect.ValueOf(arg))
d.w.Write(newlineBytes)
}
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func Fdump(w io.Writer, a ...interface{}) {
fdump(&Config, w, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(&Config, &buf, a...)
return buf.String()
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by an exported package global,
spew.Config. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func Dump(a ...interface{}) {
fdump(&Config, os.Stdout, a...)
}

@ -0,0 +1,419 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"reflect"
"strconv"
"strings"
)
// supportedFlags is a list of all the character flags supported by fmt package.
const supportedFlags = "0-+# "
// formatState implements the fmt.Formatter interface and contains information
// about the state of a formatting operation. The NewFormatter function can
// be used to get a new Formatter which can be used directly as arguments
// in standard fmt package printing calls.
type formatState struct {
value interface{}
fs fmt.State
depth int
pointers map[uintptr]int
ignoreNextType bool
cs *ConfigState
}
// buildDefaultFormat recreates the original format string without precision
// and width information to pass in to fmt.Sprintf in the case of an
// unrecognized type. Unless new types are added to the language, this
// function won't ever be called.
func (f *formatState) buildDefaultFormat() (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
buf.WriteRune('v')
format = buf.String()
return format
}
// constructOrigFormat recreates the original format string including precision
// and width information to pass along to the standard fmt package. This allows
// automatic deferral of all format strings this package doesn't support.
func (f *formatState) constructOrigFormat(verb rune) (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
if width, ok := f.fs.Width(); ok {
buf.WriteString(strconv.Itoa(width))
}
if precision, ok := f.fs.Precision(); ok {
buf.Write(precisionBytes)
buf.WriteString(strconv.Itoa(precision))
}
buf.WriteRune(verb)
format = buf.String()
return format
}
// unpackValue returns values inside of non-nil interfaces when possible and
// ensures that types for values which have been unpacked from an interface
// are displayed when the show types flag is also set.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface {
f.ignoreNextType = false
if !v.IsNil() {
v = v.Elem()
}
}
return v
}
// formatPtr handles formatting of pointers by indirecting them as necessary.
func (f *formatState) formatPtr(v reflect.Value) {
// Display nil if top level pointer is nil.
showTypes := f.fs.Flag('#')
if v.IsNil() && (!showTypes || f.ignoreNextType) {
f.fs.Write(nilAngleBytes)
return
}
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range f.pointers {
if depth >= f.depth {
delete(f.pointers, k)
}
}
// Keep list of all dereferenced pointers to possibly show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by derferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
cycleFound = true
indirects--
break
}
f.pointers[addr] = f.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type or indirection level depending on flags.
if showTypes && !f.ignoreNextType {
f.fs.Write(openParenBytes)
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
f.fs.Write([]byte(ve.Type().String()))
f.fs.Write(closeParenBytes)
} else {
if nilFound || cycleFound {
indirects += strings.Count(ve.Type().String(), "*")
}
f.fs.Write(openAngleBytes)
f.fs.Write([]byte(strings.Repeat("*", indirects)))
f.fs.Write(closeAngleBytes)
}
// Display pointer information depending on flags.
if f.fs.Flag('+') && (len(pointerChain) > 0) {
f.fs.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
f.fs.Write(pointerChainBytes)
}
printHexPtr(f.fs, addr)
}
f.fs.Write(closeParenBytes)
}
// Display dereferenced value.
switch {
case nilFound:
f.fs.Write(nilAngleBytes)
case cycleFound:
f.fs.Write(circularShortBytes)
default:
f.ignoreNextType = true
f.format(ve)
}
}
// format is the main workhorse for providing the Formatter interface. It
// uses the passed reflect value to figure out what kind of object we are
// dealing with and formats it appropriately. It is a recursive function,
// however circular data structures are detected and handled properly.
func (f *formatState) format(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
f.fs.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
f.formatPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !f.ignoreNextType && f.fs.Flag('#') {
f.fs.Write(openParenBytes)
f.fs.Write([]byte(v.Type().String()))
f.fs.Write(closeParenBytes)
}
f.ignoreNextType = false
// Call Stringer/error interfaces if they exist and the handle methods
// flag is enabled.
if !f.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(f.cs, f.fs, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(f.fs, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(f.fs, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(f.fs, v.Uint(), 10)
case reflect.Float32:
printFloat(f.fs, v.Float(), 32)
case reflect.Float64:
printFloat(f.fs, v.Float(), 64)
case reflect.Complex64:
printComplex(f.fs, v.Complex(), 32)
case reflect.Complex128:
printComplex(f.fs, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
f.fs.Write(openBracketBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
numEntries := v.Len()
for i := 0; i < numEntries; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(v.Index(i)))
}
}
f.depth--
f.fs.Write(closeBracketBytes)
case reflect.String:
f.fs.Write([]byte(v.String()))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
f.fs.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
f.fs.Write(openMapBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
keys := v.MapKeys()
if f.cs.SortKeys {
sortValues(keys, f.cs)
}
for i, key := range keys {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(key))
f.fs.Write(colonBytes)
f.ignoreNextType = true
f.format(f.unpackValue(v.MapIndex(key)))
}
}
f.depth--
f.fs.Write(closeMapBytes)
case reflect.Struct:
numFields := v.NumField()
f.fs.Write(openBraceBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
vt := v.Type()
for i := 0; i < numFields; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
vtf := vt.Field(i)
if f.fs.Flag('+') || f.fs.Flag('#') {
f.fs.Write([]byte(vtf.Name))
f.fs.Write(colonBytes)
}
f.format(f.unpackValue(v.Field(i)))
}
}
f.depth--
f.fs.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(f.fs, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(f.fs, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it if any get added.
default:
format := f.buildDefaultFormat()
if v.CanInterface() {
fmt.Fprintf(f.fs, format, v.Interface())
} else {
fmt.Fprintf(f.fs, format, v.String())
}
}
}
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
// details.
func (f *formatState) Format(fs fmt.State, verb rune) {
f.fs = fs
// Use standard formatting for verbs that are not v.
if verb != 'v' {
format := f.constructOrigFormat(verb)
fmt.Fprintf(fs, format, f.value)
return
}
if f.value == nil {
if fs.Flag('#') {
fs.Write(interfaceBytes)
}
fs.Write(nilAngleBytes)
return
}
f.format(reflect.ValueOf(f.value))
}
// newFormatter is a helper function to consolidate the logic from the various
// public methods which take varying config states.
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
fs := &formatState{value: v, cs: cs}
fs.pointers = make(map[uintptr]int)
return fs
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
Printf, Println, or Fprintf.
*/
func NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(&Config, v)
}

@ -0,0 +1,148 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"fmt"
"io"
)
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the formatted string as a value that satisfies error. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a default Formatter interface returned by NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
func Print(a ...interface{}) (n int, err error) {
return fmt.Print(convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
func Println(a ...interface{}) (n int, err error) {
return fmt.Println(convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprint(a ...interface{}) string {
return fmt.Sprint(convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintln(a ...interface{}) string {
return fmt.Sprintln(convertArgs(a)...)
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a default spew Formatter interface.
func convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = NewFormatter(arg)
}
return formatters
}

@ -0,0 +1,9 @@
.idea
.env
/lab
# Compiled test files
*.test
# Coverage output files
coverage.txt
coverage.html

@ -0,0 +1,39 @@
language: go
go:
- 1.14.x
- 1.15.x
- 1.16.x
- 1.17.x
env:
- GO111MODULE=on
services:
- mongodb
before_install:
- go get -t -v
# Add user and replica set to mongo by help of https://georgeshank.com/how-to-enable-a-mongodb-replica-set-on-travis/
before_script:
- sleep 15 # To Fix issue of https://docs.travis-ci.com/user/database-setup/#mongodb-does-not-immediately-accept-connections
- |
mongo admin --eval 'db.createUser(
{
user: "root",
pwd: "12345",
roles: [ { role: "root", db: "admin" } ]
}
)'
- 'echo "replication:" | sudo tee -a /etc/mongod.conf'
- 'echo " replSetName: rs0" | sudo tee -a /etc/mongod.conf'
- sudo systemctl restart mongod
- sleep 20 # To Fix issue of https://docs.travis-ci.com/user/database-setup/#mongodb-does-not-immediately-accept-connections
- mongo -u root -p 12345 --eval 'rs.initiate()'
script:
- go test -race -coverprofile=coverage.txt -covermode=atomic ./...
after_success:
- bash <(curl -s https://codecov.io/bash)

@ -0,0 +1,190 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2019 Kamva.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -0,0 +1,425 @@
<p align="center">
<img width="250" src="https://user-images.githubusercontent.com/22454054/71487214-759cb680-282f-11ea-9bcf-caa663b3e348.png" />
</p>
<p align="center">
<a href="https://goreportcard.com/report/github.com/Kamva/mgm">
<img src="https://goreportcard.com/badge/github.com/Kamva/mgm">
</a>
<a href="https://godoc.org/github.com/Kamva/mgm">
<img src="https://godoc.org/github.com/Kamva/mgm?status.svg" alt="GoDoc">
</a>
<a href="https://travis-ci.com/Kamva/mgm">
<img src="https://travis-ci.com/Kamva/mgm.svg?branch=master" alt="Build Status">
</a>
<a href="https://codecov.io/gh/Kamva/mgm">
<img src="https://codecov.io/gh/Kamva/mgm/branch/master/graph/badge.svg" />
</a>
</p>
# Mongo Go Models
The Mongo ODM for Go
- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Usage](#usage)
- [Bugs / Feature Requests](#bugs--feature-request)
- [Communicate With Us](#communicate-with-us)
- [Contributing](#contributing)
- [License](#license)
## Features
- Define your models and perform CRUD operations with hooks before/after each operation.
- `mgm` makes Mongo search and aggregation super easy to do in Golang.
- Just set up your configs once and get collections anywhere you need them.
- `mgm` predefines all Mongo operators and keys, so you don't have to hardcode them yourself.
- `mgm` wraps the official Mongo Go Driver.
## Requirements
- Go 1.10 or higher.
- MongoDB 2.6 and higher.
## Installation
__Important Note__: We changed the package name from
`github.com/Kamva/mgm/v3` (uppercase `Kamva`)
to `github.com/kamva/mgm/v3` (lowercase `kamva`) starting with version 3.1.0.
```bash
go get github.com/kamva/mgm/v3
```
## Usage
To get started, import the `mgm` package and setup the default config:
```go
import (
"github.com/kamva/mgm/v3"
"go.mongodb.org/mongo-driver/mongo/options"
)
func init() {
// Setup the mgm default config
err := mgm.SetDefaultConfig(nil, "mgm_lab", options.Client().ApplyURI("mongodb://root:12345@localhost:27017"))
}
```
Define your model:
```go
type Book struct {
// DefaultModel adds _id, created_at and updated_at fields to the Model
mgm.DefaultModel `bson:",inline"`
Name string `json:"name" bson:"name"`
Pages int `json:"pages" bson:"pages"`
}
func NewBook(name string, pages int) *Book {
return &Book{
Name: name,
Pages: pages,
}
}
```
Insert new document:
```go
book := NewBook("Pride and Prejudice", 345)
// Make sure to pass the model by reference.
err := mgm.Coll(book).Create(book)
```
Find one document
```go
// Get the document's collection
book := &Book{}
coll := mgm.Coll(book)
// Find and decode the doc to a book model.
_ = coll.FindByID("5e0518aa8f1a52b0b9410ee3", book)
// Get the first doc of the collection
_ = coll.First(bson.M{}, book)
// Get the first doc of a collection using a filter
_ = coll.First(bson.M{"pages":400}, book)
```
Update a document
```go
// Find your book
book := findMyFavoriteBook()
// and update it
book.Name = "Moulin Rouge!"
err := mgm.Coll(book).Update(book)
```
Delete a document
```go
// Just find and delete your document
err := mgm.Coll(book).Delete(book)
```
Find and decode a result:
```go
result := []Book{}
err := mgm.Coll(&Book{}).SimpleFind(&result, bson.M{"pages": bson.M{operator.Gt: 24}})
```
### A Model's Default Fields
Each model by default (by using `DefaultModel` struct) has
the following fields:
- `_id` : The document ID.
- `created_at`: The creation date of a doc. When saving a new doc, this is automatically populated by the `Creating` hook.
- `updated_at`: The last updated date of a doc. When saving a doc, this is automatically populated by the `Saving` hook.
### A Model's Hooks
Each model has the following hooks:
- `Creating`: Called when creating a new model.
Signature : `Creating(context.Context) error`
- `Created`: Called after a new model is created.
Signature : `Created(context.Context) error`
- `Updating`: Called when updating model.
Signature : `Updating(context.Context) error`
- `Updated` : Called after a model is updated.
Signature : `Updated(ctx context.Context, result *mongo.UpdateResult) error`
- `Saving`: Called when creating or updating a model.
Signature : `Saving(context.Context) error`
- `Saved`: Called after a model is created or updated.
Signature: `Saved(context.Context) error`
- `Deleting`: Called when deleting a model.
Signature: `Deleting(context.Context) error`
- `Deleted`: Called after a model is deleted.
Signature: `Deleted(ctx context.Context, result *mongo.DeleteResult) error`
**Notes about hooks**:
- Each model by default uses the `Creating` and `Saving` hooks, so if you want to define those hooks yourself, remember to invoke the `DefaultModel` hooks from your own hooks.
- Collection methods that call these hooks:
- `Create` & `CreateWithCtx`
- `Update` & `UpdateWithCtx`
- `Delete` & `DeleteWithCtx`
Example:
```go
func (model *Book) Creating(ctx context.Context) error {
// Call the DefaultModel Creating hook
if err := model.DefaultModel.Creating(ctx); err!=nil {
return err
}
// We can validate the fields of a model and return an error to prevent a document's insertion.
if model.Pages < 1 {
return errors.New("book must have at least one page")
}
return nil
}
```
### Configuration
The `mgm` default configuration has a context timeout:
```go
func init() {
_ = mgm.SetDefaultConfig(&mgm.Config{CtxTimeout:12 * time.Second}, "mgm_lab", options.Client().ApplyURI("mongodb://root:12345@localhost:27017"))
}
// To get the context, just call the Ctx() method, assign it to a variable
ctx := mgm.Ctx()
// and use it
coll := mgm.Coll(&Book{})
coll.FindOne(ctx, bson.M{})
// Or invoke Ctx() and use it directly
coll.FindOne(mgm.Ctx(), bson.M{})
```
### Collections
Get a model's collection:
```go
coll := mgm.Coll(&Book{})
// Do something with the collection
```
`mgm` automatically detects the name of a model's collection:
```go
book := Book{}
// Print your model's collection name.
collName := mgm.CollName(&book)
fmt.Println(collName) // output: books
```
You can also set a custom collection name for your model by implementing the `CollectionNameGetter` interface:
```go
func (model *Book) CollectionName() string {
return "my_books"
}
// mgm returns the "my_books" collection
coll := mgm.Coll(&Book{})
```
Get a collection by its name (without needing to define a model for it):
```go
coll := mgm.CollectionByName("my_coll")
// Do Aggregation, etc. with the collection
```
Customize the model db by implementing the `CollectionGetter`
interface:
```go
func (model *Book) Collection() *mgm.Collection {
// Get default connection client
_, client, _, err := mgm.DefaultConfigs()
if err != nil {
panic(err)
}
db := client.Database("another_db")
return mgm.NewCollection(db, "my_collection")
}
```
Or return a model's collection from another connection:
```go
func (model *Book) Collection() *mgm.Collection {
// Create new client
client, err := mgm.NewClient(options.Client().ApplyURI("mongodb://root:12345@localhost:27017"))
if err != nil {
panic(err)
}
// Get the model's db
db := client.Database("my_second_db")
// return the model's custom collection
return mgm.NewCollection(db, "my_collection")
}
```
### Aggregation
While we can use Mongo Go Driver Aggregate features, `mgm` also
provides simpler methods to perform aggregations:
Run an aggregation and decode the result:
```go
authorCollName := mgm.Coll(&Author{}).Name()
result := []Book{}
// Lookup with just a single line of code
_ := mgm.Coll(&Book{}).SimpleAggregate(&result, builder.Lookup(authorCollName, "auth_id", "_id", "author"))
// Multi stage (mix of mgm builders and raw stages)
_ := mgm.Coll(&Book{}).SimpleAggregate(&result,
builder.Lookup(authorCollName, "auth_id", "_id", "author"),
M{operator.Project: M{"pages": 0}},
)
// Do something with result...
```
Do aggregations using the mongo Aggregation method:
```go
import (
"github.com/kamva/mgm/v3"
"github.com/kamva/mgm/v3/builder"
"github.com/kamva/mgm/v3/field"
. "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// The Author model collection
authorColl := mgm.Coll(&Author{})
cur, err := mgm.Coll(&Book{}).Aggregate(mgm.Ctx(), A{
// The S function accepts operators as parameters and returns a bson.M type.
builder.S(builder.Lookup(authorColl.Name(), "author_id", field.Id, "author")),
})
```
A more complex example and mixes with mongo raw pipelines:
```go
import (
"github.com/kamva/mgm/v3"
"github.com/kamva/mgm/v3/builder"
"github.com/kamva/mgm/v3/field"
"github.com/kamva/mgm/v3/operator"
. "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Author model collection
authorColl := mgm.Coll(&Author{})
_, err := mgm.Coll(&Book{}).Aggregate(mgm.Ctx(), A{
// S function get operators and return bson.M type.
builder.S(builder.Lookup(authorColl.Name(), "author_id", field.Id, "author")),
builder.S(builder.Group("pages", M{"books": M{operator.Push: M{"name": "$name", "author": "$author"}}})),
M{operator.Unwind: "$books"},
})
if err != nil {
panic(err)
}
```
### Transactions
- To run a transaction on the default connection use the `mgm.Transaction()` function, e.g:
```go
d := &Doc{Name: "Mehran", Age: 10}
err := mgm.Transaction(func(session mongo.Session, sc mongo.SessionContext) error {
// do not forget to pass the session's context to the collection methods.
err := mgm.Coll(d).CreateWithCtx(sc, d)
if err != nil {
return err
}
return session.CommitTransaction(sc)
})
```
- To run a transaction with your own context, use the `mgm.TransactionWithCtx()` method.
- To run a transaction on another connection, use the `mgm.TransactionWithClient()` method.
-----------------
## Other Mongo Go Models Packages
**We implemented these packages to simplify queries and aggregations in mongo**
`builder`: simplify mongo queries and aggregations.
`operator` : contains mongo operators as predefined variables.
(e.g `Eq = "$eq"` , `Gt = "$gt"`)
`field` : contains mongo fields used in aggregations and ... as predefined variable.
(e.g `LocalField = "localField"`, `ForeignField = "foreignField"`)
Example:
```go
import (
"github.com/kamva/mgm/v3"
f "github.com/kamva/mgm/v3/field"
o "github.com/kamva/mgm/v3/operator"
"go.mongodb.org/mongo-driver/bson"
)
// Instead of hard-coding mongo operators and fields
_, _ = mgm.Coll(&Book{}).Aggregate(mgm.Ctx(), bson.A{
bson.M{"$count": ""},
bson.M{"$project": bson.M{"_id": 0}},
})
// Use the predefined operators and pipeline fields.
_, _ = mgm.Coll(&Book{}).Aggregate(mgm.Ctx(), bson.A{
bson.M{o.Count: ""},
bson.M{o.Project: bson.M{f.Id: 0}},
})
```
## Bugs / Feature request
New features can be requested and bugs can be reported on [Github issue tracker](https://github.com/Kamva/mgm/issues).
## Communicate With Us
* Create new topic at [mongo-go-models Google Group](https://groups.google.com/forum/#!forum/mongo-go-models)
* Ask your question or request new feature by creating an issue at [Github issue tracker](https://github.com/Kamva/mgm/issues)
## Contributing
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/kamva/mgm)
1. Fork the repository
1. Clone your fork (`git clone https://github.com/<your_username>/mgm && cd mgm`)
1. Create your feature branch (`git checkout -b my-new-feature`)
1. Make changes and add them (`git add .`)
1. Commit your changes (`git commit -m 'Add some feature'`)
1. Push to the branch (`git push origin my-new-feature`)
1. Create new pull request
## License
Mongo Go Models is released under the [Apache License](https://github.com/Kamva/mgm/blob/master/LICENSE)

@ -0,0 +1,21 @@
#### Upgrade Instructions
##### Upgrade from 2.x to 3.x
* Change your package import paths from `github.com/Kamva/mgm/v2`
to `github.com/Kamva/v3`.
- built-in `ID` field changed json tag value from `_id` to `id` [f665e15](https://github.com/Kamva/mgm/commit/f665e1592cdac43fb7fd00b1427a91590f14a9ff)
##### Upgrade from 1.x to 2.x
* Change your package import paths from `github.com/Kamva/mgm`
to `github.com/Kamva/v3`.
* methods `Save` and `SaveWithContext` was removed from collections.
* To Create an entity use the `Create` or `CreateWithCtx` methods.
* To update an entity use the `Update` or `UpdateWithCtx` methods.
* method `IsNew` method was removed from the `model` interface,
don't use it.

@ -0,0 +1,137 @@
package builder
import (
f "github.com/kamva/mgm/v3/field"
o "github.com/kamva/mgm/v3/operator"
"go.mongodb.org/mongo-driver/bson"
)
// Bucket function returns a mongo $bucket operator used in aggregations.
func Bucket(groupBy, boundaries, def, output interface{}) Operator {
m := bson.M{}
appendIfHasVal(m, f.GroupBy, groupBy)
appendIfHasVal(m, f.Boundaries, boundaries)
appendIfHasVal(m, f.Default, def)
appendIfHasVal(m, f.Output, output)
return New(o.Bucket, m)
}
// BucketAuto function returns a mongo $bucketAuto operator used in aggregations.
func BucketAuto(groupBy, buckets, output, granularity interface{}) Operator {
m := bson.M{}
appendIfHasVal(m, f.GroupBy, groupBy)
appendIfHasVal(m, f.Buckets, buckets)
appendIfHasVal(m, f.Output, output)
appendIfHasVal(m, f.Granularity, granularity)
return New(o.BucketAuto, m)
}
// CollStats function returns a mongo $collStats operator used in aggregations.
func CollStats(latencyStats, storageStats, count interface{}) Operator {
m := bson.M{}
appendIfHasVal(m, f.LatencyStats, latencyStats)
appendIfHasVal(m, f.StorageStats, storageStats)
appendIfHasVal(m, f.Count, count)
return New(o.CollStats, m)
}
// CurrentOp function returns a mongo $currentOp operator used in aggregations.
func CurrentOp(allUsers, idleConnections, idleCursors, idleSessions, localOps interface{}) Operator {
m := bson.M{}
appendIfHasVal(m, f.AllUsers, allUsers)
appendIfHasVal(m, f.IdleConnections, idleConnections)
appendIfHasVal(m, f.IdleCursors, idleCursors)
appendIfHasVal(m, f.IdleSessions, idleSessions)
appendIfHasVal(m, f.LocalOps, localOps)
return New(o.CurrentOp, m)
}
// $geoNear,$graphLookup has many params, those functions
// will have too many params and do not make readable code.
// Group function returns a mongo $group operator used in aggregations.
func Group(ID interface{}, params bson.M) Operator {
m := bson.M{}
appendIfHasVal(m, f.ID, ID)
for key, val := range params {
appendIfHasVal(m, key, val)
}
return New(o.Group, m)
}
// Lookup function returns a mongo $lookup operator used in aggregations.
func Lookup(from, localField, foreignField, as interface{}) Operator {
m := bson.M{}
appendIfHasVal(m, f.From, from)
appendIfHasVal(m, f.LocalField, localField)
appendIfHasVal(m, f.ForeignField, foreignField)
appendIfHasVal(m, f.As, as)
return New(o.Lookup, m)
}
// UncorrelatedLookup function returns a mongo $lookup operator used in aggregations.
func UncorrelatedLookup(from, let, pipeline, as interface{}) Operator {
m := bson.M{}
appendIfHasVal(m, f.From, from)
appendIfHasVal(m, f.Let, let)
appendIfHasVal(m, f.Pipeline, pipeline)
appendIfHasVal(m, f.As, as)
return New(o.Lookup, m)
}
// Merge function returns a mongo $merge operator used in aggregations.
func Merge(into, on, let, whenMatched, whenNotMatched interface{}) Operator {
m := bson.M{}
appendIfHasVal(m, f.Into, into)
appendIfHasVal(m, f.On, on)
appendIfHasVal(m, f.Let, let)
appendIfHasVal(m, f.WhenMatched, whenMatched)
appendIfHasVal(m, f.WhenNotMatched, whenNotMatched)
return New(o.Merge, m)
}
// ReplaceRoot function returns a mongo $replaceRoot operator used in aggregations.
func ReplaceRoot(newRoot interface{}) Operator {
m := bson.M{}
appendIfHasVal(m, f.NewRoot, newRoot)
return New(o.ReplaceRoot, m)
}
// Sample function returns a mongo sample operator used in aggregations.
func Sample(size interface{}) Operator {
m := bson.M{}
appendIfHasVal(m, f.Size, size)
return New(o.Sample, m)
}
// Unwind function returns a mongo $unwind operator used in aggregations.
func Unwind(path, includeArrayIndex, preserveNullAndEmptyArrays interface{}) Operator {
m := bson.M{}
appendIfHasVal(m, f.Path, path)
appendIfHasVal(m, f.IncludeArrayIndex, includeArrayIndex)
appendIfHasVal(m, f.PreserveNullAndEmptyArrays, preserveNullAndEmptyArrays)
return New(o.Unwind, m)
}

@ -0,0 +1,34 @@
package builder
// Operator is an interface that should be implemented by structs used as operators.
type Operator interface {
GetKey() string
GetVal() interface{}
}
// BaseOperator is a simple operator struct that implementes the Operator interface.
type BaseOperator struct {
key string
val interface{}
}
// GetKey function returns the operator's key.
func (operator *BaseOperator) GetKey() string {
return operator.key
}
// GetVal function returns the operator's value.
func (operator *BaseOperator) GetVal() interface{} {
return operator.val
}
// New function creates a new base operator with the specified key and value
func New(key string, val interface{}) Operator {
return &BaseOperator{
key: key,
val: val,
}
}
// Ensure that the BaseOperator implements the Operator interace
var _ Operator = &BaseOperator{}

@ -0,0 +1,28 @@
// Package builder help us to write aggregates, filters, update maps simpler.
package builder
import "go.mongodb.org/mongo-driver/bson"
// SMap is simple map that can be substitute of `bson.M` to
// have a simpler map structure for queries, aggregations, etc.
type SMap struct {
Operators []Operator
}
// ToMap function converts our SMap to bson.M for use in filters, stages, etc.
func (s *SMap) ToMap() bson.M {
m := bson.M{}
for _, o := range s.Operators {
m[o.GetKey()] = o.GetVal()
}
return m
}
// S receives operators as parameters and returns a bson.M that can be used in filters, stages, etc.
func S(operators ...Operator) bson.M {
s := &SMap{Operators: operators}
return s.ToMap()
}

@ -0,0 +1,13 @@
package builder
import (
"github.com/kamva/mgm/v3/internal/util"
"go.mongodb.org/mongo-driver/bson"
)
// appendIfHasVal appends the provided key and value to the map if the value is not nil.
func appendIfHasVal(m bson.M, key string, val interface{}) {
if !util.IsNil(val) {
m[key] = val
}
}

@ -0,0 +1,166 @@
package mgm
import (
"context"
"github.com/kamva/mgm/v3/builder"
"github.com/kamva/mgm/v3/field"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// Collection performs operations on models and the given Mongodb collection
type Collection struct {
*mongo.Collection
}
// FindByID method finds a doc and decodes it to a model, otherwise returns an error.
// The id field can be any value that if passed to the `PrepareID` method, it returns
// a valid ID (e.g string, bson.ObjectId).
func (coll *Collection) FindByID(id interface{}, model Model) error {
return coll.FindByIDWithCtx(ctx(), id, model)
}
// FindByIDWithCtx method finds a doc and decodes it to a model, otherwise returns an error.
// The id field can be any value that if passed to the `PrepareID` method, it returns
// a valid ID (e.g string, bson.ObjectId).
func (coll *Collection) FindByIDWithCtx(ctx context.Context, id interface{}, model Model) error {
id, err := model.PrepareID(id)
if err != nil {
return err
}
return first(ctx, coll, bson.M{field.ID: id}, model)
}
// First method searches and returns the first document in the search results.
func (coll *Collection) First(filter interface{}, model Model, opts ...*options.FindOneOptions) error {
return coll.FirstWithCtx(ctx(), filter, model, opts...)
}
// FirstWithCtx method searches and returns the first document in the search results.
func (coll *Collection) FirstWithCtx(ctx context.Context, filter interface{}, model Model, opts ...*options.FindOneOptions) error {
return first(ctx, coll, filter, model, opts...)
}
// Create method inserts a new model into the database.
func (coll *Collection) Create(model Model, opts ...*options.InsertOneOptions) error {
return coll.CreateWithCtx(ctx(), model, opts...)
}
// CreateWithCtx method inserts a new model into the database.
func (coll *Collection) CreateWithCtx(ctx context.Context, model Model, opts ...*options.InsertOneOptions) error {
return create(ctx, coll, model, opts...)
}
// Update function persists the changes made to a model to the database.
// Calling this method also invokes the model's mgm updating, updated,
// saving, and saved hooks.
func (coll *Collection) Update(model Model, opts ...*options.UpdateOptions) error {
return coll.UpdateWithCtx(ctx(), model, opts...)
}
// UpdateWithCtx function persists the changes made to a model to the database using the specified context.
// Calling this method also invokes the model's mgm updating, updated,
// saving, and saved hooks.
func (coll *Collection) UpdateWithCtx(ctx context.Context, model Model, opts ...*options.UpdateOptions) error {
return update(ctx, coll, model, opts...)
}
// Delete method deletes a model (doc) from a collection.
// To perform additional operations when deleting a model
// you should use hooks rather than overriding this method.
func (coll *Collection) Delete(model Model) error {
return del(ctx(), coll, model)
}
// DeleteWithCtx method deletes a model (doc) from a collection using the specified context.
// To perform additional operations when deleting a model
// you should use hooks rather than overriding this method.
func (coll *Collection) DeleteWithCtx(ctx context.Context, model Model) error {
return del(ctx, coll, model)
}
// SimpleFind finds, decodes and returns the results.
func (coll *Collection) SimpleFind(results interface{}, filter interface{}, opts ...*options.FindOptions) error {
return coll.SimpleFindWithCtx(ctx(), results, filter, opts...)
}
// SimpleFindWithCtx finds, decodes and returns the results using the specified context.
func (coll *Collection) SimpleFindWithCtx(ctx context.Context, results interface{}, filter interface{}, opts ...*options.FindOptions) error {
cur, err := coll.Find(ctx, filter, opts...)
if err != nil {
return err
}
return cur.All(ctx, results)
}
//--------------------------------
// Aggregation methods
//--------------------------------
// SimpleAggregateFirst is just same as SimpleAggregateFirstWithCtx, but doesnt' get context param.
func (coll *Collection) SimpleAggregateFirst(result interface{}, stages ...interface{}) (bool, error) {
return coll.SimpleAggregateFirstWithCtx(ctx(), result, stages...)
}
// SimpleAggregateFirstWithCtx performs a simple aggregation, decodes the first aggregate result and returns it using the provided result parameter.
// The value of `stages` can be Operator|bson.M
// Note: you can not use this method in a transaction because it does not accept a context.
// To participate in transactions, please use the regular aggregation method.
func (coll *Collection) SimpleAggregateFirstWithCtx(ctx context.Context, result interface{}, stages ...interface{}) (bool, error) {
cur, err := coll.SimpleAggregateCursorWithCtx(ctx, stages...)
if err != nil {
return false, err
}
if cur.Next(ctx) {
return true, cur.Decode(result)
}
return false, nil
}
// SimpleAggregate is just same as SimpleAggregateWithCtx, but doesn't get context param.
func (coll *Collection) SimpleAggregate(results interface{}, stages ...interface{}) error {
return coll.SimpleAggregateWithCtx(ctx(), results, stages...)
}
// SimpleAggregateWithCtx performs a simple aggregation, decodes the aggregate result and returns the list using the provided result parameter.
// The value of `stages` can be Operator|bson.M
// Note: you can not use this method in a transaction because it does not accept a context.
// To participate in transactions, please use the regular aggergation method.
func (coll *Collection) SimpleAggregateWithCtx(ctx context.Context, results interface{}, stages ...interface{}) error {
cur, err := coll.SimpleAggregateCursorWithCtx(ctx, stages...)
if err != nil {
return err
}
return cur.All(ctx, results)
}
// SimpleAggregateCursor is just same as SimpleAggregateCursorWithCtx, but
// doesn't get context.
func (coll *Collection) SimpleAggregateCursor(stages ...interface{}) (*mongo.Cursor, error) {
return coll.SimpleAggregateCursorWithCtx(ctx(), stages...)
}
// SimpleAggregateCursorWithCtx performs a simple aggregation and returns a cursor over the resulting documents.
// Note: you can not use this method in a transaction because it does not accept a context.
// To participate in transactions, please use the regular aggergation method.
func (coll *Collection) SimpleAggregateCursorWithCtx(ctx context.Context, stages ...interface{}) (*mongo.Cursor, error) {
pipeline := bson.A{}
for _, stage := range stages {
if operator, ok := stage.(builder.Operator); ok {
pipeline = append(pipeline, builder.S(operator))
} else {
pipeline = append(pipeline, stage)
}
}
return coll.Aggregate(ctx, pipeline, nil)
}

@ -0,0 +1,104 @@
package mgm
import (
"context"
"errors"
"github.com/kamva/mgm/v3/internal/util"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"time"
)
var config *Config
var client *mongo.Client
var db *mongo.Database
// Config struct contains extra configuration properties for the mgm package.
type Config struct {
// Set to 10 second (10*time.Second) for example.
CtxTimeout time.Duration
}
// NewCtx function creates and returns a new context with the specified timeout.
func NewCtx(timeout time.Duration) context.Context {
ctx, _ := context.WithTimeout(context.Background(), timeout)
return ctx
}
// Ctx function creates and returns a new context with a default timeout value.
func Ctx() context.Context {
return ctx()
}
func ctx() context.Context {
return NewCtx(config.CtxTimeout)
}
// NewClient returns a new mongodb client.
func NewClient(opts ...*options.ClientOptions) (*mongo.Client, error) {
client, err := mongo.NewClient(opts...)
if err != nil {
return nil, err
}
if err = client.Connect(Ctx()); err != nil {
return nil, err
}
return client, nil
}
// NewCollection returns a new collection with the supplied database.
func NewCollection(db *mongo.Database, name string, opts ...*options.CollectionOptions) *Collection {
coll := db.Collection(name, opts...)
return &Collection{Collection: coll}
}
// ResetDefaultConfig resets the configuration values, client and database.
func ResetDefaultConfig() {
config = nil
client = nil
db = nil
}
// SetDefaultConfig initializes the client and database using the specified configuration values, or default.
func SetDefaultConfig(conf *Config, dbName string, opts ...*options.ClientOptions) (err error) {
// Use the predefined configuration values as default if the user
// does not provide any.
if conf == nil {
conf = defaultConf()
}
config = conf
if client, err = NewClient(opts...); err != nil {
return err
}
db = client.Database(dbName)
return nil
}
// CollectionByName returns a new collection using the current configuration values.
func CollectionByName(name string, opts ...*options.CollectionOptions) *Collection {
return NewCollection(db, name, opts...)
}
// DefaultConfigs returns the current configuration values, client and database.
func DefaultConfigs() (*Config, *mongo.Client, *mongo.Database, error) {
if util.AnyNil(config, client, db) {
return nil, nil, nil, errors.New("please setup default config before acquiring it")
}
return config, client, db, nil
}
// defaultConf are the default configuration values when none are provided
// to the `SetDefaultConfig` method.
func defaultConf() *Config {
return &Config{CtxTimeout: 10 * time.Second}
}

@ -0,0 +1,167 @@
mode: set
github.com/Kamva/mgm/builder/types.go:13.31,16.32 2 1
github.com/Kamva/mgm/builder/types.go:20.2,20.10 1 1
github.com/Kamva/mgm/builder/types.go:16.32,18.3 1 1
github.com/Kamva/mgm/builder/types.go:24.38,28.2 2 1
github.com/Kamva/mgm/builder/util.go:9.60,10.22 1 1
github.com/Kamva/mgm/builder/util.go:10.22,12.3 1 1
github.com/Kamva/mgm/builder/aggregate_stages.go:10.68,19.2 6 1
github.com/Kamva/mgm/builder/aggregate_stages.go:22.77,31.2 6 1
github.com/Kamva/mgm/builder/aggregate_stages.go:34.72,42.2 5 1
github.com/Kamva/mgm/builder/aggregate_stages.go:45.101,55.2 7 1
github.com/Kamva/mgm/builder/aggregate_stages.go:61.52,66.31 3 1
github.com/Kamva/mgm/builder/aggregate_stages.go:70.2,70.24 1 1
github.com/Kamva/mgm/builder/aggregate_stages.go:66.31,68.3 1 1
github.com/Kamva/mgm/builder/aggregate_stages.go:74.70,83.2 6 1
github.com/Kamva/mgm/builder/aggregate_stages.go:86.71,95.2 6 1
github.com/Kamva/mgm/builder/aggregate_stages.go:98.77,108.2 7 1
github.com/Kamva/mgm/builder/aggregate_stages.go:111.48,117.2 3 1
github.com/Kamva/mgm/builder/aggregate_stages.go:120.40,126.2 3 1
github.com/Kamva/mgm/builder/aggregate_stages.go:129.87,137.2 5 1
github.com/Kamva/mgm/builder/operator.go:17.47,19.2 1 1
github.com/Kamva/mgm/builder/operator.go:22.52,24.2 1 1
github.com/Kamva/mgm/builder/operator.go:27.48,32.2 1 1
github.com/Kamva/mgm/examples/aggregate/aggregate.go:10.13,17.2 4 1
github.com/Kamva/mgm/examples/aggregate/aggregate.go:19.22,22.2 2 1
github.com/Kamva/mgm/examples/aggregate/aggregate.go:24.21,38.16 6 1
github.com/Kamva/mgm/examples/aggregate/aggregate.go:42.2,44.20 2 1
github.com/Kamva/mgm/examples/aggregate/aggregate.go:55.2,55.12 1 1
github.com/Kamva/mgm/examples/aggregate/aggregate.go:38.16,40.3 1 0
github.com/Kamva/mgm/examples/aggregate/aggregate.go:44.20,47.17 3 1
github.com/Kamva/mgm/examples/aggregate/aggregate.go:47.17,49.4 1 0
github.com/Kamva/mgm/examples/aggregate/connection.go:8.13,10.2 1 1
github.com/Kamva/mgm/examples/aggregate/model.go:15.71,21.2 1 1
github.com/Kamva/mgm/examples/aggregate/model.go:28.37,32.2 1 1
github.com/Kamva/mgm/examples/crud/crud.go:5.19,10.47 3 1
github.com/Kamva/mgm/examples/crud/crud.go:14.2,15.45 2 1
github.com/Kamva/mgm/examples/crud/crud.go:19.2,19.31 1 1
github.com/Kamva/mgm/examples/crud/crud.go:10.47,12.3 1 0
github.com/Kamva/mgm/examples/crud/crud.go:15.45,17.3 1 0
github.com/Kamva/mgm/examples/crud/crud.go:22.13,25.2 1 0
github.com/Kamva/mgm/examples/crud/model.go:13.44,18.2 1 1
github.com/Kamva/mgm/examples/crud/connection.go:8.13,10.2 1 1
github.com/Kamva/mgm/model.go:37.45,39.2 1 1
github.com/Kamva/mgm/model.go:42.43,44.2 1 1
github.com/Kamva/mgm/model_field.go:22.66,23.34 1 1
github.com/Kamva/mgm/model_field.go:28.2,28.16 1 1
github.com/Kamva/mgm/model_field.go:23.34,25.3 1 1
github.com/Kamva/mgm/model_field.go:32.32,34.2 1 1
github.com/Kamva/mgm/model_field.go:37.39,39.2 1 1
github.com/Kamva/mgm/model_field.go:42.41,44.2 1 1
github.com/Kamva/mgm/model_field.go:52.39,55.2 2 1
github.com/Kamva/mgm/model_field.go:59.37,62.2 2 1
github.com/Kamva/mgm/operation.go:10.68,12.55 1 1
github.com/Kamva/mgm/operation.go:16.2,18.16 2 1
github.com/Kamva/mgm/operation.go:23.2,25.38 2 1
github.com/Kamva/mgm/operation.go:12.55,14.3 1 1
github.com/Kamva/mgm/operation.go:18.16,20.3 1 0
github.com/Kamva/mgm/operation.go:28.120,30.2 1 1
github.com/Kamva/mgm/operation.go:32.68,34.55 1 1
github.com/Kamva/mgm/operation.go:38.2,40.16 2 1
github.com/Kamva/mgm/operation.go:44.2,44.43 1 1
github.com/Kamva/mgm/operation.go:34.55,36.3 1 1
github.com/Kamva/mgm/operation.go:40.16,42.3 1 0
github.com/Kamva/mgm/operation.go:47.65,48.55 1 1
github.com/Kamva/mgm/operation.go:51.2,52.16 2 1
github.com/Kamva/mgm/operation.go:56.2,56.43 1 1
github.com/Kamva/mgm/operation.go:48.55,50.3 1 1
github.com/Kamva/mgm/operation.go:52.16,54.3 1 0
github.com/Kamva/mgm/collection.go:20.69,22.2 1 1
github.com/Kamva/mgm/collection.go:27.97,30.16 2 1
github.com/Kamva/mgm/collection.go:34.2,34.54 1 1
github.com/Kamva/mgm/collection.go:30.16,32.3 1 1
github.com/Kamva/mgm/collection.go:38.103,40.2 1 1
github.com/Kamva/mgm/collection.go:43.131,45.2 1 1
github.com/Kamva/mgm/collection.go:48.51,50.2 1 1
github.com/Kamva/mgm/collection.go:53.79,55.2 1 1
github.com/Kamva/mgm/collection.go:60.51,62.2 1 1
github.com/Kamva/mgm/collection.go:67.79,69.2 1 1
github.com/Kamva/mgm/collection.go:72.49,74.2 1 1
github.com/Kamva/mgm/collection.go:77.77,78.19 1 1
github.com/Kamva/mgm/collection.go:82.2,82.33 1 1
github.com/Kamva/mgm/collection.go:78.19,80.3 1 1
github.com/Kamva/mgm/collection.go:88.51,90.2 1 1
github.com/Kamva/mgm/collection.go:95.79,97.2 1 0
github.com/Kamva/mgm/collection.go:100.113,102.2 1 1
github.com/Kamva/mgm/collection.go:105.141,108.16 2 1
github.com/Kamva/mgm/collection.go:112.2,112.30 1 1
github.com/Kamva/mgm/collection.go:108.16,110.3 1 0
github.com/Kamva/mgm/collection.go:123.91,125.16 2 1
github.com/Kamva/mgm/collection.go:129.2,129.32 1 1
github.com/Kamva/mgm/collection.go:125.16,127.3 1 0
github.com/Kamva/mgm/collection.go:135.93,138.31 2 1
github.com/Kamva/mgm/collection.go:146.2,146.45 1 1
github.com/Kamva/mgm/collection.go:138.31,139.51 1 1
github.com/Kamva/mgm/collection.go:139.51,141.4 1 1
github.com/Kamva/mgm/collection.go:141.9,143.4 1 1
github.com/Kamva/mgm/hooks.go:46.49,47.42 1 1
github.com/Kamva/mgm/hooks.go:53.2,53.40 1 1
github.com/Kamva/mgm/hooks.go:59.2,59.12 1 1
github.com/Kamva/mgm/hooks.go:47.42,48.41 1 1
github.com/Kamva/mgm/hooks.go:48.41,50.4 1 1
github.com/Kamva/mgm/hooks.go:53.40,54.39 1 1
github.com/Kamva/mgm/hooks.go:54.39,56.4 1 1
github.com/Kamva/mgm/hooks.go:62.49,63.42 1 1
github.com/Kamva/mgm/hooks.go:69.2,69.40 1 1
github.com/Kamva/mgm/hooks.go:75.2,75.12 1 1
github.com/Kamva/mgm/hooks.go:63.42,64.41 1 1
github.com/Kamva/mgm/hooks.go:64.41,66.4 1 1
github.com/Kamva/mgm/hooks.go:69.40,70.39 1 1
github.com/Kamva/mgm/hooks.go:70.39,72.4 1 0
github.com/Kamva/mgm/hooks.go:78.48,79.41 1 1
github.com/Kamva/mgm/hooks.go:85.2,85.39 1 1
github.com/Kamva/mgm/hooks.go:91.2,91.12 1 1
github.com/Kamva/mgm/hooks.go:79.41,80.40 1 1
github.com/Kamva/mgm/hooks.go:80.40,82.4 1 0
github.com/Kamva/mgm/hooks.go:85.39,86.38 1 1
github.com/Kamva/mgm/hooks.go:86.38,88.4 1 0
github.com/Kamva/mgm/hooks.go:94.82,95.41 1 1
github.com/Kamva/mgm/hooks.go:101.2,101.39 1 1
github.com/Kamva/mgm/hooks.go:107.2,107.12 1 1
github.com/Kamva/mgm/hooks.go:95.41,96.52 1 1
github.com/Kamva/mgm/hooks.go:96.52,98.4 1 0
github.com/Kamva/mgm/hooks.go:101.39,102.38 1 1
github.com/Kamva/mgm/hooks.go:102.38,104.4 1 0
github.com/Kamva/mgm/hooks.go:110.49,111.42 1 1
github.com/Kamva/mgm/hooks.go:117.2,117.12 1 1
github.com/Kamva/mgm/hooks.go:111.42,112.41 1 1
github.com/Kamva/mgm/hooks.go:112.41,114.4 1 1
github.com/Kamva/mgm/hooks.go:120.82,121.41 1 1
github.com/Kamva/mgm/hooks.go:127.2,127.12 1 1
github.com/Kamva/mgm/hooks.go:121.41,122.52 1 1
github.com/Kamva/mgm/hooks.go:122.52,124.4 1 0
github.com/Kamva/mgm/util.go:10.32,12.48 1 1
github.com/Kamva/mgm/util.go:16.2,16.38 1 1
github.com/Kamva/mgm/util.go:12.48,14.3 1 0
github.com/Kamva/mgm/util.go:22.31,24.56 1 1
github.com/Kamva/mgm/util.go:28.2,30.50 2 1
github.com/Kamva/mgm/util.go:24.56,26.3 1 1
github.com/Kamva/mgm/connection.go:23.52,27.2 2 1
github.com/Kamva/mgm/connection.go:31.28,33.2 1 1
github.com/Kamva/mgm/connection.go:35.28,37.2 1 1
github.com/Kamva/mgm/connection.go:40.71,42.16 2 1
github.com/Kamva/mgm/connection.go:46.2,46.45 1 1
github.com/Kamva/mgm/connection.go:50.2,50.20 1 1
github.com/Kamva/mgm/connection.go:42.16,44.3 1 0
github.com/Kamva/mgm/connection.go:46.45,48.3 1 0
github.com/Kamva/mgm/connection.go:54.101,58.2 2 1
github.com/Kamva/mgm/connection.go:61.27,65.2 3 1
github.com/Kamva/mgm/connection.go:68.96,72.17 1 1
github.com/Kamva/mgm/connection.go:76.2,78.50 2 1
github.com/Kamva/mgm/connection.go:82.2,84.12 2 1
github.com/Kamva/mgm/connection.go:72.17,74.3 1 1
github.com/Kamva/mgm/connection.go:78.50,80.3 1 0
github.com/Kamva/mgm/connection.go:88.84,90.2 1 1
github.com/Kamva/mgm/connection.go:93.72,94.37 1 1
github.com/Kamva/mgm/connection.go:98.2,98.32 1 1
github.com/Kamva/mgm/connection.go:94.37,96.3 1 1
github.com/Kamva/mgm/connection.go:103.28,105.2 1 1
github.com/Kamva/mgm/transaction.go:11.43,13.2 1 1
github.com/Kamva/mgm/transaction.go:16.71,18.2 1 1
github.com/Kamva/mgm/transaction.go:21.96,23.16 2 1
github.com/Kamva/mgm/transaction.go:27.2,29.50 2 1
github.com/Kamva/mgm/transaction.go:33.2,33.51 1 1
github.com/Kamva/mgm/transaction.go:37.2,37.51 1 1
github.com/Kamva/mgm/transaction.go:23.16,25.3 1 0
github.com/Kamva/mgm/transaction.go:29.50,31.3 1 0
github.com/Kamva/mgm/transaction.go:33.51,35.3 1 1

@ -0,0 +1,60 @@
package mgm
import (
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// IDField struct contains a model's ID field.
type IDField struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
}
// DateFields struct contains the `created_at` and `updated_at`
// fields that autofill when inserting or updating a model.
type DateFields struct {
CreatedAt time.Time `json:"created_at" bson:"created_at"`
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
}
// PrepareID method prepares the ID value to be used for filtering
// e.g convert hex-string ID value to bson.ObjectId
func (f *IDField) PrepareID(id interface{}) (interface{}, error) {
if idStr, ok := id.(string); ok {
return primitive.ObjectIDFromHex(idStr)
}
// Otherwise id must be ObjectId
return id, nil
}
// GetID method returns a model's ID
func (f *IDField) GetID() interface{} {
return f.ID
}
// SetID sets the value of a model's ID field.
func (f *IDField) SetID(id interface{}) {
f.ID = id.(primitive.ObjectID)
}
//--------------------------------
// DateField methods
//--------------------------------
// Creating hook is used here to set the `created_at` field
// value when inserting a new model into the database.
// TODO: get context as param the next version(4).
func (f *DateFields) Creating() error {
f.CreatedAt = time.Now().UTC()
return nil
}
// Saving hook is used here to set the `updated_at` field
// value when creating or updateing a model.
// TODO: get context as param the next version(4).
func (f *DateFields) Saving() error {
f.UpdatedAt = time.Now().UTC()
return nil
}

@ -0,0 +1,15 @@
package field
import "go.mongodb.org/mongo-driver/bson"
// ID field is constant for referencing the "_id" field name.
const ID = "_id"
// Empty is a predefined empty map.
var Empty = bson.M{}
// TODO: Extract all field names from :
// cont.todo: https://docs.mongodb.com/manual/reference/operator/query/
// cont.todo: https://docs.mongodb.com/manual/reference/operator/update/
// cont.todo: https://docs.mongodb.com/manual/reference/operator/aggregation/
// cont.todo: https://docs.mongodb.com/manual/reference/operator/query-modifier/

@ -0,0 +1,12 @@
package field
// JeoJSON reference: https://docs.mongodb.com/manual/reference/geojson/
const (
Point = "Point"
LineString = "LineString"
Polygon = "Polygon"
MultiPoint = "MultiPoint"
MultiLineString = "MultiLineString"
MultiPolygon = "MultiPolygon"
GeometryCollection = "GeometryCollection"
)

@ -0,0 +1,118 @@
package field
import "go.mongodb.org/mongo-driver/bson"
// $bucket fields
const (
GroupBy = "groupBy"
Boundaries = "boundaries"
Default = "default"
Output = "output"
)
// $bucketAuto
const (
// GroupBy = "groupBy" // Declared
Buckets = "buckets"
// Output = "output" // Declared
Granularity = "granularity"
)
// $collStats
const (
LatencyStats = "latencyStats"
StorageStats = "storageStats"
Count = "count"
)
// $currentOp
const (
AllUsers = "allUsers"
IdleConnections = "idleConnections"
IdleCursors = "idleCursors"
IdleSessions = "idleSessions"
LocalOps = "localOps"
)
// $geoNear
const (
Near = "near"
DistanceField = "distanceField"
Spherical = "spherical"
MaxDistance = "maxDistance"
Query = "query"
DistanceMultiplier = "distanceMultiplier"
IncludeLocs = "includeLocs"
UniqueDocs = "uniqueDocs"
MinDistance = "minDistance"
Key = "key"
)
// $graphLookup
const (
From = "from"
StartWith = "startWith"
ConnectFromField = "connectFromField"
ConnectToField = "connectToField"
As = "as"
MaxDepth = "maxDepth"
DepthField = "depthField"
RestrictSearchWithMatch = "restrictSearchWithMatch"
)
// $group
const (
// ID="_id" // Declared
)
// $listLocalSessions
const (
// AllUsers = "allUsers" // Declared
)
var (
// EmptyDoc is empty document.
EmptyDoc = bson.M{}
// AllUsersDoc is document that contains "allUsers":true value.
AllUsersDoc = bson.M{AllUsers: true}
)
// $listSessions : Same as $listLocalSessions.
// $lookup fields
const (
// From = "from" // Declared
LocalField = "localField"
ForeignField = "foreignField"
// As = "as" // Declared
Let = "let"
Pipeline = "pipeline"
)
// $merge
const (
Into = "into"
On = "on"
// Let = "let" // Declared
WhenMatched = "whenMatched"
WhenNotMatched = "whenNotMatched"
)
// $replaceRoot
const (
NewRoot = "newRoot"
)
// $sample
const (
Size = "size"
)
// $unwind
const (
Path = "path"
IncludeArrayIndex = "includeArrayIndex"
PreserveNullAndEmptyArrays = "preserveNullAndEmptyArrays"
)

@ -0,0 +1,219 @@
package mgm
import (
"context"
"go.mongodb.org/mongo-driver/mongo"
)
// CreatingHook is called before saving a new model to the database
// Deprecated: please use CreatingHookWithCtx
type CreatingHook interface {
Creating() error
}
// CreatingHookWithCtx is called before saving a new model to the database
type CreatingHookWithCtx interface {
Creating(context.Context) error
}
// CreatedHook is called after a model has been created
// Deprecated: Please use CreatedHookWithCtx
type CreatedHook interface {
Created() error
}
// CreatedHookWithCtx is called after a model has been created
type CreatedHookWithCtx interface {
Created(context.Context) error
}
// UpdatingHook is called before updating a model
// Deprecated: Please use UpdatingHookWithCtx
type UpdatingHook interface {
Updating() error
}
// UpdatingHookWithCtx is called before updating a model
type UpdatingHookWithCtx interface {
Updating(context.Context) error
}
// UpdatedHook is called after a model is updated
// Deprecated: Please use UpdatedHookWithCtx
type UpdatedHook interface {
// Deprecated:
Updated(result *mongo.UpdateResult) error
}
// UpdatedHookWithCtx is called after a model is updated
type UpdatedHookWithCtx interface {
Updated(ctx context.Context, result *mongo.UpdateResult) error
}
// SavingHook is called before a model (new or existing) is saved to the database.
// Deprecated: Please use SavingHookWithCtx
type SavingHook interface {
Saving() error
}
// SavingHookWithCtx is called before a model (new or existing) is saved to the database.
type SavingHookWithCtx interface {
Saving(context.Context) error
}
// SavedHook is called after a model is saved to the database.
// Deprecated: Please use SavedHookWithCtx
type SavedHook interface {
Saved() error
}
// SavedHookWithCtx is called after a model is saved to the database.
type SavedHookWithCtx interface {
Saved(context.Context) error
}
// DeletingHook is called before a model is deleted
// Deprecated: Please use DeletingHookWithCtx
type DeletingHook interface {
Deleting() error
}
// DeletingHookWithCtx is called before a model is deleted
type DeletingHookWithCtx interface {
Deleting(context.Context) error
}
// DeletedHook is called after a model is deleted
// Deprecated: Please use DeletedHookWithCtx
type DeletedHook interface {
Deleted(result *mongo.DeleteResult) error
}
// DeletedHookWithCtx is called after a model is deleted
type DeletedHookWithCtx interface {
Deleted(ctx context.Context, result *mongo.DeleteResult) error
}
func callToBeforeCreateHooks(ctx context.Context, model Model) error {
if hook, ok := model.(CreatingHookWithCtx); ok {
if err := hook.Creating(ctx); err != nil {
return err
}
} else if hook, ok := model.(CreatingHook); ok {
if err := hook.Creating(); err != nil {
return err
}
}
if hook, ok := model.(SavingHookWithCtx); ok {
if err := hook.Saving(ctx); err != nil {
return err
}
} else if hook, ok := model.(SavingHook); ok {
if err := hook.Saving(); err != nil {
return err
}
}
return nil
}
func callToBeforeUpdateHooks(ctx context.Context, model Model) error {
if hook, ok := model.(UpdatingHookWithCtx); ok {
if err := hook.Updating(ctx); err != nil {
return err
}
} else if hook, ok := model.(UpdatingHook); ok {
if err := hook.Updating(); err != nil {
return err
}
}
if hook, ok := model.(SavingHookWithCtx); ok {
if err := hook.Saving(ctx); err != nil {
return err
}
} else if hook, ok := model.(SavingHook); ok {
if err := hook.Saving(); err != nil {
return err
}
}
return nil
}
func callToAfterCreateHooks(ctx context.Context, model Model) error {
if hook, ok := model.(CreatedHookWithCtx); ok {
if err := hook.Created(ctx); err != nil {
return err
}
} else if hook, ok := model.(CreatedHook); ok {
if err := hook.Created(); err != nil {
return err
}
}
if hook, ok := model.(SavedHookWithCtx); ok {
if err := hook.Saved(ctx); err != nil {
return err
}
} else if hook, ok := model.(SavedHook); ok {
if err := hook.Saved(); err != nil {
return err
}
}
return nil
}
func callToAfterUpdateHooks(ctx context.Context, updateResult *mongo.UpdateResult, model Model) error {
if hook, ok := model.(UpdatedHookWithCtx); ok {
if err := hook.Updated(ctx, updateResult); err != nil {
return err
}
} else if hook, ok := model.(UpdatedHook); ok {
if err := hook.Updated(updateResult); err != nil {
return err
}
}
if hook, ok := model.(SavedHookWithCtx); ok {
if err := hook.Saved(ctx); err != nil {
return err
}
} else if hook, ok := model.(SavedHook); ok {
if err := hook.Saved(); err != nil {
return err
}
}
return nil
}
func callToBeforeDeleteHooks(ctx context.Context, model Model) error {
if hook, ok := model.(DeletingHookWithCtx); ok {
if err := hook.Deleting(ctx); err != nil {
return err
}
} else if hook, ok := model.(DeletingHook); ok {
if err := hook.Deleting(); err != nil {
return err
}
}
return nil
}
func callToAfterDeleteHooks(ctx context.Context, deleteResult *mongo.DeleteResult, model Model) error {
if hook, ok := model.(DeletedHookWithCtx); ok {
if err := hook.Deleted(ctx, deleteResult); err != nil {
return err
}
} else if hook, ok := model.(DeletedHook); ok {
if err := hook.Deleted(deleteResult); err != nil {
return err
}
}
return nil
}

@ -0,0 +1,34 @@
package util
import "reflect"
// IsNil function determines whether the parameter is nil or not. If the value itself is not nil,
// reflection is used to determine whether the underlying value is nil.
// See https://play.golang.org/p/Isoo0CcAvr for an example.
func IsNil(val interface{}) (result bool) {
if val == nil {
return true
}
switch v := reflect.ValueOf(val); v.Kind() {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer,
reflect.Interface, reflect.Slice:
return v.IsNil()
}
return
}
// AnyNil returns true if any of the passed in parameters are nil, and returns false otherwise.
func AnyNil(values ...interface{}) bool {
for _, val := range values {
if IsNil(val) {
return true
}
}
return false
}

@ -0,0 +1,16 @@
package util
import (
"regexp"
"strings"
)
var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
// ToSnakeCase returns snake_case of the provided value.
func ToSnakeCase(str string) string {
snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}")
snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}")
return strings.ToLower(snake)
}

@ -0,0 +1,13 @@
package util
import (
"testing"
"github.com/stretchr/testify/require"
)
// AssertErrIsNil function assert that the passed-in error is nil.
func AssertErrIsNil(t *testing.T, err error) {
// The inserted model's id should not be nil:
require.Nil(t, err, "Assertion err: %v", err)
}

@ -0,0 +1,8 @@
package util
// PanicErr panics using the passed-in error if it's not nil.
func PanicErr(err error) {
if err != nil {
panic(err)
}
}

@ -0,0 +1,45 @@
package mgm
// CollectionGetter interface contains a method to return
// a model's custom collection.
type CollectionGetter interface {
// Collection method return collection
Collection() *Collection
}
// CollectionNameGetter interface contains a method to return
// the collection name of a model.
type CollectionNameGetter interface {
// CollectionName method return model collection's name.
CollectionName() string
}
// Model interface contains base methods that must be implemented by
// each model. If you're using the `DefaultModel` struct in your model,
// you don't need to implement any of these methods.
type Model interface {
// PrepareID converts the id value if needed, then
// returns it (e.g convert string to objectId).
PrepareID(id interface{}) (interface{}, error)
GetID() interface{}
SetID(id interface{})
}
// DefaultModel struct contains a model's default fields.
type DefaultModel struct {
IDField `bson:",inline"`
DateFields `bson:",inline"`
}
// Creating function calls the inner fields' defined hooks
// TODO: get context as param in the next version (4).
func (model *DefaultModel) Creating() error {
return model.DateFields.Creating()
}
// Saving function calls the inner fields' defined hooks
// TODO: get context as param the next version(4).
func (model *DefaultModel) Saving() error {
return model.DateFields.Saving()
}

@ -0,0 +1,57 @@
package mgm
import (
"context"
"github.com/kamva/mgm/v3/field"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
func create(ctx context.Context, c *Collection, model Model, opts ...*options.InsertOneOptions) error {
// Call to saving hook
if err := callToBeforeCreateHooks(ctx, model); err != nil {
return err
}
res, err := c.InsertOne(ctx, model, opts...)
if err != nil {
return err
}
// Set new id
model.SetID(res.InsertedID)
return callToAfterCreateHooks(ctx, model)
}
func first(ctx context.Context, c *Collection, filter interface{}, model Model, opts ...*options.FindOneOptions) error {
return c.FindOne(ctx, filter, opts...).Decode(model)
}
func update(ctx context.Context, c *Collection, model Model, opts ...*options.UpdateOptions) error {
// Call to saving hook
if err := callToBeforeUpdateHooks(ctx, model); err != nil {
return err
}
res, err := c.UpdateOne(ctx, bson.M{field.ID: model.GetID()}, bson.M{"$set": model}, opts...)
if err != nil {
return err
}
return callToAfterUpdateHooks(ctx, res, model)
}
func del(ctx context.Context, c *Collection, model Model) error {
if err := callToBeforeDeleteHooks(ctx, model); err != nil {
return err
}
res, err := c.DeleteOne(ctx, bson.M{field.ID: model.GetID()})
if err != nil {
return err
}
return callToAfterDeleteHooks(ctx, res, model)
}

@ -0,0 +1,198 @@
package operator
// Arithmetic Expression Operators
const (
Abs = "$abs"
Add = "$add"
Ceil = "$ceil"
Divide = "$divide"
Exp = "$exp"
Floor = "$floor"
Ln = "$ln"
Log = "$log"
Log10 = "$log10"
// Mod = "$mod" // Declared
Multiply = "$multiply"
Pow = "$pow"
Round = "$round"
Sqrt = "$sqrt"
Subtract = "$subtract"
Trunc = "$trunc"
)
// Array Expression Operators
const (
ArrayToObject = "$arrayToObject"
ConcatArrays = "$concatArrays"
Filter = "$filter"
// In = "$in" // Declared
IndexOfArray = "$indexOfArray"
IsArray = "$isArray"
Map = "$map"
ObjectToArray = "$objectToArray"
Range = "$range"
Reduce = "$reduce"
ReverseArray = "$reverseArray"
// Size = "$size" // Declared
// Slice = "$slice" // Declared
Zip = "$zip"
)
// Boolean Expression Operators
const (
// And = "$and" // Declared
// Not = "$not" // Declared
// Or = "$or" // Declared
)
// Comparison Expression Operators
const (
Cmp = "$cmp"
//Eq = "$eq" // Declared
//Gt = "$gt" // Declared
//Gte = "$gte" // Declared
//Lt = "$lt" // Declared
//Lte = "$lte" // Declared
//Ne = "$ne" // Declared
)
// Conditional Expression Operators
const (
Cond = "$cond"
IfNull = "$ifNull"
Switch = "$switch"
)
// Date Expression Operators
const (
DateFromParts = "$dateFromParts"
DateFromString = "$dateFromString"
DateToParts = "$dateToParts"
DateToString = "$dateToString"
DayOfMonth = "$dayOfMonth"
DayOfWeek = "$dayOfWeek"
DayOfYear = "$dayOfYear"
Hour = "$hour"
IsoDayOfWeek = "$isoDayOfWeek"
IsoWeek = "$isoWeek"
IsoWeekYear = "$isoWeekYear"
Millisecond = "$millisecond"
Minute = "$minute"
Month = "$month"
Second = "$second"
ToDate = "$toDate"
Week = "$week"
Year = "$year"
)
// Literal Expression Operator
const (
Literal = "$literal"
)
// Object Expression Operators
const (
MergeObjects = "$mergeObjects"
// ObjectToArray = "$objectToArray" // Declared
)
// Set Expression Operators
const (
AllElementsTrue = "$allElementsTrue"
AnyElementTrue = "$anyElementTrue"
SetDifference = "$setDifference"
SetEquals = "$setEquals"
SetIntersection = "$setIntersection"
SetIsSubset = "$setIsSubset"
SetUnion = "$setUnion"
)
// String Expression Operators
const (
Concat = "$concat"
// DateFromString = "$dateFromString" // Declared
// DateToString = "$dateToString" // Declared
IndexOfBytes = "$indexOfBytes"
IndexOfCP = "$indexOfCP"
Ltrim = "$ltrim"
RegexFind = "$regexFind"
RegexFindAll = "$regexFindAll"
RegexMatch = "$regexMatch"
Rtrim = "$rtrim"
Split = "$split"
StrLenBytes = "$strLenBytes"
StrLenCP = "$strLenCP"
Strcasecmp = "$strcasecmp"
Substr = "$substr"
SubstrBytes = "$substrBytes"
SubstrCP = "$substrCP"
ToLower = "$toLower"
ToString = "$toString"
Trim = "$trim"
ToUpper = "$toUpper"
)
// Text Expression Operator
const (
// Meta = "$meta" // Declared
)
// Trigonometry Expression Operators
const (
Sin = "$sin"
Cos = "$cos"
Tan = "$tan"
Asin = "$asin"
Acos = "$acos"
Atan = "$atan"
Atan2 = "$atan2"
Asinh = "$asinh"
Acosh = "$acosh"
Atanh = "$atanh"
DegreesToRadians = "$degreesToRadians"
RadiansToDegrees = "$radiansToDegrees"
)
// Type Expression Operators
const (
Convert = "$convert"
ToBool = "$toBool"
//ToDate = "$toDate" // Declared
ToDecimal = "$toDecimal"
ToDouble = "$toDouble"
ToInt = "$toInt"
ToLong = "$toLong"
ToObjectID = "$toObjectId"
//ToString = "$toString" // Declared
//Type = "$type" // Declared
)
// Accumulators ($group)
const (
// AddToSet = "$addToSet" // Declared
Avg = "$avg"
First = "$first"
Last = "$last"
// Max = "$max" // Declared
// MergeObjects = "$mergeObjects" // Declared
// Min = "$min" // Declared
// Push = "$push" // Declared
StdDevPop = "$stdDevPop"
StdDevSamp = "$stdDevSamp"
Sum = "$sum"
)
// Accumulators (in Other Stages)
const (
// Avg = "$avg" // Declared
// Max = "$max" // Declared
// Min = "$min" // Declared
// StdDevPop = "$stdDevPop" // Declared
// StdDevSamp = "$stdDevSamp" // Declared
// Sum = "$sum" // Declared
)
// Variable Expression Operators
const (
Let = "$let"
)

@ -0,0 +1,39 @@
package operator
// Collection aggregation Stages
const (
AddFields = "$addFields"
Bucket = "$bucket"
BucketAuto = "$bucketAuto"
CollStats = "$collStats"
Count = "$count"
Facet = "$facet"
GeoNear = "$geoNear"
GraphLookup = "$graphLookup"
Group = "$group"
IndexStats = "$indexStats"
Limit = "$limit"
ListSessions = "$listSessions"
Lookup = "$lookup"
Match = "$match"
Merge = "$merge"
Out = "$out"
PlanCacheStats = "$planCacheStats"
Project = "$project"
Redact = "$redact"
ReplaceRoot = "$replaceRoot"
ReplaceWith = "$replaceWith"
Sample = "$sample"
// Set = "$set" // Declared
Skip = "$skip"
// Sort = "$sort" // Declared
SortByCount = "$sortByCount"
// Unset = "$unset" // Declared
Unwind = "$unwind"
)
// DB Aggregate stages
const (
CurrentOp = "$currentOp"
ListLocalSessions = "$listLocalSessions"
)

@ -0,0 +1,73 @@
package operator
// Comparison
const (
Eq = "$eq"
Gt = "$gt"
Gte = "$gte"
In = "$in"
Lt = "$lt"
Lte = "$lte"
Ne = "$ne"
Nin = "$nin"
)
// Logical
const (
And = "$and"
Not = "$not"
Nor = "$nor"
Or = "$or"
)
// Element
const (
Exists = "$exists"
Type = "$type"
)
// Evaluation
const (
Expr = "$expr"
JSONSchema = "$jsonSchema"
Mod = "$mod"
Regex = "$regex"
Text = "$text"
Where = "$where"
)
// Geo spatial
const (
GeoIntersects = "$geoIntersects"
GeoWithin = "$geoWithin"
Near = "$near"
NearSphere = "$nearSphere"
)
// Array
const (
All = "$all"
ElemMatch = "$elemMatch"
Size = "$size"
)
// Bitwise
const (
BitsAllClear = "$bitsAllClear"
BitsAllSet = "$bitsAllSet"
BitsAnyClear = "$bitsAnyClear"
BitsAnySet = "$bitsAnySet"
)
// Comments
const (
Comment = "$comment"
)
// Projection operators
const (
Dollar = "$"
// ElemMatch = "$elemMatch" // Declared
Meta = "$meta"
Slice = "$slice"
)

@ -0,0 +1,20 @@
package operator
// Modifiers
const (
// Comment = "$comment" // Declared
Explain = "$explain"
Hint = "$hint"
// Max = "$max" // Declared
MaxTimeMS = "$maxTimeMS"
// Min = "$min" // Declared
OrderBy = "$orderby"
Query = "$query"
ReturnKey = "$returnKey"
ShowDiskLoc = "$showDiskLoc"
)
// Sort Order
const (
Natural = "$natural"
)

@ -0,0 +1,40 @@
package operator
// Fields
const (
CurrentDate = "$currentDate"
Inc = "$inc"
Min = "$min"
Max = "$max"
Mul = "$mul"
Rename = "$rename"
Set = "$set"
SetOnInsert = "$setOnInsert"
Unset = "$unset"
)
// Array Operators
const (
// $: Act as a modifier
// $[]: Act as a modifier
// $[<identifier>]: Act as a modifier
AddToSet = "$addToSet"
Pop = "$pop"
Pull = "$pull"
Push = "$push"
PullAll = "$pullAll"
)
// Array modifiers
const (
Each = "$each"
Position = "$position"
// Slice = "$slice" // Declared
Sort = "$sort"
)
// Array bitwise
const (
Bit = "$bit"
)

@ -0,0 +1,39 @@
package mgm
import (
"context"
"go.mongodb.org/mongo-driver/mongo"
)
// TransactionFunc is a handler to manage a transaction.
type TransactionFunc func(session mongo.Session, sc mongo.SessionContext) error
// Transaction creates a transaction with the default client.
func Transaction(f TransactionFunc) error {
return TransactionWithClient(ctx(), client, f)
}
// TransactionWithCtx creates a transaction with the given context and the default client.
func TransactionWithCtx(ctx context.Context, f TransactionFunc) error {
return TransactionWithClient(ctx, client, f)
}
// TransactionWithClient creates a transaction with the given client.
func TransactionWithClient(ctx context.Context, client *mongo.Client, f TransactionFunc) error {
session, err := client.StartSession() //start session need to get options.
if err != nil {
return err
}
defer session.EndSession(ctx)
if err = session.StartTransaction(); err != nil { // startTransaction need to get options.
return err
}
wrapperFn := func(sc mongo.SessionContext) error {
return f(session, sc)
}
return mongo.WithSession(ctx, session, wrapperFn)
}

@ -0,0 +1,38 @@
package mgm
import (
"github.com/kamva/mgm/v3/internal/util"
"github.com/jinzhu/inflection"
"go.mongodb.org/mongo-driver/mongo/options"
"reflect"
)
// Coll returns the collection associated with a model.
func Coll(m Model, opts ...*options.CollectionOptions) *Collection {
if collGetter, ok := m.(CollectionGetter); ok {
return collGetter.Collection()
}
return CollectionByName(CollName(m), opts...)
}
// CollName returns a model's collection name. The `CollectionNameGetter` will be used
// if the model implements this interface. Otherwise, the collection name is inferred
// based on the model's type using reflection.
func CollName(m Model) string {
if collNameGetter, ok := m.(CollectionNameGetter); ok {
return collNameGetter.CollectionName()
}
name := reflect.TypeOf(m).Elem().Name()
return inflection.Plural(util.ToSnakeCase(name))
}
// UpsertTrueOption returns new instance of UpdateOptions with the upsert property set to true.
func UpsertTrueOption() *options.UpdateOptions {
upsert := true
return &options.UpdateOptions{Upsert: &upsert}
}

@ -0,0 +1,4 @@
package mgm
// Version of the package
const Version = "3.3.0"

@ -1,7 +1,7 @@
#ifndef USE_LIBSQLITE3
/******************************************************************************
** This file is an amalgamation of many separate C source files from SQLite
** version 3.38.0. By combining all the individual C code files into this
** version 3.38.5. By combining all the individual C code files into this
** single large file, the entire code can be compiled as a single translation
** unit. This allows many compilers to do optimizations that would not be
** possible if the files were compiled separately. Performance improvements
@ -453,9 +453,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.38.0"
#define SQLITE_VERSION_NUMBER 3038000
#define SQLITE_SOURCE_ID "2022-02-22 18:58:40 40fa792d359f84c3b9e9d6623743e1a59826274e221df1bde8f47086968a1bab"
#define SQLITE_VERSION "3.38.5"
#define SQLITE_VERSION_NUMBER 3038005
#define SQLITE_SOURCE_ID "2022-05-06 15:25:27 78d9c993d404cdfaa7fdd2973fa1052e3da9f66215cff9c5540ebe55c407d9fe"
/*
** CAPI3REF: Run-Time Library Version Numbers
@ -5286,6 +5286,10 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt);
** even empty strings, are always zero-terminated. ^The return
** value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer.
**
** ^Strings returned by sqlite3_column_text16() always have the endianness
** which is native to the platform, regardless of the text encoding set
** for the database.
**
** <b>Warning:</b> ^The object returned by [sqlite3_column_value()] is an
** [unprotected sqlite3_value] object. In a multithreaded environment,
** an unprotected sqlite3_value object may only be used safely with
@ -5299,7 +5303,7 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt);
** [application-defined SQL functions] or [virtual tables], not within
** top-level application code.
**
** The these routines may attempt to convert the datatype of the result.
** These routines may attempt to convert the datatype of the result.
** ^For example, if the internal representation is FLOAT and a text result
** is requested, [sqlite3_snprintf()] is used internally to perform the
** conversion automatically. ^(The following table details the conversions
@ -5324,7 +5328,7 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt);
** <tr><td> TEXT <td> BLOB <td> No change
** <tr><td> BLOB <td> INTEGER <td> [CAST] to INTEGER
** <tr><td> BLOB <td> FLOAT <td> [CAST] to REAL
** <tr><td> BLOB <td> TEXT <td> Add a zero terminator if needed
** <tr><td> BLOB <td> TEXT <td> [CAST] to TEXT, ensure zero terminator
** </table>
** </blockquote>)^
**
@ -10074,7 +10078,7 @@ SQLITE_API int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut);
** ^When xBestIndex returns, the sqlite3_value object returned by
** sqlite3_vtab_rhs_value() is automatically deallocated.
**
** The "_rhs_" in the name of this routine is an appreviation for
** The "_rhs_" in the name of this routine is an abbreviation for
** "Right-Hand Side".
*/
SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal);
@ -19926,6 +19930,7 @@ SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr*);
SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr*, u8);
SQLITE_PRIVATE int sqlite3ExprIsConstantOrGroupBy(Parse*, Expr*, ExprList*);
SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr*,int);
SQLITE_PRIVATE int sqlite3ExprIsTableConstraint(Expr*,const SrcItem*);
#ifdef SQLITE_ENABLE_CURSOR_HINTS
SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr*);
#endif
@ -20365,7 +20370,12 @@ SQLITE_PRIVATE int sqlite3VtabCallCreate(sqlite3*, int, const char *, char **);
SQLITE_PRIVATE int sqlite3VtabCallConnect(Parse*, Table*);
SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3*, int, const char *);
SQLITE_PRIVATE int sqlite3VtabBegin(sqlite3 *, VTable *);
SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction(sqlite3 *,FuncDef*, int nArg, Expr*);
#if (defined(SQLITE_ENABLE_DBPAGE_VTAB) || defined(SQLITE_TEST)) \
&& !defined(SQLITE_OMIT_VIRTUALTABLE)
SQLITE_PRIVATE void sqlite3VtabWriteAll(sqlite3_index_info*);
#endif
SQLITE_PRIVATE sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context*);
SQLITE_PRIVATE int sqlite3VdbeParameterIndex(Vdbe*, const char*, int);
SQLITE_PRIVATE int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *);
@ -23655,7 +23665,7 @@ static int toLocaltime(
p->D = sLocal.tm_mday;
p->h = sLocal.tm_hour;
p->m = sLocal.tm_min;
p->s = sLocal.tm_sec;
p->s = sLocal.tm_sec + (p->iJD%1000)*0.001;
p->validYMD = 1;
p->validHMS = 1;
p->validJD = 0;
@ -29329,8 +29339,9 @@ SQLITE_PRIVATE char *sqlite3DbSpanDup(sqlite3 *db, const char *zStart, const cha
** Free any prior content in *pz and replace it with a copy of zNew.
*/
SQLITE_PRIVATE void sqlite3SetString(char **pz, sqlite3 *db, const char *zNew){
char *z = sqlite3DbStrDup(db, zNew);
sqlite3DbFree(db, *pz);
*pz = sqlite3DbStrDup(db, zNew);
*pz = z;
}
/*
@ -39683,11 +39694,17 @@ static int unixShmLock(
int flags /* What to do with the lock */
){
unixFile *pDbFd = (unixFile*)fd; /* Connection holding shared memory */
unixShm *p = pDbFd->pShm; /* The shared memory being locked */
unixShmNode *pShmNode = p->pShmNode; /* The underlying file iNode */
unixShm *p; /* The shared memory being locked */
unixShmNode *pShmNode; /* The underlying file iNode */
int rc = SQLITE_OK; /* Result code */
u16 mask; /* Mask of locks to take or release */
int *aLock = pShmNode->aLock;
int *aLock;
p = pDbFd->pShm;
if( p==0 ) return SQLITE_IOERR_SHMLOCK;
pShmNode = p->pShmNode;
if( NEVER(pShmNode==0) ) return SQLITE_IOERR_SHMLOCK;
aLock = pShmNode->aLock;
assert( pShmNode==pDbFd->pInode->pShmNode );
assert( pShmNode->pInode==pDbFd->pInode );
@ -46975,10 +46992,14 @@ static int winShmLock(
winFile *pDbFd = (winFile*)fd; /* Connection holding shared memory */
winShm *p = pDbFd->pShm; /* The shared memory being locked */
winShm *pX; /* For looping over all siblings */
winShmNode *pShmNode = p->pShmNode;
winShmNode *pShmNode;
int rc = SQLITE_OK; /* Result code */
u16 mask; /* Mask of locks to take or release */
if( p==0 ) return SQLITE_IOERR_SHMLOCK;
pShmNode = p->pShmNode;
if( NEVER(pShmNode==0) ) return SQLITE_IOERR_SHMLOCK;
assert( ofst>=0 && ofst+n<=SQLITE_SHM_NLOCK );
assert( n>=1 );
assert( flags==(SQLITE_SHM_LOCK | SQLITE_SHM_SHARED)
@ -50908,7 +50929,8 @@ SQLITE_PRIVATE void sqlite3PcacheDrop(PgHdr *p){
** make it so.
*/
SQLITE_PRIVATE void sqlite3PcacheMakeDirty(PgHdr *p){
assert( p->nRef>0 );
assert( p->nRef>0 || p->pCache->bPurgeable==0 );
testcase( p->nRef==0 );
assert( sqlite3PcachePageSanity(p) );
if( p->flags & (PGHDR_CLEAN|PGHDR_DONT_WRITE) ){ /*OPTIMIZATION-IF-FALSE*/
p->flags &= ~PGHDR_DONT_WRITE;
@ -56040,6 +56062,9 @@ static int pager_playback(Pager *pPager, int isHot){
goto end_playback;
}
pPager->dbSize = mxPg;
if( pPager->mxPgno<mxPg ){
pPager->mxPgno = mxPg;
}
}
/* Copy original pages out of the journal and back into the
@ -65399,7 +65424,9 @@ struct MemPage {
u8 *apOvfl[4]; /* Pointers to the body of overflow cells */
BtShared *pBt; /* Pointer to BtShared that this page is part of */
u8 *aData; /* Pointer to disk image of the page data */
u8 *aDataEnd; /* One byte past the end of usable data */
u8 *aDataEnd; /* One byte past the end of the entire page - not just
** the usable space, the entire page. Used to prevent
** corruption-induced of buffer overflow. */
u8 *aCellIdx; /* The cell index area */
u8 *aDataOfst; /* Same as aData for leaves. aData+4 for interior */
DbPage *pDbPage; /* Pager page handle */
@ -67740,6 +67767,8 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){
** fragmented bytes within the page. */
memcpy(&aData[iAddr], &aData[pc], 2);
aData[hdr+7] += (u8)x;
testcase( pc+x>maxPC );
return &aData[pc];
}else if( x+pc > maxPC ){
/* This slot extends off the end of the usable part of the page */
*pRc = SQLITE_CORRUPT_PAGE(pPg);
@ -68184,7 +68213,7 @@ static int btreeInitPage(MemPage *pPage){
pPage->nOverflow = 0;
pPage->cellOffset = pPage->hdrOffset + 8 + pPage->childPtrSize;
pPage->aCellIdx = data + pPage->childPtrSize + 8;
pPage->aDataEnd = pPage->aData + pBt->usableSize;
pPage->aDataEnd = pPage->aData + pBt->pageSize;
pPage->aDataOfst = pPage->aData + pPage->childPtrSize;
/* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the
** number of cells on the page. */
@ -68219,7 +68248,7 @@ static void zeroPage(MemPage *pPage, int flags){
u8 hdr = pPage->hdrOffset;
u16 first;
assert( sqlite3PagerPagenumber(pPage->pDbPage)==pPage->pgno );
assert( sqlite3PagerPagenumber(pPage->pDbPage)==pPage->pgno || CORRUPT_DB );
assert( sqlite3PagerGetExtra(pPage->pDbPage) == (void*)pPage );
assert( sqlite3PagerGetData(pPage->pDbPage) == data );
assert( sqlite3PagerIswriteable(pPage->pDbPage) );
@ -68235,7 +68264,7 @@ static void zeroPage(MemPage *pPage, int flags){
pPage->nFree = (u16)(pBt->usableSize - first);
decodeFlags(pPage, flags);
pPage->cellOffset = first;
pPage->aDataEnd = &data[pBt->usableSize];
pPage->aDataEnd = &data[pBt->pageSize];
pPage->aCellIdx = &data[first];
pPage->aDataOfst = &data[pPage->childPtrSize];
pPage->nOverflow = 0;
@ -68361,7 +68390,7 @@ static int getAndInitPage(
goto getAndInitPage_error2;
}
}
assert( (*ppPage)->pgno==pgno );
assert( (*ppPage)->pgno==pgno || CORRUPT_DB );
assert( (*ppPage)->aData==sqlite3PagerGetData(pDbPage) );
/* If obtaining a child page for a cursor, we must verify that the page is
@ -71448,7 +71477,7 @@ static int moveToRoot(BtCursor *pCur){
pCur->curIntKey = pCur->pPage->intKey;
}
pRoot = pCur->pPage;
assert( pRoot->pgno==pCur->pgnoRoot );
assert( pRoot->pgno==pCur->pgnoRoot || CORRUPT_DB );
/* If pCur->pKeyInfo is not NULL, then the caller that opened this cursor
** expected to open it on an index b-tree. Otherwise, if pKeyInfo is
@ -71939,7 +71968,7 @@ SQLITE_PRIVATE int sqlite3BtreeIndexMoveto(
assert( lwr==upr+1 || (pPage->intKey && !pPage->leaf) );
assert( pPage->isInit );
if( pPage->leaf ){
assert( pCur->ix<pCur->pPage->nCell );
assert( pCur->ix<pCur->pPage->nCell || CORRUPT_DB );
pCur->ix = (u16)idx;
*pRes = c;
rc = SQLITE_OK;
@ -74463,7 +74492,7 @@ static int balance_nonroot(
iOvflSpace += sz;
assert( sz<=pBt->maxLocal+23 );
assert( iOvflSpace <= (int)pBt->pageSize );
for(k=0; b.ixNx[k]<=i && ALWAYS(k<NB*2); k++){}
for(k=0; b.ixNx[k]<=j && ALWAYS(k<NB*2); k++){}
pSrcEnd = b.apEnd[k];
if( SQLITE_WITHIN(pSrcEnd, pCell, pCell+sz) ){
rc = SQLITE_CORRUPT_BKPT;
@ -74979,24 +75008,6 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
assert( (flags & (BTREE_SAVEPOSITION|BTREE_APPEND|BTREE_PREFORMAT))==flags );
assert( (flags & BTREE_PREFORMAT)==0 || seekResult || pCur->pKeyInfo==0 );
if( pCur->eState==CURSOR_FAULT ){
assert( pCur->skipNext!=SQLITE_OK );
return pCur->skipNext;
}
assert( cursorOwnsBtShared(pCur) );
assert( (pCur->curFlags & BTCF_WriteFlag)!=0
&& pBt->inTransaction==TRANS_WRITE
&& (pBt->btsFlags & BTS_READ_ONLY)==0 );
assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) );
/* Assert that the caller has been consistent. If this cursor was opened
** expecting an index b-tree, then the caller should be inserting blob
** keys with no associated data. If the cursor was opened expecting an
** intkey table, the caller should be inserting integer keys with a
** blob of associated data. */
assert( (flags & BTREE_PREFORMAT) || (pX->pKey==0)==(pCur->pKeyInfo==0) );
/* Save the positions of any other cursors open on this table.
**
** In some cases, the call to btreeMoveto() below is a no-op. For
@ -75021,6 +75032,24 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
}
}
if( pCur->eState>=CURSOR_REQUIRESEEK ){
rc = moveToRoot(pCur);
if( rc && rc!=SQLITE_EMPTY ) return rc;
}
assert( cursorOwnsBtShared(pCur) );
assert( (pCur->curFlags & BTCF_WriteFlag)!=0
&& pBt->inTransaction==TRANS_WRITE
&& (pBt->btsFlags & BTS_READ_ONLY)==0 );
assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) );
/* Assert that the caller has been consistent. If this cursor was opened
** expecting an index b-tree, then the caller should be inserting blob
** keys with no associated data. If the cursor was opened expecting an
** intkey table, the caller should be inserting integer keys with a
** blob of associated data. */
assert( (flags & BTREE_PREFORMAT) || (pX->pKey==0)==(pCur->pKeyInfo==0) );
if( pCur->pKeyInfo==0 ){
assert( pX->pKey==0 );
/* If this is an insert into a table b-tree, invalidate any incrblob
@ -75109,14 +75138,13 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
}
}
assert( pCur->eState==CURSOR_VALID
|| (pCur->eState==CURSOR_INVALID && loc)
|| CORRUPT_DB );
|| (pCur->eState==CURSOR_INVALID && loc) );
pPage = pCur->pPage;
assert( pPage->intKey || pX->nKey>=0 || (flags & BTREE_PREFORMAT) );
assert( pPage->leaf || !pPage->intKey );
if( pPage->nFree<0 ){
if( NEVER(pCur->eState>CURSOR_INVALID) ){
if( pCur->eState>CURSOR_INVALID ){
rc = SQLITE_CORRUPT_BKPT;
}else{
rc = btreeComputeFreeSpace(pPage);
@ -75397,12 +75425,16 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) );
assert( !hasReadConflicts(p, pCur->pgnoRoot) );
assert( (flags & ~(BTREE_SAVEPOSITION | BTREE_AUXDELETE))==0 );
if( pCur->eState==CURSOR_REQUIRESEEK ){
rc = btreeRestoreCursorPosition(pCur);
assert( rc!=SQLITE_OK || CORRUPT_DB || pCur->eState==CURSOR_VALID );
if( rc || pCur->eState!=CURSOR_VALID ) return rc;
if( pCur->eState!=CURSOR_VALID ){
if( pCur->eState>=CURSOR_REQUIRESEEK ){
rc = btreeRestoreCursorPosition(pCur);
assert( rc!=SQLITE_OK || CORRUPT_DB || pCur->eState==CURSOR_VALID );
if( rc || pCur->eState!=CURSOR_VALID ) return rc;
}else{
return SQLITE_CORRUPT_BKPT;
}
}
assert( CORRUPT_DB || pCur->eState==CURSOR_VALID );
assert( pCur->eState==CURSOR_VALID );
iCellDepth = pCur->iPage;
iCellIdx = pCur->ix;
@ -78025,7 +78057,11 @@ SQLITE_PRIVATE int sqlite3VdbeChangeEncoding(Mem *pMem, int desiredEnc){
assert( !sqlite3VdbeMemIsRowSet(pMem) );
assert( desiredEnc==SQLITE_UTF8 || desiredEnc==SQLITE_UTF16LE
|| desiredEnc==SQLITE_UTF16BE );
if( !(pMem->flags&MEM_Str) || pMem->enc==desiredEnc ){
if( !(pMem->flags&MEM_Str) ){
pMem->enc = desiredEnc;
return SQLITE_OK;
}
if( pMem->enc==desiredEnc ){
return SQLITE_OK;
}
assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
@ -78737,6 +78773,7 @@ SQLITE_PRIVATE void sqlite3VdbeMemSetPointer(
void (*xDestructor)(void*)
){
assert( pMem->flags==MEM_Null );
vdbeMemClear(pMem);
pMem->u.zPType = zPType ? zPType : "";
pMem->z = pPtr;
pMem->flags = MEM_Null|MEM_Dyn|MEM_Subtype|MEM_Term;
@ -88351,6 +88388,8 @@ case OP_Gosub: { /* jump */
/* Most jump operations do a goto to this spot in order to update
** the pOp pointer. */
jump_to_p2:
assert( pOp->p2>0 ); /* There are never any jumps to instruction 0 */
assert( pOp->p2<p->nOp ); /* Jumps must be in range */
pOp = &aOp[pOp->p2 - 1];
break;
}
@ -89991,10 +90030,18 @@ case OP_Offset: { /* out3 */
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
pC = p->apCsr[pOp->p1];
pOut = &p->aMem[pOp->p3];
if( NEVER(pC==0) || pC->eCurType!=CURTYPE_BTREE ){
if( pC==0 || pC->eCurType!=CURTYPE_BTREE ){
sqlite3VdbeMemSetNull(pOut);
}else{
sqlite3VdbeMemSetInt64(pOut, sqlite3BtreeOffset(pC->uc.pCursor));
if( pC->deferredMoveto ){
rc = sqlite3VdbeFinishMoveto(pC);
if( rc ) goto abort_due_to_error;
}
if( sqlite3BtreeEof(pC->uc.pCursor) ){
sqlite3VdbeMemSetNull(pOut);
}else{
sqlite3VdbeMemSetInt64(pOut, sqlite3BtreeOffset(pC->uc.pCursor));
}
}
break;
}
@ -93073,6 +93120,10 @@ case OP_Rowid: { /* out2 */
** Move the cursor P1 to a null row. Any OP_Column operations
** that occur while the cursor is on the null row will always
** write a NULL.
**
** Or, if P1 is a Pseudo-Cursor (a cursor opened using OP_OpenPseudo)
** just reset the cache for that cursor. This causes the row of
** content held by the pseudo-cursor to be reparsed.
*/
case OP_NullRow: {
VdbeCursor *pC;
@ -104719,6 +104770,38 @@ SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr *p, int iCur){
return exprIsConst(p, 3, iCur);
}
/*
** Check pExpr to see if it is an invariant constraint on data source pSrc.
** This is an optimization. False negatives will perhaps cause slower
** queries, but false positives will yield incorrect answers. So when in
** double, return 0.
**
** To be an invariant constraint, the following must be true:
**
** (1) pExpr cannot refer to any table other than pSrc->iCursor.
**
** (2) pExpr cannot use subqueries or non-deterministic functions.
**
** (*) ** Not applicable to this branch **
**
** (4) If pSrc is the right operand of a LEFT JOIN, then...
** (4a) pExpr must come from an ON clause..
** (4b) and specifically the ON clause associated with the LEFT JOIN.
**
** (5) If pSrc is not the right operand of a LEFT JOIN or the left
** operand of a RIGHT JOIN, then pExpr must be from the WHERE
** clause, not an ON clause.
*/
SQLITE_PRIVATE int sqlite3ExprIsTableConstraint(Expr *pExpr, const SrcItem *pSrc){
if( pSrc->fg.jointype & JT_LEFT ){
if( !ExprHasProperty(pExpr, EP_FromJoin) ) return 0; /* rule (4a) */
if( pExpr->w.iRightJoinTable!=pSrc->iCursor ) return 0; /* rule (4b) */
}else{
if( ExprHasProperty(pExpr, EP_FromJoin) ) return 0; /* rule (5) */
}
return sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor); /* rules (1), (2) */
}
/*
** sqlite3WalkExpr() callback used by sqlite3ExprIsConstantOrGroupBy().
@ -109919,19 +110002,21 @@ static int renameParseSql(
){
int rc;
db->init.iDb = bTemp ? 1 : sqlite3FindDbName(db, zDb);
/* Parse the SQL statement passed as the first argument. If no error
** occurs and the parse does not result in a new table, index or
** trigger object, the database must be corrupt. */
sqlite3ParseObjectInit(p, db);
if( zSql==0 ){
return SQLITE_NOMEM;
}
if( sqlite3StrNICmp(zSql,"CREATE ",7)!=0 ){
return SQLITE_CORRUPT_BKPT;
}
db->init.iDb = bTemp ? 1 : sqlite3FindDbName(db, zDb);
p->eParseMode = PARSE_MODE_RENAME;
p->db = db;
p->nQueryLoop = 1;
rc = zSql ? sqlite3RunParser(p, zSql) : SQLITE_NOMEM;
rc = sqlite3RunParser(p, zSql);
if( db->mallocFailed ) rc = SQLITE_NOMEM;
if( rc==SQLITE_OK
&& p->pNewTable==0 && p->pNewIndex==0 && p->pNewTrigger==0
&& NEVER(p->pNewTable==0 && p->pNewIndex==0 && p->pNewTrigger==0)
){
rc = SQLITE_CORRUPT_BKPT;
}
@ -116680,6 +116765,11 @@ SQLITE_PRIVATE void sqlite3EndTable(
int addrInsLoop; /* Top of the loop for inserting rows */
Table *pSelTab; /* A table that describes the SELECT results */
if( IN_SPECIAL_PARSE ){
pParse->rc = SQLITE_ERROR;
pParse->nErr++;
return;
}
regYield = ++pParse->nMem;
regRec = ++pParse->nMem;
regRowid = ++pParse->nMem;
@ -123259,8 +123349,8 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){
INLINE_FUNC(likelihood, 2, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY),
INLINE_FUNC(likely, 1, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY),
#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
FUNCTION2(sqlite_offset, 1, 0, 0, noopFunc, SQLITE_FUNC_OFFSET|
SQLITE_FUNC_TYPEOF),
{1, SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_FUNC_OFFSET|SQLITE_FUNC_TYPEOF,
0, 0, noopFunc, 0, 0, 0, "sqlite_offset", {0} },
#endif
FUNCTION(ltrim, 1, 1, 0, trimFunc ),
FUNCTION(ltrim, 2, 1, 0, trimFunc ),
@ -125064,7 +125154,7 @@ SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){
}
for(i=j=0; i<pTab->nCol; i++){
assert( pTab->aCol[i].affinity!=0 );
assert( pTab->aCol[i].affinity!=0 || sqlite3VdbeParser(v)->nErr>0 );
if( (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){
zColAff[j++] = pTab->aCol[i].affinity;
}
@ -125678,7 +125768,11 @@ SQLITE_PRIVATE void sqlite3Insert(
**
** This is the 2nd template.
*/
if( pColumn==0 && xferOptimization(pParse, pTab, pSelect, onError, iDb) ){
if( pColumn==0
&& pSelect!=0
&& pTrigger==0
&& xferOptimization(pParse, pTab, pSelect, onError, iDb)
){
assert( !pTrigger );
assert( pList==0 );
goto insert_end;
@ -127649,18 +127743,13 @@ static int xferOptimization(
int destHasUniqueIdx = 0; /* True if pDest has a UNIQUE index */
int regData, regRowid; /* Registers holding data and rowid */
if( pSelect==0 ){
return 0; /* Must be of the form INSERT INTO ... SELECT ... */
}
assert( pSelect!=0 );
if( pParse->pWith || pSelect->pWith ){
/* Do not attempt to process this query if there are an WITH clauses
** attached to it. Proceeding may generate a false "no such table: xxx"
** error if pSelect reads from a CTE named "xxx". */
return 0;
}
if( sqlite3TriggerList(pParse, pDest) ){
return 0; /* tab1 must not have triggers */
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
if( IsVirtual(pDest) ){
return 0; /* tab1 must not be a virtual table */
@ -133506,7 +133595,7 @@ SQLITE_PRIVATE int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFl
sqlite3ResetAllSchemasOfConnection(db);
pDb = &db->aDb[iDb];
}else
if( rc==SQLITE_OK || (db->flags&SQLITE_NoSchemaError)){
if( rc==SQLITE_OK || ((db->flags&SQLITE_NoSchemaError) && rc!=SQLITE_NOMEM)){
/* Hack: If the SQLITE_NoSchemaError flag is set, then consider
** the schema loaded, even if errors (other than OOM) occurred. In
** this situation the current sqlite3_prepare() operation will fail,
@ -133780,6 +133869,14 @@ SQLITE_PRIVATE void sqlite3ParseObjectInit(Parse *pParse, sqlite3 *db){
if( db->mallocFailed ) sqlite3ErrorMsg(pParse, "out of memory");
}
/*
** Maximum number of times that we will try again to prepare a statement
** that returns SQLITE_ERROR_RETRY.
*/
#ifndef SQLITE_MAX_PREPARE_RETRY
# define SQLITE_MAX_PREPARE_RETRY 25
#endif
/*
** Compile the UTF-8 encoded SQL statement zSql into a statement handle.
*/
@ -133954,7 +134051,7 @@ static int sqlite3LockAndPrepare(
rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail);
assert( rc==SQLITE_OK || *ppStmt==0 );
if( rc==SQLITE_OK || db->mallocFailed ) break;
}while( rc==SQLITE_ERROR_RETRY
}while( (rc==SQLITE_ERROR_RETRY && (cnt++)<SQLITE_MAX_PREPARE_RETRY)
|| (rc==SQLITE_SCHEMA && (sqlite3ResetOneSchema(db,-1), cnt++)==0) );
sqlite3BtreeLeaveAll(db);
rc = sqlite3ApiExit(db, rc);
@ -138996,8 +139093,7 @@ static int pushDownWhereTerms(
Parse *pParse, /* Parse context (for malloc() and error reporting) */
Select *pSubq, /* The subquery whose WHERE clause is to be augmented */
Expr *pWhere, /* The WHERE clause of the outer query */
int iCursor, /* Cursor number of the subquery */
int isLeftJoin /* True if pSubq is the right term of a LEFT JOIN */
SrcItem *pSrc /* The subquery term of the outer FROM clause */
){
Expr *pNew;
int nChng = 0;
@ -139032,10 +139128,11 @@ static int pushDownWhereTerms(
return 0; /* restriction (3) */
}
while( pWhere->op==TK_AND ){
nChng += pushDownWhereTerms(pParse, pSubq, pWhere->pRight,
iCursor, isLeftJoin);
nChng += pushDownWhereTerms(pParse, pSubq, pWhere->pRight, pSrc);
pWhere = pWhere->pLeft;
}
#if 0 /* Legacy code. Checks now done by sqlite3ExprIsTableConstraint() */
if( isLeftJoin
&& (ExprHasProperty(pWhere,EP_FromJoin)==0
|| pWhere->w.iRightJoinTable!=iCursor)
@ -139047,7 +139144,9 @@ static int pushDownWhereTerms(
){
return 0; /* restriction (5) */
}
if( sqlite3ExprIsTableConstant(pWhere, iCursor) ){
#endif
if( sqlite3ExprIsTableConstraint(pWhere, pSrc) ){
nChng++;
pSubq->selFlags |= SF_PushDown;
while( pSubq ){
@ -139055,8 +139154,8 @@ static int pushDownWhereTerms(
pNew = sqlite3ExprDup(pParse->db, pWhere, 0);
unsetJoinExpr(pNew, -1);
x.pParse = pParse;
x.iTable = iCursor;
x.iNewTable = iCursor;
x.iTable = pSrc->iCursor;
x.iNewTable = pSrc->iCursor;
x.isLeftJoin = 0;
x.pEList = pSubq->pEList;
pNew = substExpr(&x, pNew);
@ -140838,8 +140937,7 @@ SQLITE_PRIVATE int sqlite3Select(
if( OptimizationEnabled(db, SQLITE_PushDown)
&& (pItem->fg.isCte==0
|| (pItem->u2.pCteUse->eM10d!=M10d_Yes && pItem->u2.pCteUse->nUse<2))
&& pushDownWhereTerms(pParse, pSub, p->pWhere, pItem->iCursor,
(pItem->fg.jointype & JT_OUTER)!=0)
&& pushDownWhereTerms(pParse, pSub, p->pWhere, pItem)
){
#if SELECTTRACE_ENABLED
if( sqlite3SelectTrace & 0x100 ){
@ -142373,6 +142471,7 @@ static TriggerStep *triggerStepAllocate(
sqlite3 *db = pParse->db;
TriggerStep *pTriggerStep;
if( pParse->nErr ) return 0;
pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep) + pName->n + 1);
if( pTriggerStep ){
char *z = (char*)&pTriggerStep[1];
@ -146251,6 +146350,7 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
sqlite3ParseObjectInit(&sParse, db);
sParse.eParseMode = PARSE_MODE_DECLARE_VTAB;
sParse.disableTriggers = 1;
/* We should never be able to reach this point while loading the
** schema. Nevertheless, defend against that (turn off db->init.busy)
** in case a bug arises. */
@ -148186,6 +148286,7 @@ static int codeAllEqualityTerms(
VdbeCoverageIf(v, bRev!=0);
VdbeComment((v, "begin skip-scan on %s", pIdx->zName));
j = sqlite3VdbeAddOp0(v, OP_Goto);
assert( pLevel->addrSkip==0 );
pLevel->addrSkip = sqlite3VdbeAddOp4Int(v, (bRev?OP_SeekLT:OP_SeekGT),
iIdxCur, 0, regBase, nSkip);
VdbeCoverageIf(v, bRev==0);
@ -148628,6 +148729,7 @@ static void preserveExpr(IdxExprTrans *pTrans, Expr *pExpr){
static int whereIndexExprTransNode(Walker *p, Expr *pExpr){
IdxExprTrans *pX = p->u.pIdxTrans;
if( sqlite3ExprCompare(0, pExpr, pX->pIdxExpr, pX->iTabCur)==0 ){
pExpr = sqlite3ExprSkipCollate(pExpr);
preserveExpr(pX, pExpr);
pExpr->affExpr = sqlite3ExprAffinity(pExpr);
pExpr->op = TK_COLUMN;
@ -148783,9 +148885,12 @@ static SQLITE_NOINLINE void filterPullDown(
WhereLevel *pLevel = &pWInfo->a[iLevel];
WhereLoop *pLoop = pLevel->pWLoop;
if( pLevel->regFilter==0 ) continue;
if( pLevel->pWLoop->nSkip ) continue;
/* ,--- Because sqlite3ConstructBloomFilter() has will not have set
** vvvvv--' pLevel->regFilter if this were true. */
if( NEVER(pLoop->prereq & notReady) ) continue;
assert( pLevel->addrBrk==0 );
pLevel->addrBrk = addrNxt;
if( pLoop->wsFlags & WHERE_IPK ){
WhereTerm *pTerm = pLoop->aLTerm[0];
int regRowid;
@ -148812,6 +148917,7 @@ static SQLITE_NOINLINE void filterPullDown(
VdbeCoverage(pParse->pVdbe);
}
pLevel->regFilter = 0;
pLevel->addrBrk = 0;
}
}
@ -148917,7 +149023,6 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
int iReg; /* P3 Value for OP_VFilter */
int addrNotFound;
int nConstraint = pLoop->nLTerm;
int iIn; /* Counter for IN constraints */
iReg = sqlite3GetTempRange(pParse, nConstraint+2);
addrNotFound = pLevel->addrBrk;
@ -148963,50 +149068,54 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
pLevel->op = pWInfo->eOnePass ? OP_Noop : OP_VNext;
pLevel->p2 = sqlite3VdbeCurrentAddr(v);
assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 );
if( pLoop->wsFlags & WHERE_IN_ABLE ){
iIn = pLevel->u.in.nIn;
}else{
iIn = 0;
}
for(j=nConstraint-1; j>=0; j--){
int bIn; /* True to generate byte code to loop over RHS IN values */
for(j=0; j<nConstraint; j++){
pTerm = pLoop->aLTerm[j];
if( j<16 && (pLoop->u.vtab.omitMask>>j)&1 ){
disableTerm(pLevel, pTerm);
continue;
}
if( (pTerm->eOperator & WO_IN)!=0
&& (SMASKBIT32(j) & pLoop->u.vtab.mHandleIn)==0
&& !db->mallocFailed
){
bIn = 1;
}else{
bIn = 0;
}
if( bIn ) iIn--;
if( j<16 && (pLoop->u.vtab.omitMask>>j)&1 ){
disableTerm(pLevel, pTerm);
}else if( bIn && sqlite3ExprVectorSize(pTerm->pExpr->pLeft)==1 ){
Expr *pCompare; /* The comparison operator */
Expr *pRight; /* RHS of the comparison */
VdbeOp *pOp; /* Opcode to access the value of the IN constraint */
int iIn; /* IN loop corresponding to the j-th constraint */
/* Reload the constraint value into reg[iReg+j+2]. The same value
** was loaded into the same register prior to the OP_VFilter, but
** the xFilter implementation might have changed the datatype or
** encoding of the value in the register, so it *must* be reloaded. */
assert( pLevel->u.in.aInLoop!=0 || db->mallocFailed );
if( !db->mallocFailed ){
assert( iIn>=0 && iIn<pLevel->u.in.nIn );
** encoding of the value in the register, so it *must* be reloaded.
*/
for(iIn=0; ALWAYS(iIn<pLevel->u.in.nIn); iIn++){
pOp = sqlite3VdbeGetOp(v, pLevel->u.in.aInLoop[iIn].addrInTop);
assert( pOp->opcode==OP_Column || pOp->opcode==OP_Rowid );
assert( pOp->opcode!=OP_Column || pOp->p3==iReg+j+2 );
assert( pOp->opcode!=OP_Rowid || pOp->p2==iReg+j+2 );
testcase( pOp->opcode==OP_Rowid );
sqlite3VdbeAddOp3(v, pOp->opcode, pOp->p1, pOp->p2, pOp->p3);
if( (pOp->opcode==OP_Column && pOp->p3==iReg+j+2)
|| (pOp->opcode==OP_Rowid && pOp->p2==iReg+j+2)
){
testcase( pOp->opcode==OP_Rowid );
sqlite3VdbeAddOp3(v, pOp->opcode, pOp->p1, pOp->p2, pOp->p3);
break;
}
}
/* Generate code that will continue to the next row if
** the IN constraint is not satisfied */
** the IN constraint is not satisfied
*/
pCompare = sqlite3PExpr(pParse, TK_EQ, 0, 0);
assert( pCompare!=0 || db->mallocFailed );
if( pCompare ){
pCompare->pLeft = pTerm->pExpr->pLeft;
if( !db->mallocFailed ){
int iFld = pTerm->u.x.iField;
Expr *pLeft = pTerm->pExpr->pLeft;
assert( pLeft!=0 );
if( iFld>0 ){
assert( pLeft->op==TK_VECTOR );
assert( ExprUseXList(pLeft) );
assert( iFld<=pLeft->x.pList->nExpr );
pCompare->pLeft = pLeft->x.pList->a[iFld-1].pExpr;
}else{
pCompare->pLeft = pLeft;
}
pCompare->pRight = pRight = sqlite3Expr(db, TK_REGISTER, 0);
if( pRight ){
pRight->iTable = iReg+j+2;
@ -149015,11 +149124,11 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
);
}
pCompare->pLeft = 0;
sqlite3ExprDelete(db, pCompare);
}
sqlite3ExprDelete(db, pCompare);
}
}
assert( iIn==0 || db->mallocFailed );
/* These registers need to be preserved in case there is an IN operator
** loop. So we could deallocate the registers here (and potentially
** reuse them later) if (pLoop->wsFlags & WHERE_IN_ABLE)==0. But it seems
@ -149729,6 +149838,14 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
** the initialization of the right-hand operand of the vector comparison
** might not occur, or might occur only in an OR branch that is not
** taken. dbsqlfuzz 80a9fade844b4fb43564efc972bcb2c68270f5d1.
**
** 2022-03-03: Do not push down expressions that involve subqueries.
** The subquery might get coded as a subroutine. Any table-references
** in the subquery might be resolved to index-references for the index on
** the OR branch in which the subroutine is coded. But if the subroutine
** is invoked from a different OR branch that uses a different index, such
** index-references will not work. tag-20220303a
** https://sqlite.org/forum/forumpost/36937b197273d403
*/
if( pWC->nTerm>1 ){
int iTerm;
@ -149742,7 +149859,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
continue;
}
if( (pWC->a[iTerm].eOperator & WO_ALL)==0 ) continue;
testcase( pWC->a[iTerm].wtFlags & TERM_ORINFO );
if( ExprHasProperty(pExpr, EP_Subquery) ) continue; /* tag-20220303a */
pExpr = sqlite3ExprDup(db, pExpr, 0);
pAndExpr = sqlite3ExprAnd(pParse, pAndExpr, pExpr);
}
@ -152749,8 +152866,7 @@ static SQLITE_NOINLINE void constructAutomaticIndex(
** WHERE clause (or the ON clause of a LEFT join) that constrain which
** rows of the target table (pSrc) that can be used. */
if( (pTerm->wtFlags & TERM_VIRTUAL)==0
&& ((pSrc->fg.jointype&JT_LEFT)==0 || ExprHasProperty(pExpr,EP_FromJoin))
&& sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor)
&& sqlite3ExprIsTableConstraint(pExpr, pSrc)
){
pPartial = sqlite3ExprAnd(pParse, pPartial,
sqlite3ExprDup(pParse->db, pExpr, 0));
@ -152989,7 +153105,7 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter(
for(pTerm=pWInfo->sWC.a; pTerm<pWCEnd; pTerm++){
Expr *pExpr = pTerm->pExpr;
if( (pTerm->wtFlags & TERM_VIRTUAL)==0
&& sqlite3ExprIsTableConstant(pExpr, iCur)
&& sqlite3ExprIsTableConstraint(pExpr, pItem)
){
sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL);
}
@ -153019,7 +153135,10 @@ static SQLITE_NOINLINE void sqlite3ConstructBloomFilter(
pLoop->wsFlags &= ~WHERE_BLOOMFILTER;
if( OptimizationDisabled(pParse->db, SQLITE_BloomPulldown) ) break;
while( ++iLevel < pWInfo->nLevel ){
const SrcItem *pTabItem;
pLevel = &pWInfo->a[iLevel];
pTabItem = &pWInfo->pTabList->a[pLevel->iFrom];
if( pTabItem->fg.jointype & JT_LEFT ) continue;
pLoop = pLevel->pWLoop;
if( NEVER(pLoop==0) ) continue;
if( pLoop->prereq & notReady ) continue;
@ -154532,8 +154651,17 @@ static void whereLoopOutputAdjust(
/* If there are extra terms in the WHERE clause not used by an index
** that depend only on the table being scanned, and that will tend to
** cause many rows to be omitted, then mark that table as
** "self-culling". */
pLoop->wsFlags |= WHERE_SELFCULL;
** "self-culling".
**
** 2022-03-24: Self-culling only applies if either the extra terms
** are straight comparison operators that are non-true with NULL
** operand, or if the loop is not a LEFT JOIN.
*/
if( (pTerm->eOperator & 0x3f)!=0
|| (pWC->pWInfo->pTabList->a[pLoop->iTab].fg.jointype & JT_LEFT)==0
){
pLoop->wsFlags |= WHERE_SELFCULL;
}
}
if( pTerm->truthProb<=0 ){
/* If a truth probability is specified using the likelihood() hints,
@ -155702,7 +155830,6 @@ SQLITE_API int sqlite3_vtab_rhs_value(
return rc;
}
/*
** Return true if ORDER BY clause may be handled as DISTINCT.
*/
@ -155714,6 +155841,22 @@ SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info *pIdxInfo){
return pHidden->eDistinct;
}
#if (defined(SQLITE_ENABLE_DBPAGE_VTAB) || defined(SQLITE_TEST)) \
&& !defined(SQLITE_OMIT_VIRTUALTABLE)
/*
** Cause the prepared statement that is associated with a call to
** xBestIndex to open write transactions on all attached schemas.
** This is used by the (built-in) sqlite_dbpage virtual table.
*/
SQLITE_PRIVATE void sqlite3VtabWriteAll(sqlite3_index_info *pIdxInfo){
HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1];
Parse *pParse = pHidden->pParse;
int nDb = pParse->db->nDb;
int i;
for(i=0; i<nDb; i++) sqlite3BeginWriteOperation(pParse, 0, i);
}
#endif
/*
** Add all WhereLoop objects for a table of the join identified by
** pBuilder->pNew->iTab. That table is guaranteed to be a virtual table.
@ -157817,6 +157960,26 @@ whereBeginError:
}
#endif
#ifdef SQLITE_DEBUG
/*
** Return true if cursor iCur is opened by instruction k of the
** bytecode. Used inside of assert() only.
*/
static int cursorIsOpen(Vdbe *v, int iCur, int k){
while( k>=0 ){
VdbeOp *pOp = sqlite3VdbeGetOp(v,k--);
if( pOp->p1!=iCur ) continue;
if( pOp->opcode==OP_Close ) return 0;
if( pOp->opcode==OP_OpenRead ) return 1;
if( pOp->opcode==OP_OpenWrite ) return 1;
if( pOp->opcode==OP_OpenDup ) return 1;
if( pOp->opcode==OP_OpenAutoindex ) return 1;
if( pOp->opcode==OP_OpenEphemeral ) return 1;
}
return 0;
}
#endif /* SQLITE_DEBUG */
/*
** Generate the end of the WHERE loop. See comments on
** sqlite3WhereBegin() for additional information.
@ -158069,6 +158232,11 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
){
int x = pOp->p2;
assert( pIdx->pTable==pTab );
#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
if( pOp->opcode==OP_Offset ){
/* Do not need to translate the column number */
}else
#endif
if( !HasRowid(pTab) ){
Index *pPk = sqlite3PrimaryKeyIndex(pTab);
x = pPk->aiColumn[x];
@ -158082,9 +158250,22 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
pOp->p2 = x;
pOp->p1 = pLevel->iIdxCur;
OpcodeRewriteTrace(db, k, pOp);
}else{
/* Unable to translate the table reference into an index
** reference. Verify that this is harmless - that the
** table being referenced really is open.
*/
#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0
|| cursorIsOpen(v,pOp->p1,k)
|| pOp->opcode==OP_Offset
);
#else
assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0
|| cursorIsOpen(v,pOp->p1,k)
);
#endif
}
assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 || x>=0
|| pWInfo->eOnePass );
}else if( pOp->opcode==OP_Rowid ){
pOp->p1 = pLevel->iIdxCur;
pOp->opcode = OP_IdxRowid;
@ -159072,7 +159253,11 @@ static int disallowAggregatesInOrderByCb(Walker *pWalker, Expr *pExpr){
*/
SQLITE_PRIVATE int sqlite3WindowRewrite(Parse *pParse, Select *p){
int rc = SQLITE_OK;
if( p->pWin && p->pPrior==0 && ALWAYS((p->selFlags & SF_WinRewrite)==0) ){
if( p->pWin
&& p->pPrior==0
&& ALWAYS((p->selFlags & SF_WinRewrite)==0)
&& ALWAYS(!IN_RENAME_OBJECT)
){
Vdbe *v = sqlite3GetVdbe(pParse);
sqlite3 *db = pParse->db;
Select *pSub = 0; /* The subquery */
@ -159147,6 +159332,7 @@ SQLITE_PRIVATE int sqlite3WindowRewrite(Parse *pParse, Select *p){
for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
ExprList *pArgs;
assert( ExprUseXList(pWin->pOwner) );
assert( pWin->pFunc!=0 );
pArgs = pWin->pOwner->x.pList;
if( pWin->pFunc->funcFlags & SQLITE_FUNC_SUBTYPE ){
selectWindowRewriteEList(pParse, pMWin, pSrc, pArgs, pTab, &pSublist);
@ -159839,7 +160025,7 @@ static void windowAggStep(
for(iEnd=sqlite3VdbeCurrentAddr(v); iOp<iEnd; iOp++){
VdbeOp *pOp = sqlite3VdbeGetOp(v, iOp);
if( pOp->opcode==OP_Column && pOp->p1==pWin->iEphCsr ){
if( pOp->opcode==OP_Column && pOp->p1==pMWin->iEphCsr ){
pOp->p1 = csr;
}
}
@ -194157,14 +194343,15 @@ static JsonNode *jsonLookupStep(
*pzErr = zPath;
return 0;
}
testcase( nKey==0 );
}else{
zKey = zPath;
for(i=0; zPath[i] && zPath[i]!='.' && zPath[i]!='['; i++){}
nKey = i;
}
if( nKey==0 ){
*pzErr = zPath;
return 0;
if( nKey==0 ){
*pzErr = zPath;
return 0;
}
}
j = 1;
for(;;){
@ -195312,6 +195499,33 @@ static int jsonEachNext(sqlite3_vtab_cursor *cur){
return SQLITE_OK;
}
/* Append an object label to the JSON Path being constructed
** in pStr.
*/
static void jsonAppendObjectPathElement(
JsonString *pStr,
JsonNode *pNode
){
int jj, nn;
const char *z;
assert( pNode->eType==JSON_STRING );
assert( pNode->jnFlags & JNODE_LABEL );
assert( pNode->eU==1 );
z = pNode->u.zJContent;
nn = pNode->n;
assert( nn>=2 );
assert( z[0]=='"' );
assert( z[nn-1]=='"' );
if( nn>2 && sqlite3Isalpha(z[1]) ){
for(jj=2; jj<nn-1 && sqlite3Isalnum(z[jj]); jj++){}
if( jj==nn-1 ){
z++;
nn -= 2;
}
}
jsonPrintf(nn+2, pStr, ".%.*s", nn, z);
}
/* Append the name of the path for element i to pStr
*/
static void jsonEachComputePath(
@ -195336,10 +195550,7 @@ static void jsonEachComputePath(
}else{
assert( pUp->eType==JSON_OBJECT );
if( (pNode->jnFlags & JNODE_LABEL)==0 ) pNode--;
assert( pNode->eType==JSON_STRING );
assert( pNode->jnFlags & JNODE_LABEL );
assert( pNode->eU==1 );
jsonPrintf(pNode->n+1, pStr, ".%.*s", pNode->n-2, pNode->u.zJContent+1);
jsonAppendObjectPathElement(pStr, pNode);
}
}
@ -195410,8 +195621,7 @@ static int jsonEachColumn(
if( p->eType==JSON_ARRAY ){
jsonPrintf(30, &x, "[%d]", p->iRowid);
}else if( p->eType==JSON_OBJECT ){
assert( pThis->eU==1 );
jsonPrintf(pThis->n, &x, ".%.*s", pThis->n-2, pThis->u.zJContent+1);
jsonAppendObjectPathElement(&x, pThis);
}
}
jsonResult(&x);
@ -209958,6 +210168,7 @@ static int dbpageBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
){
pIdxInfo->orderByConsumed = 1;
}
sqlite3VtabWriteAll(pIdxInfo);
return SQLITE_OK;
}
@ -232312,7 +232523,7 @@ static int fts5SorterNext(Fts5Cursor *pCsr){
rc = sqlite3_step(pSorter->pStmt);
if( rc==SQLITE_DONE ){
rc = SQLITE_OK;
CsrFlagSet(pCsr, FTS5CSR_EOF);
CsrFlagSet(pCsr, FTS5CSR_EOF|FTS5CSR_REQUIRE_CONTENT);
}else if( rc==SQLITE_ROW ){
const u8 *a;
const u8 *aBlob;
@ -234301,7 +234512,7 @@ static void fts5SourceIdFunc(
){
assert( nArg==0 );
UNUSED_PARAM2(nArg, apUnused);
sqlite3_result_text(pCtx, "fts5: 2022-02-22 18:58:40 40fa792d359f84c3b9e9d6623743e1a59826274e221df1bde8f47086968a1bab", -1, SQLITE_TRANSIENT);
sqlite3_result_text(pCtx, "fts5: 2022-05-06 15:25:27 78d9c993d404cdfaa7fdd2973fa1052e3da9f66215cff9c5540ebe55c407d9fe", -1, SQLITE_TRANSIENT);
}
/*

@ -147,9 +147,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.38.0"
#define SQLITE_VERSION_NUMBER 3038000
#define SQLITE_SOURCE_ID "2022-02-22 18:58:40 40fa792d359f84c3b9e9d6623743e1a59826274e221df1bde8f47086968a1bab"
#define SQLITE_VERSION "3.38.5"
#define SQLITE_VERSION_NUMBER 3038005
#define SQLITE_SOURCE_ID "2022-05-06 15:25:27 78d9c993d404cdfaa7fdd2973fa1052e3da9f66215cff9c5540ebe55c407d9fe"
/*
** CAPI3REF: Run-Time Library Version Numbers
@ -4980,6 +4980,10 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt);
** even empty strings, are always zero-terminated. ^The return
** value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer.
**
** ^Strings returned by sqlite3_column_text16() always have the endianness
** which is native to the platform, regardless of the text encoding set
** for the database.
**
** <b>Warning:</b> ^The object returned by [sqlite3_column_value()] is an
** [unprotected sqlite3_value] object. In a multithreaded environment,
** an unprotected sqlite3_value object may only be used safely with
@ -4993,7 +4997,7 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt);
** [application-defined SQL functions] or [virtual tables], not within
** top-level application code.
**
** The these routines may attempt to convert the datatype of the result.
** These routines may attempt to convert the datatype of the result.
** ^For example, if the internal representation is FLOAT and a text result
** is requested, [sqlite3_snprintf()] is used internally to perform the
** conversion automatically. ^(The following table details the conversions
@ -5018,7 +5022,7 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt);
** <tr><td> TEXT <td> BLOB <td> No change
** <tr><td> BLOB <td> INTEGER <td> [CAST] to INTEGER
** <tr><td> BLOB <td> FLOAT <td> [CAST] to REAL
** <tr><td> BLOB <td> TEXT <td> Add a zero terminator if needed
** <tr><td> BLOB <td> TEXT <td> [CAST] to TEXT, ensure zero terminator
** </table>
** </blockquote>)^
**
@ -9768,7 +9772,7 @@ SQLITE_API int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut);
** ^When xBestIndex returns, the sqlite3_value object returned by
** sqlite3_vtab_rhs_value() is automatically deallocated.
**
** The "_rhs_" in the name of this routine is an appreviation for
** The "_rhs_" in the name of this routine is an abbreviation for
** "Right-Hand Side".
*/
SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal);

@ -1,13 +0,0 @@
// Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// +build sqlite_json sqlite_json1 json1
package sqlite3
/*
#cgo CFLAGS: -DSQLITE_ENABLE_JSON1
*/
import "C"

@ -33,7 +33,7 @@ import (
// The callback is passed a SQLitePreUpdateData struct with the data for
// the update, as well as methods for fetching copies of impacted data.
//
// If there is an existing update hook for this connection, it will be
// If there is an existing preupdate hook for this connection, it will be
// removed. If callback is nil the existing hook (if any) will be removed
// without creating a new one.
func (c *SQLiteConn) RegisterPreUpdateHook(callback func(SQLitePreUpdateData)) {

@ -13,7 +13,7 @@ package sqlite3
// The callback is passed a SQLitePreUpdateData struct with the data for
// the update, as well as methods for fetching copies of impacted data.
//
// If there is an existing update hook for this connection, it will be
// If there is an existing preupdate hook for this connection, it will be
// removed. If callback is nil the existing hook (if any) will be removed
// without creating a new one.
func (c *SQLiteConn) RegisterPreUpdateHook(callback func(SQLitePreUpdateData)) {

@ -0,0 +1,27 @@
Copyright (c) 2013, Patrick Mezard
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -0,0 +1,772 @@
// Package difflib is a partial port of Python difflib module.
//
// It provides tools to compare sequences of strings and generate textual diffs.
//
// The following class and functions have been ported:
//
// - SequenceMatcher
//
// - unified_diff
//
// - context_diff
//
// Getting unified diffs was the main goal of the port. Keep in mind this code
// is mostly suitable to output text differences in a human friendly way, there
// are no guarantees generated diffs are consumable by patch(1).
package difflib
import (
"bufio"
"bytes"
"fmt"
"io"
"strings"
)
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func calculateRatio(matches, length int) float64 {
if length > 0 {
return 2.0 * float64(matches) / float64(length)
}
return 1.0
}
type Match struct {
A int
B int
Size int
}
type OpCode struct {
Tag byte
I1 int
I2 int
J1 int
J2 int
}
// SequenceMatcher compares sequence of strings. The basic
// algorithm predates, and is a little fancier than, an algorithm
// published in the late 1980's by Ratcliff and Obershelp under the
// hyperbolic name "gestalt pattern matching". The basic idea is to find
// the longest contiguous matching subsequence that contains no "junk"
// elements (R-O doesn't address junk). The same idea is then applied
// recursively to the pieces of the sequences to the left and to the right
// of the matching subsequence. This does not yield minimal edit
// sequences, but does tend to yield matches that "look right" to people.
//
// SequenceMatcher tries to compute a "human-friendly diff" between two
// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the
// longest *contiguous* & junk-free matching subsequence. That's what
// catches peoples' eyes. The Windows(tm) windiff has another interesting
// notion, pairing up elements that appear uniquely in each sequence.
// That, and the method here, appear to yield more intuitive difference
// reports than does diff. This method appears to be the least vulnerable
// to synching up on blocks of "junk lines", though (like blank lines in
// ordinary text files, or maybe "<P>" lines in HTML files). That may be
// because this is the only method of the 3 that has a *concept* of
// "junk" <wink>.
//
// Timing: Basic R-O is cubic time worst case and quadratic time expected
// case. SequenceMatcher is quadratic time for the worst case and has
// expected-case behavior dependent in a complicated way on how many
// elements the sequences have in common; best case time is linear.
type SequenceMatcher struct {
a []string
b []string
b2j map[string][]int
IsJunk func(string) bool
autoJunk bool
bJunk map[string]struct{}
matchingBlocks []Match
fullBCount map[string]int
bPopular map[string]struct{}
opCodes []OpCode
}
func NewMatcher(a, b []string) *SequenceMatcher {
m := SequenceMatcher{autoJunk: true}
m.SetSeqs(a, b)
return &m
}
func NewMatcherWithJunk(a, b []string, autoJunk bool,
isJunk func(string) bool) *SequenceMatcher {
m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk}
m.SetSeqs(a, b)
return &m
}
// Set two sequences to be compared.
func (m *SequenceMatcher) SetSeqs(a, b []string) {
m.SetSeq1(a)
m.SetSeq2(b)
}
// Set the first sequence to be compared. The second sequence to be compared is
// not changed.
//
// SequenceMatcher computes and caches detailed information about the second
// sequence, so if you want to compare one sequence S against many sequences,
// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other
// sequences.
//
// See also SetSeqs() and SetSeq2().
func (m *SequenceMatcher) SetSeq1(a []string) {
if &a == &m.a {
return
}
m.a = a
m.matchingBlocks = nil
m.opCodes = nil
}
// Set the second sequence to be compared. The first sequence to be compared is
// not changed.
func (m *SequenceMatcher) SetSeq2(b []string) {
if &b == &m.b {
return
}
m.b = b
m.matchingBlocks = nil
m.opCodes = nil
m.fullBCount = nil
m.chainB()
}
func (m *SequenceMatcher) chainB() {
// Populate line -> index mapping
b2j := map[string][]int{}
for i, s := range m.b {
indices := b2j[s]
indices = append(indices, i)
b2j[s] = indices
}
// Purge junk elements
m.bJunk = map[string]struct{}{}
if m.IsJunk != nil {
junk := m.bJunk
for s, _ := range b2j {
if m.IsJunk(s) {
junk[s] = struct{}{}
}
}
for s, _ := range junk {
delete(b2j, s)
}
}
// Purge remaining popular elements
popular := map[string]struct{}{}
n := len(m.b)
if m.autoJunk && n >= 200 {
ntest := n/100 + 1
for s, indices := range b2j {
if len(indices) > ntest {
popular[s] = struct{}{}
}
}
for s, _ := range popular {
delete(b2j, s)
}
}
m.bPopular = popular
m.b2j = b2j
}
func (m *SequenceMatcher) isBJunk(s string) bool {
_, ok := m.bJunk[s]
return ok
}
// Find longest matching block in a[alo:ahi] and b[blo:bhi].
//
// If IsJunk is not defined:
//
// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
// alo <= i <= i+k <= ahi
// blo <= j <= j+k <= bhi
// and for all (i',j',k') meeting those conditions,
// k >= k'
// i <= i'
// and if i == i', j <= j'
//
// In other words, of all maximal matching blocks, return one that
// starts earliest in a, and of all those maximal matching blocks that
// start earliest in a, return the one that starts earliest in b.
//
// If IsJunk is defined, first the longest matching block is
// determined as above, but with the additional restriction that no
// junk element appears in the block. Then that block is extended as
// far as possible by matching (only) junk elements on both sides. So
// the resulting block never matches on junk except as identical junk
// happens to be adjacent to an "interesting" match.
//
// If no blocks match, return (alo, blo, 0).
func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match {
// CAUTION: stripping common prefix or suffix would be incorrect.
// E.g.,
// ab
// acab
// Longest matching block is "ab", but if common prefix is
// stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
// strip, so ends up claiming that ab is changed to acab by
// inserting "ca" in the middle. That's minimal but unintuitive:
// "it's obvious" that someone inserted "ac" at the front.
// Windiff ends up at the same place as diff, but by pairing up
// the unique 'b's and then matching the first two 'a's.
besti, bestj, bestsize := alo, blo, 0
// find longest junk-free match
// during an iteration of the loop, j2len[j] = length of longest
// junk-free match ending with a[i-1] and b[j]
j2len := map[int]int{}
for i := alo; i != ahi; i++ {
// look at all instances of a[i] in b; note that because
// b2j has no junk keys, the loop is skipped if a[i] is junk
newj2len := map[int]int{}
for _, j := range m.b2j[m.a[i]] {
// a[i] matches b[j]
if j < blo {
continue
}
if j >= bhi {
break
}
k := j2len[j-1] + 1
newj2len[j] = k
if k > bestsize {
besti, bestj, bestsize = i-k+1, j-k+1, k
}
}
j2len = newj2len
}
// Extend the best by non-junk elements on each end. In particular,
// "popular" non-junk elements aren't in b2j, which greatly speeds
// the inner loop above, but also means "the best" match so far
// doesn't contain any junk *or* popular non-junk elements.
for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) &&
m.a[besti-1] == m.b[bestj-1] {
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
}
for besti+bestsize < ahi && bestj+bestsize < bhi &&
!m.isBJunk(m.b[bestj+bestsize]) &&
m.a[besti+bestsize] == m.b[bestj+bestsize] {
bestsize += 1
}
// Now that we have a wholly interesting match (albeit possibly
// empty!), we may as well suck up the matching junk on each
// side of it too. Can't think of a good reason not to, and it
// saves post-processing the (possibly considerable) expense of
// figuring out what to do with it. In the case of an empty
// interesting match, this is clearly the right thing to do,
// because no other kind of match is possible in the regions.
for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) &&
m.a[besti-1] == m.b[bestj-1] {
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
}
for besti+bestsize < ahi && bestj+bestsize < bhi &&
m.isBJunk(m.b[bestj+bestsize]) &&
m.a[besti+bestsize] == m.b[bestj+bestsize] {
bestsize += 1
}
return Match{A: besti, B: bestj, Size: bestsize}
}
// Return list of triples describing matching subsequences.
//
// Each triple is of the form (i, j, n), and means that
// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are
// adjacent triples in the list, and the second is not the last triple in the
// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe
// adjacent equal blocks.
//
// The last triple is a dummy, (len(a), len(b), 0), and is the only
// triple with n==0.
func (m *SequenceMatcher) GetMatchingBlocks() []Match {
if m.matchingBlocks != nil {
return m.matchingBlocks
}
var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match
matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match {
match := m.findLongestMatch(alo, ahi, blo, bhi)
i, j, k := match.A, match.B, match.Size
if match.Size > 0 {
if alo < i && blo < j {
matched = matchBlocks(alo, i, blo, j, matched)
}
matched = append(matched, match)
if i+k < ahi && j+k < bhi {
matched = matchBlocks(i+k, ahi, j+k, bhi, matched)
}
}
return matched
}
matched := matchBlocks(0, len(m.a), 0, len(m.b), nil)
// It's possible that we have adjacent equal blocks in the
// matching_blocks list now.
nonAdjacent := []Match{}
i1, j1, k1 := 0, 0, 0
for _, b := range matched {
// Is this block adjacent to i1, j1, k1?
i2, j2, k2 := b.A, b.B, b.Size
if i1+k1 == i2 && j1+k1 == j2 {
// Yes, so collapse them -- this just increases the length of
// the first block by the length of the second, and the first
// block so lengthened remains the block to compare against.
k1 += k2
} else {
// Not adjacent. Remember the first block (k1==0 means it's
// the dummy we started with), and make the second block the
// new block to compare against.
if k1 > 0 {
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
}
i1, j1, k1 = i2, j2, k2
}
}
if k1 > 0 {
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
}
nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0})
m.matchingBlocks = nonAdjacent
return m.matchingBlocks
}
// Return list of 5-tuples describing how to turn a into b.
//
// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
// tuple preceding it, and likewise for j1 == the previous j2.
//
// The tags are characters, with these meanings:
//
// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2]
//
// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case.
//
// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case.
//
// 'e' (equal): a[i1:i2] == b[j1:j2]
func (m *SequenceMatcher) GetOpCodes() []OpCode {
if m.opCodes != nil {
return m.opCodes
}
i, j := 0, 0
matching := m.GetMatchingBlocks()
opCodes := make([]OpCode, 0, len(matching))
for _, m := range matching {
// invariant: we've pumped out correct diffs to change
// a[:i] into b[:j], and the next matching block is
// a[ai:ai+size] == b[bj:bj+size]. So we need to pump
// out a diff to change a[i:ai] into b[j:bj], pump out
// the matching block, and move (i,j) beyond the match
ai, bj, size := m.A, m.B, m.Size
tag := byte(0)
if i < ai && j < bj {
tag = 'r'
} else if i < ai {
tag = 'd'
} else if j < bj {
tag = 'i'
}
if tag > 0 {
opCodes = append(opCodes, OpCode{tag, i, ai, j, bj})
}
i, j = ai+size, bj+size
// the list of matching blocks is terminated by a
// sentinel with size 0
if size > 0 {
opCodes = append(opCodes, OpCode{'e', ai, i, bj, j})
}
}
m.opCodes = opCodes
return m.opCodes
}
// Isolate change clusters by eliminating ranges with no changes.
//
// Return a generator of groups with up to n lines of context.
// Each group is in the same format as returned by GetOpCodes().
func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
if n < 0 {
n = 3
}
codes := m.GetOpCodes()
if len(codes) == 0 {
codes = []OpCode{OpCode{'e', 0, 1, 0, 1}}
}
// Fixup leading and trailing groups if they show no changes.
if codes[0].Tag == 'e' {
c := codes[0]
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2}
}
if codes[len(codes)-1].Tag == 'e' {
c := codes[len(codes)-1]
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)}
}
nn := n + n
groups := [][]OpCode{}
group := []OpCode{}
for _, c := range codes {
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
// End the current group and start a new one whenever
// there is a large range with no changes.
if c.Tag == 'e' && i2-i1 > nn {
group = append(group, OpCode{c.Tag, i1, min(i2, i1+n),
j1, min(j2, j1+n)})
groups = append(groups, group)
group = []OpCode{}
i1, j1 = max(i1, i2-n), max(j1, j2-n)
}
group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
}
if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') {
groups = append(groups, group)
}
return groups
}
// Return a measure of the sequences' similarity (float in [0,1]).
//
// Where T is the total number of elements in both sequences, and
// M is the number of matches, this is 2.0*M / T.
// Note that this is 1 if the sequences are identical, and 0 if
// they have nothing in common.
//
// .Ratio() is expensive to compute if you haven't already computed
// .GetMatchingBlocks() or .GetOpCodes(), in which case you may
// want to try .QuickRatio() or .RealQuickRation() first to get an
// upper bound.
func (m *SequenceMatcher) Ratio() float64 {
matches := 0
for _, m := range m.GetMatchingBlocks() {
matches += m.Size
}
return calculateRatio(matches, len(m.a)+len(m.b))
}
// Return an upper bound on ratio() relatively quickly.
//
// This isn't defined beyond that it is an upper bound on .Ratio(), and
// is faster to compute.
func (m *SequenceMatcher) QuickRatio() float64 {
// viewing a and b as multisets, set matches to the cardinality
// of their intersection; this counts the number of matches
// without regard to order, so is clearly an upper bound
if m.fullBCount == nil {
m.fullBCount = map[string]int{}
for _, s := range m.b {
m.fullBCount[s] = m.fullBCount[s] + 1
}
}
// avail[x] is the number of times x appears in 'b' less the
// number of times we've seen it in 'a' so far ... kinda
avail := map[string]int{}
matches := 0
for _, s := range m.a {
n, ok := avail[s]
if !ok {
n = m.fullBCount[s]
}
avail[s] = n - 1
if n > 0 {
matches += 1
}
}
return calculateRatio(matches, len(m.a)+len(m.b))
}
// Return an upper bound on ratio() very quickly.
//
// This isn't defined beyond that it is an upper bound on .Ratio(), and
// is faster to compute than either .Ratio() or .QuickRatio().
func (m *SequenceMatcher) RealQuickRatio() float64 {
la, lb := len(m.a), len(m.b)
return calculateRatio(min(la, lb), la+lb)
}
// Convert range to the "ed" format
func formatRangeUnified(start, stop int) string {
// Per the diff spec at http://www.unix.org/single_unix_specification/
beginning := start + 1 // lines start numbering with one
length := stop - start
if length == 1 {
return fmt.Sprintf("%d", beginning)
}
if length == 0 {
beginning -= 1 // empty ranges begin at line just before the range
}
return fmt.Sprintf("%d,%d", beginning, length)
}
// Unified diff parameters
type UnifiedDiff struct {
A []string // First sequence lines
FromFile string // First file name
FromDate string // First file time
B []string // Second sequence lines
ToFile string // Second file name
ToDate string // Second file time
Eol string // Headers end of line, defaults to LF
Context int // Number of context lines
}
// Compare two sequences of lines; generate the delta as a unified diff.
//
// Unified diffs are a compact way of showing line changes and a few
// lines of context. The number of context lines is set by 'n' which
// defaults to three.
//
// By default, the diff control lines (those with ---, +++, or @@) are
// created with a trailing newline. This is helpful so that inputs
// created from file.readlines() result in diffs that are suitable for
// file.writelines() since both the inputs and outputs have trailing
// newlines.
//
// For inputs that do not have trailing newlines, set the lineterm
// argument to "" so that the output will be uniformly newline free.
//
// The unidiff format normally has a header for filenames and modification
// times. Any or all of these may be specified using strings for
// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
// The modification times are normally expressed in the ISO 8601 format.
func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
buf := bufio.NewWriter(writer)
defer buf.Flush()
wf := func(format string, args ...interface{}) error {
_, err := buf.WriteString(fmt.Sprintf(format, args...))
return err
}
ws := func(s string) error {
_, err := buf.WriteString(s)
return err
}
if len(diff.Eol) == 0 {
diff.Eol = "\n"
}
started := false
m := NewMatcher(diff.A, diff.B)
for _, g := range m.GetGroupedOpCodes(diff.Context) {
if !started {
started = true
fromDate := ""
if len(diff.FromDate) > 0 {
fromDate = "\t" + diff.FromDate
}
toDate := ""
if len(diff.ToDate) > 0 {
toDate = "\t" + diff.ToDate
}
if diff.FromFile != "" || diff.ToFile != "" {
err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
if err != nil {
return err
}
err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
if err != nil {
return err
}
}
}
first, last := g[0], g[len(g)-1]
range1 := formatRangeUnified(first.I1, last.I2)
range2 := formatRangeUnified(first.J1, last.J2)
if err := wf("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
return err
}
for _, c := range g {
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
if c.Tag == 'e' {
for _, line := range diff.A[i1:i2] {
if err := ws(" " + line); err != nil {
return err
}
}
continue
}
if c.Tag == 'r' || c.Tag == 'd' {
for _, line := range diff.A[i1:i2] {
if err := ws("-" + line); err != nil {
return err
}
}
}
if c.Tag == 'r' || c.Tag == 'i' {
for _, line := range diff.B[j1:j2] {
if err := ws("+" + line); err != nil {
return err
}
}
}
}
}
return nil
}
// Like WriteUnifiedDiff but returns the diff a string.
func GetUnifiedDiffString(diff UnifiedDiff) (string, error) {
w := &bytes.Buffer{}
err := WriteUnifiedDiff(w, diff)
return string(w.Bytes()), err
}
// Convert range to the "ed" format.
func formatRangeContext(start, stop int) string {
// Per the diff spec at http://www.unix.org/single_unix_specification/
beginning := start + 1 // lines start numbering with one
length := stop - start
if length == 0 {
beginning -= 1 // empty ranges begin at line just before the range
}
if length <= 1 {
return fmt.Sprintf("%d", beginning)
}
return fmt.Sprintf("%d,%d", beginning, beginning+length-1)
}
type ContextDiff UnifiedDiff
// Compare two sequences of lines; generate the delta as a context diff.
//
// Context diffs are a compact way of showing line changes and a few
// lines of context. The number of context lines is set by diff.Context
// which defaults to three.
//
// By default, the diff control lines (those with *** or ---) are
// created with a trailing newline.
//
// For inputs that do not have trailing newlines, set the diff.Eol
// argument to "" so that the output will be uniformly newline free.
//
// The context diff format normally has a header for filenames and
// modification times. Any or all of these may be specified using
// strings for diff.FromFile, diff.ToFile, diff.FromDate, diff.ToDate.
// The modification times are normally expressed in the ISO 8601 format.
// If not specified, the strings default to blanks.
func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
buf := bufio.NewWriter(writer)
defer buf.Flush()
var diffErr error
wf := func(format string, args ...interface{}) {
_, err := buf.WriteString(fmt.Sprintf(format, args...))
if diffErr == nil && err != nil {
diffErr = err
}
}
ws := func(s string) {
_, err := buf.WriteString(s)
if diffErr == nil && err != nil {
diffErr = err
}
}
if len(diff.Eol) == 0 {
diff.Eol = "\n"
}
prefix := map[byte]string{
'i': "+ ",
'd': "- ",
'r': "! ",
'e': " ",
}
started := false
m := NewMatcher(diff.A, diff.B)
for _, g := range m.GetGroupedOpCodes(diff.Context) {
if !started {
started = true
fromDate := ""
if len(diff.FromDate) > 0 {
fromDate = "\t" + diff.FromDate
}
toDate := ""
if len(diff.ToDate) > 0 {
toDate = "\t" + diff.ToDate
}
if diff.FromFile != "" || diff.ToFile != "" {
wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol)
wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol)
}
}
first, last := g[0], g[len(g)-1]
ws("***************" + diff.Eol)
range1 := formatRangeContext(first.I1, last.I2)
wf("*** %s ****%s", range1, diff.Eol)
for _, c := range g {
if c.Tag == 'r' || c.Tag == 'd' {
for _, cc := range g {
if cc.Tag == 'i' {
continue
}
for _, line := range diff.A[cc.I1:cc.I2] {
ws(prefix[cc.Tag] + line)
}
}
break
}
}
range2 := formatRangeContext(first.J1, last.J2)
wf("--- %s ----%s", range2, diff.Eol)
for _, c := range g {
if c.Tag == 'r' || c.Tag == 'i' {
for _, cc := range g {
if cc.Tag == 'd' {
continue
}
for _, line := range diff.B[cc.J1:cc.J2] {
ws(prefix[cc.Tag] + line)
}
}
break
}
}
}
return diffErr
}
// Like WriteContextDiff but returns the diff a string.
func GetContextDiffString(diff ContextDiff) (string, error) {
w := &bytes.Buffer{}
err := WriteContextDiff(w, diff)
return string(w.Bytes()), err
}
// Split a string on "\n" while preserving them. The output can be used
// as input for UnifiedDiff and ContextDiff structures.
func SplitLines(s string) []string {
lines := strings.SplitAfter(s, "\n")
lines[len(lines)-1] += "\n"
return lines
}

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors.
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.

@ -0,0 +1,436 @@
package assert
import (
"fmt"
"reflect"
"time"
)
type CompareType int
const (
compareLess CompareType = iota - 1
compareEqual
compareGreater
)
var (
intType = reflect.TypeOf(int(1))
int8Type = reflect.TypeOf(int8(1))
int16Type = reflect.TypeOf(int16(1))
int32Type = reflect.TypeOf(int32(1))
int64Type = reflect.TypeOf(int64(1))
uintType = reflect.TypeOf(uint(1))
uint8Type = reflect.TypeOf(uint8(1))
uint16Type = reflect.TypeOf(uint16(1))
uint32Type = reflect.TypeOf(uint32(1))
uint64Type = reflect.TypeOf(uint64(1))
float32Type = reflect.TypeOf(float32(1))
float64Type = reflect.TypeOf(float64(1))
stringType = reflect.TypeOf("")
timeType = reflect.TypeOf(time.Time{})
)
func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
obj1Value := reflect.ValueOf(obj1)
obj2Value := reflect.ValueOf(obj2)
// throughout this switch we try and avoid calling .Convert() if possible,
// as this has a pretty big performance impact
switch kind {
case reflect.Int:
{
intobj1, ok := obj1.(int)
if !ok {
intobj1 = obj1Value.Convert(intType).Interface().(int)
}
intobj2, ok := obj2.(int)
if !ok {
intobj2 = obj2Value.Convert(intType).Interface().(int)
}
if intobj1 > intobj2 {
return compareGreater, true
}
if intobj1 == intobj2 {
return compareEqual, true
}
if intobj1 < intobj2 {
return compareLess, true
}
}
case reflect.Int8:
{
int8obj1, ok := obj1.(int8)
if !ok {
int8obj1 = obj1Value.Convert(int8Type).Interface().(int8)
}
int8obj2, ok := obj2.(int8)
if !ok {
int8obj2 = obj2Value.Convert(int8Type).Interface().(int8)
}
if int8obj1 > int8obj2 {
return compareGreater, true
}
if int8obj1 == int8obj2 {
return compareEqual, true
}
if int8obj1 < int8obj2 {
return compareLess, true
}
}
case reflect.Int16:
{
int16obj1, ok := obj1.(int16)
if !ok {
int16obj1 = obj1Value.Convert(int16Type).Interface().(int16)
}
int16obj2, ok := obj2.(int16)
if !ok {
int16obj2 = obj2Value.Convert(int16Type).Interface().(int16)
}
if int16obj1 > int16obj2 {
return compareGreater, true
}
if int16obj1 == int16obj2 {
return compareEqual, true
}
if int16obj1 < int16obj2 {
return compareLess, true
}
}
case reflect.Int32:
{
int32obj1, ok := obj1.(int32)
if !ok {
int32obj1 = obj1Value.Convert(int32Type).Interface().(int32)
}
int32obj2, ok := obj2.(int32)
if !ok {
int32obj2 = obj2Value.Convert(int32Type).Interface().(int32)
}
if int32obj1 > int32obj2 {
return compareGreater, true
}
if int32obj1 == int32obj2 {
return compareEqual, true
}
if int32obj1 < int32obj2 {
return compareLess, true
}
}
case reflect.Int64:
{
int64obj1, ok := obj1.(int64)
if !ok {
int64obj1 = obj1Value.Convert(int64Type).Interface().(int64)
}
int64obj2, ok := obj2.(int64)
if !ok {
int64obj2 = obj2Value.Convert(int64Type).Interface().(int64)
}
if int64obj1 > int64obj2 {
return compareGreater, true
}
if int64obj1 == int64obj2 {
return compareEqual, true
}
if int64obj1 < int64obj2 {
return compareLess, true
}
}
case reflect.Uint:
{
uintobj1, ok := obj1.(uint)
if !ok {
uintobj1 = obj1Value.Convert(uintType).Interface().(uint)
}
uintobj2, ok := obj2.(uint)
if !ok {
uintobj2 = obj2Value.Convert(uintType).Interface().(uint)
}
if uintobj1 > uintobj2 {
return compareGreater, true
}
if uintobj1 == uintobj2 {
return compareEqual, true
}
if uintobj1 < uintobj2 {
return compareLess, true
}
}
case reflect.Uint8:
{
uint8obj1, ok := obj1.(uint8)
if !ok {
uint8obj1 = obj1Value.Convert(uint8Type).Interface().(uint8)
}
uint8obj2, ok := obj2.(uint8)
if !ok {
uint8obj2 = obj2Value.Convert(uint8Type).Interface().(uint8)
}
if uint8obj1 > uint8obj2 {
return compareGreater, true
}
if uint8obj1 == uint8obj2 {
return compareEqual, true
}
if uint8obj1 < uint8obj2 {
return compareLess, true
}
}
case reflect.Uint16:
{
uint16obj1, ok := obj1.(uint16)
if !ok {
uint16obj1 = obj1Value.Convert(uint16Type).Interface().(uint16)
}
uint16obj2, ok := obj2.(uint16)
if !ok {
uint16obj2 = obj2Value.Convert(uint16Type).Interface().(uint16)
}
if uint16obj1 > uint16obj2 {
return compareGreater, true
}
if uint16obj1 == uint16obj2 {
return compareEqual, true
}
if uint16obj1 < uint16obj2 {
return compareLess, true
}
}
case reflect.Uint32:
{
uint32obj1, ok := obj1.(uint32)
if !ok {
uint32obj1 = obj1Value.Convert(uint32Type).Interface().(uint32)
}
uint32obj2, ok := obj2.(uint32)
if !ok {
uint32obj2 = obj2Value.Convert(uint32Type).Interface().(uint32)
}
if uint32obj1 > uint32obj2 {
return compareGreater, true
}
if uint32obj1 == uint32obj2 {
return compareEqual, true
}
if uint32obj1 < uint32obj2 {
return compareLess, true
}
}
case reflect.Uint64:
{
uint64obj1, ok := obj1.(uint64)
if !ok {
uint64obj1 = obj1Value.Convert(uint64Type).Interface().(uint64)
}
uint64obj2, ok := obj2.(uint64)
if !ok {
uint64obj2 = obj2Value.Convert(uint64Type).Interface().(uint64)
}
if uint64obj1 > uint64obj2 {
return compareGreater, true
}
if uint64obj1 == uint64obj2 {
return compareEqual, true
}
if uint64obj1 < uint64obj2 {
return compareLess, true
}
}
case reflect.Float32:
{
float32obj1, ok := obj1.(float32)
if !ok {
float32obj1 = obj1Value.Convert(float32Type).Interface().(float32)
}
float32obj2, ok := obj2.(float32)
if !ok {
float32obj2 = obj2Value.Convert(float32Type).Interface().(float32)
}
if float32obj1 > float32obj2 {
return compareGreater, true
}
if float32obj1 == float32obj2 {
return compareEqual, true
}
if float32obj1 < float32obj2 {
return compareLess, true
}
}
case reflect.Float64:
{
float64obj1, ok := obj1.(float64)
if !ok {
float64obj1 = obj1Value.Convert(float64Type).Interface().(float64)
}
float64obj2, ok := obj2.(float64)
if !ok {
float64obj2 = obj2Value.Convert(float64Type).Interface().(float64)
}
if float64obj1 > float64obj2 {
return compareGreater, true
}
if float64obj1 == float64obj2 {
return compareEqual, true
}
if float64obj1 < float64obj2 {
return compareLess, true
}
}
case reflect.String:
{
stringobj1, ok := obj1.(string)
if !ok {
stringobj1 = obj1Value.Convert(stringType).Interface().(string)
}
stringobj2, ok := obj2.(string)
if !ok {
stringobj2 = obj2Value.Convert(stringType).Interface().(string)
}
if stringobj1 > stringobj2 {
return compareGreater, true
}
if stringobj1 == stringobj2 {
return compareEqual, true
}
if stringobj1 < stringobj2 {
return compareLess, true
}
}
// Check for known struct types we can check for compare results.
case reflect.Struct:
{
// All structs enter here. We're not interested in most types.
if !canConvert(obj1Value, timeType) {
break
}
// time.Time can compared!
timeObj1, ok := obj1.(time.Time)
if !ok {
timeObj1 = obj1Value.Convert(timeType).Interface().(time.Time)
}
timeObj2, ok := obj2.(time.Time)
if !ok {
timeObj2 = obj2Value.Convert(timeType).Interface().(time.Time)
}
return compare(timeObj1.UnixNano(), timeObj2.UnixNano(), reflect.Int64)
}
}
return compareEqual, false
}
// Greater asserts that the first element is greater than the second
//
// assert.Greater(t, 2, 1)
// assert.Greater(t, float64(2), float64(1))
// assert.Greater(t, "b", "a")
func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return compareTwoValues(t, e1, e2, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...)
}
// GreaterOrEqual asserts that the first element is greater than or equal to the second
//
// assert.GreaterOrEqual(t, 2, 1)
// assert.GreaterOrEqual(t, 2, 2)
// assert.GreaterOrEqual(t, "b", "a")
// assert.GreaterOrEqual(t, "b", "b")
func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return compareTwoValues(t, e1, e2, []CompareType{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...)
}
// Less asserts that the first element is less than the second
//
// assert.Less(t, 1, 2)
// assert.Less(t, float64(1), float64(2))
// assert.Less(t, "a", "b")
func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return compareTwoValues(t, e1, e2, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...)
}
// LessOrEqual asserts that the first element is less than or equal to the second
//
// assert.LessOrEqual(t, 1, 2)
// assert.LessOrEqual(t, 2, 2)
// assert.LessOrEqual(t, "a", "b")
// assert.LessOrEqual(t, "b", "b")
func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...)
}
// Positive asserts that the specified element is positive
//
// assert.Positive(t, 1)
// assert.Positive(t, 1.23)
func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
zero := reflect.Zero(reflect.TypeOf(e))
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareGreater}, "\"%v\" is not positive", msgAndArgs...)
}
// Negative asserts that the specified element is negative
//
// assert.Negative(t, -1)
// assert.Negative(t, -1.23)
func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
zero := reflect.Zero(reflect.TypeOf(e))
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareLess}, "\"%v\" is not negative", msgAndArgs...)
}
func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
e1Kind := reflect.ValueOf(e1).Kind()
e2Kind := reflect.ValueOf(e2).Kind()
if e1Kind != e2Kind {
return Fail(t, "Elements should be the same type", msgAndArgs...)
}
compareResult, isComparable := compare(e1, e2, e1Kind)
if !isComparable {
return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
}
if !containsValue(allowedComparesResults, compareResult) {
return Fail(t, fmt.Sprintf(failMessage, e1, e2), msgAndArgs...)
}
return true
}
func containsValue(values []CompareType, value CompareType) bool {
for _, v := range values {
if v == value {
return true
}
}
return false
}

@ -0,0 +1,16 @@
//go:build go1.17
// +build go1.17
// TODO: once support for Go 1.16 is dropped, this file can be
// merged/removed with assertion_compare_go1.17_test.go and
// assertion_compare_legacy.go
package assert
import "reflect"
// Wrapper around reflect.Value.CanConvert, for compatibility
// reasons.
func canConvert(value reflect.Value, to reflect.Type) bool {
return value.CanConvert(to)
}

@ -0,0 +1,16 @@
//go:build !go1.17
// +build !go1.17
// TODO: once support for Go 1.16 is dropped, this file can be
// merged/removed with assertion_compare_go1.17_test.go and
// assertion_compare_can_convert.go
package assert
import "reflect"
// Older versions of Go does not have the reflect.Value.CanConvert
// method.
func canConvert(value reflect.Value, to reflect.Type) bool {
return false
}

@ -0,0 +1,753 @@
/*
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
* THIS FILE MUST NOT BE EDITED BY HAND
*/
package assert
import (
http "net/http"
url "net/url"
time "time"
)
// Conditionf uses a Comparison to assert a complex condition.
func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Condition(t, comp, append([]interface{}{msg}, args...)...)
}
// Containsf asserts that the specified string, list(array, slice...) or map contains the
// specified substring or element.
//
// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted")
// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Contains(t, s, contains, append([]interface{}{msg}, args...)...)
}
// DirExistsf checks whether a directory exists in the given path. It also fails
// if the path is a file rather a directory or there is an error checking whether it exists.
func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return DirExists(t, path, append([]interface{}{msg}, args...)...)
}
// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
// the number of appearances of each of them in both lists should match.
//
// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
}
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
// a slice or a channel with len == 0.
//
// assert.Emptyf(t, obj, "error message %s", "formatted")
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Empty(t, object, append([]interface{}{msg}, args...)...)
}
// Equalf asserts that two objects are equal.
//
// assert.Equalf(t, 123, 123, "error message %s", "formatted")
//
// Pointer variable equality is determined based on the equality of the
// referenced values (as opposed to the memory addresses). Function equality
// cannot be determined and will always fail.
func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Equal(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
// and that it is equal to the provided error.
//
// actualObj, err := SomeFunction()
// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted")
func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...)
}
// EqualValuesf asserts that two objects are equal or convertable to the same types
// and equal.
//
// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted")
func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return EqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// Errorf asserts that a function returned an error (i.e. not `nil`).
//
// actualObj, err := SomeFunction()
// if assert.Errorf(t, err, "error message %s", "formatted") {
// assert.Equal(t, expectedErrorf, err)
// }
func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Error(t, err, append([]interface{}{msg}, args...)...)
}
// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
// This is a wrapper for errors.As.
func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return ErrorAs(t, err, target, append([]interface{}{msg}, args...)...)
}
// ErrorContainsf asserts that a function returned an error (i.e. not `nil`)
// and that the error contains the specified substring.
//
// actualObj, err := SomeFunction()
// assert.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted")
func ErrorContainsf(t TestingT, theError error, contains string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return ErrorContains(t, theError, contains, append([]interface{}{msg}, args...)...)
}
// ErrorIsf asserts that at least one of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return ErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
}
// Eventuallyf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick.
//
// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Eventually(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
}
// Exactlyf asserts that two objects are equal in value and type.
//
// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted")
func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Exactly(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// Failf reports a failure through
func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Fail(t, failureMessage, append([]interface{}{msg}, args...)...)
}
// FailNowf fails test
func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return FailNow(t, failureMessage, append([]interface{}{msg}, args...)...)
}
// Falsef asserts that the specified value is false.
//
// assert.Falsef(t, myBool, "error message %s", "formatted")
func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return False(t, value, append([]interface{}{msg}, args...)...)
}
// FileExistsf checks whether a file exists in the given path. It also fails if
// the path points to a directory or there is an error when trying to check the file.
func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return FileExists(t, path, append([]interface{}{msg}, args...)...)
}
// Greaterf asserts that the first element is greater than the second
//
// assert.Greaterf(t, 2, 1, "error message %s", "formatted")
// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted")
// assert.Greaterf(t, "b", "a", "error message %s", "formatted")
func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Greater(t, e1, e2, append([]interface{}{msg}, args...)...)
}
// GreaterOrEqualf asserts that the first element is greater than or equal to the second
//
// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted")
// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted")
// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted")
// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted")
func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return GreaterOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
}
// HTTPBodyContainsf asserts that a specified handler returns a
// body that contains a string.
//
// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPBodyContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
}
// HTTPBodyNotContainsf asserts that a specified handler returns a
// body that does not contain a string.
//
// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPBodyNotContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
}
// HTTPErrorf asserts that a specified handler returns an error status code.
//
// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPError(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
}
// HTTPRedirectf asserts that a specified handler returns a redirect status code.
//
// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
}
// HTTPStatusCodef asserts that a specified handler returns a specified status code.
//
// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPStatusCode(t, handler, method, url, values, statuscode, append([]interface{}{msg}, args...)...)
}
// HTTPSuccessf asserts that a specified handler returns a success status code.
//
// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return HTTPSuccess(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
}
// Implementsf asserts that an object is implemented by the specified interface.
//
// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Implements(t, interfaceObject, object, append([]interface{}{msg}, args...)...)
}
// InDeltaf asserts that the two numerals are within delta of each other.
//
// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted")
func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return InDelta(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return InDeltaMapValues(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
// InDeltaSlicef is the same as InDelta, except it compares two slices.
func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return InDeltaSlice(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
// InEpsilonf asserts that expected and actual have a relative error less than epsilon
func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return InEpsilon(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
}
// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
}
// IsDecreasingf asserts that the collection is decreasing
//
// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted")
// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted")
// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsDecreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsIncreasingf asserts that the collection is increasing
//
// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted")
// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted")
// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsIncreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsNonDecreasingf asserts that the collection is not decreasing
//
// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted")
// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted")
// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsNonDecreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsNonIncreasingf asserts that the collection is not increasing
//
// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted")
// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted")
// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsNonIncreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsTypef asserts that the specified objects are of the same type.
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsType(t, expectedType, object, append([]interface{}{msg}, args...)...)
}
// JSONEqf asserts that two JSON strings are equivalent.
//
// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// Lenf asserts that the specified object has specific length.
// Lenf also fails if the object has a type that len() not accept.
//
// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Len(t, object, length, append([]interface{}{msg}, args...)...)
}
// Lessf asserts that the first element is less than the second
//
// assert.Lessf(t, 1, 2, "error message %s", "formatted")
// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted")
// assert.Lessf(t, "a", "b", "error message %s", "formatted")
func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Less(t, e1, e2, append([]interface{}{msg}, args...)...)
}
// LessOrEqualf asserts that the first element is less than or equal to the second
//
// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted")
// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted")
// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted")
// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted")
func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return LessOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
}
// Negativef asserts that the specified element is negative
//
// assert.Negativef(t, -1, "error message %s", "formatted")
// assert.Negativef(t, -1.23, "error message %s", "formatted")
func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Negative(t, e, append([]interface{}{msg}, args...)...)
}
// Neverf asserts that the given condition doesn't satisfy in waitFor time,
// periodically checking the target function each tick.
//
// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Never(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
}
// Nilf asserts that the specified object is nil.
//
// assert.Nilf(t, err, "error message %s", "formatted")
func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Nil(t, object, append([]interface{}{msg}, args...)...)
}
// NoDirExistsf checks whether a directory does not exist in the given path.
// It fails if the path points to an existing _directory_ only.
func NoDirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NoDirExists(t, path, append([]interface{}{msg}, args...)...)
}
// NoErrorf asserts that a function returned no error (i.e. `nil`).
//
// actualObj, err := SomeFunction()
// if assert.NoErrorf(t, err, "error message %s", "formatted") {
// assert.Equal(t, expectedObj, actualObj)
// }
func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NoError(t, err, append([]interface{}{msg}, args...)...)
}
// NoFileExistsf checks whether a file does not exist in a given path. It fails
// if the path points to an existing _file_ only.
func NoFileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NoFileExists(t, path, append([]interface{}{msg}, args...)...)
}
// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
// specified substring or element.
//
// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted")
// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted")
// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted")
func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotContains(t, s, contains, append([]interface{}{msg}, args...)...)
}
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
// a slice or a channel with len == 0.
//
// if assert.NotEmptyf(t, obj, "error message %s", "formatted") {
// assert.Equal(t, "two", obj[1])
// }
func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotEmpty(t, object, append([]interface{}{msg}, args...)...)
}
// NotEqualf asserts that the specified values are NOT equal.
//
// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted")
//
// Pointer variable equality is determined based on the equality of the
// referenced values (as opposed to the memory addresses).
func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// NotEqualValuesf asserts that two objects are not equal even when converted to the same type
//
// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted")
func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// NotErrorIsf asserts that at none of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
}
// NotNilf asserts that the specified object is not nil.
//
// assert.NotNilf(t, err, "error message %s", "formatted")
func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotNil(t, object, append([]interface{}{msg}, args...)...)
}
// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
//
// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted")
func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotPanics(t, f, append([]interface{}{msg}, args...)...)
}
// NotRegexpf asserts that a specified regexp does not match a string.
//
// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted")
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...)
}
// NotSamef asserts that two pointers do not reference the same object.
//
// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted")
//
// Both arguments must be pointer variables. Pointer variable sameness is
// determined based on the equality of both type and value.
func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotSame(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// NotSubsetf asserts that the specified list(array, slice...) contains not all
// elements given in the specified subset(array, slice...).
//
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotSubset(t, list, subset, append([]interface{}{msg}, args...)...)
}
// NotZerof asserts that i is not the zero value for its type.
func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotZero(t, i, append([]interface{}{msg}, args...)...)
}
// Panicsf asserts that the code inside the specified PanicTestFunc panics.
//
// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted")
func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Panics(t, f, append([]interface{}{msg}, args...)...)
}
// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc
// panics, and that the recovered panic value is an error that satisfies the
// EqualError comparison.
//
// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
func PanicsWithErrorf(t TestingT, errString string, f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return PanicsWithError(t, errString, f, append([]interface{}{msg}, args...)...)
}
// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
// the recovered panic value equals the expected panic value.
//
// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...)
}
// Positivef asserts that the specified element is positive
//
// assert.Positivef(t, 1, "error message %s", "formatted")
// assert.Positivef(t, 1.23, "error message %s", "formatted")
func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Positive(t, e, append([]interface{}{msg}, args...)...)
}
// Regexpf asserts that a specified regexp matches a string.
//
// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted")
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Regexp(t, rx, str, append([]interface{}{msg}, args...)...)
}
// Samef asserts that two pointers reference the same object.
//
// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted")
//
// Both arguments must be pointer variables. Pointer variable sameness is
// determined based on the equality of both type and value.
func Samef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Same(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// Subsetf asserts that the specified list(array, slice...) contains all
// elements given in the specified subset(array, slice...).
//
// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Subset(t, list, subset, append([]interface{}{msg}, args...)...)
}
// Truef asserts that the specified value is true.
//
// assert.Truef(t, myBool, "error message %s", "formatted")
func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return True(t, value, append([]interface{}{msg}, args...)...)
}
// WithinDurationf asserts that the two times are within duration delta of each other.
//
// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
}
// YAMLEqf asserts that two YAML strings are equivalent.
func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return YAMLEq(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// Zerof asserts that i is the zero value for its type.
func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Zero(t, i, append([]interface{}{msg}, args...)...)
}

@ -0,0 +1,5 @@
{{.CommentFormat}}
func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool {
if h, ok := t.(tHelper); ok { h.Helper() }
return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}})
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,5 @@
{{.CommentWithoutT "a"}}
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool {
if h, ok := a.t.(tHelper); ok { h.Helper() }
return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
}

@ -0,0 +1,81 @@
package assert
import (
"fmt"
"reflect"
)
// isOrdered checks that collection contains orderable elements.
func isOrdered(t TestingT, object interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool {
objKind := reflect.TypeOf(object).Kind()
if objKind != reflect.Slice && objKind != reflect.Array {
return false
}
objValue := reflect.ValueOf(object)
objLen := objValue.Len()
if objLen <= 1 {
return true
}
value := objValue.Index(0)
valueInterface := value.Interface()
firstValueKind := value.Kind()
for i := 1; i < objLen; i++ {
prevValue := value
prevValueInterface := valueInterface
value = objValue.Index(i)
valueInterface = value.Interface()
compareResult, isComparable := compare(prevValueInterface, valueInterface, firstValueKind)
if !isComparable {
return Fail(t, fmt.Sprintf("Can not compare type \"%s\" and \"%s\"", reflect.TypeOf(value), reflect.TypeOf(prevValue)), msgAndArgs...)
}
if !containsValue(allowedComparesResults, compareResult) {
return Fail(t, fmt.Sprintf(failMessage, prevValue, value), msgAndArgs...)
}
}
return true
}
// IsIncreasing asserts that the collection is increasing
//
// assert.IsIncreasing(t, []int{1, 2, 3})
// assert.IsIncreasing(t, []float{1, 2})
// assert.IsIncreasing(t, []string{"a", "b"})
func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...)
}
// IsNonIncreasing asserts that the collection is not increasing
//
// assert.IsNonIncreasing(t, []int{2, 1, 1})
// assert.IsNonIncreasing(t, []float{2, 1})
// assert.IsNonIncreasing(t, []string{"b", "a"})
func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []CompareType{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...)
}
// IsDecreasing asserts that the collection is decreasing
//
// assert.IsDecreasing(t, []int{2, 1, 0})
// assert.IsDecreasing(t, []float{2, 1})
// assert.IsDecreasing(t, []string{"b", "a"})
func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...)
}
// IsNonDecreasing asserts that the collection is not decreasing
//
// assert.IsNonDecreasing(t, []int{1, 1, 2})
// assert.IsNonDecreasing(t, []float{1, 2})
// assert.IsNonDecreasing(t, []string{"a", "b"})
func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...)
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,45 @@
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
//
// Example Usage
//
// The following is a complete example using assert in a standard test function:
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// )
//
// func TestSomething(t *testing.T) {
//
// var a string = "Hello"
// var b string = "Hello"
//
// assert.Equal(t, a, b, "The two words should be the same.")
//
// }
//
// if you assert many times, use the format below:
//
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// )
//
// func TestSomething(t *testing.T) {
// assert := assert.New(t)
//
// var a string = "Hello"
// var b string = "Hello"
//
// assert.Equal(a, b, "The two words should be the same.")
// }
//
// Assertions
//
// Assertions allow you to easily write test code, and are global funcs in the `assert` package.
// All assertion functions take, as the first argument, the `*testing.T` object provided by the
// testing framework. This allows the assertion funcs to write the failings and other details to
// the correct place.
//
// Every assertion function also takes an optional string message as the final argument,
// allowing custom error messages to be appended to the message the assertion method outputs.
package assert

@ -0,0 +1,10 @@
package assert
import (
"errors"
)
// AnError is an error instance useful for testing. If the code does not care
// about error specifics, and only needs to return the error for example, this
// error should be used to make the test code more readable.
var AnError = errors.New("assert.AnError general error for testing")

@ -0,0 +1,16 @@
package assert
// Assertions provides assertion methods around the
// TestingT interface.
type Assertions struct {
t TestingT
}
// New makes a new Assertions object for the specified TestingT.
func New(t TestingT) *Assertions {
return &Assertions{
t: t,
}
}
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_forward.go.tmpl -include-format-funcs"

@ -0,0 +1,162 @@
package assert
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
)
// httpCode is a helper that returns HTTP code of the response. It returns -1 and
// an error if building a new request fails.
func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) {
w := httptest.NewRecorder()
req, err := http.NewRequest(method, url, nil)
if err != nil {
return -1, err
}
req.URL.RawQuery = values.Encode()
handler(w, req)
return w.Code, nil
}
// HTTPSuccess asserts that a specified handler returns a success status code.
//
// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil)
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
}
isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent
if !isSuccessCode {
Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code))
}
return isSuccessCode
}
// HTTPRedirect asserts that a specified handler returns a redirect status code.
//
// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
}
isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect
if !isRedirectCode {
Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code))
}
return isRedirectCode
}
// HTTPError asserts that a specified handler returns an error status code.
//
// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
}
isErrorCode := code >= http.StatusBadRequest
if !isErrorCode {
Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code))
}
return isErrorCode
}
// HTTPStatusCode asserts that a specified handler returns a specified status code.
//
// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501)
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
}
successful := code == statuscode
if !successful {
Fail(t, fmt.Sprintf("Expected HTTP status code %d for %q but received %d", statuscode, url+"?"+values.Encode(), code))
}
return successful
}
// HTTPBody is a helper that returns HTTP body of the response. It returns
// empty string if building a new request fails.
func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string {
w := httptest.NewRecorder()
req, err := http.NewRequest(method, url+"?"+values.Encode(), nil)
if err != nil {
return ""
}
handler(w, req)
return w.Body.String()
}
// HTTPBodyContains asserts that a specified handler returns a
// body that contains a string.
//
// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
body := HTTPBody(handler, method, url, values)
contains := strings.Contains(body, fmt.Sprint(str))
if !contains {
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
}
return contains
}
// HTTPBodyNotContains asserts that a specified handler returns a
// body that does not contain a string.
//
// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
body := HTTPBody(handler, method, url, values)
contains := strings.Contains(body, fmt.Sprint(str))
if contains {
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
}
return !contains
}

@ -0,0 +1,28 @@
// Package require implements the same assertions as the `assert` package but
// stops test execution when a test fails.
//
// Example Usage
//
// The following is a complete example using require in a standard test function:
// import (
// "testing"
// "github.com/stretchr/testify/require"
// )
//
// func TestSomething(t *testing.T) {
//
// var a string = "Hello"
// var b string = "Hello"
//
// require.Equal(t, a, b, "The two words should be the same.")
//
// }
//
// Assertions
//
// The `require` package have same global functions as in the `assert` package,
// but instead of returning a boolean result they call `t.FailNow()`.
//
// Every assertion function also takes an optional string message as the final argument,
// allowing custom error messages to be appended to the message the assertion method outputs.
package require

@ -0,0 +1,16 @@
package require
// Assertions provides assertion methods around the
// TestingT interface.
type Assertions struct {
t TestingT
}
// New makes a new Assertions object for the specified TestingT.
func New(t TestingT) *Assertions {
return &Assertions{
t: t,
}
}
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require_forward.go.tmpl -include-format-funcs"

File diff suppressed because it is too large Load Diff

@ -0,0 +1,6 @@
{{.Comment}}
func {{.DocInfo.Name}}(t TestingT, {{.Params}}) {
if h, ok := t.(tHelper); ok { h.Helper() }
if assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { return }
t.FailNow()
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,5 @@
{{.CommentWithoutT "a"}}
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) {
if h, ok := a.t.(tHelper); ok { h.Helper() }
{{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
}

@ -0,0 +1,29 @@
package require
// TestingT is an interface wrapper around *testing.T
type TestingT interface {
Errorf(format string, args ...interface{})
FailNow()
}
type tHelper interface {
Helper()
}
// ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful
// for table driven tests.
type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{})
// ValueAssertionFunc is a common function prototype when validating a single value. Can be useful
// for table driven tests.
type ValueAssertionFunc func(TestingT, interface{}, ...interface{})
// BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful
// for table driven tests.
type BoolAssertionFunc func(TestingT, bool, ...interface{})
// ErrorAssertionFunc is a common function prototype when validating an error value. Can be useful
// for table driven tests.
type ErrorAssertionFunc func(TestingT, error, ...interface{})
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require.go.tmpl -include-format-funcs"

50
vendor/gopkg.in/yaml.v3/LICENSE generated vendored

@ -0,0 +1,50 @@
This project is covered by two different licenses: MIT and Apache.
#### MIT License ####
The following files were ported to Go from C files of libyaml, and thus
are still covered by their original MIT license, with the additional
copyright staring in 2011 when the project was ported over:
apic.go emitterc.go parserc.go readerc.go scannerc.go
writerc.go yamlh.go yamlprivateh.go
Copyright (c) 2006-2010 Kirill Simonov
Copyright (c) 2006-2011 Kirill Simonov
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.
### Apache License ###
All the remaining project files are covered by the Apache license:
Copyright (c) 2011-2019 Canonical Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

13
vendor/gopkg.in/yaml.v3/NOTICE generated vendored

@ -0,0 +1,13 @@
Copyright 2011-2016 Canonical Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

150
vendor/gopkg.in/yaml.v3/README.md generated vendored

@ -0,0 +1,150 @@
# YAML support for the Go language
Introduction
------------
The yaml package enables Go programs to comfortably encode and decode YAML
values. It was developed within [Canonical](https://www.canonical.com) as
part of the [juju](https://juju.ubuntu.com) project, and is based on a
pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML)
C library to parse and generate YAML data quickly and reliably.
Compatibility
-------------
The yaml package supports most of YAML 1.2, but preserves some behavior
from 1.1 for backwards compatibility.
Specifically, as of v3 of the yaml package:
- YAML 1.1 bools (_yes/no, on/off_) are supported as long as they are being
decoded into a typed bool value. Otherwise they behave as a string. Booleans
in YAML 1.2 are _true/false_ only.
- Octals encode and decode as _0777_ per YAML 1.1, rather than _0o777_
as specified in YAML 1.2, because most parsers still use the old format.
Octals in the _0o777_ format are supported though, so new files work.
- Does not support base-60 floats. These are gone from YAML 1.2, and were
actually never supported by this package as it's clearly a poor choice.
and offers backwards
compatibility with YAML 1.1 in some cases.
1.2, including support for
anchors, tags, map merging, etc. Multi-document unmarshalling is not yet
implemented, and base-60 floats from YAML 1.1 are purposefully not
supported since they're a poor design and are gone in YAML 1.2.
Installation and usage
----------------------
The import path for the package is *gopkg.in/yaml.v3*.
To install it, run:
go get gopkg.in/yaml.v3
API documentation
-----------------
If opened in a browser, the import path itself leads to the API documentation:
- [https://gopkg.in/yaml.v3](https://gopkg.in/yaml.v3)
API stability
-------------
The package API for yaml v3 will remain stable as described in [gopkg.in](https://gopkg.in).
License
-------
The yaml package is licensed under the MIT and Apache License 2.0 licenses.
Please see the LICENSE file for details.
Example
-------
```Go
package main
import (
"fmt"
"log"
"gopkg.in/yaml.v3"
)
var data = `
a: Easy!
b:
c: 2
d: [3, 4]
`
// Note: struct fields must be public in order for unmarshal to
// correctly populate the data.
type T struct {
A string
B struct {
RenamedC int `yaml:"c"`
D []int `yaml:",flow"`
}
}
func main() {
t := T{}
err := yaml.Unmarshal([]byte(data), &t)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("--- t:\n%v\n\n", t)
d, err := yaml.Marshal(&t)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("--- t dump:\n%s\n\n", string(d))
m := make(map[interface{}]interface{})
err = yaml.Unmarshal([]byte(data), &m)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("--- m:\n%v\n\n", m)
d, err = yaml.Marshal(&m)
if err != nil {
log.Fatalf("error: %v", err)
}
fmt.Printf("--- m dump:\n%s\n\n", string(d))
}
```
This example will generate the following output:
```
--- t:
{Easy! {2 [3 4]}}
--- t dump:
a: Easy!
b:
c: 2
d: [3, 4]
--- m:
map[a:Easy! b:map[c:2 d:[3 4]]]
--- m dump:
a: Easy!
b:
c: 2
d:
- 3
- 4
```

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save