- update dorm

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

@ -43,6 +43,7 @@ require (
github.com/shopspring/decimal v1.3.1
github.com/sirupsen/logrus v1.8.1
github.com/tencentyun/cos-go-sdk-v5 v0.7.35
github.com/upper/db/v4 v4.5.4
github.com/upyun/go-sdk/v3 v3.0.2
go.etcd.io/etcd/api/v3 v3.5.4
go.etcd.io/etcd/client/v3 v3.5.4
@ -75,6 +76,7 @@ require (
github.com/denisenkom/go-mssqldb v0.12.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/edsrzf/mmap-go v1.0.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/glebarez/go-sqlite v1.17.3 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
@ -136,9 +138,20 @@ require (
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/b v1.0.2 // indirect
modernc.org/db v1.0.3 // indirect
modernc.org/file v1.0.3 // indirect
modernc.org/fileutil v1.0.0 // indirect
modernc.org/golex v1.0.1 // indirect
modernc.org/internal v1.0.2 // indirect
modernc.org/libc v1.16.8 // indirect
modernc.org/lldb v1.0.2 // indirect
modernc.org/mathutil v1.4.1 // indirect
modernc.org/memory v1.1.1 // indirect
modernc.org/ql v1.4.0 // indirect
modernc.org/sortutil v1.1.0 // indirect
modernc.org/sqlite v1.17.3 // indirect
modernc.org/strutil v1.1.1 // indirect
modernc.org/zappy v1.0.3 // indirect
xorm.io/builder v0.3.12 // indirect
)

@ -104,6 +104,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/denisenkom/go-mssqldb v0.12.0 h1:VtrkII767ttSPNRfFekePK3sctr+joXgO58stqQbtUA=
github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU=
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
@ -120,6 +121,7 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -244,6 +246,7 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -308,6 +311,8 @@ github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.12+incompatible/go.mod h1:l7
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk=
github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
@ -438,6 +443,7 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ=
github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
@ -578,6 +584,7 @@ github.com/qiniu/qmgo v1.1.1 h1:flz0h/524mAOjlpU3ahliyL7v5JfHmw4NqKmqcV4plo=
github.com/qiniu/qmgo v1.1.1/go.mod h1:gTj5P+fOyGwtTkumPa8YTFspsf0Ndpw+MtRPwU1FHL4=
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
@ -658,6 +665,8 @@ github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/upper/db/v4 v4.5.4 h1:Hxho4jSx4E+3fxlFgdH4wQTRKygtL0YQPDLQPCUu9wg=
github.com/upper/db/v4 v4.5.4/go.mod h1:wyu5BM5Y2gowOt4i6C4LbxftH9QeUF338XVGH4uk+Eo=
github.com/upyun/go-sdk/v3 v3.0.2 h1:Ke+iOipK5CT0xzMwsgJsi7faJV7ID4lAs+wrH1RH0dA=
github.com/upyun/go-sdk/v3 v3.0.2/go.mod h1:P/SnuuwhrIgAVRd/ZpzDWqCsBAf/oHg7UggbAxyZa0E=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
@ -740,8 +749,10 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -842,6 +853,7 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e h1:NHvCuwuS43lGnYhten69ZWqi2QOj/CiDNcKbVqwVoew=
@ -1000,6 +1012,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/b v1.0.2 h1:iPC2u39ebzq12GOC2yXT4mve0HrWcH85cz+midWjzeo=
modernc.org/b v1.0.2/go.mod h1:fVGfCIzkZw5RsuF2A2WHbJmY7FiMIq30nP4s52uWsoY=
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
@ -1058,7 +1072,20 @@ modernc.org/ccgo/v3 v3.16.6 h1:3l18poV+iUemQ98O3X5OMr97LOqlzis+ytivU4NqGhA=
modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/db v1.0.3 h1:apxOlWU69je04bY22OT6J0RL23mzvUy22EgTAVyw+Yg=
modernc.org/db v1.0.3/go.mod h1:L4ltUg8tu2pkSJk+fKaRrXs/3EdW79ZKYQ5PfVDT53U=
modernc.org/file v1.0.3 h1:McYGAMMuqjRp6ptmpcLr3r5yw3gNPsonFCAJ0tNK74U=
modernc.org/file v1.0.3/go.mod h1:CNj/pwOfCtCbqiHcXDUlHBB2vWrzdaDCWdcnjtS1+XY=
modernc.org/fileutil v1.0.0 h1:Z1AFLZwl6BO8A5NldQg/xTSjGLetp+1Ubvl4alfGx8w=
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
modernc.org/golex v1.0.1 h1:EYKY1a3wStt0RzHaH8mdSRNg78Ub0OHxYfCRWw35YtM=
modernc.org/golex v1.0.1/go.mod h1:QCA53QtsT1NdGkaZZkF5ezFwk4IXh4BGNafAARTC254=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM=
modernc.org/internal v1.0.2 h1:Sn3+ojjMRnPaOR6jFISs6KAdRHnR4q9KNuwfKINKmZA=
modernc.org/internal v1.0.2/go.mod h1:bycJAcev709ZU/47nil584PeBD+kbu8nv61ozeMso9E=
modernc.org/lex v1.0.0/go.mod h1:G6rxMTy3cH2iA0iXL/HRRv4Znu8MK4higxph/lE7ypk=
modernc.org/lexer v1.0.0/go.mod h1:F/Dld0YKYdZCLQ7bD0USbWL4YKCyTDRDHiDTOs0q0vk=
modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
@ -1100,6 +1127,9 @@ modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU=
modernc.org/libc v1.16.7/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
modernc.org/libc v1.16.8 h1:Ux98PaOMvolgoFX/YwusFOHBnanXdGRmWgI8ciI2z4o=
modernc.org/libc v1.16.8/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
modernc.org/lldb v1.0.2 h1:LBw58xVFl01OuM5U9++tLy3wmu+PoWok6T3dHuNjcZk=
modernc.org/lldb v1.0.2/go.mod h1:ovbKqyzA9H/iPwHkAOH0qJbIQVT9rlijecenxDwVUi0=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
@ -1111,6 +1141,10 @@ modernc.org/memory v1.1.1 h1:bDOL0DIDLQv7bWhP3gMvIrnoFw+Eo6F7a2QK9HPDiFU=
modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/ql v1.4.0 h1:CqLAho+y4N8JwvqT7NJsYsp7YPwiRv6RE2n0n1ksSCU=
modernc.org/ql v1.4.0/go.mod h1:q4c29Bgdx+iAtxx47ODW5Xo2X0PDkjSCK9NdQl6KFxc=
modernc.org/sortutil v1.1.0 h1:oP3U4uM+NT/qBQcbg/K2iqAX0Nx7B1b6YZtq3Gk/PjM=
modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k=
modernc.org/sqlite v1.14.2/go.mod h1:yqfn85u8wVOE6ub5UT8VI9JjhrwBUUCNyTACN0h6Sx8=
modernc.org/sqlite v1.17.3 h1:iE+coC5g17LtByDYDWKpR6m2Z9022YrSh3bumwOnIrI=
modernc.org/sqlite v1.17.3/go.mod h1:10hPVYar9C0kfXuTWGz8s0XtB8uAGymUy51ZzStYe3k=
@ -1122,6 +1156,8 @@ modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY=
modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
modernc.org/zappy v1.0.3 h1:Tr+P3kclDSrvC6zYBW2hWmOmu5SjG6PtvCt3RCjRmss=
modernc.org/zappy v1.0.3/go.mod h1:w/Akq8ipfols/xZJdR5IYiQNOqC80qz2mVvsEwEbkiI=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

@ -0,0 +1,9 @@
package dorm
import "github.com/upper/db/v4"
// UpperClient
// https://upper.io/
type UpperClient struct {
Db *db.Session // 驱动
}

@ -0,0 +1,23 @@
package dorm
import (
"errors"
"fmt"
"github.com/upper/db/v4/adapter/cockroachdb"
)
func NewUpperCockroachdbClient(settings cockroachdb.ConnectionURL) (*UpperClient, error) {
var err error
c := &UpperClient{}
sess, err := cockroachdb.Open(settings)
if err != nil {
return nil, errors.New(fmt.Sprintf("连接失败:%v", err))
}
defer sess.Close()
c.Db = &sess
return c, nil
}

@ -0,0 +1,23 @@
package dorm
import (
"errors"
"fmt"
"github.com/upper/db/v4/adapter/mongo"
)
func NewUpperMongodbClient(settings mongo.ConnectionURL) (*UpperClient, error) {
var err error
c := &UpperClient{}
sess, err := mongo.Open(settings)
if err != nil {
return nil, errors.New(fmt.Sprintf("连接失败:%v", err))
}
defer sess.Close()
c.Db = &sess
return c, nil
}

@ -0,0 +1,23 @@
package dorm
import (
"errors"
"fmt"
"github.com/upper/db/v4/adapter/mssql"
)
func NewUpperMssqlClient(settings mssql.ConnectionURL) (*UpperClient, error) {
var err error
c := &UpperClient{}
sess, err := mssql.Open(settings)
if err != nil {
return nil, errors.New(fmt.Sprintf("连接失败:%v", err))
}
defer sess.Close()
c.Db = &sess
return c, nil
}

@ -0,0 +1,23 @@
package dorm
import (
"errors"
"fmt"
"github.com/upper/db/v4/adapter/mysql"
)
func NewUpperMysqlClient(settings mysql.ConnectionURL) (*UpperClient, error) {
var err error
c := &UpperClient{}
sess, err := mysql.Open(settings)
if err != nil {
return nil, errors.New(fmt.Sprintf("连接失败:%v", err))
}
defer sess.Close()
c.Db = &sess
return c, nil
}

@ -0,0 +1,23 @@
package dorm
import (
"errors"
"fmt"
"github.com/upper/db/v4/adapter/postgresql"
)
func NewUpperPostgresqlClient(settings postgresql.ConnectionURL) (*UpperClient, error) {
var err error
c := &UpperClient{}
sess, err := postgresql.Open(settings)
if err != nil {
return nil, errors.New(fmt.Sprintf("连接失败:%v", err))
}
defer sess.Close()
c.Db = &sess
return c, nil
}

@ -0,0 +1,23 @@
package dorm
import (
"errors"
"fmt"
"github.com/upper/db/v4/adapter/ql"
)
func NewUpperQlClient(settings ql.ConnectionURL) (*UpperClient, error) {
var err error
c := &UpperClient{}
sess, err := ql.Open(settings)
if err != nil {
return nil, errors.New(fmt.Sprintf("连接失败:%v", err))
}
defer sess.Close()
c.Db = &sess
return c, nil
}

@ -0,0 +1,23 @@
package dorm
import (
"errors"
"fmt"
"github.com/upper/db/v4/adapter/sqlite"
)
func NewUpperSqliteClient(settings sqlite.ConnectionURL) (*UpperClient, error) {
var err error
c := &UpperClient{}
sess, err := sqlite.Open(settings)
if err != nil {
return nil, errors.New(fmt.Sprintf("连接失败:%v", err))
}
defer sess.Close()
c.Db = &sess
return c, nil
}

@ -0,0 +1,8 @@
*.out
*.5
*.6
*.8
*.swp
_obj
_test
testdata

@ -0,0 +1,25 @@
Copyright (c) 2011, Evan Shaw <edsrzf@gmail.com>
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.
* Neither the name of the copyright holder nor the
names of its contributors may 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 <COPYRIGHT HOLDER> 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,12 @@
mmap-go
=======
mmap-go is a portable mmap package for the [Go programming language](http://golang.org).
It has been tested on Linux (386, amd64), OS X, and Windows (386). It should also
work on other Unix-like platforms, but hasn't been tested with them. I'm interested
to hear about the results.
I haven't been able to add more features without adding significant complexity,
so mmap-go doesn't support mprotect, mincore, and maybe a few other things.
If you're running on a Unix-like platform and need some of these features,
I suggest Gustavo Niemeyer's [gommap](http://labix.org/gommap).

@ -0,0 +1,117 @@
// Copyright 2011 Evan Shaw. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file defines the common package interface and contains a little bit of
// factored out logic.
// Package mmap allows mapping files into memory. It tries to provide a simple, reasonably portable interface,
// but doesn't go out of its way to abstract away every little platform detail.
// This specifically means:
// * forked processes may or may not inherit mappings
// * a file's timestamp may or may not be updated by writes through mappings
// * specifying a size larger than the file's actual size can increase the file's size
// * If the mapped file is being modified by another process while your program's running, don't expect consistent results between platforms
package mmap
import (
"errors"
"os"
"reflect"
"unsafe"
)
const (
// RDONLY maps the memory read-only.
// Attempts to write to the MMap object will result in undefined behavior.
RDONLY = 0
// RDWR maps the memory as read-write. Writes to the MMap object will update the
// underlying file.
RDWR = 1 << iota
// COPY maps the memory as copy-on-write. Writes to the MMap object will affect
// memory, but the underlying file will remain unchanged.
COPY
// If EXEC is set, the mapped memory is marked as executable.
EXEC
)
const (
// If the ANON flag is set, the mapped memory will not be backed by a file.
ANON = 1 << iota
)
// MMap represents a file mapped into memory.
type MMap []byte
// Map maps an entire file into memory.
// If ANON is set in flags, f is ignored.
func Map(f *os.File, prot, flags int) (MMap, error) {
return MapRegion(f, -1, prot, flags, 0)
}
// MapRegion maps part of a file into memory.
// The offset parameter must be a multiple of the system's page size.
// If length < 0, the entire file will be mapped.
// If ANON is set in flags, f is ignored.
func MapRegion(f *os.File, length int, prot, flags int, offset int64) (MMap, error) {
if offset%int64(os.Getpagesize()) != 0 {
return nil, errors.New("offset parameter must be a multiple of the system's page size")
}
var fd uintptr
if flags&ANON == 0 {
fd = uintptr(f.Fd())
if length < 0 {
fi, err := f.Stat()
if err != nil {
return nil, err
}
length = int(fi.Size())
}
} else {
if length <= 0 {
return nil, errors.New("anonymous mapping requires non-zero length")
}
fd = ^uintptr(0)
}
return mmap(length, uintptr(prot), uintptr(flags), fd, offset)
}
func (m *MMap) header() *reflect.SliceHeader {
return (*reflect.SliceHeader)(unsafe.Pointer(m))
}
func (m *MMap) addrLen() (uintptr, uintptr) {
header := m.header()
return header.Data, uintptr(header.Len)
}
// Lock keeps the mapped region in physical memory, ensuring that it will not be
// swapped out.
func (m MMap) Lock() error {
return m.lock()
}
// Unlock reverses the effect of Lock, allowing the mapped region to potentially
// be swapped out.
// If m is already unlocked, aan error will result.
func (m MMap) Unlock() error {
return m.unlock()
}
// Flush synchronizes the mapping's contents to the file's contents on disk.
func (m MMap) Flush() error {
return m.flush()
}
// Unmap deletes the memory mapped region, flushes any remaining changes, and sets
// m to nil.
// Trying to read or write any remaining references to m after Unmap is called will
// result in undefined behavior.
// Unmap should only be called on the slice value that was originally returned from
// a call to Map. Calling Unmap on a derived slice may cause errors.
func (m *MMap) Unmap() error {
err := m.unmap()
*m = nil
return err
}

@ -0,0 +1,51 @@
// Copyright 2011 Evan Shaw. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin dragonfly freebsd linux openbsd solaris netbsd
package mmap
import (
"golang.org/x/sys/unix"
)
func mmap(len int, inprot, inflags, fd uintptr, off int64) ([]byte, error) {
flags := unix.MAP_SHARED
prot := unix.PROT_READ
switch {
case inprot&COPY != 0:
prot |= unix.PROT_WRITE
flags = unix.MAP_PRIVATE
case inprot&RDWR != 0:
prot |= unix.PROT_WRITE
}
if inprot&EXEC != 0 {
prot |= unix.PROT_EXEC
}
if inflags&ANON != 0 {
flags |= unix.MAP_ANON
}
b, err := unix.Mmap(int(fd), off, len, prot, flags)
if err != nil {
return nil, err
}
return b, nil
}
func (m MMap) flush() error {
return unix.Msync([]byte(m), unix.MS_SYNC)
}
func (m MMap) lock() error {
return unix.Mlock([]byte(m))
}
func (m MMap) unlock() error {
return unix.Munlock([]byte(m))
}
func (m MMap) unmap() error {
return unix.Munmap([]byte(m))
}

@ -0,0 +1,143 @@
// Copyright 2011 Evan Shaw. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mmap
import (
"errors"
"os"
"sync"
"golang.org/x/sys/windows"
)
// mmap on Windows is a two-step process.
// First, we call CreateFileMapping to get a handle.
// Then, we call MapviewToFile to get an actual pointer into memory.
// Because we want to emulate a POSIX-style mmap, we don't want to expose
// the handle -- only the pointer. We also want to return only a byte slice,
// not a struct, so it's convenient to manipulate.
// We keep this map so that we can get back the original handle from the memory address.
type addrinfo struct {
file windows.Handle
mapview windows.Handle
}
var handleLock sync.Mutex
var handleMap = map[uintptr]*addrinfo{}
func mmap(len int, prot, flags, hfile uintptr, off int64) ([]byte, error) {
flProtect := uint32(windows.PAGE_READONLY)
dwDesiredAccess := uint32(windows.FILE_MAP_READ)
switch {
case prot&COPY != 0:
flProtect = windows.PAGE_WRITECOPY
dwDesiredAccess = windows.FILE_MAP_COPY
case prot&RDWR != 0:
flProtect = windows.PAGE_READWRITE
dwDesiredAccess = windows.FILE_MAP_WRITE
}
if prot&EXEC != 0 {
flProtect <<= 4
dwDesiredAccess |= windows.FILE_MAP_EXECUTE
}
// The maximum size is the area of the file, starting from 0,
// that we wish to allow to be mappable. It is the sum of
// the length the user requested, plus the offset where that length
// is starting from. This does not map the data into memory.
maxSizeHigh := uint32((off + int64(len)) >> 32)
maxSizeLow := uint32((off + int64(len)) & 0xFFFFFFFF)
// TODO: Do we need to set some security attributes? It might help portability.
h, errno := windows.CreateFileMapping(windows.Handle(hfile), nil, flProtect, maxSizeHigh, maxSizeLow, nil)
if h == 0 {
return nil, os.NewSyscallError("CreateFileMapping", errno)
}
// Actually map a view of the data into memory. The view's size
// is the length the user requested.
fileOffsetHigh := uint32(off >> 32)
fileOffsetLow := uint32(off & 0xFFFFFFFF)
addr, errno := windows.MapViewOfFile(h, dwDesiredAccess, fileOffsetHigh, fileOffsetLow, uintptr(len))
if addr == 0 {
return nil, os.NewSyscallError("MapViewOfFile", errno)
}
handleLock.Lock()
handleMap[addr] = &addrinfo{
file: windows.Handle(hfile),
mapview: h,
}
handleLock.Unlock()
m := MMap{}
dh := m.header()
dh.Data = addr
dh.Len = len
dh.Cap = dh.Len
return m, nil
}
func (m MMap) flush() error {
addr, len := m.addrLen()
errno := windows.FlushViewOfFile(addr, len)
if errno != nil {
return os.NewSyscallError("FlushViewOfFile", errno)
}
handleLock.Lock()
defer handleLock.Unlock()
handle, ok := handleMap[addr]
if !ok {
// should be impossible; we would've errored above
return errors.New("unknown base address")
}
errno = windows.FlushFileBuffers(handle.file)
return os.NewSyscallError("FlushFileBuffers", errno)
}
func (m MMap) lock() error {
addr, len := m.addrLen()
errno := windows.VirtualLock(addr, len)
return os.NewSyscallError("VirtualLock", errno)
}
func (m MMap) unlock() error {
addr, len := m.addrLen()
errno := windows.VirtualUnlock(addr, len)
return os.NewSyscallError("VirtualUnlock", errno)
}
func (m MMap) unmap() error {
err := m.flush()
if err != nil {
return err
}
addr := m.header().Data
// Lock the UnmapViewOfFile along with the handleMap deletion.
// As soon as we unmap the view, the OS is free to give the
// same addr to another new map. We don't want another goroutine
// to insert and remove the same addr into handleMap while
// we're trying to remove our old addr/handle pair.
handleLock.Lock()
defer handleLock.Unlock()
err = windows.UnmapViewOfFile(addr)
if err != nil {
return err
}
handle, ok := handleMap[addr]
if !ok {
// should be impossible; we would've errored above
return errors.New("unknown base address")
}
delete(handleMap, addr)
e := windows.CloseHandle(windows.Handle(handle.mapview))
return os.NewSyscallError("CloseHandle", e)
}

@ -0,0 +1,4 @@
*.sw?
*.db
*.tmp
generated_*.go

@ -0,0 +1,20 @@
Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
MIT License
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,42 @@
SHELL ?= /bin/bash
PARALLEL_FLAGS ?= --halt-on-error 2 --jobs=4 -v -u
TEST_FLAGS ?=
UPPER_DB_LOG ?= WARN
export TEST_FLAGS
export PARALLEL_FLAGS
export UPPER_DB_LOG
test: go-test-internal test-adapters
benchmark: go-benchmark-internal
go-benchmark-%:
go test -v -benchtime=500ms -bench=. ./$*/...
go-test-%:
go test -v ./$*/...
test-adapters: \
test-adapter-postgresql \
test-adapter-cockroachdb \
test-adapter-mysql \
test-adapter-mssql \
test-adapter-sqlite \
test-adapter-ql \
test-adapter-mongo
test-adapter-%:
($(MAKE) -C adapter/$* test-extended || exit 1)
test-generic:
export TEST_FLAGS="-run TestGeneric"; \
$(MAKE) test-adapters
goimports:
for FILE in $$(find -name "*.go" | grep -v vendor); do \
goimports -w $$FILE; \
done

@ -0,0 +1,37 @@
<p align="center">
<img src="https://upper.io/img/gopher.svg" width="256">
</p>
<p align="center">
<a href="https://github.com/upper/db/actions?query=workflow%3Aunit-tests"><img alt="upper/db unit tests status" src="https://github.com/upper/db/workflows/unit-tests/badge.svg"></a>
</p>
# upper/db
`upper/db` is a productive data access layer (DAL) for [Go](https://golang.org)
that provides agnostic tools to work with different data sources, such as:
* [PostgreSQL](https://upper.io/v4/adapter/postgresql)
* [MySQL](https://upper.io/v4/adapter/mysql)
* [MSSQL](https://upper.io/v4/adapter/mssql)
* [CockroachDB](https://upper.io/v4/adapter/cockroachdb)
* [MongoDB](https://upper.io/v4/adapter/mongo)
* [QL](https://upper.io/v4/adapter/ql)
* [SQLite](https://upper.io/v4/adapter/sqlite)
See [upper.io/v4](//upper.io/v4) for documentation and code samples.
## The tour
![tour](https://user-images.githubusercontent.com/385670/91495824-c6fabb00-e880-11ea-925b-a30b94474610.png)
Take the [tour](https://tour.upper.io) to see real live examples in your
browser.
## License
Licensed under [MIT License](./LICENSE)
## Contributors
See the [list of contributors](https://github.com/upper/db/graphs/contributors).

@ -0,0 +1,75 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package db
import (
"fmt"
"sync"
)
var (
adapterMap = make(map[string]Adapter)
adapterMapMu sync.RWMutex
)
// Adapter interface defines an adapter
type Adapter interface {
Open(ConnectionURL) (Session, error)
}
type missingAdapter struct {
name string
}
func (ma *missingAdapter) Open(ConnectionURL) (Session, error) {
return nil, fmt.Errorf("upper: Missing adapter %q, did you forget to import it?", ma.name)
}
// RegisterAdapter registers a generic database adapter.
func RegisterAdapter(name string, adapter Adapter) {
adapterMapMu.Lock()
defer adapterMapMu.Unlock()
if name == "" {
panic(`Missing adapter name`)
}
if _, ok := adapterMap[name]; ok {
panic(`db.RegisterAdapter() called twice for adapter: ` + name)
}
adapterMap[name] = adapter
}
// LookupAdapter returns a previously registered adapter by name.
func LookupAdapter(name string) Adapter {
adapterMapMu.RLock()
defer adapterMapMu.RUnlock()
if adapter, ok := adapterMap[name]; ok {
return adapter
}
return &missingAdapter{name: name}
}
// Open attempts to stablish a connection with a database.
func Open(adapterName string, settings ConnectionURL) (Session, error) {
return LookupAdapter(adapterName).Open(settings)
}

@ -0,0 +1,49 @@
SHELL ?= bash
COCKROACHDB_VERSION ?= v21.2.2
COCKROACHDB_SUPPORTED ?= $(COCKROACHDB_VERSION) v20.2.18 v19.2.12
PROJECT ?= upper_cockroachdb_$(COCKROACHDB_VERSION)
DB_HOST ?= 127.0.0.1
DB_PORT ?= 26257
DB_NAME ?= upperio
DB_USERNAME ?= upperio_user
TEST_FLAGS ?=
PARALLEL_FLAGS ?= --halt-on-error 2 --jobs 1
export COCKROACHDB_VERSION
export DB_HOST
export DB_NAME
export DB_PASSWORD
export DB_PORT
export DB_USERNAME
export TEST_FLAGS
test:
go test -v -failfast -race -timeout 20m $(TEST_FLAGS)
test-no-race:
go test -v -failfast $(TEST_FLAGS)
server-up: server-down
docker-compose -p $(PROJECT) up -d && \
sleep 5 && \
psql -Uroot -h$(DB_HOST) -p$(DB_PORT) postgres -c "\
DROP USER IF EXISTS $(DB_USERNAME); \
DROP DATABASE IF EXISTS $(DB_NAME); \
CREATE DATABASE $(DB_NAME); \
CREATE USER $(DB_USERNAME); \
GRANT ALL ON DATABASE $(DB_NAME) TO $(DB_USERNAME); \
"
server-down:
docker-compose -p $(PROJECT) down
test-extended:
parallel $(PARALLEL_FLAGS) \
"COCKROACHDB_VERSION={} DB_PORT=\$$(($(DB_PORT)+{#})) $(MAKE) server-up test server-down" ::: \
$(COCKROACHDB_SUPPORTED)

@ -0,0 +1,4 @@
# CockroachDB adapter for upper/db
Please read the full docs, acknowledgements and examples at
[https://upper.io/v4/adapter/cockroachdb/](https://upper.io/v4/adapter/cockroachdb/).

@ -0,0 +1,51 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package cockroachdb
import (
"database/sql"
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
"github.com/upper/db/v4/internal/sqlbuilder"
)
// Adapter is the unique name that you can use to refer to this adapter.
const Adapter = `cockroachdb`
var registeredAdapter = sqladapter.RegisterAdapter(Adapter, &database{})
// Open establishes a connection to the database server and returns a
// db.Session instance (which is compatible with db.Session).
func Open(connURL db.ConnectionURL) (db.Session, error) {
return registeredAdapter.OpenDSN(connURL)
}
// NewTx creates a sqlbuilder.Tx instance by wrapping a *sql.Tx value.
func NewTx(sqlTx *sql.Tx) (sqlbuilder.Tx, error) {
return registeredAdapter.NewTx(sqlTx)
}
// New creates a sqlbuilder.Sesion instance by wrapping a *sql.DB value.
func New(sqlDB *sql.DB) (db.Session, error) {
return registeredAdapter.New(sqlDB)
}

@ -0,0 +1,71 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package cockroachdb
import (
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
)
type collectionAdapter struct {
}
func (*collectionAdapter) Insert(col sqladapter.Collection, item interface{}) (interface{}, error) {
pKey, err := col.PrimaryKeys()
if err != nil {
return nil, err
}
q := col.SQL().InsertInto(col.Name()).Values(item)
if len(pKey) == 0 {
// There is no primary key.
res, err := q.Exec()
if err != nil {
return nil, err
}
// Attempt to use LastInsertId() (probably won't work, but the Exec()
// succeeded, so we can safely ignore the error from LastInsertId()).
lastID, err := res.LastInsertId()
if err != nil {
return 0, nil
}
return lastID, nil
}
// Asking the database to return the primary key after insertion.
q = q.Returning(pKey...)
var keyMap db.Cond
if err := q.Iterator().One(&keyMap); err != nil {
return nil, err
}
// The IDSetter interface does not match, look for another interface match.
if len(keyMap) == 1 {
return keyMap[pKey[0]], nil
}
// This was a compound key and no interface matched it, let's return a map.
return keyMap, nil
}

@ -0,0 +1,229 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package cockroachdb
import (
"fmt"
"strings"
"unicode"
"github.com/lib/pq"
)
// scanner implements a tokenizer for libpq-style option strings.
type scanner struct {
s []rune
i int
}
// Next returns the next rune. It returns 0, false if the end of the text has
// been reached.
func (s *scanner) Next() (rune, bool) {
if s.i >= len(s.s) {
return 0, false
}
r := s.s[s.i]
s.i++
return r, true
}
// SkipSpaces returns the next non-whitespace rune. It returns 0, false if the
// end of the text has been reached.
func (s *scanner) SkipSpaces() (rune, bool) {
r, ok := s.Next()
for unicode.IsSpace(r) && ok {
r, ok = s.Next()
}
return r, ok
}
type values map[string]string
func (vs values) Set(k, v string) {
vs[k] = v
}
func (vs values) Get(k string) (v string) {
return vs[k]
}
func (vs values) Isset(k string) bool {
_, ok := vs[k]
return ok
}
// ConnectionURL represents a parsed CockroachDB connection URL.
//
// You can use a ConnectionURL struct as an argument for Open:
//
// var settings = cockroachdb.ConnectionURL{
// Host: "localhost", // Database server IP or host name.
// Database: "peanuts", // Database name.
// User: "cbrown", // Optional user name.
// Password: "snoopy", // Optional user password.
// }
//
// sess, err = cockroachdb.Open(settings)
//
// If you already have a valid DSN, you can use ParseURL to convert it into
// a ConnectionURL before passing it to Open.
type ConnectionURL struct {
User string
Password string
Host string
Socket string
Database string
Options map[string]string
}
var escaper = strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`)
// ParseURL parses the given DSN into a ConnectionURL struct.
func ParseURL(s string) (u ConnectionURL, err error) {
o := make(values)
if strings.HasPrefix(s, "postgres://") {
s, err = pq.ParseURL(s)
if err != nil {
return u, err
}
}
if err := parseOpts(s, o); err != nil {
return u, err
}
u.User = o.Get("user")
u.Password = o.Get("password")
h := o.Get("host")
p := o.Get("port")
if strings.HasPrefix(h, "/") {
u.Socket = h
} else {
if p == "" {
u.Host = fmt.Sprintf("%s:26257", h)
} else {
u.Host = fmt.Sprintf("%s:%s", h, p)
}
}
u.Database = o.Get("dbname")
u.Options = make(map[string]string)
for k := range o {
switch k {
case "user", "password", "host", "port", "dbname":
// Skip
default:
u.Options[k] = o[k]
}
}
return u, err
}
// parseOpts parses the options from name and adds them to the values.
//
// The parsing code is based on conninfo_parse from libpq's fe-connect.c
func parseOpts(name string, o values) error {
s := newScanner(name)
for {
var (
keyRunes, valRunes []rune
r rune
ok bool
)
if r, ok = s.SkipSpaces(); !ok {
break
}
// Scan the key
for !unicode.IsSpace(r) && r != '=' {
keyRunes = append(keyRunes, r)
if r, ok = s.Next(); !ok {
break
}
}
// Skip any whitespace if we're not at the = yet
if r != '=' {
r, ok = s.SkipSpaces()
}
// The current character should be =
if r != '=' || !ok {
return fmt.Errorf(`missing "=" after %q in connection info string"`, string(keyRunes))
}
// Skip any whitespace after the =
if r, ok = s.SkipSpaces(); !ok {
// If we reach the end here, the last value is just an empty string as per libpq.
o.Set(string(keyRunes), "")
break
}
if r != '\'' {
for !unicode.IsSpace(r) {
if r == '\\' {
if r, ok = s.Next(); !ok {
return fmt.Errorf(`missing character after backslash`)
}
}
valRunes = append(valRunes, r)
if r, ok = s.Next(); !ok {
break
}
}
} else {
quote:
for {
if r, ok = s.Next(); !ok {
return fmt.Errorf(`unterminated quoted string literal in connection string`)
}
switch r {
case '\'':
break quote
case '\\':
r, _ = s.Next()
fallthrough
default:
valRunes = append(valRunes, r)
}
}
}
o.Set(string(keyRunes), string(valRunes))
}
return nil
}
// newScanner returns a new scanner initialized with the option string s.
func newScanner(s string) *scanner {
return &scanner{[]rune(s), 0}
}

@ -0,0 +1,94 @@
//go:build !pq
// +build !pq
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package cockroachdb
import (
"net"
"sort"
"strings"
)
// String reassembles the parsed PostgreSQL connection URL into a valid DSN.
func (c ConnectionURL) String() (s string) {
u := []string{}
// TODO: This surely needs some sort of escaping.
if c.User != "" {
u = append(u, "user="+escaper.Replace(c.User))
}
if c.Password != "" {
u = append(u, "password="+escaper.Replace(c.Password))
}
if c.Host != "" {
host, port, err := net.SplitHostPort(c.Host)
if err == nil {
if host == "" {
host = "127.0.0.1"
}
u = append(u, "host="+escaper.Replace(host))
} else {
u = append(u, "host="+escaper.Replace(c.Host))
}
if port == "" {
port = "26257"
}
u = append(u, "port="+escaper.Replace(port))
}
if c.Socket != "" {
u = append(u, "host="+escaper.Replace(c.Socket))
}
if c.Database != "" {
u = append(u, "dbname="+escaper.Replace(c.Database))
}
// Is there actually any connection data?
if len(u) == 0 {
return ""
}
if c.Options == nil {
c.Options = map[string]string{}
}
// If not present, SSL mode is assumed "prefer".
if sslMode, ok := c.Options["sslmode"]; !ok || sslMode == "" {
c.Options["sslmode"] = "prefer"
}
// Disabled by default
c.Options["statement_cache_capacity"] = "0"
for k, v := range c.Options {
u = append(u, escaper.Replace(k)+"="+escaper.Replace(v))
}
sort.Strings(u)
return strings.Join(u, " ")
}

@ -0,0 +1,91 @@
//go:build pq
// +build pq
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package cockroachdb
import (
"net"
"sort"
"strings"
)
// String reassembles the parsed PostgreSQL connection URL into a valid DSN.
func (c ConnectionURL) String() (s string) {
u := []string{}
// TODO: This surely needs some sort of escaping.
if c.User != "" {
u = append(u, "user="+escaper.Replace(c.User))
}
if c.Password != "" {
u = append(u, "password="+escaper.Replace(c.Password))
}
if c.Host != "" {
host, port, err := net.SplitHostPort(c.Host)
if err == nil {
if host == "" {
host = "127.0.0.1"
}
u = append(u, "host="+escaper.Replace(host))
} else {
u = append(u, "host="+escaper.Replace(c.Host))
}
if port == "" {
port = "26257"
}
u = append(u, "port="+escaper.Replace(port))
}
if c.Socket != "" {
u = append(u, "host="+escaper.Replace(c.Socket))
}
if c.Database != "" {
u = append(u, "dbname="+escaper.Replace(c.Database))
}
// Is there actually any connection data?
if len(u) == 0 {
return ""
}
if c.Options == nil {
c.Options = map[string]string{}
}
// If not present, SSL mode is assumed "prefer".
if sslMode, ok := c.Options["sslmode"]; !ok || sslMode == "" {
c.Options["sslmode"] = "prefer"
}
for k, v := range c.Options {
u = append(u, escaper.Replace(k)+"="+escaper.Replace(v))
}
sort.Strings(u)
return strings.Join(u, " ")
}

@ -0,0 +1,167 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package cockroachdb
import (
"context"
"database/sql"
"database/sql/driver"
"time"
"github.com/upper/db/v4/internal/sqlbuilder"
)
// JSONBMap represents a map of interfaces with string keys
// (`map[string]interface{}`) that is compatible with PostgreSQL's JSONB type.
// JSONBMap satisfies sqlbuilder.ScannerValuer.
type JSONBMap map[string]interface{}
// Value satisfies the driver.Valuer interface.
func (m JSONBMap) Value() (driver.Value, error) {
return JSONBValue(m)
}
// Scan satisfies the sql.Scanner interface.
func (m *JSONBMap) Scan(src interface{}) error {
*m = map[string]interface{}(nil)
return ScanJSONB(m, src)
}
// JSONBArray represents an array of any type (`[]interface{}`) that is
// compatible with PostgreSQL's JSONB type. JSONBArray satisfies
// sqlbuilder.ScannerValuer.
type JSONBArray []interface{}
// Value satisfies the driver.Valuer interface.
func (a JSONBArray) Value() (driver.Value, error) {
return JSONBValue(a)
}
// Scan satisfies the sql.Scanner interface.
func (a *JSONBArray) Scan(src interface{}) error {
return ScanJSONB(a, src)
}
// JSONBValue takes an interface and provides a driver.Value that can be
// stored as a JSONB column.
func JSONBValue(i interface{}) (driver.Value, error) {
v := JSONB{i}
return v.Value()
}
// ScanJSONB decodes a JSON byte stream into the passed dst value.
func ScanJSONB(dst interface{}, src interface{}) error {
v := JSONB{dst}
return v.Scan(src)
}
// EncodeJSONB is deprecated and going to be removed. Use ScanJSONB instead.
func EncodeJSONB(i interface{}) (driver.Value, error) {
return JSONBValue(i)
}
// DecodeJSONB is deprecated and going to be removed. Use JSONBValue instead.
func DecodeJSONB(dst interface{}, src interface{}) error {
return ScanJSONB(dst, src)
}
// JSONBConverter provides a helper method WrapValue that satisfies
// sqlbuilder.ValueWrapper, can be used to encode Go structs into JSONB
// PostgreSQL types and vice versa.
//
// Example:
//
// type MyCustomStruct struct {
// ID int64 `db:"id" json:"id"`
// Name string `db:"name" json:"name"`
// ...
// cockroachdb.JSONBConverter
// }
type JSONBConverter struct {
}
func (*JSONBConverter) ConvertValue(src interface{}) interface {
sql.Scanner
driver.Valuer
} {
return &JSONB{src}
}
type timeWrapper struct {
v **time.Time
loc *time.Location
}
func (t timeWrapper) Value() (driver.Value, error) {
if *t.v != nil {
return **t.v, nil
}
return nil, nil
}
func (t *timeWrapper) Scan(src interface{}) error {
if src == nil {
nilTime := (*time.Time)(nil)
if t.v == nil {
t.v = &nilTime
} else {
*(t.v) = nilTime
}
return nil
}
tz := src.(time.Time)
if t.loc != nil && (tz.Location() == time.Local) {
tz = tz.In(t.loc)
}
if tz.Location().String() == "" {
tz = tz.In(time.UTC)
}
if *(t.v) == nil {
*(t.v) = &tz
} else {
**t.v = tz
}
return nil
}
func (d *database) ConvertValueContext(ctx context.Context, in interface{}) interface{} {
tz, _ := ctx.Value("timezone").(*time.Location)
switch v := in.(type) {
case *time.Time:
return &timeWrapper{&v, tz}
case **time.Time:
return &timeWrapper{v, tz}
}
return d.ConvertValue(in)
}
// Type checks.
var (
_ sqlbuilder.ScannerValuer = &StringArray{}
_ sqlbuilder.ScannerValuer = &Int64Array{}
_ sqlbuilder.ScannerValuer = &Float64Array{}
_ sqlbuilder.ScannerValuer = &BoolArray{}
_ sqlbuilder.ScannerValuer = &JSONBMap{}
_ sqlbuilder.ScannerValuer = &JSONBArray{}
)

@ -0,0 +1,306 @@
// +build !pq
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package cockroachdb
import (
"database/sql/driver"
"github.com/jackc/pgtype"
)
// JSONB represents a PostgreSQL's JSONB value:
// https://www.postgresql.org/docs/9.6/static/datatype-json.html. JSONB
// satisfies sqlbuilder.ScannerValuer.
type JSONB struct {
Data interface{}
}
// MarshalJSON encodes the wrapper value as JSON.
func (j JSONB) MarshalJSON() ([]byte, error) {
t := &pgtype.JSONB{}
if err := t.Set(j.Data); err != nil {
return nil, err
}
return t.MarshalJSON()
}
// UnmarshalJSON decodes the given JSON into the wrapped value.
func (j *JSONB) UnmarshalJSON(b []byte) error {
t := &pgtype.JSONB{}
if err := t.UnmarshalJSON(b); err != nil {
return err
}
if j.Data == nil {
j.Data = t.Get()
return nil
}
if err := t.AssignTo(&j.Data); err != nil {
return err
}
return nil
}
// Scan satisfies the sql.Scanner interface.
func (j *JSONB) Scan(src interface{}) error {
t := &pgtype.JSONB{}
if err := t.Scan(src); err != nil {
return err
}
if j.Data == nil {
j.Data = t.Get()
return nil
}
if err := t.AssignTo(j.Data); err != nil {
return err
}
return nil
}
// Value satisfies the driver.Valuer interface.
func (j JSONB) Value() (driver.Value, error) {
t := &pgtype.JSONB{}
if err := t.Set(j.Data); err != nil {
return nil, err
}
return t.Value()
}
// StringArray represents a one-dimensional array of strings (`[]string{}`)
// that is compatible with PostgreSQL's text array (`text[]`). StringArray
// satisfies sqlbuilder.ScannerValuer.
type StringArray []string
// Value satisfies the driver.Valuer interface.
func (a StringArray) Value() (driver.Value, error) {
t := pgtype.TextArray{}
if err := t.Set(a); err != nil {
return nil, err
}
return t.Value()
}
// Scan satisfies the sql.Scanner interface.
func (sa *StringArray) Scan(src interface{}) error {
d := []string{}
t := pgtype.TextArray{}
if err := t.Scan(src); err != nil {
return err
}
if err := t.AssignTo(&d); err != nil {
return err
}
*sa = StringArray(d)
return nil
}
type Bytea []byte
func (b Bytea) Value() (driver.Value, error) {
t := pgtype.Bytea{Bytes: b}
if err := t.Set(b); err != nil {
return nil, err
}
return t.Value()
}
func (b *Bytea) Scan(src interface{}) error {
d := []byte{}
t := pgtype.Bytea{}
if err := t.Scan(src); err != nil {
return err
}
if err := t.AssignTo(&d); err != nil {
return err
}
*b = Bytea(d)
return nil
}
// ByteaArray represents a one-dimensional array of strings (`[]string{}`)
// that is compatible with PostgreSQL's text array (`text[]`). ByteaArray
// satisfies sqlbuilder.ScannerValuer.
type ByteaArray [][]byte
// Value satisfies the driver.Valuer interface.
func (a ByteaArray) Value() (driver.Value, error) {
t := pgtype.ByteaArray{}
if err := t.Set(a); err != nil {
return nil, err
}
return t.Value()
}
// Scan satisfies the sql.Scanner interface.
func (ba *ByteaArray) Scan(src interface{}) error {
d := [][]byte{}
t := pgtype.ByteaArray{}
if err := t.Scan(src); err != nil {
return err
}
if err := t.AssignTo(&d); err != nil {
return err
}
*ba = ByteaArray(d)
return nil
}
// Int64Array represents a one-dimensional array of int64s (`[]int64{}`) that
// is compatible with PostgreSQL's integer array (`integer[]`). Int64Array
// satisfies sqlbuilder.ScannerValuer.
type Int64Array []int64
// Value satisfies the driver.Valuer interface.
func (i64a Int64Array) Value() (driver.Value, error) {
t := pgtype.Int8Array{}
if err := t.Set(i64a); err != nil {
return nil, err
}
return t.Value()
}
// Scan satisfies the sql.Scanner interface.
func (i64a *Int64Array) Scan(src interface{}) error {
d := []int64{}
t := pgtype.Int8Array{}
if err := t.Scan(src); err != nil {
return err
}
if err := t.AssignTo(&d); err != nil {
return err
}
*i64a = Int64Array(d)
return nil
}
// Int32Array represents a one-dimensional array of int32s (`[]int32{}`) that
// is compatible with PostgreSQL's integer array (`integer[]`). Int32Array
// satisfies sqlbuilder.ScannerValuer.
type Int32Array []int32
// Value satisfies the driver.Valuer interface.
func (i32a Int32Array) Value() (driver.Value, error) {
t := pgtype.Int4Array{}
if err := t.Set(i32a); err != nil {
return nil, err
}
return t.Value()
}
// Scan satisfies the sql.Scanner interface.
func (i32a *Int32Array) Scan(src interface{}) error {
d := []int32{}
t := pgtype.Int4Array{}
if err := t.Scan(src); err != nil {
return err
}
if err := t.AssignTo(&d); err != nil {
return err
}
*i32a = Int32Array(d)
return nil
}
// Float64Array represents a one-dimensional array of float64s (`[]float64{}`)
// that is compatible with PostgreSQL's double precision array (`double
// precision[]`). Float64Array satisfies sqlbuilder.ScannerValuer.
type Float64Array []float64
// Value satisfies the driver.Valuer interface.
func (f64a Float64Array) Value() (driver.Value, error) {
t := pgtype.Float8Array{}
if err := t.Set(f64a); err != nil {
return nil, err
}
return t.Value()
}
// Scan satisfies the sql.Scanner interface.
func (f64a *Float64Array) Scan(src interface{}) error {
d := []float64{}
t := pgtype.Float8Array{}
if err := t.Scan(src); err != nil {
return err
}
if err := t.AssignTo(&d); err != nil {
return err
}
*f64a = Float64Array(d)
return nil
}
// Float32Array represents a one-dimensional array of float32s (`[]float32{}`)
// that is compatible with PostgreSQL's double precision array (`double
// precision[]`). Float32Array satisfies sqlbuilder.ScannerValuer.
type Float32Array []float32
// Value satisfies the driver.Valuer interface.
func (f32a Float32Array) Value() (driver.Value, error) {
t := pgtype.Float8Array{}
if err := t.Set(f32a); err != nil {
return nil, err
}
return t.Value()
}
// Scan satisfies the sql.Scanner interface.
func (f32a *Float32Array) Scan(src interface{}) error {
d := []float32{}
t := pgtype.Float8Array{}
if err := t.Scan(src); err != nil {
return err
}
if err := t.AssignTo(&d); err != nil {
return err
}
*f32a = Float32Array(d)
return nil
}
// BoolArray represents a one-dimensional array of int64s (`[]bool{}`) that
// is compatible with PostgreSQL's boolean type (`boolean[]`). BoolArray
// satisfies sqlbuilder.ScannerValuer.
type BoolArray []bool
// Value satisfies the driver.Valuer interface.
func (ba BoolArray) Value() (driver.Value, error) {
t := pgtype.BoolArray{}
if err := t.Set(ba); err != nil {
return nil, err
}
return t.Value()
}
// Scan satisfies the sql.Scanner interface.
func (ba *BoolArray) Scan(src interface{}) error {
d := []bool{}
t := pgtype.BoolArray{}
if err := t.Scan(src); err != nil {
return err
}
if err := t.AssignTo(&d); err != nil {
return err
}
*ba = BoolArray(d)
return nil
}

@ -0,0 +1,269 @@
// +build pq
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package cockroachdb
import (
"bytes"
"database/sql/driver"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"reflect"
"strconv"
"time"
"github.com/lib/pq"
)
// JSONB represents a PostgreSQL's JSONB value:
// https://www.postgresql.org/docs/9.6/static/datatype-json.html. JSONB
// satisfies sqlbuilder.ScannerValuer.
type JSONB struct {
Data interface{}
}
// MarshalJSON encodes the wrapper value as JSON.
func (j JSONB) MarshalJSON() ([]byte, error) {
return json.Marshal(j.Data)
}
// UnmarshalJSON decodes the given JSON into the wrapped value.
func (j *JSONB) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
j.Data = v
return nil
}
// Scan satisfies the sql.Scanner interface.
func (j *JSONB) Scan(src interface{}) error {
if j.Data == nil {
return nil
}
if src == nil {
dv := reflect.Indirect(reflect.ValueOf(j.Data))
dv.Set(reflect.Zero(dv.Type()))
return nil
}
b, ok := src.([]byte)
if !ok {
return errors.New("Scan source was not []bytes")
}
if err := json.Unmarshal(b, j.Data); err != nil {
return err
}
return nil
}
// Value satisfies the driver.Valuer interface.
func (j JSONB) Value() (driver.Value, error) {
// See https://github.com/lib/pq/issues/528#issuecomment-257197239 on why are
// we returning string instead of []byte.
if j.Data == nil {
return nil, nil
}
if v, ok := j.Data.(json.RawMessage); ok {
return string(v), nil
}
b, err := json.Marshal(j.Data)
if err != nil {
return nil, err
}
return string(b), nil
}
// StringArray represents a one-dimensional array of strings (`[]string{}`)
// that is compatible with PostgreSQL's text array (`text[]`). StringArray
// satisfies sqlbuilder.ScannerValuer.
type StringArray pq.StringArray
// Value satisfies the driver.Valuer interface.
func (a StringArray) Value() (driver.Value, error) {
return pq.StringArray(a).Value()
}
// Scan satisfies the sql.Scanner interface.
func (a *StringArray) Scan(src interface{}) error {
s := pq.StringArray(*a)
if err := s.Scan(src); err != nil {
return err
}
*a = StringArray(s)
return nil
}
// Int64Array represents a one-dimensional array of int64s (`[]int64{}`) that
// is compatible with PostgreSQL's integer array (`integer[]`). Int64Array
// satisfies sqlbuilder.ScannerValuer.
type Int64Array pq.Int64Array
// Value satisfies the driver.Valuer interface.
func (i Int64Array) Value() (driver.Value, error) {
return pq.Int64Array(i).Value()
}
// Scan satisfies the sql.Scanner interface.
func (i *Int64Array) Scan(src interface{}) error {
s := pq.Int64Array(*i)
if err := s.Scan(src); err != nil {
return err
}
*i = Int64Array(s)
return nil
}
// Float64Array represents a one-dimensional array of float64s (`[]float64{}`)
// that is compatible with PostgreSQL's double precision array (`double
// precision[]`). Float64Array satisfies sqlbuilder.ScannerValuer.
type Float64Array pq.Float64Array
// Value satisfies the driver.Valuer interface.
func (f Float64Array) Value() (driver.Value, error) {
return pq.Float64Array(f).Value()
}
// Scan satisfies the sql.Scanner interface.
func (f *Float64Array) Scan(src interface{}) error {
s := pq.Float64Array(*f)
if err := s.Scan(src); err != nil {
return err
}
*f = Float64Array(s)
return nil
}
// Float32Array represents a one-dimensional array of float32s (`[]float32{}`)
// that is compatible with PostgreSQL's double precision array (`double
// precision[]`). Float32Array satisfies sqlbuilder.ScannerValuer.
type Float32Array pq.Float32Array
// Value satisfies the driver.Valuer interface.
func (f Float32Array) Value() (driver.Value, error) {
return pq.Float32Array(f).Value()
}
// Scan satisfies the sql.Scanner interface.
func (f *Float32Array) Scan(src interface{}) error {
s := pq.Float32Array(*f)
if err := s.Scan(src); err != nil {
return err
}
*f = Float32Array(s)
return nil
}
// BoolArray represents a one-dimensional array of int64s (`[]bool{}`) that
// is compatible with PostgreSQL's boolean type (`boolean[]`). BoolArray
// satisfies sqlbuilder.ScannerValuer.
type BoolArray pq.BoolArray
// Value satisfies the driver.Valuer interface.
func (b BoolArray) Value() (driver.Value, error) {
return pq.BoolArray(b).Value()
}
// Scan satisfies the sql.Scanner interface.
func (b *BoolArray) Scan(src interface{}) error {
s := pq.BoolArray(*b)
if err := s.Scan(src); err != nil {
return err
}
*b = BoolArray(s)
return nil
}
type Bytea []byte
// Scan satisfies the sql.Scanner interface.
func (b *Bytea) Scan(src interface{}) error {
decoded, err := parseBytea(src.([]byte))
if err != nil {
return err
}
if len(decoded) < 1 {
*b = nil
return nil
}
(*b) = make(Bytea, len(decoded))
for i := range decoded {
(*b)[i] = decoded[i]
}
return nil
}
type Time time.Time
// Parse a bytea value received from the server. Both "hex" and the legacy
// "escape" format are supported.
func parseBytea(s []byte) (result []byte, err error) {
if len(s) >= 2 && bytes.Equal(s[:2], []byte("\\x")) {
// bytea_output = hex
s = s[2:] // trim off leading "\\x"
result = make([]byte, hex.DecodedLen(len(s)))
_, err := hex.Decode(result, s)
if err != nil {
return nil, err
}
} else {
// bytea_output = escape
for len(s) > 0 {
if s[0] == '\\' {
// escaped '\\'
if len(s) >= 2 && s[1] == '\\' {
result = append(result, '\\')
s = s[2:]
continue
}
// '\\' followed by an octal number
if len(s) < 4 {
return nil, fmt.Errorf("invalid bytea sequence %v", s)
}
r, err := strconv.ParseInt(string(s[1:4]), 8, 9)
if err != nil {
return nil, fmt.Errorf("could not parse bytea value: %s", err.Error())
}
result = append(result, byte(r))
s = s[4:]
} else {
// We hit an unescaped, raw byte. Try to read in as many as
// possible in one go.
i := bytes.IndexByte(s, '\\')
if i == -1 {
result = append(result, s...)
break
}
result = append(result, s[:i]...)
s = s[i:]
}
}
}
return result, nil
}

@ -0,0 +1,235 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
// Package cockroachdb wraps the github.com/lib/pq driver and provides a
// compatibility later with CockroachDB. See
// https://github.com/upper/db/adapter/cockroachdb for documentation,
// particularities and usage examples.
package cockroachdb
import (
"database/sql"
"database/sql/driver"
"fmt"
"reflect"
"strings"
pq "github.com/lib/pq"
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
"github.com/upper/db/v4/internal/sqladapter/exql"
"github.com/upper/db/v4/internal/sqlbuilder"
)
type database struct {
}
func (*database) Template() *exql.Template {
return template
}
func (*database) Collections(sess sqladapter.Session) (collections []string, err error) {
q := sess.SQL().
Select("table_name").
From("information_schema.tables").
Where("table_schema = ?", "public")
iter := q.Iterator()
defer iter.Close()
for iter.Next() {
var tableName string
if err := iter.Scan(&tableName); err != nil {
return nil, err
}
collections = append(collections, tableName)
}
if err := iter.Err(); err != nil {
return nil, err
}
return collections, nil
}
func (*database) ConvertValue(in interface{}) interface{} {
switch v := in.(type) {
case *[]int64:
return (*Int64Array)(v)
case *[]string:
return (*StringArray)(v)
case *[]float64:
return (*Float64Array)(v)
case *[]bool:
return (*BoolArray)(v)
case *map[string]interface{}:
return (*JSONBMap)(v)
case []int64:
return (*Int64Array)(&v)
case []string:
return (*StringArray)(&v)
case []float64:
return (*Float64Array)(&v)
case []bool:
return (*BoolArray)(&v)
case map[string]interface{}:
return (*JSONBMap)(&v)
case sql.Scanner, driver.Valuer:
return v
case *sql.Scanner, *driver.Valuer:
return v
}
dv := reflect.ValueOf(in)
if dv.IsValid() {
if dv.Type().Kind() == reflect.Ptr {
dv = dv.Elem()
}
switch dv.Kind() {
case reflect.Map:
if reflect.TypeOf(in).Kind() == reflect.Ptr {
w := reflect.ValueOf(in)
z := reflect.New(w.Elem().Type())
w.Elem().Set(z.Elem())
}
return &JSONB{in}
case reflect.Slice:
return &JSONB{in}
}
}
return in
}
func (*database) CompileStatement(sess sqladapter.Session, stmt *exql.Statement, args []interface{}) (string, []interface{}, error) {
compiled, err := stmt.Compile(template)
if err != nil {
return "", nil, err
}
query, args := sqlbuilder.Preprocess(compiled, args)
query = sqladapter.ReplaceWithDollarSign(query)
return query, args, nil
}
func (*database) Err(err error) error {
if err != nil {
s := err.Error()
// These errors are not exported so we have to check them by they string value.
if strings.Contains(s, `too many clients`) || strings.Contains(s, `remaining connection slots are reserved`) || strings.Contains(s, `too many open`) {
return db.ErrTooManyClients
}
if pqErr, ok := err.(*pq.Error); ok {
switch pqErr.Code {
case "25P02", "40001":
return db.ErrTransactionAborted
}
}
}
return err
}
func (*database) NewCollection() sqladapter.CollectionAdapter {
return &collectionAdapter{}
}
func (*database) LookupName(sess sqladapter.Session) (string, error) {
q := sess.SQL().
Select(db.Raw("CURRENT_DATABASE() AS name"))
iter := q.Iterator()
defer iter.Close()
if iter.Next() {
var name string
err := iter.Scan(&name)
return name, err
}
return "", iter.Err()
}
func (*database) TableExists(sess sqladapter.Session, name string) error {
q := sess.SQL().
Select("table_name").
From("information_schema.tables").
Where("table_catalog = ? AND table_name = ?", sess.Name(), name)
iter := q.Iterator()
defer iter.Close()
if iter.Next() {
var name string
if err := iter.Scan(&name); err != nil {
return err
}
return nil
}
if err := iter.Err(); err != nil {
return err
}
return db.ErrCollectionDoesNotExist
}
func (*database) PrimaryKeys(sess sqladapter.Session, tableName string) ([]string, error) {
q := sess.SQL().
Select("pg_attribute.attname AS pkey").
From("pg_index", "pg_class", "pg_attribute").
Where(`
pg_class.oid = '` + quotedTableName(tableName) + `'::regclass
AND indrelid = pg_class.oid
AND pg_attribute.attrelid = pg_class.oid
AND pg_attribute.attnum = ANY(pg_index.indkey)
AND indisprimary
`).OrderBy("pkey")
iter := q.Iterator()
defer iter.Close()
pk := []string{}
for iter.Next() {
var k string
if err := iter.Scan(&k); err != nil {
return nil, err
}
pk = append(pk, k)
}
if err := iter.Err(); err != nil {
return nil, err
}
return pk, nil
}
// quotedTableName returns a valid regclass name for both regular tables and
// for schemas.
func quotedTableName(s string) string {
chunks := strings.Split(s, ".")
for i := range chunks {
chunks[i] = fmt.Sprintf("%q", chunks[i])
}
return strings.Join(chunks, ".")
}

@ -0,0 +1,45 @@
// +build !pq
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package cockroachdb
import (
"context"
"database/sql"
_ "github.com/jackc/pgx/v4/stdlib"
"github.com/upper/db/v4/internal/sqladapter"
"time"
)
func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) {
connURL, err := ParseURL(dsn)
if err != nil {
return nil, err
}
if tz := connURL.Options["timezone"]; tz != "" {
loc, _ := time.LoadLocation(tz)
ctx := context.WithValue(sess.Context(), "timezone", loc)
sess.SetContext(ctx)
}
return sql.Open("pgx", dsn)
}

@ -0,0 +1,45 @@
// +build pq
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package cockroachdb
import (
"context"
"database/sql"
_ "github.com/lib/pq"
"github.com/upper/db/v4/internal/sqladapter"
"time"
)
func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) {
connURL, err := ParseURL(dsn)
if err != nil {
return nil, err
}
if tz := connURL.Options["timezone"]; tz != "" {
loc, _ := time.LoadLocation(tz)
ctx := context.WithValue(sess.Context(), "timezone", loc)
sess.SetContext(ctx)
}
return sql.Open("postgres", dsn)
}

@ -0,0 +1,12 @@
version: '3'
services:
server:
image: cockroachdb/cockroach:${COCKROACHDB_VERSION:-v2}
environment:
COCKROACHDB_USER: ${DB_USERNAME:-upperio_user}
COCKROACHDB_DB: ${DB_NAME:-upperio}
command: "start-single-node --insecure"
ports:
- '${DB_HOST:-127.0.0.1}:${DB_PORT:-26257}:26257'

@ -0,0 +1,210 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package cockroachdb
import (
"github.com/upper/db/v4/internal/adapter"
"github.com/upper/db/v4/internal/cache"
"github.com/upper/db/v4/internal/sqladapter/exql"
)
const (
adapterColumnSeparator = `.`
adapterIdentifierSeparator = `, `
adapterIdentifierQuote = `"{{.Value}}"`
adapterValueSeparator = `, `
adapterValueQuote = `'{{.}}'`
adapterAndKeyword = `AND`
adapterOrKeyword = `OR`
adapterDescKeyword = `DESC`
adapterAscKeyword = `ASC`
adapterAssignmentOperator = `=`
adapterClauseGroup = `({{.}})`
adapterClauseOperator = ` {{.}} `
adapterColumnValue = `{{.Column}} {{.Operator}} {{.Value}}`
adapterTableAliasLayout = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}`
adapterColumnAliasLayout = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}`
adapterSortByColumnLayout = `{{.Column}} {{.Order}}`
adapterOrderByLayout = `
{{if .SortColumns}}
ORDER BY {{.SortColumns}}
{{end}}
`
adapterWhereLayout = `
{{if .Conds}}
WHERE {{.Conds}}
{{end}}
`
adapterUsingLayout = `
{{if .Columns}}
USING ({{.Columns}})
{{end}}
`
adapterJoinLayout = `
{{if .Table}}
{{ if .On }}
{{.Type}} JOIN {{.Table}}
{{.On}}
{{ else if .Using }}
{{.Type}} JOIN {{.Table}}
{{.Using}}
{{ else if .Type | eq "CROSS" }}
{{.Type}} JOIN {{.Table}}
{{else}}
NATURAL {{.Type}} JOIN {{.Table}}
{{end}}
{{end}}
`
adapterOnLayout = `
{{if .Conds}}
ON {{.Conds}}
{{end}}
`
adapterSelectLayout = `
SELECT
{{if .Distinct}}
DISTINCT
{{end}}
{{if defined .Columns}}
{{.Columns | compile}}
{{else}}
*
{{end}}
{{if defined .Table}}
FROM {{.Table | compile}}
{{end}}
{{.Joins | compile}}
{{.Where | compile}}
{{if defined .GroupBy}}
{{.GroupBy | compile}}
{{end}}
{{.OrderBy | compile}}
{{if .Limit}}
LIMIT {{.Limit}}
{{end}}
{{if .Offset}}
OFFSET {{.Offset}}
{{end}}
`
adapterDeleteLayout = `
DELETE
FROM {{.Table | compile}}
{{.Where | compile}}
`
adapterUpdateLayout = `
UPDATE
{{.Table | compile}}
SET {{.ColumnValues | compile}}
{{.Where | compile}}
`
adapterSelectCountLayout = `
SELECT
COUNT(1) AS _t
FROM {{.Table | compile}}
{{.Where | compile}}
`
adapterInsertLayout = `
INSERT INTO {{.Table | compile}}
{{if defined .Columns}}({{.Columns | compile}}){{end}}
VALUES
{{if defined .Values}}
{{.Values | compile}}
{{else}}
(default)
{{end}}
{{if defined .Returning}}
RETURNING {{.Returning | compile}}
{{end}}
`
adapterTruncateLayout = `
DELETE FROM {{.Table | compile}}
`
adapterDropDatabaseLayout = `
DROP DATABASE {{.Database | compile}}
`
adapterDropTableLayout = `
DROP TABLE {{.Table | compile}}
`
adapterGroupByLayout = `
{{if .GroupColumns}}
GROUP BY {{.GroupColumns}}
{{end}}
`
)
var template = &exql.Template{
ColumnSeparator: adapterColumnSeparator,
IdentifierSeparator: adapterIdentifierSeparator,
IdentifierQuote: adapterIdentifierQuote,
ValueSeparator: adapterValueSeparator,
ValueQuote: adapterValueQuote,
AndKeyword: adapterAndKeyword,
OrKeyword: adapterOrKeyword,
DescKeyword: adapterDescKeyword,
AscKeyword: adapterAscKeyword,
AssignmentOperator: adapterAssignmentOperator,
ClauseGroup: adapterClauseGroup,
ClauseOperator: adapterClauseOperator,
ColumnValue: adapterColumnValue,
TableAliasLayout: adapterTableAliasLayout,
ColumnAliasLayout: adapterColumnAliasLayout,
SortByColumnLayout: adapterSortByColumnLayout,
WhereLayout: adapterWhereLayout,
JoinLayout: adapterJoinLayout,
OnLayout: adapterOnLayout,
UsingLayout: adapterUsingLayout,
OrderByLayout: adapterOrderByLayout,
InsertLayout: adapterInsertLayout,
SelectLayout: adapterSelectLayout,
UpdateLayout: adapterUpdateLayout,
DeleteLayout: adapterDeleteLayout,
TruncateLayout: adapterTruncateLayout,
DropDatabaseLayout: adapterDropDatabaseLayout,
DropTableLayout: adapterDropTableLayout,
CountLayout: adapterSelectCountLayout,
GroupByLayout: adapterGroupByLayout,
Cache: cache.NewCache(),
ComparisonOperator: map[adapter.ComparisonOperator]string{
adapter.ComparisonOperatorRegExp: "~",
adapter.ComparisonOperatorNotRegExp: "!~",
},
}

@ -0,0 +1,43 @@
SHELL ?= bash
MONGO_VERSION ?= 4
MONGO_SUPPORTED ?= $(MONGO_VERSION) 3
PROJECT ?= upper_mongo_$(MONGO_VERSION)
DB_HOST ?= 127.0.0.1
DB_PORT ?= 27017
DB_NAME ?= admin
DB_USERNAME ?= upperio_user
DB_PASSWORD ?= upperio//s3cr37
TEST_FLAGS ?=
PARALLEL_FLAGS ?= --halt-on-error 2 --jobs 1
export MONGO_VERSION
export DB_HOST
export DB_NAME
export DB_PASSWORD
export DB_PORT
export DB_USERNAME
export TEST_FLAGS
test:
go test -v -failfast -race -timeout 20m $(TEST_FLAGS)
test-no-race:
go test -v -failfast $(TEST_FLAGS)
server-up: server-down
docker-compose -p $(PROJECT) up -d && \
sleep 10
server-down:
docker-compose -p $(PROJECT) down
test-extended:
parallel $(PARALLEL_FLAGS) \
"MONGO_VERSION={} DB_PORT=\$$((27017+{#})) $(MAKE) server-up test server-down" ::: \
$(MONGO_SUPPORTED)

@ -0,0 +1,4 @@
# MongoDB adapter for upper/db
Please read the full docs, acknowledgements and examples at
[https://upper.io/v4/adapter/mongo/](https://upper.io/v4/adapter/mongo/).

@ -0,0 +1,367 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package mongo
import (
"fmt"
"strings"
"sync"
"reflect"
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/adapter"
mgo "gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
// Collection represents a mongodb collection.
type Collection struct {
parent *Source
collection *mgo.Collection
}
var (
// idCache should be a struct if we're going to cache more than just
// _id field here
idCache = make(map[reflect.Type]string)
idCacheMutex sync.RWMutex
)
// Find creates a result set with the given conditions.
func (col *Collection) Find(terms ...interface{}) db.Result {
fields := []string{"*"}
conditions := col.compileQuery(terms...)
res := &result{}
res = res.frame(func(r *resultQuery) error {
r.c = col
r.conditions = conditions
r.fields = fields
return nil
})
return res
}
var comparisonOperators = map[adapter.ComparisonOperator]string{
adapter.ComparisonOperatorEqual: "$eq",
adapter.ComparisonOperatorNotEqual: "$ne",
adapter.ComparisonOperatorLessThan: "$lt",
adapter.ComparisonOperatorGreaterThan: "$gt",
adapter.ComparisonOperatorLessThanOrEqualTo: "$lte",
adapter.ComparisonOperatorGreaterThanOrEqualTo: "$gte",
adapter.ComparisonOperatorIn: "$in",
adapter.ComparisonOperatorNotIn: "$nin",
}
func compare(field string, cmp *adapter.Comparison) (string, interface{}) {
op := cmp.Operator()
value := cmp.Value()
switch op {
case adapter.ComparisonOperatorEqual:
return field, value
case adapter.ComparisonOperatorBetween:
values := value.([]interface{})
return field, bson.M{
"$gte": values[0],
"$lte": values[1],
}
case adapter.ComparisonOperatorNotBetween:
values := value.([]interface{})
return "$or", []bson.M{
{field: bson.M{"$gt": values[1]}},
{field: bson.M{"$lt": values[0]}},
}
case adapter.ComparisonOperatorIs:
if value == nil {
return field, bson.M{"$exists": false}
}
return field, bson.M{"$eq": value}
case adapter.ComparisonOperatorIsNot:
if value == nil {
return field, bson.M{"$exists": true}
}
return field, bson.M{"$ne": value}
case adapter.ComparisonOperatorRegExp, adapter.ComparisonOperatorLike:
return field, bson.RegEx{Pattern: value.(string), Options: ""}
case adapter.ComparisonOperatorNotRegExp, adapter.ComparisonOperatorNotLike:
return field, bson.M{"$not": bson.RegEx{Pattern: value.(string), Options: ""}}
}
if cmpOp, ok := comparisonOperators[op]; ok {
return field, bson.M{
cmpOp: value,
}
}
panic(fmt.Sprintf("Unsupported operator %v", op))
}
// compileStatement transforms conditions into something *mgo.Session can
// understand.
func compileStatement(cond db.Cond) bson.M {
conds := bson.M{}
// Walking over conditions
for fieldI, value := range cond {
field := strings.TrimSpace(fmt.Sprintf("%v", fieldI))
if cmp, ok := value.(*db.Comparison); ok {
k, v := compare(field, cmp.Comparison)
conds[k] = v
continue
}
var op string
chunks := strings.SplitN(field, ` `, 2)
if len(chunks) > 1 {
switch chunks[1] {
case `IN`:
op = `$in`
case `NOT IN`:
op = `$nin`
case `>`:
op = `$gt`
case `<`:
op = `$lt`
case `<=`:
op = `$lte`
case `>=`:
op = `$gte`
default:
op = chunks[1]
}
}
field = chunks[0]
if op == "" {
conds[field] = value
} else {
conds[field] = bson.M{op: value}
}
}
return conds
}
// compileConditions compiles terms into something *mgo.Session can
// understand.
func (col *Collection) compileConditions(term interface{}) interface{} {
switch t := term.(type) {
case []interface{}:
values := []interface{}{}
for i := range t {
value := col.compileConditions(t[i])
if value != nil {
values = append(values, value)
}
}
if len(values) > 0 {
return values
}
case db.Cond:
return compileStatement(t)
case adapter.LogicalExpr:
values := []interface{}{}
for _, s := range t.Expressions() {
values = append(values, col.compileConditions(s))
}
var op string
switch t.Operator() {
case adapter.LogicalOperatorOr:
op = `$or`
default:
op = `$and`
}
return bson.M{op: values}
}
return nil
}
// compileQuery compiles terms into something that *mgo.Session can
// understand.
func (col *Collection) compileQuery(terms ...interface{}) interface{} {
compiled := col.compileConditions(terms)
if compiled == nil {
return nil
}
conditions := compiled.([]interface{})
if len(conditions) == 1 {
return conditions[0]
}
// this should be correct.
// query = map[string]interface{}{"$and": conditions}
// attempt to workaround https://jira.mongodb.org/browse/SERVER-4572
mapped := map[string]interface{}{}
for _, v := range conditions {
for kk := range v.(map[string]interface{}) {
mapped[kk] = v.(map[string]interface{})[kk]
}
}
return mapped
}
// Name returns the name of the table or tables that form the collection.
func (col *Collection) Name() string {
return col.collection.Name
}
// Truncate deletes all rows from the table.
func (col *Collection) Truncate() error {
err := col.collection.DropCollection()
if err != nil {
return err
}
return nil
}
func (col *Collection) Session() db.Session {
return col.parent
}
func (col *Collection) Count() (uint64, error) {
return col.Find().Count()
}
func (col *Collection) InsertReturning(item interface{}) error {
return db.ErrUnsupported
}
func (col *Collection) UpdateReturning(item interface{}) error {
return db.ErrUnsupported
}
// Insert inserts a record (map or struct) into the collection.
func (col *Collection) Insert(item interface{}) (db.InsertResult, error) {
var err error
id := getID(item)
if col.parent.versionAtLeast(2, 6, 0, 0) {
// this breaks MongoDb older than 2.6
if _, err = col.collection.Upsert(bson.M{"_id": id}, item); err != nil {
return nil, err
}
} else {
// Allocating a new ID.
if err = col.collection.Insert(bson.M{"_id": id}); err != nil {
return nil, err
}
// Now append data the user wants to append.
if err = col.collection.Update(bson.M{"_id": id}, item); err != nil {
// Cleanup allocated ID
if err := col.collection.Remove(bson.M{"_id": id}); err != nil {
return nil, err
}
return nil, err
}
}
return db.NewInsertResult(id), nil
}
// Exists returns true if the collection exists.
func (col *Collection) Exists() (bool, error) {
query := col.parent.database.C(`system.namespaces`).Find(map[string]string{`name`: fmt.Sprintf(`%s.%s`, col.parent.database.Name, col.collection.Name)})
count, err := query.Count()
return count > 0, err
}
// Fetches object _id or generates a new one if object doesn't have one or the one it has is invalid
func getID(item interface{}) interface{} {
v := reflect.ValueOf(item) // convert interface to Value
v = reflect.Indirect(v) // convert pointers
switch v.Kind() {
case reflect.Map:
if inItem, ok := item.(map[string]interface{}); ok {
if id, ok := inItem["_id"]; ok {
bsonID, ok := id.(bson.ObjectId)
if ok {
return bsonID
}
}
}
case reflect.Struct:
t := v.Type()
idCacheMutex.RLock()
fieldName, found := idCache[t]
idCacheMutex.RUnlock()
if !found {
for n := 0; n < t.NumField(); n++ {
field := t.Field(n)
if field.PkgPath != "" {
continue // Private field
}
tag := field.Tag.Get("bson")
if tag == "" {
tag = field.Tag.Get("db")
}
if tag == "" {
continue
}
parts := strings.Split(tag, ",")
if parts[0] == "_id" {
fieldName = field.Name
idCacheMutex.RLock()
idCache[t] = fieldName
idCacheMutex.RUnlock()
break
}
}
}
if fieldName != "" {
if bsonID, ok := v.FieldByName(fieldName).Interface().(bson.ObjectId); ok {
if bsonID.Valid() {
return bsonID
}
} else {
return v.FieldByName(fieldName).Interface()
}
}
}
return bson.NewObjectId()
}

@ -0,0 +1,119 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package mongo
import (
"fmt"
"net/url"
"strings"
)
const connectionScheme = `mongodb`
// ConnectionURL implements a MongoDB connection struct.
type ConnectionURL struct {
User string
Password string
Host string
Database string
Options map[string]string
}
func (c ConnectionURL) String() (s string) {
if c.Database == "" {
return ""
}
vv := url.Values{}
// Do we have any options?
if c.Options == nil {
c.Options = map[string]string{}
}
// Converting options into URL values.
for k, v := range c.Options {
vv.Set(k, v)
}
// Has user?
var userInfo *url.Userinfo
if c.User != "" {
if c.Password == "" {
userInfo = url.User(c.User)
} else {
userInfo = url.UserPassword(c.User, c.Password)
}
}
// Building URL.
u := url.URL{
Scheme: connectionScheme,
Path: c.Database,
Host: c.Host,
User: userInfo,
RawQuery: vv.Encode(),
}
return u.String()
}
// ParseURL parses s into a ConnectionURL struct.
func ParseURL(s string) (conn ConnectionURL, err error) {
var u *url.URL
if !strings.HasPrefix(s, connectionScheme+"://") {
return conn, fmt.Errorf(`Expecting mongodb:// connection scheme.`)
}
if u, err = url.Parse(s); err != nil {
return conn, err
}
conn.Host = u.Host
// Deleting / from start of the string.
conn.Database = strings.Trim(u.Path, "/")
// Adding user / password.
if u.User != nil {
conn.User = u.User.Username()
conn.Password, _ = u.User.Password()
}
// Adding options.
conn.Options = map[string]string{}
var vv url.Values
if vv, err = url.ParseQuery(u.RawQuery); err != nil {
return conn, err
}
for k := range vv {
conn.Options[k] = vv.Get(k)
}
return conn, err
}

@ -0,0 +1,266 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
// Package mongo wraps the gopkg.in/mgo.v2 MongoDB driver. See
// https://github.com/upper/db/adapter/mongo for documentation, particularities and usage
// examples.
package mongo
import (
"context"
"database/sql"
"strings"
"sync"
"time"
db "github.com/upper/db/v4"
mgo "gopkg.in/mgo.v2"
)
// Adapter holds the name of the mongodb adapter.
const Adapter = `mongo`
var connTimeout = time.Second * 5
// Source represents a MongoDB database.
type Source struct {
db.Settings
ctx context.Context
name string
connURL db.ConnectionURL
session *mgo.Session
database *mgo.Database
version []int
collections map[string]*Collection
collectionsMu sync.Mutex
}
type mongoAdapter struct {
}
func (mongoAdapter) Open(dsn db.ConnectionURL) (db.Session, error) {
return Open(dsn)
}
func init() {
db.RegisterAdapter(Adapter, db.Adapter(&mongoAdapter{}))
}
// Open stablishes a new connection to a SQL server.
func Open(settings db.ConnectionURL) (db.Session, error) {
d := &Source{Settings: db.NewSettings(), ctx: context.Background()}
if err := d.Open(settings); err != nil {
return nil, err
}
return d, nil
}
func (s *Source) TxContext(context.Context, func(tx db.Session) error, *sql.TxOptions) error {
return db.ErrNotSupportedByAdapter
}
func (s *Source) Tx(func(db.Session) error) error {
return db.ErrNotSupportedByAdapter
}
func (s *Source) SQL() db.SQL {
// Not supported
panic("sql builder is not supported by mongodb")
}
func (s *Source) ConnectionURL() db.ConnectionURL {
return s.connURL
}
// SetConnMaxLifetime is not supported.
func (s *Source) SetConnMaxLifetime(time.Duration) {
s.Settings.SetConnMaxLifetime(time.Duration(0))
}
// SetMaxIdleConns is not supported.
func (s *Source) SetMaxIdleConns(int) {
s.Settings.SetMaxIdleConns(0)
}
// SetMaxOpenConns is not supported.
func (s *Source) SetMaxOpenConns(int) {
s.Settings.SetMaxOpenConns(0)
}
// Name returns the name of the database.
func (s *Source) Name() string {
return s.name
}
// Open attempts to connect to the database.
func (s *Source) Open(connURL db.ConnectionURL) error {
s.connURL = connURL
return s.open()
}
// Clone returns a cloned db.Session session.
func (s *Source) Clone() (db.Session, error) {
newSession := s.session.Copy()
clone := &Source{
Settings: db.NewSettings(),
name: s.name,
connURL: s.connURL,
session: newSession,
database: newSession.DB(s.database.Name),
version: s.version,
collections: map[string]*Collection{},
}
return clone, nil
}
// Ping checks whether a connection to the database is still alive by pinging
// it, establishing a connection if necessary.
func (s *Source) Ping() error {
return s.session.Ping()
}
func (s *Source) Reset() {
s.collectionsMu.Lock()
defer s.collectionsMu.Unlock()
s.collections = make(map[string]*Collection)
}
// Driver returns the underlying *mgo.Session instance.
func (s *Source) Driver() interface{} {
return s.session
}
func (s *Source) open() error {
var err error
if s.session, err = mgo.DialWithTimeout(s.connURL.String(), connTimeout); err != nil {
return err
}
s.collections = map[string]*Collection{}
s.database = s.session.DB("")
return nil
}
// Close terminates the current database session.
func (s *Source) Close() error {
if s.session != nil {
s.session.Close()
}
return nil
}
// Collections returns a list of non-system tables from the database.
func (s *Source) Collections() (cols []db.Collection, err error) {
var rawcols []string
var col string
if rawcols, err = s.database.CollectionNames(); err != nil {
return nil, err
}
cols = make([]db.Collection, 0, len(rawcols))
for _, col = range rawcols {
if !strings.HasPrefix(col, "system.") {
cols = append(cols, s.Collection(col))
}
}
return cols, nil
}
func (s *Source) Delete(db.Record) error {
return db.ErrNotImplemented
}
func (s *Source) Get(db.Record, interface{}) error {
return db.ErrNotImplemented
}
func (s *Source) Save(db.Record) error {
return db.ErrNotImplemented
}
func (s *Source) Context() context.Context {
return s.ctx
}
func (s *Source) WithContext(ctx context.Context) db.Session {
return &Source{
ctx: ctx,
Settings: s.Settings,
name: s.name,
connURL: s.connURL,
session: s.session,
database: s.database,
version: s.version,
}
}
// Collection returns a collection by name.
func (s *Source) Collection(name string) db.Collection {
s.collectionsMu.Lock()
defer s.collectionsMu.Unlock()
var col *Collection
var ok bool
if col, ok = s.collections[name]; !ok {
col = &Collection{
parent: s,
collection: s.database.C(name),
}
s.collections[name] = col
}
return col
}
func (s *Source) versionAtLeast(version ...int) bool {
// only fetch this once - it makes a db call
if len(s.version) == 0 {
buildInfo, err := s.database.Session.BuildInfo()
if err != nil {
return false
}
s.version = buildInfo.VersionArray
}
// Check major version first
if s.version[0] > version[0] {
return true
}
for i := range version {
if i == len(s.version) {
return false
}
if s.version[i] < version[i] {
return false
}
}
return true
}

@ -0,0 +1,13 @@
version: '3'
services:
server:
image: mongo:${MONGO_VERSION:-3}
environment:
MONGO_INITDB_ROOT_USERNAME: ${DB_USERNAME:-upperio_user}
MONGO_INITDB_ROOT_PASSWORD: ${DB_PASSWORD:-upperio//s3cr37}
MONGO_INITDB_DATABASE: ${DB_NAME:-upperio}
ports:
- '${BIND_HOST:-127.0.0.1}:${DB_PORT:-27017}:27017'

@ -0,0 +1,587 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package mongo
import (
"errors"
"fmt"
"math"
"strings"
"sync"
"time"
"encoding/json"
db "github.com/upper/db/v4"
mgo "gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"github.com/upper/db/v4/internal/immutable"
)
type resultQuery struct {
c *Collection
fields []string
limit int
offset int
sort []string
conditions interface{}
groupBy []interface{}
pageSize uint
pageNumber uint
cursorColumn string
cursorValue interface{}
cursorCond db.Cond
cursorReverseOrder bool
}
type result struct {
iter *mgo.Iter
err error
errMu sync.Mutex
fn func(*resultQuery) error
prev *result
}
var _ = immutable.Immutable(&result{})
func (res *result) frame(fn func(*resultQuery) error) *result {
return &result{prev: res, fn: fn}
}
func (r *resultQuery) and(terms ...interface{}) error {
if r.conditions == nil {
return r.where(terms...)
}
r.conditions = map[string]interface{}{
"$and": []interface{}{
r.conditions,
r.c.compileQuery(terms...),
},
}
return nil
}
func (r *resultQuery) where(terms ...interface{}) error {
r.conditions = r.c.compileQuery(terms...)
return nil
}
func (res *result) And(terms ...interface{}) db.Result {
return res.frame(func(r *resultQuery) error {
return r.and(terms...)
})
}
func (res *result) Where(terms ...interface{}) db.Result {
return res.frame(func(r *resultQuery) error {
return r.where(terms...)
})
}
func (res *result) Paginate(pageSize uint) db.Result {
return res.frame(func(r *resultQuery) error {
r.pageSize = pageSize
return nil
})
}
func (res *result) Page(pageNumber uint) db.Result {
return res.frame(func(r *resultQuery) error {
r.pageNumber = pageNumber
return nil
})
}
func (res *result) Cursor(cursorColumn string) db.Result {
return res.frame(func(r *resultQuery) error {
r.cursorColumn = cursorColumn
return nil
})
}
func (res *result) NextPage(cursorValue interface{}) db.Result {
return res.frame(func(r *resultQuery) error {
r.cursorValue = cursorValue
r.cursorReverseOrder = false
r.cursorCond = db.Cond{
r.cursorColumn: bson.M{"$gt": cursorValue},
}
return nil
})
}
func (res *result) PrevPage(cursorValue interface{}) db.Result {
return res.frame(func(r *resultQuery) error {
r.cursorValue = cursorValue
r.cursorReverseOrder = true
r.cursorCond = db.Cond{
r.cursorColumn: bson.M{"$lt": cursorValue},
}
return nil
})
}
func (res *result) TotalEntries() (uint64, error) {
return res.Count()
}
func (res *result) TotalPages() (uint, error) {
count, err := res.Count()
if err != nil {
return 0, err
}
rq, err := res.build()
if err != nil {
return 0, err
}
if rq.pageSize < 1 {
return 1, nil
}
total := uint(math.Ceil(float64(count) / float64(rq.pageSize)))
return total, nil
}
// Limit determines the maximum limit of results to be returned.
func (res *result) Limit(n int) db.Result {
return res.frame(func(r *resultQuery) error {
r.limit = n
return nil
})
}
// Offset determines how many documents will be skipped before starting to grab
// results.
func (res *result) Offset(n int) db.Result {
return res.frame(func(r *resultQuery) error {
r.offset = n
return nil
})
}
// OrderBy determines sorting of results according to the provided names. Fields
// may be prefixed by - (minus) which means descending order, ascending order
// would be used otherwise.
func (res *result) OrderBy(fields ...interface{}) db.Result {
return res.frame(func(r *resultQuery) error {
ss := make([]string, len(fields))
for i, field := range fields {
ss[i] = fmt.Sprintf(`%v`, field)
}
r.sort = ss
return nil
})
}
// String satisfies fmt.Stringer
func (res *result) String() string {
return ""
}
// Select marks the specific fields the user wants to retrieve.
func (res *result) Select(fields ...interface{}) db.Result {
return res.frame(func(r *resultQuery) error {
fieldslen := len(fields)
r.fields = make([]string, 0, fieldslen)
for i := 0; i < fieldslen; i++ {
r.fields = append(r.fields, fmt.Sprintf(`%v`, fields[i]))
}
return nil
})
}
// All dumps all results into a pointer to an slice of structs or maps.
func (res *result) All(dst interface{}) error {
rq, err := res.build()
if err != nil {
return err
}
q, err := rq.query()
if err != nil {
return err
}
defer func(start time.Time) {
queryLog(&db.QueryStatus{
RawQuery: rq.debugQuery("Find.All"),
Err: err,
Start: start,
End: time.Now(),
})
}(time.Now())
err = q.All(dst)
if errors.Is(err, mgo.ErrNotFound) {
return db.ErrNoMoreRows
}
return err
}
// GroupBy is used to group results that have the same value in the same column
// or columns.
func (res *result) GroupBy(fields ...interface{}) db.Result {
return res.frame(func(r *resultQuery) error {
r.groupBy = fields
return nil
})
}
// One fetches only one result from the resultset.
func (res *result) One(dst interface{}) error {
rq, err := res.build()
if err != nil {
return err
}
q, err := rq.query()
if err != nil {
return err
}
defer func(start time.Time) {
queryLog(&db.QueryStatus{
RawQuery: rq.debugQuery("Find.One"),
Err: err,
Start: start,
End: time.Now(),
})
}(time.Now())
err = q.One(dst)
if errors.Is(err, mgo.ErrNotFound) {
return db.ErrNoMoreRows
}
return err
}
func (res *result) Err() error {
res.errMu.Lock()
defer res.errMu.Unlock()
return res.err
}
func (res *result) setErr(err error) {
res.errMu.Lock()
defer res.errMu.Unlock()
res.err = err
}
func (res *result) Next(dst interface{}) bool {
if res.iter == nil {
rq, err := res.build()
if err != nil {
return false
}
q, err := rq.query()
if err != nil {
return false
}
defer func(start time.Time) {
queryLog(&db.QueryStatus{
RawQuery: rq.debugQuery("Find.Next"),
Err: err,
Start: start,
End: time.Now(),
})
}(time.Now())
res.iter = q.Iter()
}
if !res.iter.Next(dst) {
res.setErr(res.iter.Err())
return false
}
return true
}
// Delete remove the matching items from the collection.
func (res *result) Delete() error {
rq, err := res.build()
if err != nil {
return err
}
defer func(start time.Time) {
queryLog(&db.QueryStatus{
RawQuery: rq.debugQuery("Remove"),
Err: err,
Start: start,
End: time.Now(),
})
}(time.Now())
_, err = rq.c.collection.RemoveAll(rq.conditions)
if err != nil {
return err
}
return nil
}
// Close closes the result set.
func (r *result) Close() error {
var err error
if r.iter != nil {
err = r.iter.Close()
r.iter = nil
}
return err
}
// Update modified matching items from the collection with values of the given
// map or struct.
func (res *result) Update(src interface{}) (err error) {
updateSet := map[string]interface{}{"$set": src}
rq, err := res.build()
if err != nil {
return err
}
defer func(start time.Time) {
queryLog(&db.QueryStatus{
RawQuery: rq.debugQuery("Update"),
Err: err,
Start: start,
End: time.Now(),
})
}(time.Now())
_, err = rq.c.collection.UpdateAll(rq.conditions, updateSet)
if err != nil {
return err
}
return nil
}
func (res *result) build() (*resultQuery, error) {
rqi, err := immutable.FastForward(res)
if err != nil {
return nil, err
}
rq := rqi.(*resultQuery)
if !rq.cursorCond.Empty() {
if err := rq.and(rq.cursorCond); err != nil {
return nil, err
}
}
if rq.cursorColumn != "" {
if rq.cursorReverseOrder {
rq.sort = append(rq.sort, "-"+rq.cursorColumn)
} else {
rq.sort = append(rq.sort, rq.cursorColumn)
}
}
return rq, nil
}
// query executes a mgo query.
func (r *resultQuery) query() (*mgo.Query, error) {
if len(r.groupBy) > 0 {
return nil, db.ErrUnsupported
}
q := r.c.collection.Find(r.conditions)
if r.pageSize > 0 {
r.offset = int(r.pageSize * r.pageNumber)
r.limit = int(r.pageSize)
}
if r.offset > 0 {
q.Skip(r.offset)
}
if r.limit > 0 {
q.Limit(r.limit)
}
if len(r.sort) > 0 {
q.Sort(r.sort...)
}
selectedFields := bson.M{}
if len(r.fields) > 0 {
for _, field := range r.fields {
if field == `*` {
break
}
selectedFields[field] = true
}
}
if r.cursorReverseOrder {
ids := make([]bson.ObjectId, 0, r.limit)
iter := q.Select(bson.M{"_id": true}).Iter()
defer iter.Close()
var item map[string]bson.ObjectId
for iter.Next(&item) {
ids = append(ids, item["_id"])
}
r.conditions = bson.M{"_id": bson.M{"$in": ids}}
q = r.c.collection.Find(r.conditions)
}
if len(selectedFields) > 0 {
q.Select(selectedFields)
}
return q, nil
}
func (res *result) Exists() (bool, error) {
total, err := res.Count()
if err != nil {
return false, err
}
if total > 0 {
return true, nil
}
return false, nil
}
// Count counts matching elements.
func (res *result) Count() (total uint64, err error) {
rq, err := res.build()
if err != nil {
return 0, err
}
defer func(start time.Time) {
queryLog(&db.QueryStatus{
RawQuery: rq.debugQuery("Find.Count"),
Err: err,
Start: start,
End: time.Now(),
})
}(time.Now())
q := rq.c.collection.Find(rq.conditions)
var c int
c, err = q.Count()
return uint64(c), err
}
func (res *result) Prev() immutable.Immutable {
if res == nil {
return nil
}
return res.prev
}
func (res *result) Fn(in interface{}) error {
if res.fn == nil {
return nil
}
return res.fn(in.(*resultQuery))
}
func (res *result) Base() interface{} {
return &resultQuery{}
}
func (r *resultQuery) debugQuery(action string) string {
query := fmt.Sprintf("db.%s.%s", r.c.collection.Name, action)
if r.conditions != nil {
query = fmt.Sprintf("%s.conds(%v)", query, r.conditions)
}
if r.limit > 0 {
query = fmt.Sprintf("%s.limit(%d)", query, r.limit)
}
if r.offset > 0 {
query = fmt.Sprintf("%s.offset(%d)", query, r.offset)
}
if len(r.fields) > 0 {
selectedFields := bson.M{}
for _, field := range r.fields {
if field == `*` {
break
}
selectedFields[field] = true
}
if len(selectedFields) > 0 {
query = fmt.Sprintf("%s.select(%v)", query, selectedFields)
}
}
if len(r.groupBy) > 0 {
escaped := make([]string, len(r.groupBy))
for i := range r.groupBy {
escaped[i] = string(mustJSON(r.groupBy[i]))
}
query = fmt.Sprintf("%s.groupBy(%v)", query, strings.Join(escaped, ", "))
}
if len(r.sort) > 0 {
escaped := make([]string, len(r.sort))
for i := range r.sort {
escaped[i] = string(mustJSON(r.sort[i]))
}
query = fmt.Sprintf("%s.sort(%s)", query, strings.Join(escaped, ", "))
}
return query
}
func mustJSON(in interface{}) (out []byte) {
out, err := json.Marshal(in)
if err != nil {
panic(err)
}
return out
}
func queryLog(status *db.QueryStatus) {
diff := status.End.Sub(status.Start)
slowQuery := false
if diff >= time.Millisecond*100 {
status.Err = db.ErrWarnSlowQuery
slowQuery = true
}
if status.Err != nil || slowQuery {
db.LC().Warn(status)
return
}
db.LC().Debug(status)
}

@ -0,0 +1,43 @@
SHELL ?= bash
MSSQL_VERSION ?= 2019-latest
MSSQL_SUPPORTED ?= $(MSSQL_VERSION) 2017-latest
PROJECT ?= upper_mssql_$(MSSQL_VERSION)
DB_HOST ?= 127.0.0.1
DB_PORT ?= 1433
DB_NAME ?= master
DB_USERNAME ?= sa
DB_PASSWORD ?= upperio//s3cr37
TEST_FLAGS ?=
PARALLEL_FLAGS ?= --halt-on-error 2 --jobs 1
export MSSQL_VERSION
export DB_HOST
export DB_NAME
export DB_PASSWORD
export DB_PORT
export DB_USERNAME
export TEST_FLAGS
test:
go test -v -failfast -race -timeout 20m $(TEST_FLAGS)
test-no-race:
go test -v -failfast $(TEST_FLAGS)
server-up: server-down
docker-compose -p $(PROJECT) up -d && \
sleep 20
server-down:
docker-compose -p $(PROJECT) down
test-extended:
parallel $(PARALLEL_FLAGS) \
"MSSQL_VERSION={} DB_PORT=\$$((1433+{#})) $(MAKE) server-up test server-down" ::: \
$(MSSQL_SUPPORTED)

@ -0,0 +1,4 @@
# SQLServer adapter for upper/db
Please read the full docs, acknowledgements and examples at
[https://upper.io/v4/adapter/mssql/](https://upper.io/v4/adapter/mssql/).

@ -0,0 +1,116 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package mssql
import (
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
"github.com/upper/db/v4/internal/sqlbuilder"
)
type collectionAdapter struct {
hasIdentityColumn *bool
}
func (adt *collectionAdapter) Insert(col sqladapter.Collection, item interface{}) (interface{}, error) {
columnNames, columnValues, err := sqlbuilder.Map(item, nil)
if err != nil {
return nil, err
}
pKey, err := col.PrimaryKeys()
if err != nil {
return nil, err
}
var hasKeys bool
for i := range columnNames {
for j := 0; j < len(pKey); j++ {
if pKey[j] == columnNames[i] {
if columnValues[i] != nil {
hasKeys = true
break
}
}
}
}
if hasKeys {
if adt.hasIdentityColumn == nil {
var hasIdentityColumn bool
var identityColumns int
row, err := col.SQL().QueryRow("SELECT COUNT(1) FROM sys.identity_columns WHERE OBJECT_NAME(object_id) = ?", col.Name())
if err != nil {
return nil, err
}
err = row.Scan(&identityColumns)
if err != nil {
return nil, err
}
if identityColumns > 0 {
hasIdentityColumn = true
}
adt.hasIdentityColumn = &hasIdentityColumn
}
if *adt.hasIdentityColumn {
_, err = col.SQL().Exec("SET IDENTITY_INSERT " + col.Name() + " ON")
if err != nil {
return nil, err
}
defer func() {
_, _ = col.SQL().Exec("SET IDENTITY_INSERT " + col.Name() + " OFF")
}()
}
}
q := col.SQL().InsertInto(col.Name()).
Columns(columnNames...).
Values(columnValues...)
if len(pKey) < 1 {
_, err = q.Exec()
if err != nil {
return nil, err
}
return nil, nil
}
q = q.Returning(pKey...)
var keyMap db.Cond
if err = q.Iterator().One(&keyMap); err != nil {
return nil, err
}
// The IDSetter interface does not match, look for another interface match.
if len(keyMap) == 1 {
return keyMap[pKey[0]], nil
}
// This was a compound key and no interface matched it, let's return a map.
return keyMap, nil
}

@ -0,0 +1,113 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package mssql
import (
"errors"
"net/url"
)
// ConnectionURL implements a MSSQL connection struct.
type ConnectionURL struct {
User string
Password string
Database string
Host string
Socket string
Options map[string]string
}
func (c ConnectionURL) String() (s string) {
if c.Host == "" && c.Database == "" && c.User == "" && c.Password == "" {
return ""
}
if c.Host == "" {
c.Host = "127.0.0.1"
}
if c.Database == "" {
c.Database = "master"
}
params := url.Values{}
for k, v := range c.Options {
if k == "instance" {
continue
}
params.Add(k, v)
}
params.Set("database", c.Database)
u := url.URL{
Scheme: "sqlserver",
Host: c.Host,
RawQuery: params.Encode(),
}
u.Path = c.Options["instance"]
if c.User != "" || c.Password != "" {
u.User = url.UserPassword(c.User, c.Password)
}
return u.String()
}
// ParseURL parses s into a ConnectionURL struct.
func ParseURL(s string) (conn ConnectionURL, err error) {
var u *url.URL
u, err = url.Parse(s)
if err != nil {
return
}
if u.Scheme != "sqlserver" && u.Scheme != "mssql" {
return conn, errors.New(`Expecting "sqlserver" or "mssql" schema`)
}
if u.Host == "" {
conn.Host = "127.0.0.1"
} else {
conn.Host = u.Host
}
if u.User != nil {
conn.User = u.User.Username()
conn.Password, _ = u.User.Password()
}
q := u.Query()
for k := range q {
if k == "database" {
conn.Database = q.Get(k)
continue
}
if conn.Options == nil {
conn.Options = make(map[string]string)
}
conn.Options[k] = q.Get(k)
}
return
}

@ -0,0 +1,158 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
// Package mssql wraps the github.com/go-sql-driver/mssql MySQL driver. See
// https://github.com/upper/db/adapter/mssql for documentation, particularities and usage
// examples.
package mssql
import (
"strings"
"database/sql"
_ "github.com/denisenkom/go-mssqldb" // MSSQL driver
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
"github.com/upper/db/v4/internal/sqladapter/exql"
)
type database struct {
}
func (*database) Template() *exql.Template {
return template
}
func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) {
return sql.Open("mssql", dsn)
}
func (*database) Collections(sess sqladapter.Session) (collections []string, err error) {
q := sess.SQL().
Select(`table_name`).
From(`information_schema.tables`).
Where(`table_type`, `BASE TABLE`).
And(`table_catalog`, sess.Name())
iter := q.Iterator()
defer iter.Close()
for iter.Next() {
var tableName string
if err := iter.Scan(&tableName); err != nil {
return nil, err
}
collections = append(collections, tableName)
}
if err := iter.Err(); err != nil {
return nil, err
}
return collections, nil
}
func (*database) Err(err error) error {
if err != nil {
// This error is not exported so we have to check it by its string value.
s := err.Error()
if strings.Contains(s, `many connections`) {
return db.ErrTooManyClients
}
}
return err
}
func (*database) NewCollection() sqladapter.CollectionAdapter {
return &collectionAdapter{}
}
func (*database) LookupName(sess sqladapter.Session) (string, error) {
q := sess.SQL().
Select(db.Raw(`DB_NAME() AS name`))
iter := q.Iterator()
defer iter.Close()
if iter.Next() {
var name string
err := iter.Scan(&name)
return name, err
}
return "", iter.Err()
}
func (*database) TableExists(sess sqladapter.Session, name string) error {
q := sess.SQL().
Select(`table_name`).
From(`information_schema.tables`).
Where(`table_schema`, sess.Name()).
And(`table_name`, name)
iter := q.Iterator()
defer iter.Close()
if iter.Next() {
var name string
if err := iter.Scan(&name); err != nil {
return err
}
return nil
}
if err := iter.Err(); err != nil {
return err
}
return db.ErrCollectionDoesNotExist
}
func (*database) PrimaryKeys(sess sqladapter.Session, tableName string) ([]string, error) {
q := sess.SQL().
Select(`k.column_name`).
From(
`information_schema.table_constraints AS t`,
`information_schema.key_column_usage AS k`,
).
Where(`k.constraint_name = t.constraint_name`).
And(`k.table_name = t.table_name`).
And(`t.constraint_type = ?`, `PRIMARY KEY`).
And(`t.table_name = ?`, tableName).
OrderBy(`k.ordinal_position`)
iter := q.Iterator()
defer iter.Close()
pk := []string{}
for iter.Next() {
var k string
if err := iter.Scan(&k); err != nil {
return nil, err
}
pk = append(pk, k)
}
if err := iter.Err(); err != nil {
return nil, err
}
return pk, nil
}

@ -0,0 +1,12 @@
version: '3'
services:
server:
image: mcr.microsoft.com/mssql/server:${MSSQL_VERSION:-2017-latest-ubuntu}
environment:
ACCEPT_EULA: "Y"
SA_PASSWORD: ${DB_PASSWORD:-upperio//s3cr37}
ports:
- '${BIND_HOST:-127.0.0.1}:${DB_PORT:-1433}:1433'

@ -0,0 +1,51 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package mssql
import (
"database/sql"
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
"github.com/upper/db/v4/internal/sqlbuilder"
)
// Adapter is the public name of the adapter.
const Adapter = `mssql`
var registeredAdapter = sqladapter.RegisterAdapter(Adapter, &database{})
// Open establishes a connection to the database server and returns a
// db.Session instance (which is compatible with db.Session).
func Open(connURL db.ConnectionURL) (db.Session, error) {
return registeredAdapter.OpenDSN(connURL)
}
// NewTx creates a sqlbuilder.Tx instance by wrapping a *sql.Tx value.
func NewTx(sqlTx *sql.Tx) (sqlbuilder.Tx, error) {
return registeredAdapter.NewTx(sqlTx)
}
// New creates a sqlbuilder.Sesion instance by wrapping a *sql.DB value.
func New(sqlDB *sql.DB) (db.Session, error) {
return registeredAdapter.New(sqlDB)
}

@ -0,0 +1,218 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package mssql
import (
"github.com/upper/db/v4/internal/cache"
"github.com/upper/db/v4/internal/sqladapter/exql"
)
const (
adapterColumnSeparator = `.`
adapterIdentifierSeparator = `, `
adapterIdentifierQuote = "[{{.Value}}]"
adapterValueSeparator = `, `
adapterValueQuote = `'{{.}}'`
adapterAndKeyword = `AND`
adapterOrKeyword = `OR`
adapterDescKeyword = `DESC`
adapterAscKeyword = `ASC`
adapterAssignmentOperator = `=`
adapterClauseGroup = `({{.}})`
adapterClauseOperator = ` {{.}} `
adapterColumnValue = `{{.Column}} {{.Operator}} {{.Value}}`
adapterTableAliasLayout = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}`
adapterColumnAliasLayout = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}`
adapterSortByColumnLayout = `{{.Column}} {{.Order}}`
adapterOrderByLayout = `{{if .SortColumns}}ORDER BY {{.SortColumns}}{{end}}`
adapterWhereLayout = `
{{if .Conds}}
WHERE {{.Conds}}
{{end}}
`
adapterUsingLayout = `
{{if .Columns}}
USING ({{.Columns}})
{{end}}
`
adapterJoinLayout = `
{{if .Table}}
{{ if .On }}
{{.Type}} JOIN {{.Table}}
{{.On}}
{{ else if .Using }}
{{.Type}} JOIN {{.Table}}
{{.Using}}
{{ else if .Type | eq "CROSS" }}
{{.Type}} JOIN {{.Table}}
{{else}}
NATURAL {{.Type}} JOIN {{.Table}}
{{end}}
{{end}}
`
adapterOnLayout = `
{{if .Conds}}
ON {{.Conds}}
{{end}}
`
adapterSelectLayout = `
{{if or .Limit .Offset}}
SELECT __q0.* FROM (
SELECT TOP 100 PERCENT __q1.*,
ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS rnum FROM
(
{{end}}
SELECT
{{if .Distinct}}
DISTINCT
{{end}}
{{if or .Limit .Offset}}
{{if gt .Limit 0 }}
TOP ({{.Limit}} + {{if gt .Offset 0}}{{.Offset}}{{else}}0{{end}})
{{else}}
TOP 100 PERCENT
{{end}}
{{end}}
{{if defined .Columns}}
{{.Columns | compile}}
{{else}}
*
{{end}}
{{if defined .Table}}
FROM {{.Table | compile}}
{{end}}
{{.Joins | compile}}
{{.Where | compile}}
{{if defined .GroupBy}}
{{.GroupBy | compile}}
{{end}}
{{.OrderBy | compile}}
{{if or .Limit .Offset}}
) __q1) __q0 WHERE rnum > {{if gt .Offset 0}}{{.Offset}}{{else}}0{{end}}
{{end}}
`
adapterDeleteLayout = `
DELETE
FROM {{.Table | compile}}
{{.Where | compile}}
`
adapterUpdateLayout = `
UPDATE
{{.Table | compile}}
SET {{.ColumnValues | compile}}
{{.Where | compile}}
`
adapterSelectCountLayout = `
SELECT
COUNT(1) AS _t
FROM {{.Table | compile}}
{{.Where | compile}}}
`
adapterInsertLayout = `
INSERT INTO {{.Table | compile}}
{{if .Columns }}({{.Columns | compile}}){{end}}
{{if .Returning }}
OUTPUT
{{range $key, $value := .Returning.Columns.Columns}}
{{- if $key}},{{end}}
[inserted].{{ $value | compile }}
{{end}}
{{end}}
VALUES
{{if defined .Values}}
{{.Values | compile}}
{{else}}
(DEFAULT)
{{end}}
`
adapterTruncateLayout = `
TRUNCATE TABLE {{.Table | compile}}
`
adapterDropDatabaseLayout = `
DROP DATABASE {{.Database | compile}}
`
adapterDropTableLayout = `
DROP TABLE {{.Table | compile}}
`
adapterGroupByLayout = `
{{if .GroupColumns}}
GROUP BY {{.GroupColumns}}
{{end}}
`
)
var template = &exql.Template{
ColumnSeparator: adapterColumnSeparator,
IdentifierSeparator: adapterIdentifierSeparator,
IdentifierQuote: adapterIdentifierQuote,
ValueSeparator: adapterValueSeparator,
ValueQuote: adapterValueQuote,
AndKeyword: adapterAndKeyword,
OrKeyword: adapterOrKeyword,
DescKeyword: adapterDescKeyword,
AscKeyword: adapterAscKeyword,
AssignmentOperator: adapterAssignmentOperator,
ClauseGroup: adapterClauseGroup,
ClauseOperator: adapterClauseOperator,
ColumnValue: adapterColumnValue,
TableAliasLayout: adapterTableAliasLayout,
ColumnAliasLayout: adapterColumnAliasLayout,
SortByColumnLayout: adapterSortByColumnLayout,
WhereLayout: adapterWhereLayout,
JoinLayout: adapterJoinLayout,
OnLayout: adapterOnLayout,
UsingLayout: adapterUsingLayout,
OrderByLayout: adapterOrderByLayout,
InsertLayout: adapterInsertLayout,
SelectLayout: adapterSelectLayout,
UpdateLayout: adapterUpdateLayout,
DeleteLayout: adapterDeleteLayout,
TruncateLayout: adapterTruncateLayout,
DropDatabaseLayout: adapterDropDatabaseLayout,
DropTableLayout: adapterDropTableLayout,
CountLayout: adapterSelectCountLayout,
GroupByLayout: adapterGroupByLayout,
Cache: cache.NewCache(),
}

@ -0,0 +1,43 @@
SHELL ?= bash
MYSQL_VERSION ?= 8
MYSQL_SUPPORTED ?= $(MYSQL_VERSION) 5.7
PROJECT ?= upper_mysql_$(MYSQL_VERSION)
DB_HOST ?= 127.0.0.1
DB_PORT ?= 3306
DB_NAME ?= upperio
DB_USERNAME ?= upperio_user
DB_PASSWORD ?= upperio//s3cr37
TEST_FLAGS ?=
PARALLEL_FLAGS ?= --halt-on-error 2 --jobs 1
export MYSQL_VERSION
export DB_HOST
export DB_NAME
export DB_PASSWORD
export DB_PORT
export DB_USERNAME
export TEST_FLAGS
test:
go test -v -failfast -race -timeout 20m $(TEST_FLAGS)
test-no-race:
go test -v -failfast $(TEST_FLAGS)
server-up: server-down
docker-compose -p $(PROJECT) up -d && \
sleep 15
server-down:
docker-compose -p $(PROJECT) down
test-extended:
parallel $(PARALLEL_FLAGS) \
"MYSQL_VERSION={} DB_PORT=\$$((3306+{#})) $(MAKE) server-up test server-down" ::: \
$(MYSQL_SUPPORTED)

@ -0,0 +1,5 @@
# MySQL adapter for upper/db
Please read the full docs, acknowledgements and examples at
[https://upper.io/v4/adapter/mysql/](https://upper.io/v4/adapter/mysql/).

@ -0,0 +1,77 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package mysql
import (
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
"github.com/upper/db/v4/internal/sqlbuilder"
)
type collectionAdapter struct {
}
func (*collectionAdapter) Insert(col sqladapter.Collection, item interface{}) (interface{}, error) {
columnNames, columnValues, err := sqlbuilder.Map(item, nil)
if err != nil {
return nil, err
}
pKey, err := col.PrimaryKeys()
if err != nil {
return nil, err
}
q := col.SQL().InsertInto(col.Name()).
Columns(columnNames...).
Values(columnValues...)
res, err := q.Exec()
if err != nil {
return nil, err
}
lastID, err := res.LastInsertId()
if err == nil && len(pKey) <= 1 {
return lastID, nil
}
keyMap := db.Cond{}
for i := range columnNames {
for j := 0; j < len(pKey); j++ {
if pKey[j] == columnNames[i] {
keyMap[pKey[j]] = columnValues[i]
}
}
}
// There was an auto column among primary keys, let's search for it.
if lastID > 0 {
for j := 0; j < len(pKey); j++ {
if keyMap[pKey[j]] == nil {
keyMap[pKey[j]] = lastID
}
}
}
return keyMap, nil
}

@ -0,0 +1,265 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package mysql
import (
"errors"
"fmt"
"net"
"net/url"
"strings"
)
// From https://github.com/go-sql-driver/mysql/blob/master/utils.go
var (
errInvalidDSNUnescaped = errors.New("Invalid DSN: Did you forget to escape a param value?")
errInvalidDSNAddr = errors.New("Invalid DSN: Network Address not terminated (missing closing brace)")
errInvalidDSNNoSlash = errors.New("Invalid DSN: Missing the slash separating the database name")
)
// From https://github.com/go-sql-driver/mysql/blob/master/utils.go
type config struct {
user string
passwd string
net string
addr string
dbname string
params map[string]string
}
// ConnectionURL implements a MySQL connection struct.
type ConnectionURL struct {
User string
Password string
Database string
Host string
Socket string
Options map[string]string
}
func (c ConnectionURL) String() (s string) {
if c.Database == "" {
return ""
}
// Adding username.
if c.User != "" {
s = s + c.User
// Adding password.
if c.Password != "" {
s = s + ":" + c.Password
}
s = s + "@"
}
// Adding protocol and address
if c.Socket != "" {
s = s + fmt.Sprintf("unix(%s)", c.Socket)
} else if c.Host != "" {
host, port, err := net.SplitHostPort(c.Host)
if err != nil {
host = c.Host
port = "3306"
}
s = s + fmt.Sprintf("tcp(%s:%s)", host, port)
}
// Adding database
s = s + "/" + c.Database
// Do we have any options?
if c.Options == nil {
c.Options = map[string]string{}
}
// Default options.
if _, ok := c.Options["charset"]; !ok {
c.Options["charset"] = "utf8"
}
if _, ok := c.Options["parseTime"]; !ok {
c.Options["parseTime"] = "true"
}
// Converting options into URL values.
vv := url.Values{}
for k, v := range c.Options {
vv.Set(k, v)
}
// Inserting options.
if p := vv.Encode(); p != "" {
s = s + "?" + p
}
return s
}
// ParseURL parses s into a ConnectionURL struct.
func ParseURL(s string) (conn ConnectionURL, err error) {
var cfg *config
if cfg, err = parseDSN(s); err != nil {
return
}
conn.User = cfg.user
conn.Password = cfg.passwd
if cfg.net == "unix" {
conn.Socket = cfg.addr
} else if cfg.net == "tcp" {
conn.Host = cfg.addr
}
conn.Database = cfg.dbname
conn.Options = map[string]string{}
for k, v := range cfg.params {
conn.Options[k] = v
}
return
}
// from https://github.com/go-sql-driver/mysql/blob/master/utils.go
// parseDSN parses the DSN string to a config
func parseDSN(dsn string) (cfg *config, err error) {
// New config with some default values
cfg = &config{}
// TODO: use strings.IndexByte when we can depend on Go 1.2
// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
// Find the last '/' (since the password or the net addr might contain a '/')
foundSlash := false
for i := len(dsn) - 1; i >= 0; i-- {
if dsn[i] == '/' {
foundSlash = true
var j, k int
// left part is empty if i <= 0
if i > 0 {
// [username[:password]@][protocol[(address)]]
// Find the last '@' in dsn[:i]
for j = i; j >= 0; j-- {
if dsn[j] == '@' {
// username[:password]
// Find the first ':' in dsn[:j]
for k = 0; k < j; k++ {
if dsn[k] == ':' {
cfg.passwd = dsn[k+1 : j]
break
}
}
cfg.user = dsn[:k]
break
}
}
// [protocol[(address)]]
// Find the first '(' in dsn[j+1:i]
for k = j + 1; k < i; k++ {
if dsn[k] == '(' {
// dsn[i-1] must be == ')' if an address is specified
if dsn[i-1] != ')' {
if strings.ContainsRune(dsn[k+1:i], ')') {
return nil, errInvalidDSNUnescaped
}
return nil, errInvalidDSNAddr
}
cfg.addr = dsn[k+1 : i-1]
break
}
}
cfg.net = dsn[j+1 : k]
}
// dbname[?param1=value1&...&paramN=valueN]
// Find the first '?' in dsn[i+1:]
for j = i + 1; j < len(dsn); j++ {
if dsn[j] == '?' {
if err = parseDSNParams(cfg, dsn[j+1:]); err != nil {
return
}
break
}
}
cfg.dbname = dsn[i+1 : j]
break
}
}
if !foundSlash && len(dsn) > 0 {
return nil, errInvalidDSNNoSlash
}
// Set default network if empty
if cfg.net == "" {
cfg.net = "tcp"
}
// Set default address if empty
if cfg.addr == "" {
switch cfg.net {
case "tcp":
cfg.addr = "127.0.0.1:3306"
case "unix":
cfg.addr = "/tmp/mysql.sock"
default:
return nil, errors.New("Default addr for network '" + cfg.net + "' unknown")
}
}
return
}
// From https://github.com/go-sql-driver/mysql/blob/master/utils.go
// parseDSNParams parses the DSN "query string"
// Values must be url.QueryEscape'ed
func parseDSNParams(cfg *config, params string) (err error) {
for _, v := range strings.Split(params, "&") {
param := strings.SplitN(v, "=", 2)
if len(param) != 2 {
continue
}
value := param[1]
// lazy init
if cfg.params == nil {
cfg.params = make(map[string]string)
}
if cfg.params[param[0]], err = url.QueryUnescape(value); err != nil {
return
}
}
return
}

@ -0,0 +1,172 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package mysql
import (
"database/sql"
"database/sql/driver"
"encoding/json"
"errors"
"reflect"
"github.com/upper/db/v4/internal/sqlbuilder"
)
// JSON represents a MySQL's JSON value:
// https://www.mysql.org/docs/9.6/static/datatype-json.html. JSON
// satisfies sqlbuilder.ScannerValuer.
type JSON struct {
V interface{}
}
// MarshalJSON encodes the wrapper value as JSON.
func (j JSON) MarshalJSON() ([]byte, error) {
return json.Marshal(j.V)
}
// UnmarshalJSON decodes the given JSON into the wrapped value.
func (j *JSON) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
j.V = v
return nil
}
// Scan satisfies the sql.Scanner interface.
func (j *JSON) Scan(src interface{}) error {
if j.V == nil {
return nil
}
if src == nil {
dv := reflect.Indirect(reflect.ValueOf(j.V))
dv.Set(reflect.Zero(dv.Type()))
return nil
}
b, ok := src.([]byte)
if !ok {
return errors.New("Scan source was not []bytes")
}
if err := json.Unmarshal(b, j.V); err != nil {
return err
}
return nil
}
// Value satisfies the driver.Valuer interface.
func (j JSON) Value() (driver.Value, error) {
if j.V == nil {
return nil, nil
}
if v, ok := j.V.(json.RawMessage); ok {
return string(v), nil
}
b, err := json.Marshal(j.V)
if err != nil {
return nil, err
}
return string(b), nil
}
// JSONMap represents a map of interfaces with string keys
// (`map[string]interface{}`) that is compatible with MySQL's JSON type.
// JSONMap satisfies sqlbuilder.ScannerValuer.
type JSONMap map[string]interface{}
// Value satisfies the driver.Valuer interface.
func (m JSONMap) Value() (driver.Value, error) {
return JSONValue(m)
}
// Scan satisfies the sql.Scanner interface.
func (m *JSONMap) Scan(src interface{}) error {
*m = map[string]interface{}(nil)
return ScanJSON(m, src)
}
// JSONArray represents an array of any type (`[]interface{}`) that is
// compatible with MySQL's JSON type. JSONArray satisfies
// sqlbuilder.ScannerValuer.
type JSONArray []interface{}
// Value satisfies the driver.Valuer interface.
func (a JSONArray) Value() (driver.Value, error) {
return JSONValue(a)
}
// Scan satisfies the sql.Scanner interface.
func (a *JSONArray) Scan(src interface{}) error {
return ScanJSON(a, src)
}
// JSONValue takes an interface and provides a driver.Value that can be
// stored as a JSON column.
func JSONValue(i interface{}) (driver.Value, error) {
v := JSON{i}
return v.Value()
}
// ScanJSON decodes a JSON byte stream into the passed dst value.
func ScanJSON(dst interface{}, src interface{}) error {
v := JSON{dst}
return v.Scan(src)
}
// EncodeJSON is deprecated and going to be removed. Use ScanJSON instead.
func EncodeJSON(i interface{}) (driver.Value, error) {
return JSONValue(i)
}
// DecodeJSON is deprecated and going to be removed. Use JSONValue instead.
func DecodeJSON(dst interface{}, src interface{}) error {
return ScanJSON(dst, src)
}
// JSONConverter provides a helper method WrapValue that satisfies
// sqlbuilder.ValueWrapper, can be used to encode Go structs into JSON
// MySQL types and vice versa.
//
// Example:
//
// type MyCustomStruct struct {
// ID int64 `db:"id" json:"id"`
// Name string `db:"name" json:"name"`
// ...
// mysql.JSONConverter
// }
type JSONConverter struct{}
func (*JSONConverter) ConvertValue(in interface{}) interface {
sql.Scanner
driver.Valuer
} {
return &JSON{in}
}
// Type checks.
var (
_ sqlbuilder.ScannerValuer = &JSONMap{}
_ sqlbuilder.ScannerValuer = &JSONArray{}
_ sqlbuilder.ScannerValuer = &JSON{}
)

@ -0,0 +1,189 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
// Package mysql wraps the github.com/go-sql-driver/mysql MySQL driver. See
// https://github.com/upper/db/adapter/mysql for documentation, particularities and usage
// examples.
package mysql
import (
"reflect"
"strings"
"database/sql"
_ "github.com/go-sql-driver/mysql" // MySQL driver.
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
"github.com/upper/db/v4/internal/sqladapter/exql"
)
// database is the actual implementation of Database
type database struct {
}
func (*database) Template() *exql.Template {
return template
}
func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) {
return sql.Open("mysql", dsn)
}
func (*database) Collections(sess sqladapter.Session) (collections []string, err error) {
q := sess.SQL().
Select("table_name").
From("information_schema.tables").
Where("table_schema = ?", sess.Name())
iter := q.Iterator()
defer iter.Close()
for iter.Next() {
var tableName string
if err := iter.Scan(&tableName); err != nil {
return nil, err
}
collections = append(collections, tableName)
}
if err := iter.Err(); err != nil {
return nil, err
}
return collections, nil
}
func (d *database) ConvertValue(in interface{}) interface{} {
switch v := in.(type) {
case *map[string]interface{}:
return (*JSONMap)(v)
case map[string]interface{}:
return (*JSONMap)(&v)
}
dv := reflect.ValueOf(in)
if dv.IsValid() {
if dv.Type().Kind() == reflect.Ptr {
dv = dv.Elem()
}
switch dv.Kind() {
case reflect.Map:
if reflect.TypeOf(in).Kind() == reflect.Ptr {
w := reflect.ValueOf(in)
z := reflect.New(w.Elem().Type())
w.Elem().Set(z.Elem())
}
return &JSON{in}
case reflect.Slice:
return &JSON{in}
}
}
return in
}
func (*database) Err(err error) error {
if err != nil {
// This error is not exported so we have to check it by its string value.
s := err.Error()
if strings.Contains(s, `many connections`) {
return db.ErrTooManyClients
}
}
return err
}
func (*database) NewCollection() sqladapter.CollectionAdapter {
return &collectionAdapter{}
}
func (*database) LookupName(sess sqladapter.Session) (string, error) {
q := sess.SQL().
Select(db.Raw("DATABASE() AS name"))
iter := q.Iterator()
defer iter.Close()
if iter.Next() {
var name string
if err := iter.Scan(&name); err != nil {
return "", err
}
return name, nil
}
return "", iter.Err()
}
func (*database) TableExists(sess sqladapter.Session, name string) error {
q := sess.SQL().
Select("table_name").
From("information_schema.tables").
Where("table_schema = ? AND table_name = ?", sess.Name(), name)
iter := q.Iterator()
defer iter.Close()
if iter.Next() {
var name string
if err := iter.Scan(&name); err != nil {
return err
}
return nil
}
if err := iter.Err(); err != nil {
return err
}
return db.ErrCollectionDoesNotExist
}
func (*database) PrimaryKeys(sess sqladapter.Session, tableName string) ([]string, error) {
q := sess.SQL().
Select("k.column_name").
From("information_schema.key_column_usage AS k").
Where(`
k.constraint_name = 'PRIMARY'
AND k.table_schema = ?
AND k.table_name = ?
`, sess.Name(), tableName).
OrderBy("k.ordinal_position")
iter := q.Iterator()
defer iter.Close()
pk := []string{}
for iter.Next() {
var k string
if err := iter.Scan(&k); err != nil {
return nil, err
}
pk = append(pk, k)
}
if err := iter.Err(); err != nil {
return nil, err
}
return pk, nil
}

@ -0,0 +1,14 @@
version: '3'
services:
server:
image: mysql:${MYSQL_VERSION:-5}
environment:
MYSQL_USER: ${DB_USERNAME:-upperio_user}
MYSQL_PASSWORD: ${DB_PASSWORD:-upperio//s3cr37}
MYSQL_ALLOW_EMPTY_PASSWORD: 1
MYSQL_DATABASE: ${DB_NAME:-upperio}
ports:
- '${DB_HOST:-127.0.0.1}:${DB_PORT:-3306}:3306'

@ -0,0 +1,51 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package mysql
import (
"database/sql"
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
"github.com/upper/db/v4/internal/sqlbuilder"
)
// Adapter is the public name of the adapter.
const Adapter = `mysql`
var registeredAdapter = sqladapter.RegisterAdapter(Adapter, &database{})
// Open establishes a connection to the database server and returns a
// db.Session instance (which is compatible with db.Session).
func Open(connURL db.ConnectionURL) (db.Session, error) {
return registeredAdapter.OpenDSN(connURL)
}
// NewTx creates a sqlbuilder.Tx instance by wrapping a *sql.Tx value.
func NewTx(sqlTx *sql.Tx) (sqlbuilder.Tx, error) {
return registeredAdapter.NewTx(sqlTx)
}
// New creates a sqlbuilder.Sesion instance by wrapping a *sql.DB value.
func New(sqlDB *sql.DB) (db.Session, error) {
return registeredAdapter.New(sqlDB)
}

@ -0,0 +1,219 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package mysql
import (
"github.com/upper/db/v4/internal/cache"
"github.com/upper/db/v4/internal/sqladapter/exql"
)
const (
adapterColumnSeparator = `.`
adapterIdentifierSeparator = `, `
adapterIdentifierQuote = "`{{.Value}}`"
adapterValueSeparator = `, `
adapterValueQuote = `'{{.}}'`
adapterAndKeyword = `AND`
adapterOrKeyword = `OR`
adapterDescKeyword = `DESC`
adapterAscKeyword = `ASC`
adapterAssignmentOperator = `=`
adapterClauseGroup = `({{.}})`
adapterClauseOperator = ` {{.}} `
adapterColumnValue = `{{.Column}} {{.Operator}} {{.Value}}`
adapterTableAliasLayout = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}`
adapterColumnAliasLayout = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}`
adapterSortByColumnLayout = `{{.Column}} {{.Order}}`
adapterOrderByLayout = `
{{if .SortColumns}}
ORDER BY {{.SortColumns}}
{{end}}
`
adapterWhereLayout = `
{{if .Conds}}
WHERE {{.Conds}}
{{end}}
`
adapterUsingLayout = `
{{if .Columns}}
USING ({{.Columns}})
{{end}}
`
adapterJoinLayout = `
{{if .Table}}
{{ if .On }}
{{.Type}} JOIN {{.Table}}
{{.On}}
{{ else if .Using }}
{{.Type}} JOIN {{.Table}}
{{.Using}}
{{ else if .Type | eq "CROSS" }}
{{.Type}} JOIN {{.Table}}
{{else}}
NATURAL {{.Type}} JOIN {{.Table}}
{{end}}
{{end}}
`
adapterOnLayout = `
{{if .Conds}}
ON {{.Conds}}
{{end}}
`
adapterSelectLayout = `
SELECT
{{if .Distinct}}
DISTINCT
{{end}}
{{if defined .Columns}}
{{.Columns | compile}}
{{else}}
*
{{end}}
{{if defined .Table}}
FROM {{.Table | compile}}
{{end}}
{{.Joins | compile}}
{{.Where | compile}}
{{if defined .GroupBy}}
{{.GroupBy | compile}}
{{end}}
{{.OrderBy | compile}}
{{if .Limit}}
LIMIT {{.Limit}}
{{end}}
` +
// The argument for LIMIT when only OFFSET is specified is a pretty odd magic
// number; this comes directly from MySQL's manual, see:
// https://dev.mysql.com/doc/refman/5.7/en/select.html
//
// "To retrieve all rows from a certain offset up to the end of the result
// set, you can use some large number for the second parameter. This
// statement retrieves all rows from the 96th row to the last:
// SELECT * FROM tbl LIMIT 95,18446744073709551615; "
//
// ¯\_(ツ)_/¯
`
{{if .Offset}}
{{if not .Limit}}
LIMIT 18446744073709551615
{{end}}
OFFSET {{.Offset}}
{{end}}
`
adapterDeleteLayout = `
DELETE
FROM {{.Table | compile}}
{{.Where | compile}}
`
adapterUpdateLayout = `
UPDATE
{{.Table | compile}}
SET {{.ColumnValues | compile}}
{{.Where | compile}}
`
adapterSelectCountLayout = `
SELECT
COUNT(1) AS _t
FROM {{.Table | compile}}
{{.Where | compile}}
`
adapterInsertLayout = `
INSERT INTO {{.Table | compile}}
{{if defined .Columns}}({{.Columns | compile}}){{end}}
VALUES
{{if defined .Values}}
{{.Values | compile}}
{{else}}
()
{{end}}
{{if defined .Returning}}
RETURNING {{.Returning | compile}}
{{end}}
`
adapterTruncateLayout = `
TRUNCATE TABLE {{.Table | compile}}
`
adapterDropDatabaseLayout = `
DROP DATABASE {{.Database | compile}}
`
adapterDropTableLayout = `
DROP TABLE {{.Table | compile}}
`
adapterGroupByLayout = `
{{if .GroupColumns}}
GROUP BY {{.GroupColumns}}
{{end}}
`
)
var template = &exql.Template{
ColumnSeparator: adapterColumnSeparator,
IdentifierSeparator: adapterIdentifierSeparator,
IdentifierQuote: adapterIdentifierQuote,
ValueSeparator: adapterValueSeparator,
ValueQuote: adapterValueQuote,
AndKeyword: adapterAndKeyword,
OrKeyword: adapterOrKeyword,
DescKeyword: adapterDescKeyword,
AscKeyword: adapterAscKeyword,
AssignmentOperator: adapterAssignmentOperator,
ClauseGroup: adapterClauseGroup,
ClauseOperator: adapterClauseOperator,
ColumnValue: adapterColumnValue,
TableAliasLayout: adapterTableAliasLayout,
ColumnAliasLayout: adapterColumnAliasLayout,
SortByColumnLayout: adapterSortByColumnLayout,
WhereLayout: adapterWhereLayout,
JoinLayout: adapterJoinLayout,
OnLayout: adapterOnLayout,
UsingLayout: adapterUsingLayout,
OrderByLayout: adapterOrderByLayout,
InsertLayout: adapterInsertLayout,
SelectLayout: adapterSelectLayout,
UpdateLayout: adapterUpdateLayout,
DeleteLayout: adapterDeleteLayout,
TruncateLayout: adapterTruncateLayout,
DropDatabaseLayout: adapterDropDatabaseLayout,
DropTableLayout: adapterDropTableLayout,
CountLayout: adapterSelectCountLayout,
GroupByLayout: adapterGroupByLayout,
Cache: cache.NewCache(),
}

@ -0,0 +1,44 @@
SHELL ?= bash
POSTGRES_VERSION ?= 14-alpine
POSTGRES_SUPPORTED ?= $(POSTGRES_VERSION) 13-alpine 11-alpine 12-alpine
PROJECT ?= upper_postgres_$(POSTGRES_VERSION)
DB_HOST ?= 127.0.0.1
DB_PORT ?= 5432
DB_NAME ?= upperio
DB_USERNAME ?= upperio_user
DB_PASSWORD ?= upperio//s3cr37
TEST_FLAGS ?=
PARALLEL_FLAGS ?= --halt-on-error 2 --jobs 1
export POSTGRES_VERSION
export DB_HOST
export DB_NAME
export DB_PASSWORD
export DB_PORT
export DB_USERNAME
export TEST_FLAGS
test:
go test -v -failfast -race -timeout 20m $(TEST_FLAGS)
test-no-race:
go test -v -failfast $(TEST_FLAGS)
server-up: server-down
docker-compose -p $(PROJECT) up -d && \
sleep 10
server-down:
docker-compose -p $(PROJECT) down
test-extended:
parallel $(PARALLEL_FLAGS) \
"POSTGRES_VERSION={} DB_PORT=\$$((5432+{#})) $(MAKE) server-up test server-down" ::: \
$(POSTGRES_SUPPORTED)

@ -0,0 +1,5 @@
# PostgreSQL adapter for upper/db
Please read the full docs, acknowledgements and examples at
[https://upper.io/v4/adapter/postgresql/](https://upper.io/v4/adapter/postgresql/).

@ -0,0 +1,71 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package postgresql
import (
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
)
type collectionAdapter struct {
}
func (*collectionAdapter) Insert(col sqladapter.Collection, item interface{}) (interface{}, error) {
pKey, err := col.PrimaryKeys()
if err != nil {
return nil, err
}
q := col.SQL().InsertInto(col.Name()).Values(item)
if len(pKey) == 0 {
// There is no primary key.
res, err := q.Exec()
if err != nil {
return nil, err
}
// Attempt to use LastInsertId() (probably won't work, but the Exec()
// succeeded, so we can safely ignore the error from LastInsertId()).
lastID, err := res.LastInsertId()
if err != nil {
return nil, nil
}
return lastID, nil
}
// Asking the database to return the primary key after insertion.
q = q.Returning(pKey...)
var keyMap db.Cond
if err := q.Iterator().One(&keyMap); err != nil {
return nil, err
}
// The IDSetter interface does not match, look for another interface match.
if len(keyMap) == 1 {
return keyMap[pKey[0]], nil
}
// This was a compound key and no interface matched it, let's return a map.
return keyMap, nil
}

@ -0,0 +1,310 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package postgresql
import (
"fmt"
"net"
"net/url"
"sort"
"strings"
"time"
"unicode"
)
// scanner implements a tokenizer for libpq-style option strings.
type scanner struct {
s []rune
i int
}
// Next returns the next rune. It returns 0, false if the end of the text has
// been reached.
func (s *scanner) Next() (rune, bool) {
if s.i >= len(s.s) {
return 0, false
}
r := s.s[s.i]
s.i++
return r, true
}
// SkipSpaces returns the next non-whitespace rune. It returns 0, false if the
// end of the text has been reached.
func (s *scanner) SkipSpaces() (rune, bool) {
r, ok := s.Next()
for unicode.IsSpace(r) && ok {
r, ok = s.Next()
}
return r, ok
}
type values map[string]string
func (vs values) Set(k, v string) {
vs[k] = v
}
func (vs values) Get(k string) (v string) {
return vs[k]
}
func (vs values) Isset(k string) bool {
_, ok := vs[k]
return ok
}
// ConnectionURL represents a parsed PostgreSQL connection URL.
//
// You can use a ConnectionURL struct as an argument for Open:
//
// var settings = postgresql.ConnectionURL{
// Host: "localhost", // PostgreSQL server IP or name.
// Database: "peanuts", // Database name.
// User: "cbrown", // Optional user name.
// Password: "snoopy", // Optional user password.
// }
//
// sess, err = postgresql.Open(settings)
//
// If you already have a valid DSN, you can use ParseURL to convert it into
// a ConnectionURL before passing it to Open.
type ConnectionURL struct {
User string
Password string
Host string
Socket string
Database string
Options map[string]string
timezone *time.Location
}
var escaper = strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`)
// ParseURL parses the given DSN into a ConnectionURL struct.
// A typical PostgreSQL connection URL looks like:
//
// postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full
func ParseURL(s string) (u *ConnectionURL, err error) {
o := make(values)
if strings.HasPrefix(s, "postgres://") || strings.HasPrefix(s, "postgresql://") {
s, err = parseURL(s)
if err != nil {
return u, err
}
}
if err := parseOpts(s, o); err != nil {
return u, err
}
u = &ConnectionURL{}
u.User = o.Get("user")
u.Password = o.Get("password")
h := o.Get("host")
p := o.Get("port")
if strings.HasPrefix(h, "/") {
u.Socket = h
} else {
if p == "" {
u.Host = h
} else {
u.Host = fmt.Sprintf("%s:%s", h, p)
}
}
u.Database = o.Get("dbname")
u.Options = make(map[string]string)
for k := range o {
switch k {
case "user", "password", "host", "port", "dbname":
// Skip
default:
u.Options[k] = o[k]
}
}
if timezone, ok := u.Options["timezone"]; ok {
u.timezone, _ = time.LoadLocation(timezone)
}
return u, err
}
// parseOpts parses the options from name and adds them to the values.
//
// The parsing code is based on conninfo_parse from libpq's fe-connect.c
func parseOpts(name string, o values) error {
s := newScanner(name)
for {
var (
keyRunes, valRunes []rune
r rune
ok bool
)
if r, ok = s.SkipSpaces(); !ok {
break
}
// Scan the key
for !unicode.IsSpace(r) && r != '=' {
keyRunes = append(keyRunes, r)
if r, ok = s.Next(); !ok {
break
}
}
// Skip any whitespace if we're not at the = yet
if r != '=' {
r, ok = s.SkipSpaces()
}
// The current character should be =
if r != '=' || !ok {
return fmt.Errorf(`missing "=" after %q in connection info string"`, string(keyRunes))
}
// Skip any whitespace after the =
if r, ok = s.SkipSpaces(); !ok {
// If we reach the end here, the last value is just an empty string as per libpq.
o.Set(string(keyRunes), "")
break
}
if r != '\'' {
for !unicode.IsSpace(r) {
if r == '\\' {
if r, ok = s.Next(); !ok {
return fmt.Errorf(`missing character after backslash`)
}
}
valRunes = append(valRunes, r)
if r, ok = s.Next(); !ok {
break
}
}
} else {
quote:
for {
if r, ok = s.Next(); !ok {
return fmt.Errorf(`unterminated quoted string literal in connection string`)
}
switch r {
case '\'':
break quote
case '\\':
r, _ = s.Next()
fallthrough
default:
valRunes = append(valRunes, r)
}
}
}
o.Set(string(keyRunes), string(valRunes))
}
return nil
}
// newScanner returns a new scanner initialized with the option string s.
func newScanner(s string) *scanner {
return &scanner{[]rune(s), 0}
}
// ParseURL no longer needs to be used by clients of this library since supplying a URL as a
// connection string to sql.Open() is now supported:
//
// sql.Open("postgres", "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full")
//
// It remains exported here for backwards-compatibility.
//
// ParseURL converts a url to a connection string for driver.Open.
// Example:
//
// "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full"
//
// converts to:
//
// "user=bob password=secret host=1.2.3.4 port=5432 dbname=mydb sslmode=verify-full"
//
// A minimal example:
//
// "postgres://"
//
// This will be blank, causing driver.Open to use all of the defaults
//
// NOTE: vendored/copied from github.com/lib/pq
func parseURL(uri string) (string, error) {
u, err := url.Parse(uri)
if err != nil {
return "", err
}
if u.Scheme != "postgres" && u.Scheme != "postgresql" {
return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme)
}
var kvs []string
escaper := strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`)
accrue := func(k, v string) {
if v != "" {
kvs = append(kvs, k+"="+escaper.Replace(v))
}
}
if u.User != nil {
v := u.User.Username()
accrue("user", v)
v, _ = u.User.Password()
accrue("password", v)
}
if host, port, err := net.SplitHostPort(u.Host); err != nil {
accrue("host", u.Host)
} else {
accrue("host", host)
accrue("port", port)
}
if u.Path != "" {
accrue("dbname", u.Path[1:])
}
q := u.Query()
for k := range q {
accrue(k, q.Get(k))
}
sort.Strings(kvs) // Makes testing easier (not a performance concern)
return strings.Join(kvs, " "), nil
}

@ -0,0 +1,94 @@
//go:build !pq
// +build !pq
package postgresql
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
import (
"net"
"sort"
"strings"
)
// String reassembles the parsed PostgreSQL connection URL into a valid DSN.
func (c ConnectionURL) String() (s string) {
u := []string{}
// TODO: This surely needs some sort of escaping.
if c.User != "" {
u = append(u, "user="+escaper.Replace(c.User))
}
if c.Password != "" {
u = append(u, "password="+escaper.Replace(c.Password))
}
if c.Host != "" {
host, port, err := net.SplitHostPort(c.Host)
if err == nil {
if host == "" {
host = "127.0.0.1"
}
if port == "" {
port = "5432"
}
u = append(u, "host="+escaper.Replace(host))
u = append(u, "port="+escaper.Replace(port))
} else {
u = append(u, "host="+escaper.Replace(c.Host))
}
}
if c.Socket != "" {
u = append(u, "host="+escaper.Replace(c.Socket))
}
if c.Database != "" {
u = append(u, "dbname="+escaper.Replace(c.Database))
}
// Is there actually any connection data?
if len(u) == 0 {
return ""
}
if c.Options == nil {
c.Options = map[string]string{}
}
// If not present, SSL mode is assumed "prefer".
if sslMode, ok := c.Options["sslmode"]; !ok || sslMode == "" {
c.Options["sslmode"] = "prefer"
}
// Disabled by default
c.Options["statement_cache_capacity"] = "0"
for k, v := range c.Options {
u = append(u, escaper.Replace(k)+"="+escaper.Replace(v))
}
sort.Strings(u)
return strings.Join(u, " ")
}

@ -0,0 +1,91 @@
//go:build pq
// +build pq
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package postgresql
import (
"net"
"sort"
"strings"
)
// String reassembles the parsed PostgreSQL connection URL into a valid DSN.
func (c ConnectionURL) String() (s string) {
u := []string{}
// TODO: This surely needs some sort of escaping.
if c.User != "" {
u = append(u, "user="+escaper.Replace(c.User))
}
if c.Password != "" {
u = append(u, "password="+escaper.Replace(c.Password))
}
if c.Host != "" {
host, port, err := net.SplitHostPort(c.Host)
if err == nil {
if host == "" {
host = "127.0.0.1"
}
if port == "" {
port = "5432"
}
u = append(u, "host="+escaper.Replace(host))
u = append(u, "port="+escaper.Replace(port))
} else {
u = append(u, "host="+escaper.Replace(c.Host))
}
}
if c.Socket != "" {
u = append(u, "host="+escaper.Replace(c.Socket))
}
if c.Database != "" {
u = append(u, "dbname="+escaper.Replace(c.Database))
}
// Is there actually any connection data?
if len(u) == 0 {
return ""
}
if c.Options == nil {
c.Options = map[string]string{}
}
// If not present, SSL mode is assumed "prefer".
if sslMode, ok := c.Options["sslmode"]; !ok || sslMode == "" {
c.Options["sslmode"] = "prefer"
}
for k, v := range c.Options {
u = append(u, escaper.Replace(k)+"="+escaper.Replace(v))
}
sort.Strings(u)
return strings.Join(u, " ")
}

@ -0,0 +1,147 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package postgresql
import (
"context"
"database/sql"
"database/sql/driver"
"time"
"github.com/upper/db/v4/internal/sqlbuilder"
)
// JSONBMap represents a map of interfaces with string keys
// (`map[string]interface{}`) that is compatible with PostgreSQL's JSONB type.
// JSONBMap satisfies sqlbuilder.ScannerValuer.
type JSONBMap map[string]interface{}
// Value satisfies the driver.Valuer interface.
func (m JSONBMap) Value() (driver.Value, error) {
return JSONBValue(m)
}
// Scan satisfies the sql.Scanner interface.
func (m *JSONBMap) Scan(src interface{}) error {
*m = map[string]interface{}(nil)
return ScanJSONB(m, src)
}
// JSONBArray represents an array of any type (`[]interface{}`) that is
// compatible with PostgreSQL's JSONB type. JSONBArray satisfies
// sqlbuilder.ScannerValuer.
type JSONBArray []interface{}
// Value satisfies the driver.Valuer interface.
func (a JSONBArray) Value() (driver.Value, error) {
return JSONBValue(a)
}
// Scan satisfies the sql.Scanner interface.
func (a *JSONBArray) Scan(src interface{}) error {
return ScanJSONB(a, src)
}
// JSONBValue takes an interface and provides a driver.Value that can be
// stored as a JSONB column.
func JSONBValue(i interface{}) (driver.Value, error) {
v := JSONB{i}
return v.Value()
}
// ScanJSONB decodes a JSON byte stream into the passed dst value.
func ScanJSONB(dst interface{}, src interface{}) error {
v := JSONB{dst}
return v.Scan(src)
}
type JSONBConverter struct {
}
func (*JSONBConverter) ConvertValue(in interface{}) interface {
sql.Scanner
driver.Valuer
} {
return &JSONB{in}
}
type timeWrapper struct {
v **time.Time
loc *time.Location
}
func (t timeWrapper) Value() (driver.Value, error) {
if *t.v != nil {
return **t.v, nil
}
return nil, nil
}
func (t *timeWrapper) Scan(src interface{}) error {
if src == nil {
nilTime := (*time.Time)(nil)
if t.v == nil {
t.v = &nilTime
} else {
*(t.v) = nilTime
}
return nil
}
tz := src.(time.Time)
if t.loc != nil && (tz.Location() == time.Local) {
tz = tz.In(t.loc)
}
if tz.Location().String() == "" {
tz = tz.In(time.UTC)
}
if *(t.v) == nil {
*(t.v) = &tz
} else {
**t.v = tz
}
return nil
}
func (d *database) ConvertValueContext(ctx context.Context, in interface{}) interface{} {
tz, _ := ctx.Value("timezone").(*time.Location)
switch v := in.(type) {
case *time.Time:
return &timeWrapper{&v, tz}
case **time.Time:
return &timeWrapper{v, tz}
}
return d.ConvertValue(in)
}
// Type checks.
var (
_ sqlbuilder.ScannerValuer = &StringArray{}
_ sqlbuilder.ScannerValuer = &Int64Array{}
_ sqlbuilder.ScannerValuer = &Float64Array{}
_ sqlbuilder.ScannerValuer = &Float32Array{}
_ sqlbuilder.ScannerValuer = &BoolArray{}
_ sqlbuilder.ScannerValuer = &JSONBMap{}
_ sqlbuilder.ScannerValuer = &JSONBArray{}
_ sqlbuilder.ScannerValuer = &JSONB{}
)

@ -0,0 +1,306 @@
// +build !pq
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package postgresql
import (
"database/sql/driver"
"github.com/jackc/pgtype"
)
// JSONB represents a PostgreSQL's JSONB value:
// https://www.postgresql.org/docs/9.6/static/datatype-json.html. JSONB
// satisfies sqlbuilder.ScannerValuer.
type JSONB struct {
Data interface{}
}
// MarshalJSON encodes the wrapper value as JSON.
func (j JSONB) MarshalJSON() ([]byte, error) {
t := &pgtype.JSONB{}
if err := t.Set(j.Data); err != nil {
return nil, err
}
return t.MarshalJSON()
}
// UnmarshalJSON decodes the given JSON into the wrapped value.
func (j *JSONB) UnmarshalJSON(b []byte) error {
t := &pgtype.JSONB{}
if err := t.UnmarshalJSON(b); err != nil {
return err
}
if j.Data == nil {
j.Data = t.Get()
return nil
}
if err := t.AssignTo(&j.Data); err != nil {
return err
}
return nil
}
// Scan satisfies the sql.Scanner interface.
func (j *JSONB) Scan(src interface{}) error {
t := &pgtype.JSONB{}
if err := t.Scan(src); err != nil {
return err
}
if j.Data == nil {
j.Data = t.Get()
return nil
}
if err := t.AssignTo(j.Data); err != nil {
return err
}
return nil
}
// Value satisfies the driver.Valuer interface.
func (j JSONB) Value() (driver.Value, error) {
t := &pgtype.JSONB{}
if err := t.Set(j.Data); err != nil {
return nil, err
}
return t.Value()
}
// StringArray represents a one-dimensional array of strings (`[]string{}`)
// that is compatible with PostgreSQL's text array (`text[]`). StringArray
// satisfies sqlbuilder.ScannerValuer.
type StringArray []string
// Value satisfies the driver.Valuer interface.
func (a StringArray) Value() (driver.Value, error) {
t := pgtype.TextArray{}
if err := t.Set(a); err != nil {
return nil, err
}
return t.Value()
}
// Scan satisfies the sql.Scanner interface.
func (sa *StringArray) Scan(src interface{}) error {
d := []string{}
t := pgtype.TextArray{}
if err := t.Scan(src); err != nil {
return err
}
if err := t.AssignTo(&d); err != nil {
return err
}
*sa = StringArray(d)
return nil
}
type Bytea []byte
func (b Bytea) Value() (driver.Value, error) {
t := pgtype.Bytea{Bytes: b}
if err := t.Set(b); err != nil {
return nil, err
}
return t.Value()
}
func (b *Bytea) Scan(src interface{}) error {
d := []byte{}
t := pgtype.Bytea{}
if err := t.Scan(src); err != nil {
return err
}
if err := t.AssignTo(&d); err != nil {
return err
}
*b = Bytea(d)
return nil
}
// ByteaArray represents a one-dimensional array of strings (`[]string{}`)
// that is compatible with PostgreSQL's text array (`text[]`). ByteaArray
// satisfies sqlbuilder.ScannerValuer.
type ByteaArray [][]byte
// Value satisfies the driver.Valuer interface.
func (a ByteaArray) Value() (driver.Value, error) {
t := pgtype.ByteaArray{}
if err := t.Set(a); err != nil {
return nil, err
}
return t.Value()
}
// Scan satisfies the sql.Scanner interface.
func (ba *ByteaArray) Scan(src interface{}) error {
d := [][]byte{}
t := pgtype.ByteaArray{}
if err := t.Scan(src); err != nil {
return err
}
if err := t.AssignTo(&d); err != nil {
return err
}
*ba = ByteaArray(d)
return nil
}
// Int64Array represents a one-dimensional array of int64s (`[]int64{}`) that
// is compatible with PostgreSQL's integer array (`integer[]`). Int64Array
// satisfies sqlbuilder.ScannerValuer.
type Int64Array []int64
// Value satisfies the driver.Valuer interface.
func (i64a Int64Array) Value() (driver.Value, error) {
t := pgtype.Int8Array{}
if err := t.Set(i64a); err != nil {
return nil, err
}
return t.Value()
}
// Scan satisfies the sql.Scanner interface.
func (i64a *Int64Array) Scan(src interface{}) error {
d := []int64{}
t := pgtype.Int8Array{}
if err := t.Scan(src); err != nil {
return err
}
if err := t.AssignTo(&d); err != nil {
return err
}
*i64a = Int64Array(d)
return nil
}
// Int32Array represents a one-dimensional array of int32s (`[]int32{}`) that
// is compatible with PostgreSQL's integer array (`integer[]`). Int32Array
// satisfies sqlbuilder.ScannerValuer.
type Int32Array []int32
// Value satisfies the driver.Valuer interface.
func (i32a Int32Array) Value() (driver.Value, error) {
t := pgtype.Int4Array{}
if err := t.Set(i32a); err != nil {
return nil, err
}
return t.Value()
}
// Scan satisfies the sql.Scanner interface.
func (i32a *Int32Array) Scan(src interface{}) error {
d := []int32{}
t := pgtype.Int4Array{}
if err := t.Scan(src); err != nil {
return err
}
if err := t.AssignTo(&d); err != nil {
return err
}
*i32a = Int32Array(d)
return nil
}
// Float64Array represents a one-dimensional array of float64s (`[]float64{}`)
// that is compatible with PostgreSQL's double precision array (`double
// precision[]`). Float64Array satisfies sqlbuilder.ScannerValuer.
type Float64Array []float64
// Value satisfies the driver.Valuer interface.
func (f64a Float64Array) Value() (driver.Value, error) {
t := pgtype.Float8Array{}
if err := t.Set(f64a); err != nil {
return nil, err
}
return t.Value()
}
// Scan satisfies the sql.Scanner interface.
func (f64a *Float64Array) Scan(src interface{}) error {
d := []float64{}
t := pgtype.Float8Array{}
if err := t.Scan(src); err != nil {
return err
}
if err := t.AssignTo(&d); err != nil {
return err
}
*f64a = Float64Array(d)
return nil
}
// Float32Array represents a one-dimensional array of float32s (`[]float32{}`)
// that is compatible with PostgreSQL's double precision array (`double
// precision[]`). Float32Array satisfies sqlbuilder.ScannerValuer.
type Float32Array []float32
// Value satisfies the driver.Valuer interface.
func (f32a Float32Array) Value() (driver.Value, error) {
t := pgtype.Float8Array{}
if err := t.Set(f32a); err != nil {
return nil, err
}
return t.Value()
}
// Scan satisfies the sql.Scanner interface.
func (f32a *Float32Array) Scan(src interface{}) error {
d := []float32{}
t := pgtype.Float8Array{}
if err := t.Scan(src); err != nil {
return err
}
if err := t.AssignTo(&d); err != nil {
return err
}
*f32a = Float32Array(d)
return nil
}
// BoolArray represents a one-dimensional array of int64s (`[]bool{}`) that
// is compatible with PostgreSQL's boolean type (`boolean[]`). BoolArray
// satisfies sqlbuilder.ScannerValuer.
type BoolArray []bool
// Value satisfies the driver.Valuer interface.
func (ba BoolArray) Value() (driver.Value, error) {
t := pgtype.BoolArray{}
if err := t.Set(ba); err != nil {
return nil, err
}
return t.Value()
}
// Scan satisfies the sql.Scanner interface.
func (ba *BoolArray) Scan(src interface{}) error {
d := []bool{}
t := pgtype.BoolArray{}
if err := t.Scan(src); err != nil {
return err
}
if err := t.AssignTo(&d); err != nil {
return err
}
*ba = BoolArray(d)
return nil
}

@ -0,0 +1,269 @@
// +build pq
package postgresql
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
import (
"bytes"
"database/sql/driver"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"reflect"
"strconv"
"time"
"github.com/lib/pq"
)
// JSONB represents a PostgreSQL's JSONB value:
// https://www.postgresql.org/docs/9.6/static/datatype-json.html. JSONB
// satisfies sqlbuilder.ScannerValuer.
type JSONB struct {
Data interface{}
}
// MarshalJSON encodes the wrapper value as JSON.
func (j JSONB) MarshalJSON() ([]byte, error) {
return json.Marshal(j.Data)
}
// UnmarshalJSON decodes the given JSON into the wrapped value.
func (j *JSONB) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
j.Data = v
return nil
}
// Scan satisfies the sql.Scanner interface.
func (j *JSONB) Scan(src interface{}) error {
if j.Data == nil {
return nil
}
if src == nil {
dv := reflect.Indirect(reflect.ValueOf(j.Data))
dv.Set(reflect.Zero(dv.Type()))
return nil
}
b, ok := src.([]byte)
if !ok {
return errors.New("Scan source was not []bytes")
}
if err := json.Unmarshal(b, j.Data); err != nil {
return err
}
return nil
}
// Value satisfies the driver.Valuer interface.
func (j JSONB) Value() (driver.Value, error) {
// See https://github.com/lib/pq/issues/528#issuecomment-257197239 on why are
// we returning string instead of []byte.
if j.Data == nil {
return nil, nil
}
if v, ok := j.Data.(json.RawMessage); ok {
return string(v), nil
}
b, err := json.Marshal(j.Data)
if err != nil {
return nil, err
}
return string(b), nil
}
// StringArray represents a one-dimensional array of strings (`[]string{}`)
// that is compatible with PostgreSQL's text array (`text[]`). StringArray
// satisfies sqlbuilder.ScannerValuer.
type StringArray pq.StringArray
// Value satisfies the driver.Valuer interface.
func (a StringArray) Value() (driver.Value, error) {
return pq.StringArray(a).Value()
}
// Scan satisfies the sql.Scanner interface.
func (a *StringArray) Scan(src interface{}) error {
s := pq.StringArray(*a)
if err := s.Scan(src); err != nil {
return err
}
*a = StringArray(s)
return nil
}
// Int64Array represents a one-dimensional array of int64s (`[]int64{}`) that
// is compatible with PostgreSQL's integer array (`integer[]`). Int64Array
// satisfies sqlbuilder.ScannerValuer.
type Int64Array pq.Int64Array
// Value satisfies the driver.Valuer interface.
func (i Int64Array) Value() (driver.Value, error) {
return pq.Int64Array(i).Value()
}
// Scan satisfies the sql.Scanner interface.
func (i *Int64Array) Scan(src interface{}) error {
s := pq.Int64Array(*i)
if err := s.Scan(src); err != nil {
return err
}
*i = Int64Array(s)
return nil
}
// Float64Array represents a one-dimensional array of float64s (`[]float64{}`)
// that is compatible with PostgreSQL's double precision array (`double
// precision[]`). Float64Array satisfies sqlbuilder.ScannerValuer.
type Float64Array pq.Float64Array
// Value satisfies the driver.Valuer interface.
func (f Float64Array) Value() (driver.Value, error) {
return pq.Float64Array(f).Value()
}
// Scan satisfies the sql.Scanner interface.
func (f *Float64Array) Scan(src interface{}) error {
s := pq.Float64Array(*f)
if err := s.Scan(src); err != nil {
return err
}
*f = Float64Array(s)
return nil
}
// Float32Array represents a one-dimensional array of float32s (`[]float32{}`)
// that is compatible with PostgreSQL's double precision array (`double
// precision[]`). Float32Array satisfies sqlbuilder.ScannerValuer.
type Float32Array pq.Float32Array
// Value satisfies the driver.Valuer interface.
func (f Float32Array) Value() (driver.Value, error) {
return pq.Float32Array(f).Value()
}
// Scan satisfies the sql.Scanner interface.
func (f *Float32Array) Scan(src interface{}) error {
s := pq.Float32Array(*f)
if err := s.Scan(src); err != nil {
return err
}
*f = Float32Array(s)
return nil
}
// BoolArray represents a one-dimensional array of int64s (`[]bool{}`) that
// is compatible with PostgreSQL's boolean type (`boolean[]`). BoolArray
// satisfies sqlbuilder.ScannerValuer.
type BoolArray pq.BoolArray
// Value satisfies the driver.Valuer interface.
func (b BoolArray) Value() (driver.Value, error) {
return pq.BoolArray(b).Value()
}
// Scan satisfies the sql.Scanner interface.
func (b *BoolArray) Scan(src interface{}) error {
s := pq.BoolArray(*b)
if err := s.Scan(src); err != nil {
return err
}
*b = BoolArray(s)
return nil
}
type Bytea []byte
// Scan satisfies the sql.Scanner interface.
func (b *Bytea) Scan(src interface{}) error {
decoded, err := parseBytea(src.([]byte))
if err != nil {
return err
}
if len(decoded) < 1 {
*b = nil
return nil
}
(*b) = make(Bytea, len(decoded))
for i := range decoded {
(*b)[i] = decoded[i]
}
return nil
}
type Time time.Time
// Parse a bytea value received from the server. Both "hex" and the legacy
// "escape" format are supported.
func parseBytea(s []byte) (result []byte, err error) {
if len(s) >= 2 && bytes.Equal(s[:2], []byte("\\x")) {
// bytea_output = hex
s = s[2:] // trim off leading "\\x"
result = make([]byte, hex.DecodedLen(len(s)))
_, err := hex.Decode(result, s)
if err != nil {
return nil, err
}
} else {
// bytea_output = escape
for len(s) > 0 {
if s[0] == '\\' {
// escaped '\\'
if len(s) >= 2 && s[1] == '\\' {
result = append(result, '\\')
s = s[2:]
continue
}
// '\\' followed by an octal number
if len(s) < 4 {
return nil, fmt.Errorf("invalid bytea sequence %v", s)
}
r, err := strconv.ParseInt(string(s[1:4]), 8, 9)
if err != nil {
return nil, fmt.Errorf("could not parse bytea value: %s", err.Error())
}
result = append(result, byte(r))
s = s[4:]
} else {
// We hit an unescaped, raw byte. Try to read in as many as
// possible in one go.
i := bytes.IndexByte(s, '\\')
if i == -1 {
result = append(result, s...)
break
}
result = append(result, s[:i]...)
s = s[i:]
}
}
}
return result, nil
}

@ -0,0 +1,201 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
// Package postgresql provides an adapter for PostgreSQL.
// See https://github.com/upper/db/adapter/postgresql for documentation,
// particularities and usage examples.
package postgresql
import (
"fmt"
"strings"
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
"github.com/upper/db/v4/internal/sqladapter/exql"
"github.com/upper/db/v4/internal/sqlbuilder"
)
type database struct {
}
func (*database) Template() *exql.Template {
return template
}
func (*database) Collections(sess sqladapter.Session) (collections []string, err error) {
q := sess.SQL().
Select("table_name").
From("information_schema.tables").
Where("table_schema = ?", "public")
iter := q.Iterator()
defer iter.Close()
for iter.Next() {
var name string
if err := iter.Scan(&name); err != nil {
return nil, err
}
collections = append(collections, name)
}
if err := iter.Err(); err != nil {
return nil, err
}
return collections, nil
}
func (*database) ConvertValue(in interface{}) interface{} {
switch v := in.(type) {
case *[]int64:
return (*Int64Array)(v)
case *[]string:
return (*StringArray)(v)
case *[]float64:
return (*Float64Array)(v)
case *[]bool:
return (*BoolArray)(v)
case *map[string]interface{}:
return (*JSONBMap)(v)
case []int64:
return (*Int64Array)(&v)
case []string:
return (*StringArray)(&v)
case []float64:
return (*Float64Array)(&v)
case []bool:
return (*BoolArray)(&v)
case map[string]interface{}:
return (*JSONBMap)(&v)
}
return in
}
func (*database) CompileStatement(sess sqladapter.Session, stmt *exql.Statement, args []interface{}) (string, []interface{}, error) {
compiled, err := stmt.Compile(template)
if err != nil {
return "", nil, err
}
query, args := sqlbuilder.Preprocess(compiled, args)
query = sqladapter.ReplaceWithDollarSign(query)
return query, args, nil
}
func (*database) Err(err error) error {
if err != nil {
s := err.Error()
// These errors are not exported so we have to check them by they string value.
if strings.Contains(s, `too many clients`) || strings.Contains(s, `remaining connection slots are reserved`) || strings.Contains(s, `too many open`) {
return db.ErrTooManyClients
}
}
return err
}
func (*database) NewCollection() sqladapter.CollectionAdapter {
return &collectionAdapter{}
}
func (*database) LookupName(sess sqladapter.Session) (string, error) {
q := sess.SQL().
Select(db.Raw("CURRENT_DATABASE() AS name"))
iter := q.Iterator()
defer iter.Close()
if iter.Next() {
var name string
if err := iter.Scan(&name); err != nil {
return "", err
}
return name, nil
}
return "", iter.Err()
}
func (*database) TableExists(sess sqladapter.Session, name string) error {
q := sess.SQL().
Select("table_name").
From("information_schema.tables").
Where("table_catalog = ? AND table_name = ?", sess.Name(), name)
iter := q.Iterator()
defer iter.Close()
if iter.Next() {
var name string
if err := iter.Scan(&name); err != nil {
return err
}
return nil
}
if err := iter.Err(); err != nil {
return err
}
return db.ErrCollectionDoesNotExist
}
func (*database) PrimaryKeys(sess sqladapter.Session, tableName string) ([]string, error) {
q := sess.SQL().
Select("pg_attribute.attname AS pkey").
From("pg_index", "pg_class", "pg_attribute").
Where(`
pg_class.oid = '` + quotedTableName(tableName) + `'::regclass
AND indrelid = pg_class.oid
AND pg_attribute.attrelid = pg_class.oid
AND pg_attribute.attnum = ANY(pg_index.indkey)
AND indisprimary
`).OrderBy("pkey")
iter := q.Iterator()
defer iter.Close()
pk := []string{}
for iter.Next() {
var k string
if err := iter.Scan(&k); err != nil {
return nil, err
}
pk = append(pk, k)
}
if err := iter.Err(); err != nil {
return nil, err
}
return pk, nil
}
// quotedTableName returns a valid regclass name for both regular tables and
// for schemas.
func quotedTableName(s string) string {
chunks := strings.Split(s, ".")
for i := range chunks {
chunks[i] = fmt.Sprintf("%q", chunks[i])
}
return strings.Join(chunks, ".")
}

@ -0,0 +1,46 @@
//go:build !pq
// +build !pq
package postgresql
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
import (
"context"
"database/sql"
_ "github.com/jackc/pgx/v4/stdlib"
"github.com/upper/db/v4/internal/sqladapter"
"time"
)
func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) {
connURL, err := ParseURL(dsn)
if err != nil {
return nil, err
}
if tz := connURL.Options["timezone"]; tz != "" {
loc, _ := time.LoadLocation(tz)
ctx := context.WithValue(sess.Context(), "timezone", loc)
sess.SetContext(ctx)
}
return sql.Open("pgx", dsn)
}

@ -0,0 +1,45 @@
// +build pq
package postgresql
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
import (
"context"
"database/sql"
_ "github.com/lib/pq"
"github.com/upper/db/v4/internal/sqladapter"
"time"
)
func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) {
connURL, err := ParseURL(dsn)
if err != nil {
return nil, err
}
if tz := connURL.Options["timezone"]; tz != "" {
loc, _ := time.LoadLocation(tz)
ctx := context.WithValue(sess.Context(), "timezone", loc)
sess.SetContext(ctx)
}
return sql.Open("postgres", dsn)
}

@ -0,0 +1,13 @@
version: '3'
services:
server:
image: postgres:${POSTGRES_VERSION:-11}
environment:
POSTGRES_USER: ${DB_USERNAME:-upperio_user}
POSTGRES_PASSWORD: ${DB_PASSWORD:-upperio//s3cr37}
POSTGRES_DB: ${DB_NAME:-upperio}
ports:
- '${DB_HOST:-127.0.0.1}:${DB_PORT:-5432}:5432'

@ -0,0 +1,51 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package postgresql
import (
"database/sql"
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
"github.com/upper/db/v4/internal/sqlbuilder"
)
// Adapter is the internal name of the adapter.
const Adapter = "postgresql"
var registeredAdapter = sqladapter.RegisterAdapter(Adapter, &database{})
// Open establishes a connection to the database server and returns a
// sqlbuilder.Session instance (which is compatible with db.Session).
func Open(connURL db.ConnectionURL) (db.Session, error) {
return registeredAdapter.OpenDSN(connURL)
}
// NewTx creates a sqlbuilder.Tx instance by wrapping a *sql.Tx value.
func NewTx(sqlTx *sql.Tx) (sqlbuilder.Tx, error) {
return registeredAdapter.NewTx(sqlTx)
}
// New creates a sqlbuilder.Sesion instance by wrapping a *sql.DB value.
func New(sqlDB *sql.DB) (db.Session, error) {
return registeredAdapter.New(sqlDB)
}

@ -0,0 +1,210 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package postgresql
import (
"github.com/upper/db/v4/internal/adapter"
"github.com/upper/db/v4/internal/cache"
"github.com/upper/db/v4/internal/sqladapter/exql"
)
const (
adapterColumnSeparator = `.`
adapterIdentifierSeparator = `, `
adapterIdentifierQuote = `"{{.Value}}"`
adapterValueSeparator = `, `
adapterValueQuote = `'{{.}}'`
adapterAndKeyword = `AND`
adapterOrKeyword = `OR`
adapterDescKeyword = `DESC`
adapterAscKeyword = `ASC`
adapterAssignmentOperator = `=`
adapterClauseGroup = `({{.}})`
adapterClauseOperator = ` {{.}} `
adapterColumnValue = `{{.Column}} {{.Operator}} {{.Value}}`
adapterTableAliasLayout = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}`
adapterColumnAliasLayout = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}`
adapterSortByColumnLayout = `{{.Column}} {{.Order}}`
adapterOrderByLayout = `
{{if .SortColumns}}
ORDER BY {{.SortColumns}}
{{end}}
`
adapterWhereLayout = `
{{if .Conds}}
WHERE {{.Conds}}
{{end}}
`
adapterUsingLayout = `
{{if .Columns}}
USING ({{.Columns}})
{{end}}
`
adapterJoinLayout = `
{{if .Table}}
{{ if .On }}
{{.Type}} JOIN {{.Table}}
{{.On}}
{{ else if .Using }}
{{.Type}} JOIN {{.Table}}
{{.Using}}
{{ else if .Type | eq "CROSS" }}
{{.Type}} JOIN {{.Table}}
{{else}}
NATURAL {{.Type}} JOIN {{.Table}}
{{end}}
{{end}}
`
adapterOnLayout = `
{{if .Conds}}
ON {{.Conds}}
{{end}}
`
adapterSelectLayout = `
SELECT
{{if .Distinct}}
DISTINCT
{{end}}
{{if defined .Columns}}
{{.Columns | compile}}
{{else}}
*
{{end}}
{{if defined .Table}}
FROM {{.Table | compile}}
{{end}}
{{.Joins | compile}}
{{.Where | compile}}
{{if defined .GroupBy}}
{{.GroupBy | compile}}
{{end}}
{{.OrderBy | compile}}
{{if .Limit}}
LIMIT {{.Limit}}
{{end}}
{{if .Offset}}
OFFSET {{.Offset}}
{{end}}
`
adapterDeleteLayout = `
DELETE
FROM {{.Table | compile}}
{{.Where | compile}}
`
adapterUpdateLayout = `
UPDATE
{{.Table | compile}}
SET {{.ColumnValues | compile}}
{{.Where | compile}}
`
adapterSelectCountLayout = `
SELECT
COUNT(1) AS _t
FROM {{.Table | compile}}
{{.Where | compile}}
`
adapterInsertLayout = `
INSERT INTO {{.Table | compile}}
{{if defined .Columns}}({{.Columns | compile}}){{end}}
VALUES
{{if defined .Values}}
{{.Values | compile}}
{{else}}
(default)
{{end}}
{{if defined .Returning}}
RETURNING {{.Returning | compile}}
{{end}}
`
adapterTruncateLayout = `
TRUNCATE TABLE {{.Table | compile}} RESTART IDENTITY
`
adapterDropDatabaseLayout = `
DROP DATABASE {{.Database | compile}}
`
adapterDropTableLayout = `
DROP TABLE {{.Table | compile}}
`
adapterGroupByLayout = `
{{if .GroupColumns}}
GROUP BY {{.GroupColumns}}
{{end}}
`
)
var template = &exql.Template{
ColumnSeparator: adapterColumnSeparator,
IdentifierSeparator: adapterIdentifierSeparator,
IdentifierQuote: adapterIdentifierQuote,
ValueSeparator: adapterValueSeparator,
ValueQuote: adapterValueQuote,
AndKeyword: adapterAndKeyword,
OrKeyword: adapterOrKeyword,
DescKeyword: adapterDescKeyword,
AscKeyword: adapterAscKeyword,
AssignmentOperator: adapterAssignmentOperator,
ClauseGroup: adapterClauseGroup,
ClauseOperator: adapterClauseOperator,
ColumnValue: adapterColumnValue,
TableAliasLayout: adapterTableAliasLayout,
ColumnAliasLayout: adapterColumnAliasLayout,
SortByColumnLayout: adapterSortByColumnLayout,
WhereLayout: adapterWhereLayout,
JoinLayout: adapterJoinLayout,
OnLayout: adapterOnLayout,
UsingLayout: adapterUsingLayout,
OrderByLayout: adapterOrderByLayout,
InsertLayout: adapterInsertLayout,
SelectLayout: adapterSelectLayout,
UpdateLayout: adapterUpdateLayout,
DeleteLayout: adapterDeleteLayout,
TruncateLayout: adapterTruncateLayout,
DropDatabaseLayout: adapterDropDatabaseLayout,
DropTableLayout: adapterDropTableLayout,
CountLayout: adapterSelectCountLayout,
GroupByLayout: adapterGroupByLayout,
Cache: cache.NewCache(),
ComparisonOperator: map[adapter.ComparisonOperator]string{
adapter.ComparisonOperatorRegExp: "~",
adapter.ComparisonOperatorNotRegExp: "!~",
},
}

@ -0,0 +1,20 @@
SHELL ?= bash
DB_NAME ?= memory://virtual.db
TEST_FLAGS ?=
export DB_NAME
build:
go build && go install
# Temporarily disabled due to https://gitlab.com/cznic/ql/-/issues/225
#test:
# go test -v -race -timeout 20m $(TEST_FLAGS)
test: test-no-race
test-no-race:
go test -v -failfast $(TEST_FLAGS)
test-extended: test

@ -0,0 +1,99 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package ql
import (
"database/sql"
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
"github.com/upper/db/v4/internal/sqlbuilder"
)
type resultProxy struct {
db.Result
col sqladapter.Collection
}
func (r *resultProxy) Select(fields ...interface{}) db.Result {
if len(fields) == 1 {
if s, ok := fields[0].(string); ok && s == "*" {
var columns []struct {
Name string `db:"Name"`
}
err := r.col.SQL().Select("Name").
From("__Column").
Where("TableName", r.col.Name()).
Iterator().All(&columns)
if err == nil {
fields = make([]interface{}, 0, len(columns)+1)
fields = append(fields, "id() AS id")
for _, column := range columns {
fields = append(fields, column.Name)
}
}
}
}
return r.Result.Select(fields...)
}
type collectionAdapter struct {
}
func (*collectionAdapter) FilterConds(conds ...interface{}) []interface{} {
if len(conds) == 1 {
switch conds[0].(type) {
case int, int64, uint, uint64:
// This is an special QL index, I'm not sure if it allows the user to
// create special indexes with custom names.
conds[0] = db.Cond{"id()": db.Eq(conds[0])}
}
}
return conds
}
func (*collectionAdapter) Find(col sqladapter.Collection, res *sqladapter.Result, conds ...interface{}) db.Result {
proxy := &resultProxy{
Result: res,
col: col,
}
return proxy.Select("*")
}
func (*collectionAdapter) Insert(col sqladapter.Collection, item interface{}) (interface{}, error) {
columnNames, columnValues, err := sqlbuilder.Map(item, nil)
if err != nil {
return nil, err
}
q := col.SQL().InsertInto(col.Name()).
Columns(columnNames...).
Values(columnValues...)
var res sql.Result
if res, err = q.Exec(); err != nil {
return nil, err
}
return res.LastInsertId()
}

@ -0,0 +1,100 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package ql
import (
"fmt"
"net/url"
"path/filepath"
"strings"
)
const defaultConnectionScheme = `file`
// ConnectionURL implements a QL connection struct.
type ConnectionURL struct {
Scheme string
Database string
Options map[string]string
}
func (c ConnectionURL) String() (s string) {
vv := url.Values{}
if c.Database == "" {
return ""
}
// Does the database have an existing name?
if !strings.HasPrefix(c.Database, "/") {
c.Database, _ = filepath.Abs(c.Database)
}
// Do we have any options?
if c.Options == nil {
c.Options = map[string]string{}
}
// Converting options into URL values.
for k, v := range c.Options {
vv.Set(k, v)
}
// Building URL.
u := url.URL{
Scheme: c.Scheme,
Path: c.Database,
RawQuery: vv.Encode(),
}
if u.Scheme == "" {
u.Scheme = defaultConnectionScheme
}
return u.String()
}
// ParseURL parses s into a ConnectionURL struct.
func ParseURL(s string) (conn ConnectionURL, err error) {
var u *url.URL
if u, err = url.Parse(s); err != nil {
return conn, err
}
if u.Scheme != "file" && u.Scheme != "memory" && u.Scheme != "" {
return conn, fmt.Errorf(`Expecting file:// or memory:// connection scheme.`)
}
var vv url.Values
if vv, err = url.ParseQuery(u.RawQuery); err != nil {
return conn, err
}
conn.Scheme = u.Scheme
conn.Database = u.Host + u.Path
conn.Options = map[string]string{}
for k := range vv {
conn.Options[k] = vv.Get(k)
}
return conn, err
}

@ -0,0 +1,145 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
// Package ql wraps the modernc.org/ql/driver QL driver. See
// https://github.com/upper/db/adapter/ql for documentation, particularities and usage
// examples.
package ql
import (
"context"
"database/sql"
"strings"
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
"github.com/upper/db/v4/internal/sqladapter/compat"
"github.com/upper/db/v4/internal/sqladapter/exql"
"github.com/upper/db/v4/internal/sqlbuilder"
_ "modernc.org/ql/driver" // QL driver
)
// database is the actual implementation of Database
type database struct {
}
func (*database) Template() *exql.Template {
return template
}
func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) {
return sql.Open("ql2", dsn)
}
func (*database) Collections(sess sqladapter.Session) (collections []string, err error) {
q := sess.SQL().
Select("Name").
From("__Table")
iter := q.Iterator()
defer iter.Close()
for iter.Next() {
var tableName string
if err := iter.Scan(&tableName); err != nil {
return nil, err
}
if strings.HasPrefix(tableName, "__") {
continue
}
collections = append(collections, tableName)
}
if err := iter.Err(); err != nil {
return nil, err
}
return collections, nil
}
func (*database) CompileStatement(sess sqladapter.Session, stmt *exql.Statement, args []interface{}) (string, []interface{}, error) {
compiled, err := stmt.Compile(template)
if err != nil {
return "", nil, err
}
query, args := sqlbuilder.Preprocess(compiled, args)
query = sqladapter.ReplaceWithDollarSign(query)
return query, args, nil
}
func (*database) StatementExec(sess sqladapter.Session, ctx context.Context, query string, args ...interface{}) (res sql.Result, err error) {
if sess.Transaction() != nil {
return compat.ExecContext(sess.Driver().(*sql.Tx), ctx, query, args)
}
sqlTx, err := compat.BeginTx(sess.Driver().(*sql.DB), ctx, nil)
if err != nil {
return nil, err
}
if res, err = compat.ExecContext(sqlTx, ctx, query, args); err != nil {
return nil, err
}
if err = sqlTx.Commit(); err != nil {
return nil, err
}
return res, err
}
func (*database) NewCollection() sqladapter.CollectionAdapter {
return &collectionAdapter{}
}
func (*database) LookupName(sess sqladapter.Session) (string, error) {
connURL, err := ParseURL(sess.ConnectionURL().String())
if err != nil {
return "", err
}
return connURL.Database, nil
}
func (*database) TableExists(sess sqladapter.Session, name string) error {
q := sess.SQL().Select("Name").
From("__Table").
Where("Name == ?", name)
iter := q.Iterator()
defer iter.Close()
if iter.Next() {
var name string
if err := iter.Scan(&name); err != nil {
return err
}
return nil
}
if err := iter.Err(); err != nil {
return err
}
return db.ErrCollectionDoesNotExist
}
func (*database) PrimaryKeys(sess sqladapter.Session, tableName string) ([]string, error) {
return []string{"id()"}, nil
}

@ -0,0 +1,51 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package ql
import (
"database/sql"
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
"github.com/upper/db/v4/internal/sqlbuilder"
)
// Adapter is the public name of the adapter.
const Adapter = `ql`
var registeredAdapter = sqladapter.RegisterAdapter(Adapter, &database{})
// Open establishes a connection to the database server and returns a
// db.Session instance (which is compatible with db.Session).
func Open(connURL db.ConnectionURL) (db.Session, error) {
return registeredAdapter.OpenDSN(connURL)
}
// NewTx creates a sqlbuilder.Tx instance by wrapping a *sql.Tx value.
func NewTx(sqlTx *sql.Tx) (sqlbuilder.Tx, error) {
return registeredAdapter.NewTx(sqlTx)
}
// New creates a sqlbuilder.Sesion instance by wrapping a *sql.DB value.
func New(sqlDB *sql.DB) (db.Session, error) {
return registeredAdapter.New(sqlDB)
}

@ -0,0 +1,214 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package ql
import (
"github.com/upper/db/v4/internal/adapter"
"github.com/upper/db/v4/internal/cache"
"github.com/upper/db/v4/internal/sqladapter/exql"
)
const (
adapterColumnSeparator = `.`
adapterIdentifierSeparator = `, `
adapterIdentifierQuote = `{{.Value}}`
adapterValueSeparator = `, `
adapterValueQuote = `"{{.}}"`
adapterAndKeyword = `&&`
adapterOrKeyword = `||`
adapterDescKeyword = `DESC`
adapterAscKeyword = `ASC`
adapterAssignmentOperator = `=`
adapterClauseGroup = `({{.}})`
adapterClauseOperator = ` {{.}} `
adapterColumnValue = `{{.Column}} {{.Operator}} {{.Value}}`
adapterTableAliasLayout = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}`
adapterColumnAliasLayout = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}`
adapterSortByColumnLayout = `{{.Column}} {{.Order}}`
adapterOrderByLayout = `{{if .SortColumns}}ORDER BY {{.SortColumns}}{{end}}`
adapterWhereLayout = `
{{if .Conds}}
WHERE {{.Conds}}
{{end}}
`
adapterUsingLayout = `
{{if .Columns}}
USING ({{.Columns}})
{{end}}
`
adapterJoinLayout = `
{{if .Table}}
{{ if .On }}
{{.Type}} JOIN {{.Table}}
{{.On}}
{{ else if .Using }}
{{.Type}} JOIN {{.Table}}
{{.Using}}
{{ else if .Type | eq "CROSS" }}
{{.Type}} JOIN {{.Table}}
{{else}}
NATURAL {{.Type}} JOIN {{.Table}}
{{end}}
{{end}}
`
adapterOnLayout = `
{{if .Conds}}
ON {{.Conds}}
{{end}}
`
adapterSelectLayout = `
SELECT
{{if .Distinct}}
DISTINCT
{{end}}
{{if defined .Columns}}
{{.Columns | compile}}
{{else}}
*
{{end}}
{{if defined .Table}}
FROM {{.Table | compile}}
{{end}}
{{.Joins | compile}}
{{.Where | compile}}
{{if defined .GroupBy}}
{{.GroupBy | compile}}
{{end}}
{{if defined .OrderBy}}
{{.OrderBy | compile}}
{{else}}
{{if defined .Table}}
ORDER BY id() ASC
{{end}}
{{end}}
{{if .Limit}}
LIMIT {{.Limit}}
{{end}}
{{if .Offset}}
OFFSET {{.Offset}}
{{end}}
`
adapterDeleteLayout = `
DELETE
FROM {{.Table | compile}}
{{.Where | compile}}
`
adapterUpdateLayout = `
UPDATE
{{.Table | compile}}
SET {{.ColumnValues | compile}}
{{.Where | compile}}
`
adapterSelectCountLayout = `
SELECT
count(1) AS total
FROM {{.Table | compile}}
{{.Where | compile}}
`
adapterInsertLayout = `
INSERT INTO {{.Table | compile}}
{{if defined .Columns }}({{.Columns | compile}}){{end}}
{{if defined .Values}}
VALUES
{{.Values | compile}}
{{else}}
DEFAULT VALUES
{{end}}
{{if defined .Returning}}
RETURNING {{.Returning | compile}}
{{end}}
`
adapterTruncateLayout = `
TRUNCATE TABLE {{.Table | compile}}
`
adapterDropDatabaseLayout = `
DROP DATABASE {{.Database | compile}}
`
adapterDropTableLayout = `
DROP TABLE {{.Table | compile}}
`
adapterGroupByLayout = `
{{if .GroupColumns}}
GROUP BY {{.GroupColumns}}
{{end}}
`
)
var template = &exql.Template{
ColumnSeparator: adapterColumnSeparator,
IdentifierSeparator: adapterIdentifierSeparator,
IdentifierQuote: adapterIdentifierQuote,
ValueSeparator: adapterValueSeparator,
ValueQuote: adapterValueQuote,
AndKeyword: adapterAndKeyword,
OrKeyword: adapterOrKeyword,
DescKeyword: adapterDescKeyword,
AscKeyword: adapterAscKeyword,
AssignmentOperator: adapterAssignmentOperator,
ClauseGroup: adapterClauseGroup,
ClauseOperator: adapterClauseOperator,
ColumnValue: adapterColumnValue,
TableAliasLayout: adapterTableAliasLayout,
ColumnAliasLayout: adapterColumnAliasLayout,
SortByColumnLayout: adapterSortByColumnLayout,
WhereLayout: adapterWhereLayout,
JoinLayout: adapterJoinLayout,
OnLayout: adapterOnLayout,
UsingLayout: adapterUsingLayout,
OrderByLayout: adapterOrderByLayout,
InsertLayout: adapterInsertLayout,
SelectLayout: adapterSelectLayout,
UpdateLayout: adapterUpdateLayout,
DeleteLayout: adapterDeleteLayout,
TruncateLayout: adapterTruncateLayout,
DropDatabaseLayout: adapterDropDatabaseLayout,
DropTableLayout: adapterDropTableLayout,
CountLayout: adapterSelectCountLayout,
GroupByLayout: adapterGroupByLayout,
Cache: cache.NewCache(),
ComparisonOperator: map[adapter.ComparisonOperator]string{
adapter.ComparisonOperatorEqual: "==",
adapter.ComparisonOperatorNotLike: "!(:column LIKE ?)",
adapter.ComparisonOperatorRegExp: "LIKE",
adapter.ComparisonOperatorNotRegExp: "!(:column LIKE ?)",
},
}

@ -0,0 +1,27 @@
SHELL ?= bash
DB_NAME ?= sqlite3-test.db
TEST_FLAGS ?=
export DB_NAME
export TEST_FLAGS
build:
go build && go install
require-client:
@if [ -z "$$(which sqlite3)" ]; then \
echo 'Missing "sqlite3" command. Please install SQLite3 and try again.' && \
exit 1; \
fi
reset-db: require-client
rm -f $(DB_NAME)
test: reset-db
go test -v -failfast -race -timeout 20m $(TEST_FLAGS)
test-no-race:
go test -v -failfast $(TEST_FLAGS)
test-extended: test

@ -0,0 +1,4 @@
# SQLite adapter for upper/db
Please read the full docs, acknowledgements and examples at
[https://upper.io/v4/adapter/sqlite/](https://upper.io/v4/adapter/sqlite/).

@ -0,0 +1,70 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package sqlite
import (
"database/sql"
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
"github.com/upper/db/v4/internal/sqlbuilder"
)
type collectionAdapter struct {
}
func (*collectionAdapter) Insert(col sqladapter.Collection, item interface{}) (interface{}, error) {
columnNames, columnValues, err := sqlbuilder.Map(item, nil)
if err != nil {
return nil, err
}
pKey, err := col.PrimaryKeys()
if err != nil {
return nil, err
}
q := col.SQL().InsertInto(col.Name()).
Columns(columnNames...).
Values(columnValues...)
var res sql.Result
if res, err = q.Exec(); err != nil {
return nil, err
}
if len(pKey) <= 1 {
return res.LastInsertId()
}
keyMap := db.Cond{}
for i := range columnNames {
for j := 0; j < len(pKey); j++ {
if pKey[j] == columnNames[i] {
keyMap[pKey[j]] = columnValues[i]
}
}
}
return keyMap, nil
}

@ -0,0 +1,110 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package sqlite
import (
"fmt"
"net/url"
"path/filepath"
"runtime"
"strings"
)
const connectionScheme = `file`
// ConnectionURL implements a SQLite connection struct.
type ConnectionURL struct {
Database string
Options map[string]string
}
func (c ConnectionURL) String() (s string) {
vv := url.Values{}
if c.Database == "" {
return ""
}
// Did the user provided a full database path?
if !strings.HasPrefix(c.Database, "/") {
c.Database, _ = filepath.Abs(c.Database)
if runtime.GOOS == "windows" {
// Closes https://github.com/upper/db/issues/60
c.Database = "/" + strings.Replace(c.Database, `\`, `/`, -1)
}
}
// Do we have any options?
if c.Options == nil {
c.Options = map[string]string{}
}
if _, ok := c.Options["_busy_timeout"]; !ok {
c.Options["_busy_timeout"] = "10000"
}
// Converting options into URL values.
for k, v := range c.Options {
vv.Set(k, v)
}
// Building URL.
u := url.URL{
Scheme: connectionScheme,
Path: c.Database,
RawQuery: vv.Encode(),
}
return u.String()
}
// ParseURL parses s into a ConnectionURL struct.
func ParseURL(s string) (conn ConnectionURL, err error) {
var u *url.URL
if !strings.HasPrefix(s, connectionScheme+"://") {
return conn, fmt.Errorf(`Expecting file:// connection scheme.`)
}
if u, err = url.Parse(s); err != nil {
return conn, err
}
conn.Database = u.Host + u.Path
conn.Options = map[string]string{}
var vv url.Values
if vv, err = url.ParseQuery(u.RawQuery); err != nil {
return conn, err
}
for k := range vv {
conn.Options[k] = vv.Get(k)
}
if _, ok := conn.Options["cache"]; !ok {
conn.Options["cache"] = "shared"
}
return conn, err
}

@ -0,0 +1,188 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
// Package sqlite wraps the github.com/lib/sqlite SQLite driver. See
// https://github.com/upper/db/adapter/sqlite for documentation, particularities and
// usage examples.
package sqlite
import (
"context"
"database/sql"
"fmt"
_ "github.com/mattn/go-sqlite3" // SQLite3 driver.
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
"github.com/upper/db/v4/internal/sqladapter/compat"
"github.com/upper/db/v4/internal/sqladapter/exql"
)
// database is the actual implementation of Database
type database struct {
}
func (*database) Template() *exql.Template {
return template
}
func (*database) OpenDSN(sess sqladapter.Session, dsn string) (*sql.DB, error) {
return sql.Open("sqlite3", dsn)
}
func (*database) Collections(sess sqladapter.Session) (collections []string, err error) {
q := sess.SQL().
Select("tbl_name").
From("sqlite_master").
Where("type = ?", "table")
iter := q.Iterator()
defer iter.Close()
for iter.Next() {
var tableName string
if err := iter.Scan(&tableName); err != nil {
return nil, err
}
collections = append(collections, tableName)
}
if err := iter.Err(); err != nil {
return nil, err
}
return collections, nil
}
func (*database) StatementExec(sess sqladapter.Session, ctx context.Context, query string, args ...interface{}) (res sql.Result, err error) {
if sess.Transaction() != nil {
return compat.ExecContext(sess.Driver().(*sql.Tx), ctx, query, args)
}
sqlTx, err := compat.BeginTx(sess.Driver().(*sql.DB), ctx, nil)
if err != nil {
return nil, err
}
if res, err = compat.ExecContext(sqlTx, ctx, query, args); err != nil {
return nil, err
}
if err = sqlTx.Commit(); err != nil {
return nil, err
}
return res, err
}
func (*database) NewCollection() sqladapter.CollectionAdapter {
return &collectionAdapter{}
}
func (*database) LookupName(sess sqladapter.Session) (string, error) {
connURL := sess.ConnectionURL()
if connURL != nil {
connURL, err := ParseURL(connURL.String())
if err != nil {
return "", err
}
return connURL.Database, nil
}
// sess.ConnectionURL() is nil if using sqlite.New
rows, err := sess.SQL().Query(exql.RawSQL("PRAGMA database_list"))
if err != nil {
return "", err
}
dbInfo := struct {
Name string `db:"name"`
File string `db:"file"`
}{}
if err := sess.SQL().NewIterator(rows).One(&dbInfo); err != nil {
return "", err
}
if dbInfo.File != "" {
return dbInfo.File, nil
}
// dbInfo.File is empty if in memory mode
return dbInfo.Name, nil
}
func (*database) TableExists(sess sqladapter.Session, name string) error {
q := sess.SQL().
Select("tbl_name").
From("sqlite_master").
Where("type = 'table' AND tbl_name = ?", name)
iter := q.Iterator()
defer iter.Close()
if iter.Next() {
var name string
if err := iter.Scan(&name); err != nil {
return err
}
return nil
}
if err := iter.Err(); err != nil {
return err
}
return db.ErrCollectionDoesNotExist
}
func (*database) PrimaryKeys(sess sqladapter.Session, tableName string) ([]string, error) {
pk := make([]string, 0, 1)
stmt := exql.RawSQL(fmt.Sprintf("PRAGMA TABLE_INFO('%s')", tableName))
rows, err := sess.SQL().Query(stmt)
if err != nil {
return nil, err
}
columns := []struct {
Name string `db:"name"`
PK int `db:"pk"`
}{}
if err := sess.SQL().NewIterator(rows).All(&columns); err != nil {
return nil, err
}
maxValue := -1
for _, column := range columns {
if column.PK > 0 && column.PK > maxValue {
maxValue = column.PK
}
}
if maxValue > 0 {
for _, column := range columns {
if column.PK > 0 {
pk = append(pk, column.Name)
}
}
}
return pk, nil
}

@ -0,0 +1,52 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package sqlite
import (
"database/sql"
db "github.com/upper/db/v4"
"github.com/upper/db/v4/internal/sqladapter"
"github.com/upper/db/v4/internal/sqlbuilder"
)
// Adapter is the public name of the adapter.
const Adapter = `sqlite`
var registeredAdapter = sqladapter.RegisterAdapter(Adapter, &database{})
// Open establishes a connection to the database server and returns a
// db.Session instance (which is compatible with db.Session).
func Open(connURL db.ConnectionURL) (db.Session, error) {
return registeredAdapter.OpenDSN(connURL)
}
// NewTx creates a sqlbuilder.Tx instance by wrapping a *sql.Tx value.
func NewTx(sqlTx *sql.Tx) (sqlbuilder.Tx, error) {
return registeredAdapter.NewTx(sqlTx)
}
// New creates a sqlbuilder.Sesion instance by wrapping a *sql.DB value.
func New(sqlDB *sql.DB) (db.Session, error) {
return registeredAdapter.New(sqlDB)
}

@ -0,0 +1,208 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package sqlite
import (
"github.com/upper/db/v4/internal/cache"
"github.com/upper/db/v4/internal/sqladapter/exql"
)
const (
adapterColumnSeparator = `.`
adapterIdentifierSeparator = `, `
adapterIdentifierQuote = `"{{.Value}}"`
adapterValueSeparator = `, `
adapterValueQuote = `'{{.}}'`
adapterAndKeyword = `AND`
adapterOrKeyword = `OR`
adapterDescKeyword = `DESC`
adapterAscKeyword = `ASC`
adapterAssignmentOperator = `=`
adapterClauseGroup = `({{.}})`
adapterClauseOperator = ` {{.}} `
adapterColumnValue = `{{.Column}} {{.Operator}} {{.Value}}`
adapterTableAliasLayout = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}`
adapterColumnAliasLayout = `{{.Name}}{{if .Alias}} AS {{.Alias}}{{end}}`
adapterSortByColumnLayout = `{{.Column}} {{.Order}}`
adapterOrderByLayout = `
{{if .SortColumns}}
ORDER BY {{.SortColumns}}
{{end}}
`
adapterWhereLayout = `
{{if .Conds}}
WHERE {{.Conds}}
{{end}}
`
adapterUsingLayout = `
{{if .Columns}}
USING ({{.Columns}})
{{end}}
`
adapterJoinLayout = `
{{if .Table}}
{{ if .On }}
{{.Type}} JOIN {{.Table}}
{{.On}}
{{ else if .Using }}
{{.Type}} JOIN {{.Table}}
{{.Using}}
{{ else if .Type | eq "CROSS" }}
{{.Type}} JOIN {{.Table}}
{{else}}
NATURAL {{.Type}} JOIN {{.Table}}
{{end}}
{{end}}
`
adapterOnLayout = `
{{if .Conds}}
ON {{.Conds}}
{{end}}
`
adapterSelectLayout = `
SELECT
{{if .Distinct}}
DISTINCT
{{end}}
{{if defined .Columns}}
{{.Columns | compile}}
{{else}}
*
{{end}}
{{if defined .Table}}
FROM {{.Table | compile}}
{{end}}
{{.Joins | compile}}
{{.Where | compile}}
{{if defined .GroupBy}}
{{.GroupBy | compile}}
{{end}}
{{.OrderBy | compile}}
{{if .Limit}}
LIMIT {{.Limit}}
{{end}}
{{if .Offset}}
{{if not .Limit}}
LIMIT -1
{{end}}
OFFSET {{.Offset}}
{{end}}
`
adapterDeleteLayout = `
DELETE
FROM {{.Table | compile}}
{{.Where | compile}}
`
adapterUpdateLayout = `
UPDATE
{{.Table | compile}}
SET {{.ColumnValues | compile}}
{{.Where | compile}}
`
adapterSelectCountLayout = `
SELECT
COUNT(1) AS _t
FROM {{.Table | compile}}
{{.Where | compile}}
`
adapterInsertLayout = `
INSERT INTO {{.Table | compile}}
{{if .Columns }}({{.Columns | compile}}){{end}}
{{if defined .Values}}
VALUES
{{.Values | compile}}
{{else}}
DEFAULT VALUES
{{end}}
{{if defined .Returning}}
RETURNING {{.Returning | compile}}
{{end}}
`
adapterTruncateLayout = `
DELETE FROM {{.Table | compile}}
`
adapterDropDatabaseLayout = `
DROP DATABASE {{.Database | compile}}
`
adapterDropTableLayout = `
DROP TABLE {{.Table | compile}}
`
adapterGroupByLayout = `
{{if .GroupColumns}}
GROUP BY {{.GroupColumns}}
{{end}}
`
)
var template = &exql.Template{
ColumnSeparator: adapterColumnSeparator,
IdentifierSeparator: adapterIdentifierSeparator,
IdentifierQuote: adapterIdentifierQuote,
ValueSeparator: adapterValueSeparator,
ValueQuote: adapterValueQuote,
AndKeyword: adapterAndKeyword,
OrKeyword: adapterOrKeyword,
DescKeyword: adapterDescKeyword,
AscKeyword: adapterAscKeyword,
AssignmentOperator: adapterAssignmentOperator,
ClauseGroup: adapterClauseGroup,
ClauseOperator: adapterClauseOperator,
ColumnValue: adapterColumnValue,
TableAliasLayout: adapterTableAliasLayout,
ColumnAliasLayout: adapterColumnAliasLayout,
SortByColumnLayout: adapterSortByColumnLayout,
WhereLayout: adapterWhereLayout,
JoinLayout: adapterJoinLayout,
OnLayout: adapterOnLayout,
UsingLayout: adapterUsingLayout,
OrderByLayout: adapterOrderByLayout,
InsertLayout: adapterInsertLayout,
SelectLayout: adapterSelectLayout,
UpdateLayout: adapterUpdateLayout,
DeleteLayout: adapterDeleteLayout,
TruncateLayout: adapterTruncateLayout,
DropDatabaseLayout: adapterDropDatabaseLayout,
DropTableLayout: adapterDropTableLayout,
CountLayout: adapterSelectCountLayout,
GroupByLayout: adapterGroupByLayout,
Cache: cache.NewCache(),
}

@ -0,0 +1,489 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package db
import (
"context"
"fmt"
)
// Selector represents a SELECT statement.
type Selector interface {
// Columns defines which columns to retrive.
//
// You should call From() after Columns() if you want to query data from an
// specific table.
//
// s.Columns("name", "last_name").From(...)
//
// It is also possible to use an alias for the column, this could be handy if
// you plan to use the alias later, use the "AS" keyword to denote an alias.
//
// s.Columns("name AS n")
//
// or the shortcut:
//
// s.Columns("name n")
//
// If you don't want the column to be escaped use the db.Raw
// function.
//
// s.Columns(db.Raw("MAX(id)"))
//
// The above statement is equivalent to:
//
// s.Columns(db.Func("MAX", "id"))
Columns(columns ...interface{}) Selector
// From represents a FROM clause and is tipically used after Columns().
//
// FROM defines from which table data is going to be retrieved
//
// s.Columns(...).From("people")
//
// It is also possible to use an alias for the table, this could be handy if
// you plan to use the alias later:
//
// s.Columns(...).From("people AS p").Where("p.name = ?", ...)
//
// Or with the shortcut:
//
// s.Columns(...).From("people p").Where("p.name = ?", ...)
From(tables ...interface{}) Selector
// Distict represents a DISTINCT clause
//
// DISTINCT is used to ask the database to return only values that are
// different.
Distinct(columns ...interface{}) Selector
// As defines an alias for a table.
As(string) Selector
// Where specifies the conditions that columns must match in order to be
// retrieved.
//
// Where accepts raw strings and fmt.Stringer to define conditions and
// interface{} to specify parameters. Be careful not to embed any parameters
// within the SQL part as that could lead to security problems. You can use
// que question mark (?) as placeholder for parameters.
//
// s.Where("name = ?", "max")
//
// s.Where("name = ? AND last_name = ?", "Mary", "Doe")
//
// s.Where("last_name IS NULL")
//
// You can also use other types of parameters besides only strings, like:
//
// s.Where("online = ? AND last_logged <= ?", true, time.Now())
//
// and Where() will transform them into strings before feeding them to the
// database.
//
// When an unknown type is provided, Where() will first try to match it with
// the Marshaler interface, then with fmt.Stringer and finally, if the
// argument does not satisfy any of those interfaces Where() will use
// fmt.Sprintf("%v", arg) to transform the type into a string.
//
// Subsequent calls to Where() will overwrite previously set conditions, if
// you want these new conditions to be appended use And() instead.
Where(conds ...interface{}) Selector
// And appends more constraints to the WHERE clause without overwriting
// conditions that have been already set.
And(conds ...interface{}) Selector
// GroupBy represents a GROUP BY statement.
//
// GROUP BY defines which columns should be used to aggregate and group
// results.
//
// s.GroupBy("country_id")
//
// GroupBy accepts more than one column:
//
// s.GroupBy("country_id", "city_id")
GroupBy(columns ...interface{}) Selector
// Having(...interface{}) Selector
// OrderBy represents a ORDER BY statement.
//
// ORDER BY is used to define which columns are going to be used to sort
// results.
//
// Use the column name to sort results in ascendent order.
//
// // "last_name" ASC
// s.OrderBy("last_name")
//
// Prefix the column name with the minus sign (-) to sort results in
// descendent order.
//
// // "last_name" DESC
// s.OrderBy("-last_name")
//
// If you would rather be very explicit, you can also use ASC and DESC.
//
// s.OrderBy("last_name ASC")
//
// s.OrderBy("last_name DESC", "name ASC")
OrderBy(columns ...interface{}) Selector
// Join represents a JOIN statement.
//
// JOIN statements are used to define external tables that the user wants to
// include as part of the result.
//
// You can use the On() method after Join() to define the conditions of the
// join.
//
// s.Join("author").On("author.id = book.author_id")
//
// If you don't specify conditions for the join, a NATURAL JOIN will be used.
//
// On() accepts the same arguments as Where()
//
// You can also use Using() after Join().
//
// s.Join("employee").Using("department_id")
Join(table ...interface{}) Selector
// FullJoin is like Join() but with FULL JOIN.
FullJoin(...interface{}) Selector
// CrossJoin is like Join() but with CROSS JOIN.
CrossJoin(...interface{}) Selector
// RightJoin is like Join() but with RIGHT JOIN.
RightJoin(...interface{}) Selector
// LeftJoin is like Join() but with LEFT JOIN.
LeftJoin(...interface{}) Selector
// Using represents the USING clause.
//
// USING is used to specifiy columns to join results.
//
// s.LeftJoin(...).Using("country_id")
Using(...interface{}) Selector
// On represents the ON clause.
//
// ON is used to define conditions on a join.
//
// s.Join(...).On("b.author_id = a.id")
On(...interface{}) Selector
// Limit represents the LIMIT parameter.
//
// LIMIT defines the maximum number of rows to return from the table. A
// negative limit cancels any previous limit settings.
//
// s.Limit(42)
Limit(int) Selector
// Offset represents the OFFSET parameter.
//
// OFFSET defines how many results are going to be skipped before starting to
// return results. A negative offset cancels any previous offset settings.
//
// s.Offset(56)
Offset(int) Selector
// Amend lets you alter the query's text just before sending it to the
// database server.
Amend(func(queryIn string) (queryOut string)) Selector
// Paginate returns a paginator that can display a paginated lists of items.
// Paginators ignore previous Offset and Limit settings. Page numbering
// starts at 1.
Paginate(uint) Paginator
// Iterator provides methods to iterate over the results returned by the
// Selector.
Iterator() Iterator
// IteratorContext provides methods to iterate over the results returned by
// the Selector.
IteratorContext(ctx context.Context) Iterator
// SQLPreparer provides methods for creating prepared statements.
SQLPreparer
// SQLGetter provides methods to compile and execute a query that returns
// results.
SQLGetter
// ResultMapper provides methods to retrieve and map results.
ResultMapper
// fmt.Stringer provides `String() string`, you can use `String()` to compile
// the `Selector` into a string.
fmt.Stringer
// Arguments returns the arguments that are prepared for this query.
Arguments() []interface{}
}
// Inserter represents an INSERT statement.
type Inserter interface {
// Columns represents the COLUMNS clause.
//
// COLUMNS defines the columns that we are going to provide values for.
//
// i.Columns("name", "last_name").Values(...)
Columns(...string) Inserter
// Values represents the VALUES clause.
//
// VALUES defines the values of the columns.
//
// i.Columns(...).Values("María", "Méndez")
//
// i.Values(map[string][string]{"name": "María"})
Values(...interface{}) Inserter
// Arguments returns the arguments that are prepared for this query.
Arguments() []interface{}
// Returning represents a RETURNING clause.
//
// RETURNING specifies which columns should be returned after INSERT.
//
// RETURNING may not be supported by all SQL databases.
Returning(columns ...string) Inserter
// Iterator provides methods to iterate over the results returned by the
// Inserter. This is only possible when using Returning().
Iterator() Iterator
// IteratorContext provides methods to iterate over the results returned by
// the Inserter. This is only possible when using Returning().
IteratorContext(ctx context.Context) Iterator
// Amend lets you alter the query's text just before sending it to the
// database server.
Amend(func(queryIn string) (queryOut string)) Inserter
// Batch provies a BatchInserter that can be used to insert many elements at
// once by issuing several calls to Values(). It accepts a size parameter
// which defines the batch size. If size is < 1, the batch size is set to 1.
Batch(size int) BatchInserter
// SQLExecer provides the Exec method.
SQLExecer
// SQLPreparer provides methods for creating prepared statements.
SQLPreparer
// SQLGetter provides methods to return query results from INSERT statements
// that support such feature (e.g.: queries with Returning).
SQLGetter
// fmt.Stringer provides `String() string`, you can use `String()` to compile
// the `Inserter` into a string.
fmt.Stringer
}
// Deleter represents a DELETE statement.
type Deleter interface {
// Where represents the WHERE clause.
//
// See Selector.Where for documentation and usage examples.
Where(...interface{}) Deleter
// And appends more constraints to the WHERE clause without overwriting
// conditions that have been already set.
And(conds ...interface{}) Deleter
// Limit represents the LIMIT clause.
//
// See Selector.Limit for documentation and usage examples.
Limit(int) Deleter
// Amend lets you alter the query's text just before sending it to the
// database server.
Amend(func(queryIn string) (queryOut string)) Deleter
// SQLPreparer provides methods for creating prepared statements.
SQLPreparer
// SQLExecer provides the Exec method.
SQLExecer
// fmt.Stringer provides `String() string`, you can use `String()` to compile
// the `Inserter` into a string.
fmt.Stringer
// Arguments returns the arguments that are prepared for this query.
Arguments() []interface{}
}
// Updater represents an UPDATE statement.
type Updater interface {
// Set represents the SET clause.
Set(...interface{}) Updater
// Where represents the WHERE clause.
//
// See Selector.Where for documentation and usage examples.
Where(...interface{}) Updater
// And appends more constraints to the WHERE clause without overwriting
// conditions that have been already set.
And(conds ...interface{}) Updater
// Limit represents the LIMIT parameter.
//
// See Selector.Limit for documentation and usage examples.
Limit(int) Updater
// SQLPreparer provides methods for creating prepared statements.
SQLPreparer
// SQLExecer provides the Exec method.
SQLExecer
// fmt.Stringer provides `String() string`, you can use `String()` to compile
// the `Inserter` into a string.
fmt.Stringer
// Arguments returns the arguments that are prepared for this query.
Arguments() []interface{}
// Amend lets you alter the query's text just before sending it to the
// database server.
Amend(func(queryIn string) (queryOut string)) Updater
}
// Paginator provides tools for splitting the results of a query into chunks
// containing a fixed number of items.
type Paginator interface {
// Page sets the page number.
Page(uint) Paginator
// Cursor defines the column that is going to be taken as basis for
// cursor-based pagination.
//
// Example:
//
// a = q.Paginate(10).Cursor("id")
// b = q.Paginate(12).Cursor("-id")
//
// You can set "" as cursorColumn to disable cursors.
Cursor(cursorColumn string) Paginator
// NextPage returns the next page according to the cursor. It expects a
// cursorValue, which is the value the cursor column has on the last item of
// the current result set (lower bound).
//
// Example:
//
// p = q.NextPage(items[len(items)-1].ID)
NextPage(cursorValue interface{}) Paginator
// PrevPage returns the previous page according to the cursor. It expects a
// cursorValue, which is the value the cursor column has on the fist item of
// the current result set (upper bound).
//
// Example:
//
// p = q.PrevPage(items[0].ID)
PrevPage(cursorValue interface{}) Paginator
// TotalPages returns the total number of pages in the query.
TotalPages() (uint, error)
// TotalEntries returns the total number of entries in the query.
TotalEntries() (uint64, error)
// SQLPreparer provides methods for creating prepared statements.
SQLPreparer
// SQLGetter provides methods to compile and execute a query that returns
// results.
SQLGetter
// Iterator provides methods to iterate over the results returned by the
// Selector.
Iterator() Iterator
// IteratorContext provides methods to iterate over the results returned by
// the Selector.
IteratorContext(ctx context.Context) Iterator
// ResultMapper provides methods to retrieve and map results.
ResultMapper
// fmt.Stringer provides `String() string`, you can use `String()` to compile
// the `Selector` into a string.
fmt.Stringer
// Arguments returns the arguments that are prepared for this query.
Arguments() []interface{}
}
// ResultMapper defined methods for a result mapper.
type ResultMapper interface {
// All dumps all the results into the given slice, All() expects a pointer to
// slice of maps or structs.
//
// The behaviour of One() extends to each one of the results.
All(destSlice interface{}) error
// One maps the row that is in the current query cursor into the
// given interface, which can be a pointer to either a map or a
// struct.
//
// If dest is a pointer to map, each one of the columns will create a new map
// key and the values of the result will be set as values for the keys.
//
// Depending on the type of map key and value, the results columns and values
// may need to be transformed.
//
// If dest if a pointer to struct, each one of the fields will be tested for
// a `db` tag which defines the column mapping. The value of the result will
// be set as the value of the field.
One(dest interface{}) error
}
// BatchInserter provides an interface to do massive insertions in batches.
type BatchInserter interface {
// Values pushes column values to be inserted as part of the batch.
Values(...interface{}) BatchInserter
// NextResult dumps the next slice of results to dst, which can mean having
// the IDs of all inserted elements in the batch.
NextResult(dst interface{}) bool
// Done signals that no more elements are going to be added.
Done()
// Wait blocks until the whole batch is executed.
Wait() error
// Err returns the last error that happened while executing the batch (or nil
// if no error happened).
Err() error
}

@ -0,0 +1,66 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package db
// Collection defines methods to work with database tables or collections.
type Collection interface {
// Name returns the name of the collection.
Name() string
// Session returns the Session that was used to create the collection
// reference.
Session() Session
// Find defines a new result set.
Find(...interface{}) Result
Count() (uint64, error)
// Insert inserts a new item into the collection, the type of this item could
// be a map, a struct or pointer to either of them. If the call succeeds and
// if the collection has a primary key, Insert returns the ID of the newly
// added element as an `interface{}`. The underlying type of this ID depends
// on both the database adapter and the column storing the ID. The ID
// returned by Insert() could be passed directly to Find() to retrieve the
// newly added element.
Insert(interface{}) (InsertResult, error)
// InsertReturning is like Insert() but it takes a pointer to map or struct
// and, if the operation succeeds, updates it with data from the newly
// inserted row. If the database does not support transactions this method
// returns db.ErrUnsupported.
InsertReturning(interface{}) error
// UpdateReturning takes a pointer to a map or struct and tries to update the
// row the item is refering to. If the element is updated sucessfully,
// UpdateReturning will fetch the row and update the fields of the passed
// item. If the database does not support transactions this method returns
// db.ErrUnsupported
UpdateReturning(interface{}) error
// Exists returns true if the collection exists, false otherwise.
Exists() (bool, error)
// Truncate removes all elements on the collection.
Truncate() error
}

@ -0,0 +1,179 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package db
import (
"reflect"
"time"
"github.com/upper/db/v4/internal/adapter"
)
// Comparison represents a relationship between values.
type Comparison struct {
*adapter.Comparison
}
// Gte is a comparison that means: is greater than or equal to value.
func Gte(value interface{}) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorGreaterThanOrEqualTo, value)}
}
// Lte is a comparison that means: is less than or equal to value.
func Lte(value interface{}) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorLessThanOrEqualTo, value)}
}
// Eq is a comparison that means: is equal to value.
func Eq(value interface{}) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorEqual, value)}
}
// NotEq is a comparison that means: is not equal to value.
func NotEq(value interface{}) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorNotEqual, value)}
}
// Gt is a comparison that means: is greater than value.
func Gt(value interface{}) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorGreaterThan, value)}
}
// Lt is a comparison that means: is less than value.
func Lt(value interface{}) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorLessThan, value)}
}
// In is a comparison that means: is any of the values.
func In(value ...interface{}) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorIn, toInterfaceArray(value))}
}
// AnyOf is a comparison that means: is any of the values of the slice.
func AnyOf(value interface{}) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorIn, toInterfaceArray(value))}
}
// NotIn is a comparison that means: is none of the values.
func NotIn(value ...interface{}) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorNotIn, toInterfaceArray(value))}
}
// NotAnyOf is a comparison that means: is none of the values of the slice.
func NotAnyOf(value interface{}) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorNotIn, toInterfaceArray(value))}
}
// After is a comparison that means: is after the (time.Time) value.
func After(value time.Time) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorGreaterThan, value)}
}
// Before is a comparison that means: is before the (time.Time) value.
func Before(value time.Time) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorLessThan, value)}
}
// OnOrAfter is a comparison that means: is on or after the (time.Time) value.
func OnOrAfter(value time.Time) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorGreaterThanOrEqualTo, value)}
}
// OnOrBefore is a comparison that means: is on or before the (time.Time) value.
func OnOrBefore(value time.Time) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorLessThanOrEqualTo, value)}
}
// Between is a comparison that means: is between lowerBound and upperBound.
func Between(lowerBound interface{}, upperBound interface{}) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorBetween, []interface{}{lowerBound, upperBound})}
}
// NotBetween is a comparison that means: is not between lowerBound and upperBound.
func NotBetween(lowerBound interface{}, upperBound interface{}) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorNotBetween, []interface{}{lowerBound, upperBound})}
}
// Is is a comparison that means: is equivalent to nil, true or false.
func Is(value interface{}) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorIs, value)}
}
// IsNot is a comparison that means: is not equivalent to nil, true nor false.
func IsNot(value interface{}) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorIsNot, value)}
}
// IsNull is a comparison that means: is equivalent to nil.
func IsNull() *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorIs, nil)}
}
// IsNotNull is a comparison that means: is not equivalent to nil.
func IsNotNull() *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorIsNot, nil)}
}
// Like is a comparison that checks whether the reference matches the wildcard
// value.
func Like(value string) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorLike, value)}
}
// NotLike is a comparison that checks whether the reference does not match the
// wildcard value.
func NotLike(value string) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorNotLike, value)}
}
// RegExp is a comparison that checks whether the reference matches the regular
// expression.
func RegExp(value string) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorRegExp, value)}
}
// NotRegExp is a comparison that checks whether the reference does not match
// the regular expression.
func NotRegExp(value string) *Comparison {
return &Comparison{adapter.NewComparisonOperator(adapter.ComparisonOperatorNotRegExp, value)}
}
// Op returns a custom comparison operator.
func Op(customOperator string, value interface{}) *Comparison {
return &Comparison{adapter.NewCustomComparisonOperator(customOperator, value)}
}
func toInterfaceArray(value interface{}) []interface{} {
rv := reflect.ValueOf(value)
switch rv.Type().Kind() {
case reflect.Ptr:
return toInterfaceArray(rv.Elem().Interface())
case reflect.Slice:
elems := rv.Len()
args := make([]interface{}, elems)
for i := 0; i < elems; i++ {
args[i] = rv.Index(i).Interface()
}
return args
}
return []interface{}{value}
}

@ -0,0 +1,130 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package db
import (
"fmt"
"sort"
"github.com/upper/db/v4/internal/adapter"
)
// LogicalExpr represents an expression to be used in logical statements.
type LogicalExpr = adapter.LogicalExpr
// LogicalOperator represents a logical operation.
type LogicalOperator = adapter.LogicalOperator
// Cond is a map that defines conditions for a query.
//
// Each entry of the map represents a condition (a column-value relation bound
// by a comparison Operator). The comparison can be specified after the column
// name, if no comparison operator is provided the equality operator is used as
// default.
//
// Examples:
//
// // Age equals 18.
// db.Cond{"age": 18}
//
// // Age is greater than or equal to 18.
// db.Cond{"age >=": 18}
//
// // id is any of the values 1, 2 or 3.
// db.Cond{"id IN": []{1, 2, 3}}
//
// // Age is lower than 18 (MongoDB syntax)
// db.Cond{"age $lt": 18}
//
// // age > 32 and age < 35
// db.Cond{"age >": 32, "age <": 35}
type Cond map[interface{}]interface{}
// Empty returns false if there are no conditions.
func (c Cond) Empty() bool {
for range c {
return false
}
return true
}
// Constraints returns each one of the Cond map entires as a constraint.
func (c Cond) Constraints() []adapter.Constraint {
z := make([]adapter.Constraint, 0, len(c))
for _, k := range c.keys() {
z = append(z, adapter.NewConstraint(k, c[k]))
}
return z
}
// Operator returns the equality operator.
func (c Cond) Operator() LogicalOperator {
return adapter.DefaultLogicalOperator
}
func (c Cond) keys() []interface{} {
keys := make(condKeys, 0, len(c))
for k := range c {
keys = append(keys, k)
}
if len(c) > 1 {
sort.Sort(keys)
}
return keys
}
// Expressions returns all the expressions contained in the condition.
func (c Cond) Expressions() []LogicalExpr {
z := make([]LogicalExpr, 0, len(c))
for _, k := range c.keys() {
z = append(z, Cond{k: c[k]})
}
return z
}
type condKeys []interface{}
func (ck condKeys) Len() int {
return len(ck)
}
func (ck condKeys) Less(i, j int) bool {
return fmt.Sprintf("%v", ck[i]) < fmt.Sprintf("%v", ck[j])
}
func (ck condKeys) Swap(i, j int) {
ck[i], ck[j] = ck[j], ck[i]
}
func defaultJoin(in ...adapter.LogicalExpr) []adapter.LogicalExpr {
for i := range in {
cond, ok := in[i].(Cond)
if ok && !cond.Empty() {
in[i] = And(cond)
}
}
return in
}
var (
_ = LogicalExpr(Cond{})
)

@ -0,0 +1,29 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package db
// ConnectionURL represents a data source name (DSN).
type ConnectionURL interface {
// String returns the connection string that is going to be passed to the
// adapter.
String() string
}

@ -0,0 +1,71 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
// Package db (or upper/db) provides an agnostic data access layer to work with
// different databases.
//
// Install upper/db:
//
// go get github.com/upper/db
//
// Usage
//
// package main
//
// import (
// "log"
//
// "github.com/upper/db/v4/adapter/postgresql" // Imports the postgresql adapter.
// )
//
// var settings = postgresql.ConnectionURL{
// Database: `booktown`,
// Host: `demo.upper.io`,
// User: `demouser`,
// Password: `demop4ss`,
// }
//
// // Book represents a book.
// type Book struct {
// ID uint `db:"id"`
// Title string `db:"title"`
// AuthorID uint `db:"author_id"`
// SubjectID uint `db:"subject_id"`
// }
//
// func main() {
// sess, err := postgresql.Open(settings)
// if err != nil {
// log.Fatal(err)
// }
// defer sess.Close()
//
// var books []Book
// if err := sess.Collection("books").Find().OrderBy("title").All(&books); err != nil {
// log.Fatal(err)
// }
//
// log.Println("Books:")
// for _, book := range books {
// log.Printf("%q (ID: %d)\n", book.Title, book.ID)
// }
// }
package db

@ -0,0 +1,63 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package db
import (
"errors"
)
// Error messages
var (
ErrMissingAdapter = errors.New(`upper: missing adapter`)
ErrAlreadyWithinTransaction = errors.New(`upper: already within a transaction`)
ErrCollectionDoesNotExist = errors.New(`upper: collection does not exist`)
ErrExpectingNonNilModel = errors.New(`upper: expecting non nil model`)
ErrExpectingPointerToStruct = errors.New(`upper: expecting pointer to struct`)
ErrGivingUpTryingToConnect = errors.New(`upper: giving up trying to connect: too many clients`)
ErrInvalidCollection = errors.New(`upper: invalid collection`)
ErrMissingCollectionName = errors.New(`upper: missing collection name`)
ErrMissingConditions = errors.New(`upper: missing selector conditions`)
ErrMissingConnURL = errors.New(`upper: missing DSN`)
ErrMissingDatabaseName = errors.New(`upper: missing database name`)
ErrNoMoreRows = errors.New(`upper: no more rows in this result set`)
ErrNotConnected = errors.New(`upper: not connected to a database`)
ErrNotImplemented = errors.New(`upper: call not implemented`)
ErrQueryIsPending = errors.New(`upper: can't execute this instruction while the result set is still open`)
ErrQueryLimitParam = errors.New(`upper: a query can accept only one limit parameter`)
ErrQueryOffsetParam = errors.New(`upper: a query can accept only one offset parameter`)
ErrQuerySortParam = errors.New(`upper: a query can accept only one order-by parameter`)
ErrSockerOrHost = errors.New(`upper: you may connect either to a UNIX socket or a TCP address, but not both`)
ErrTooManyClients = errors.New(`upper: can't connect to database server: too many clients`)
ErrUndefined = errors.New(`upper: value is undefined`)
ErrUnknownConditionType = errors.New(`upper: arguments of type %T can't be used as constraints`)
ErrUnsupported = errors.New(`upper: action is not supported by the DBMS`)
ErrUnsupportedDestination = errors.New(`upper: unsupported destination type`)
ErrUnsupportedType = errors.New(`upper: type does not support marshaling`)
ErrUnsupportedValue = errors.New(`upper: value does not support unmarshaling`)
ErrNilRecord = errors.New(`upper: invalid item (nil)`)
ErrRecordIDIsZero = errors.New(`upper: item ID is not defined`)
ErrMissingPrimaryKeys = errors.New(`upper: collection %q has no primary keys`)
ErrWarnSlowQuery = errors.New(`upper: slow query`)
ErrTransactionAborted = errors.New(`upper: transaction was aborted`)
ErrNotWithinTransaction = errors.New(`upper: not within transaction`)
ErrNotSupportedByAdapter = errors.New(`upper: not supported by adapter`)
)

@ -0,0 +1,48 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package db
import (
"github.com/upper/db/v4/internal/adapter"
)
// FuncExpr represents functions.
type FuncExpr = adapter.FuncExpr
// Func returns a database function expression.
//
// Examples:
//
// // MOD(29, 9)
// db.Func("MOD", 29, 9)
//
// // CONCAT("foo", "bar")
// db.Func("CONCAT", "foo", "bar")
//
// // NOW()
// db.Func("NOW")
//
// // RTRIM("Hello ")
// db.Func("RTRIM", "Hello ")
func Func(name string, args ...interface{}) *FuncExpr {
return adapter.NewFuncExpr(name, args)
}

@ -0,0 +1,60 @@
package adapter
// ComparisonOperator is the base type for comparison operators.
type ComparisonOperator uint8
// Comparison operators
const (
ComparisonOperatorNone ComparisonOperator = iota
ComparisonOperatorCustom
ComparisonOperatorEqual
ComparisonOperatorNotEqual
ComparisonOperatorLessThan
ComparisonOperatorGreaterThan
ComparisonOperatorLessThanOrEqualTo
ComparisonOperatorGreaterThanOrEqualTo
ComparisonOperatorBetween
ComparisonOperatorNotBetween
ComparisonOperatorIn
ComparisonOperatorNotIn
ComparisonOperatorIs
ComparisonOperatorIsNot
ComparisonOperatorLike
ComparisonOperatorNotLike
ComparisonOperatorRegExp
ComparisonOperatorNotRegExp
)
type Comparison struct {
t ComparisonOperator
op string
v interface{}
}
func (c *Comparison) CustomOperator() string {
return c.op
}
func (c *Comparison) Operator() ComparisonOperator {
return c.t
}
func (c *Comparison) Value() interface{} {
return c.v
}
func NewComparisonOperator(t ComparisonOperator, v interface{}) *Comparison {
return &Comparison{t: t, v: v}
}
func NewCustomComparisonOperator(op string, v interface{}) *Comparison {
return &Comparison{t: ComparisonOperatorCustom, op: op, v: v}
}

@ -0,0 +1,72 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package adapter
// ConstraintValuer allows constraints to use specific values of their own.
type ConstraintValuer interface {
ConstraintValue() interface{}
}
// Constraint interface represents a single condition, like "a = 1". where `a`
// is the key and `1` is the value. This is an exported interface but it's
// rarely used directly, you may want to use the `db.Cond{}` map instead.
type Constraint interface {
// Key is the leftmost part of the constraint and usually contains a column
// name.
Key() interface{}
// Value if the rightmost part of the constraint and usually contains a
// column value.
Value() interface{}
}
// Constraints interface represents an array of constraints, like "a = 1, b =
// 2, c = 3".
type Constraints interface {
// Constraints returns an array of constraints.
Constraints() []Constraint
}
type constraint struct {
k interface{}
v interface{}
}
func (c constraint) Key() interface{} {
return c.k
}
func (c constraint) Value() interface{} {
if constraintValuer, ok := c.v.(ConstraintValuer); ok {
return constraintValuer.ConstraintValue()
}
return c.v
}
// NewConstraint creates a constraint.
func NewConstraint(key interface{}, value interface{}) Constraint {
return &constraint{k: key, v: value}
}
var (
_ = Constraint(&constraint{})
)

@ -0,0 +1,39 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package adapter
type FuncExpr struct {
name string
args []interface{}
}
func (f *FuncExpr) Arguments() []interface{} {
return f.args
}
func (f *FuncExpr) Name() string {
return f.name
}
func NewFuncExpr(name string, args []interface{}) *FuncExpr {
return &FuncExpr{name: name, args: args}
}

@ -0,0 +1,123 @@
// Copyright (c) 2012-present The upper.io/db authors. All rights reserved.
//
// 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.
package adapter
import (
"github.com/upper/db/v4/internal/immutable"
)
// LogicalExpr represents a group formed by one or more sentences joined by
// an Operator like "AND" or "OR".
type LogicalExpr interface {
// Expressions returns child sentences.
Expressions() []LogicalExpr
// Operator returns the Operator that joins all the sentences in the group.
Operator() LogicalOperator
// Empty returns true if the compound has zero children, false otherwise.
Empty() bool
}
// LogicalOperator represents the operation on a compound statement.
type LogicalOperator uint
// LogicalExpr Operators.
const (
LogicalOperatorNone LogicalOperator = iota
LogicalOperatorAnd
LogicalOperatorOr
)
const DefaultLogicalOperator = LogicalOperatorAnd
type LogicalExprGroup struct {
op LogicalOperator
prev *LogicalExprGroup
fn func(*[]LogicalExpr) error
}
func NewLogicalExprGroup(op LogicalOperator, conds ...LogicalExpr) *LogicalExprGroup {
group := &LogicalExprGroup{op: op}
if len(conds) == 0 {
return group
}
return group.Frame(func(in *[]LogicalExpr) error {
*in = append(*in, conds...)
return nil
})
}
// Expressions returns each one of the conditions as a compound.
func (g *LogicalExprGroup) Expressions() []LogicalExpr {
conds, err := immutable.FastForward(g)
if err == nil {
return *(conds.(*[]LogicalExpr))
}
return nil
}
// Operator is undefined for a logical group.
func (g *LogicalExprGroup) Operator() LogicalOperator {
if g.op == LogicalOperatorNone {
panic("operator is not defined")
}
return g.op
}
// Empty returns true if this condition has no elements. False otherwise.
func (g *LogicalExprGroup) Empty() bool {
if g.fn != nil {
return false
}
if g.prev != nil {
return g.prev.Empty()
}
return true
}
func (g *LogicalExprGroup) Frame(fn func(*[]LogicalExpr) error) *LogicalExprGroup {
return &LogicalExprGroup{prev: g, op: g.op, fn: fn}
}
func (g *LogicalExprGroup) Prev() immutable.Immutable {
if g == nil {
return nil
}
return g.prev
}
func (g *LogicalExprGroup) Fn(in interface{}) error {
if g.fn == nil {
return nil
}
return g.fn(in.(*[]LogicalExpr))
}
func (g *LogicalExprGroup) Base() interface{} {
return &[]LogicalExpr{}
}
var (
_ = immutable.Immutable(&LogicalExprGroup{})
)

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

Loading…
Cancel
Save