diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9f426e2..b43d0a9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,21 +6,7 @@ on: jobs: test: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: - - ubuntu-latest - # Container operations, such as postgres, are only supported on Linux runners - # - macOS-latest - # - windows-latest - go: - - "1.21" - - "1.20" - - "1.19" - - "1.18" - - "1.17" + runs-on: ubuntu-latest services: postgres: image: postgres:12 @@ -31,13 +17,14 @@ jobs: options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install Go uses: actions/setup-go@v5 with: - go-version: ${{ matrix.go }} - - - name: Checkout code - uses: actions/checkout@v4 + go-version-file: go.mod + cache: false - name: Lint uses: golangci/golangci-lint-action@v4 diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..d18b610 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,12 @@ +linters: + enable: + - gofmt + - gosimple + - misspell +run: + timeout: 5m +issues: + exclude-rules: + - path: _test\.go + linters: + - errcheck diff --git a/Makefile b/Makefile index 34f4b26..ec5fe8b 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,10 @@ test: vet vet: go vet ./... +.PHONY: lint +lint: + golangci-lint run + .PHONY: db db: psql -U postgres -h localhost -d postgres -c 'CREATE USER qgtest;' diff --git a/README.md b/README.md index 4bf00be..0e023cf 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This library is still under heavy development, and might significantly change AP ## Test ``` -docker-compose up -d +docker compose up -d make db make table make test diff --git a/connector.go b/connector.go new file mode 100644 index 0000000..51fc26f --- /dev/null +++ b/connector.go @@ -0,0 +1,37 @@ +package qg + +import ( + "database/sql/driver" + "fmt" + "net/url" + + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/stdlib" +) + +func GetConnector(host string, port int, username string, password string, database string) (driver.Connector, error) { + u := &url.URL{ + Scheme: "postgres", + Host: fmt.Sprintf("%s:%d", host, port), + User: url.UserPassword(username, password), + Path: database, + } + + return GetConnectorFromURL(u) +} + +func GetConnectorFromURL(u *url.URL) (driver.Connector, error) { + return GetConnectorFromConnStr(u.String()) +} + +func GetConnectorFromConnStr(connStr string) (driver.Connector, error) { + cfg, err := pgx.ParseConfig(connStr) + + if err != nil { + return nil, err + } + + connector := stdlib.GetConnector(*cfg, stdlib.OptionAfterConnect(PrepareStatements)) + + return connector, nil +} diff --git a/go.mod b/go.mod index 8f71f57..63d1076 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,22 @@ module github.com/kanmu/qg -go 1.16 +go 1.21 require ( - github.com/hashicorp/go-version v1.3.0 // indirect - github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect - github.com/jackc/pgx v3.0.1-0.20170728201109-511b90478f17+incompatible + github.com/jackc/pgx/v4 v4.18.3 + gopkg.in/guregu/null.v3 v3.0.2-0.20160228005316-41961cea0328 +) + +require ( + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.3 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgtype v1.14.0 // indirect github.com/lib/pq v1.10.3 // indirect - github.com/pkg/errors v0.8.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect - gopkg.in/guregu/null.v3 v3.0.2-0.20160228005316-41961cea0328 + golang.org/x/crypto v0.20.0 // indirect + golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index 69a98fe..664fe72 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,184 @@ -github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= -github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= -github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= -github.com/jackc/pgx v3.0.1-0.20170728201109-511b90478f17+incompatible h1:gODIIrHcjpWFN5yw777R1Ju3aOB07lhy0vxutBGDgFg= -github.com/jackc/pgx v3.0.1-0.20170728201109-511b90478f17+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +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= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +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.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg= github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/guregu/null.v3 v3.0.2-0.20160228005316-41961cea0328 h1:hU8cvgSqUQ5u4yLmjxkBal15GhqW77yRklOkbEOlJ38= gopkg.in/guregu/null.v3 v3.0.2-0.20160228005316-41961cea0328/go.mod h1:E4tX2Qe3h7QdL+uZ3a0vqvYwKQsRSQKM5V4YltdgH9Y= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/que.go b/que.go index 3e33e65..3451dc0 100644 --- a/que.go +++ b/que.go @@ -10,8 +10,8 @@ import ( null "gopkg.in/guregu/null.v3" - "github.com/jackc/pgx" - "github.com/jackc/pgx/stdlib" + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/stdlib" ) // Job is a single unit of work for Que to perform. @@ -50,7 +50,7 @@ type Job struct { mu sync.Mutex deleted bool c *Client - conn *pgx.Conn + conn *sql.Conn tx Txer } @@ -91,7 +91,9 @@ type JobStats struct { } // Conn returns transaction -func (j *Job) Conn() *pgx.Conn { +// +// Deprecated: This is an internal method. DON'T USE IT. +func (j *Job) Conn() *sql.Conn { j.mu.Lock() defer j.mu.Unlock() @@ -106,11 +108,16 @@ func (j *Job) Tx() Txer { return j.tx } +// Same as DeleteContext function (without context.Context). +func (j *Job) Delete() error { + return j.DeleteContext(context.Background()) +} + // Delete marks this job as complete by deleting it form the database. // // You must also later call Done() to return this job's database connection to // the pool. -func (j *Job) Delete() error { +func (j *Job) DeleteContext(ctx context.Context) error { j.mu.Lock() defer j.mu.Unlock() @@ -118,7 +125,12 @@ func (j *Job) Delete() error { return nil } - _, err := j.conn.Exec("que_destroy_job", j.Queue, j.Priority, j.RunAt, j.ID) + err := j.conn.Raw(func(driverConn any) error { + pgxConn := driverConn.(*stdlib.Conn).Conn() + _, err := pgxConn.Exec(ctx, "que_destroy_job", j.Queue, j.Priority, j.RunAt, j.ID) + return err + }) + if err != nil { return err } @@ -127,9 +139,14 @@ func (j *Job) Delete() error { return nil } +// Same as DoneContext function (without context.Context). +func (j *Job) Done() { + j.DoneContext(context.Background()) +} + // Done releases the Postgres advisory lock on the job and returns the database // connection to the pool. -func (j *Job) Done() { +func (j *Job) DoneContext(ctx context.Context) { j.mu.Lock() defer j.mu.Unlock() @@ -141,28 +158,42 @@ func (j *Job) Done() { var ok bool // Swallow this error because we don't want an unlock failure to cause work to // stop. - if err := j.conn.QueryRow("que_unlock_job", j.ID).Scan(&ok); err != nil { + err := j.conn.Raw(func(driverConn any) error { + pgxConn := driverConn.(*stdlib.Conn).Conn() + return pgxConn.QueryRow(ctx, "que_unlock_job", j.ID).Scan(&ok) + }) + + if err != nil { log.Printf("failed to unlock job job_id=%d job_type=%s", j.ID, j.Type) } - stdlib.ReleaseConn(j.c.pool, j.conn) //nolint:errcheck - // j.pool.Release(j.conn) + j.conn.Close() j.c.dischargeJob(j) j.c = nil j.conn = nil } +// Same as ErrorContext function (without context.Context). +func (j *Job) Error(msg string) error { + return j.ErrorContext(context.Background(), msg) +} + // Error marks the job as failed and schedules it to be reworked. An error // message or backtrace can be provided as msg, which will be saved on the job. // It will also increase the error count. // // You must also later call Done() to return this job's database connection to // the pool. -func (j *Job) Error(msg string) error { +func (j *Job) ErrorContext(ctx context.Context, msg string) error { errorCount := j.ErrorCount + 1 delay := intPow(int(errorCount), 4) + 3 // TODO: configurable delay - _, err := j.conn.Exec("que_set_error", errorCount, delay, msg, j.Queue, j.Priority, j.RunAt, j.ID) + err := j.conn.Raw(func(driverConn any) error { + pgxConn := driverConn.(*stdlib.Conn).Conn() + _, err := pgxConn.Exec(ctx, "que_set_error", errorCount, delay, msg, j.Queue, j.Priority, j.RunAt, j.ID) + return err + }) + if err != nil { return err } @@ -180,8 +211,8 @@ type Client struct { // TODO: add a way to specify default queueing options } -// NewClient2 creates a new Client that uses the pgx pool. -func NewClient2(pool *sql.DB) (*Client, error) { +// NewClient creates a new Client that uses the pgx pool. +func NewClient(pool *sql.DB) (*Client, error) { stmtJobStats, err := pool.Prepare(sqlJobStats) if err != nil { return nil, err @@ -193,11 +224,11 @@ func NewClient2(pool *sql.DB) (*Client, error) { }, nil } -// NewClient creates a new Client that uses the pgx pool. Returns nil if the initialization fails. -func NewClient(pool *sql.DB) *Client { - c, err := NewClient2(pool) +// MustNewClient creates a new Client that uses the pgx pool. Panic if the initialization fails. +func MustNewClient(pool *sql.DB) *Client { + c, err := NewClient(pool) if err != nil { - return nil + panic(err) } return c } @@ -307,9 +338,10 @@ const maxLockJobAttempts = 10 // concurrency. var ErrAgain = errors.New("maximum number of LockJob attempts reached") -// TODO: consider an alternate Enqueue func that also returns the newly -// enqueued Job struct. The query sqlInsertJobAndReturn was already written for -// this. +// Same as LockJobContext function (without context.Context). +func (c *Client) LockJob(queue string) (*Job, error) { + return c.LockJobContext(context.Background(), queue) +} // LockJob attempts to retrieve a Job from the database in the specified queue. // If a job is found, a session-level Postgres advisory lock is created for the @@ -321,8 +353,8 @@ var ErrAgain = errors.New("maximum number of LockJob attempts reached") // // After the Job has been worked, you must call either Done() or Error() on it // in order to return the database connection to the pool and remove the lock. -func (c *Client) LockJob(queue string) (*Job, error) { - conn, err := stdlib.AcquireConn(c.pool) +func (c *Client) LockJobContext(ctx context.Context, queue string) (*Job, error) { + conn, err := c.pool.Conn(ctx) if err != nil { return nil, err } @@ -330,19 +362,21 @@ func (c *Client) LockJob(queue string) (*Job, error) { j := Job{c: c, conn: conn} for i := 0; i < maxLockJobAttempts; i++ { - err = conn.QueryRow("que_lock_job", queue).Scan( - &j.Queue, - &j.Priority, - &j.RunAt, - &j.ID, - &j.Type, - &j.Args, - &j.ErrorCount, - ) + err = conn.Raw(func(driverConn any) error { + pgxConn := driverConn.(*stdlib.Conn).Conn() + return pgxConn.QueryRow(ctx, "que_lock_job", queue).Scan( + &j.Queue, + &j.Priority, + &j.RunAt, + &j.ID, + &j.Type, + &j.Args, + &j.ErrorCount, + ) + }) + if err != nil { - // stdConn.Close() - stdlib.ReleaseConn(c.pool, conn) //nolint:errcheck - // c.pool.Release(conn) + conn.Close() if err == pgx.ErrNoRows { return nil, nil } @@ -362,7 +396,10 @@ func (c *Client) LockJob(queue string) (*Job, error) { // I'm not sure how to reliably commit a transaction that deletes // the job in a separate thread between lock_job and check_job. var ok bool - err = conn.QueryRow("que_check_job", j.Queue, j.Priority, j.RunAt, j.ID).Scan(&ok) + err = conn.Raw(func(driverConn any) error { + pgxConn := driverConn.(*stdlib.Conn).Conn() + return pgxConn.QueryRow(ctx, "que_check_job", j.Queue, j.Priority, j.RunAt, j.ID).Scan(&ok) + }) if err == nil { c.manageJob(&j) return &j, nil @@ -373,16 +410,18 @@ func (c *Client) LockJob(queue string) (*Job, error) { // eventually causing the server to run out of locks. // // Also swallow the possible error, exactly like in Done. - _ = conn.QueryRow("que_unlock_job", j.ID).Scan(&ok) + conn.Raw(func(driverConn any) error { //nolint:errcheck + pgxConn := driverConn.(*stdlib.Conn).Conn() + pgxConn.QueryRow(ctx, "que_unlock_job", j.ID).Scan(&ok) //nolint:errcheck + return nil + }) continue } else { - // stdConn.Close() - // c.pool.Release(conn) - stdlib.ReleaseConn(c.pool, conn) //nolint:errcheck + conn.Close() return nil, err } } - stdlib.ReleaseConn(c.pool, conn) //nolint:errcheck + conn.Close() return nil, ErrAgain } @@ -396,9 +435,9 @@ var preparedStatements = map[string]string{ } // PrepareStatements prepar statements -func PrepareStatements(conn *pgx.Conn) error { +func PrepareStatements(ctx context.Context, conn *pgx.Conn) error { for name, sql := range preparedStatements { - if _, err := conn.Prepare(name, sql); err != nil { + if _, err := conn.Prepare(ctx, name, sql); err != nil { return err } } diff --git a/que_test.go b/que_test.go index 9014fbc..121c29d 100644 --- a/que_test.go +++ b/que_test.go @@ -5,51 +5,32 @@ import ( "testing" "time" - "github.com/jackc/pgx" - "github.com/jackc/pgx/stdlib" + "github.com/jackc/pgx/v4" ) -var testConnConfig = pgx.ConnConfig{ - Host: "localhost", - Database: "qgtest", - User: "qgtest", - // LogLevel: pgx.LogLevelDebug, - // Logger: log15.New("testlogger", "test/qg"), -} +var testConnConfig = func() *pgx.ConnConfig { + conn, _ := pgx.ParseConfig("postgres://qgtest@localhost:5432/qgtest") + // conn.LogLevel = pgx.LogLevelDebug + // conn.Logger = log15.New("testlogger", "test/qg") + return conn +}() const maxConn = 5 func openTestClientMaxConns(t testing.TB, maxConnections int) *Client { - // connPoolConfig := pgx.ConnPoolConfig{ - // ConnConfig: testConnConfig, - // MaxConnections: maxConnections, - // AfterConnect: PrepareStatements, - // } - // pool, err := pgx.NewConnPool(connPoolConfig) - // if err != nil { - // t.Fatal(err) - // } - driverConfig := stdlib.DriverConfig{ - ConnConfig: pgx.ConnConfig{ - Host: "localhost", - Database: "qgtest", - User: "qgtest", - }, - AfterConnect: PrepareStatements, - } - stdlib.RegisterDriverConfig(&driverConfig) - db, err := sql.Open("pgx", driverConfig.ConnectionString("")) + connector, err := GetConnector("localhost", 5432, "qgtest", "", "qgtest") if err != nil { t.Fatal(err) } - // using stdlib, it's difficult to open max conn from the begining + db := sql.OpenDB(connector) + // using stdlib, it's difficult to open max conn from the beginning // if we want to open connections till its limit, need to use go routine to // concurrently open connections db.SetMaxOpenConns(maxConnections) db.SetMaxIdleConns(maxConnections) // make lifetime sufficiently long db.SetConnMaxLifetime(time.Duration(5 * time.Minute)) - c, err := NewClient2(db) + c, err := NewClient(db) if err != nil { t.Fatal(err) } diff --git a/testing.go b/testing.go index ab7bae7..49aeb0a 100644 --- a/testing.go +++ b/testing.go @@ -1,9 +1,9 @@ package qg -import "github.com/jackc/pgx" +import "database/sql" // // TestInjectJobConn injects *pgx.Conn to Job -func TestInjectJobConn(j *Job, conn *pgx.Conn) *Job { +func TestInjectJobConn(j *Job, conn *sql.Conn) *Job { j.conn = conn return j } diff --git a/work_test.go b/work_test.go index 586a55b..0819ce8 100644 --- a/work_test.go +++ b/work_test.go @@ -1,13 +1,14 @@ package qg import ( + "context" "fmt" "sync" "testing" "time" - "github.com/jackc/pgx" - "github.com/jackc/pgx/stdlib" + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/stdlib" ) func TestLockJob(t *testing.T) { @@ -211,15 +212,7 @@ func TestJobConnRace(t *testing.T) { func TestLockJobAdvisoryRace(t *testing.T) { c := openTestClientMaxConns(t, 4) defer truncateAndClose(c) - - // *pgx.ConnPool doesn't support pools of only one connection. Make sure - // the other one is busy so we know which backend will be used by LockJob - // below. - unusedConn, err := stdlib.AcquireConn(c.pool) - if err != nil { - t.Fatal(err) - } - defer stdlib.ReleaseConn(c.pool, unusedConn) //nolint:errcheck + ctx := context.Background() // We use two jobs: the first one is concurrently deleted, and the second // one is returned by LockJob after recovering from the race condition. @@ -231,7 +224,7 @@ func TestLockJobAdvisoryRace(t *testing.T) { // helper functions newConn := func() *pgx.Conn { - conn, err := pgx.Connect(testConnConfig) + conn, err := pgx.ConnectConfig(ctx, testConnConfig) if err != nil { panic(err) } @@ -239,7 +232,7 @@ func TestLockJobAdvisoryRace(t *testing.T) { } getBackendID := func(conn *pgx.Conn) int32 { var backendID int32 - err := conn.QueryRow(` + err := conn.QueryRow(ctx, ` SELECT pg_backend_pid() `).Scan(&backendID) if err != nil { @@ -252,7 +245,7 @@ func TestLockJobAdvisoryRace(t *testing.T) { i := 0 for { var waiting bool - err := conn.QueryRow(`SELECT wait_event is not null from pg_stat_activity where pid=$1`, backendID).Scan(&waiting) + err := conn.QueryRow(ctx, `SELECT wait_event is not null from pg_stat_activity where pid=$1`, backendID).Scan(&waiting) if err != nil { panic(err) } @@ -289,13 +282,13 @@ func TestLockJobAdvisoryRace(t *testing.T) { go func() { conn := newConn() - defer conn.Close() + defer conn.Close(ctx) - tx, err := conn.Begin() + tx, err := conn.Begin(ctx) if err != nil { panic(err) } - _, err = tx.Exec(`LOCK TABLE que_jobs IN ACCESS EXCLUSIVE MODE`) + _, err = tx.Exec(ctx, `LOCK TABLE que_jobs IN ACCESS EXCLUSIVE MODE`) if err != nil { panic(err) } @@ -308,7 +301,7 @@ func TestLockJobAdvisoryRace(t *testing.T) { backendID = <-secondAccessExclusiveBackendIDChan waitUntilBackendIsWaiting(backendID, "second access exclusive lock") - err = tx.Rollback() + err = tx.Rollback(ctx) if err != nil { panic(err) } @@ -316,23 +309,23 @@ func TestLockJobAdvisoryRace(t *testing.T) { go func() { conn := newConn() - defer conn.Close() + defer conn.Close(ctx) // synchronization point secondAccessExclusiveBackendIDChan <- getBackendID(conn) - tx, err := conn.Begin() + tx, err := conn.Begin(ctx) if err != nil { panic(err) } - _, err = tx.Exec(`LOCK TABLE que_jobs IN ACCESS EXCLUSIVE MODE`) + _, err = tx.Exec(ctx, `LOCK TABLE que_jobs IN ACCESS EXCLUSIVE MODE`) if err != nil { panic(err) } // Fake a concurrent transaction grabbing the job var jid int64 - err = tx.QueryRow(` + err = tx.QueryRow(ctx, ` DELETE FROM que_jobs WHERE job_id = (SELECT min(job_id) @@ -345,18 +338,23 @@ func TestLockJobAdvisoryRace(t *testing.T) { deletedJobIDChan <- jid - err = tx.Commit() + err = tx.Commit(ctx) if err != nil { panic(err) } }() - conn, err := stdlib.AcquireConn(c.pool) + var ourBackendID int32 + conn, err := c.pool.Conn(ctx) if err != nil { panic(err) } - ourBackendID := getBackendID(conn) - stdlib.ReleaseConn(c.pool, conn) //nolint:errcheck + conn.Raw(func(driverConn any) error { + pgxConn := driverConn.(*stdlib.Conn).Conn() + ourBackendID = getBackendID(pgxConn) + return nil + }) + conn.Close() // synchronization point lockJobBackendIDChan <- ourBackendID @@ -477,6 +475,7 @@ func TestJobDoneMultiple(t *testing.T) { func TestJobDeleteFromTx(t *testing.T) { c := openTestClient(t) defer truncateAndClose(c) + ctx := context.Background() if err := c.Enqueue(&Job{Type: "MyJob"}); err != nil { t.Fatal(err) @@ -497,7 +496,7 @@ func TestJobDeleteFromTx(t *testing.T) { } // start a transaction - tx, err := conn.Begin() + tx, err := conn.BeginTx(ctx, nil) if err != nil { t.Fatal(err) } @@ -528,6 +527,7 @@ func TestJobDeleteFromTx(t *testing.T) { func TestJobDeleteFromTxRollback(t *testing.T) { c := openTestClient(t) defer truncateAndClose(c) + ctx := context.Background() if err := c.Enqueue(&Job{Type: "MyJob"}); err != nil { t.Fatal(err) @@ -548,7 +548,7 @@ func TestJobDeleteFromTxRollback(t *testing.T) { } // start a transaction - tx, err := conn.Begin() + tx, err := conn.BeginTx(ctx, nil) if err != nil { t.Fatal(err) }