diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f5157c5..8d5afa3 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -13,11 +13,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Go 1.24 - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: "1.24" cache: true @@ -27,7 +27,7 @@ jobs: run: | make build-release - name: Create release and add artifacts - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: files: | dist/*.tar.gz diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 8bce6c6..c3dbbde 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -12,22 +12,26 @@ jobs: test: name: Test gatewayd-plugin-cache runs-on: ubuntu-latest - # Timeout after 5 minutes, to avoid hanging tests timeout-minutes: 5 steps: - name: Checkout 🛎️ - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install Go 🧑‍💻 - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: "1.24" - name: Lint code issues 🚨 uses: golangci/golangci-lint-action@v6 + - name: Run govulncheck 🔒 + uses: golang/govulncheck-action@v1 + with: + go-version-input: "1.24" + - name: Run Go tests 🔬 run: go test -cover -covermode atomic -coverprofile=profile.cov -v ./... diff --git a/.golangci.yaml b/.golangci.yaml index 061224d..155d2a5 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -35,7 +35,7 @@ linters-settings: - "github.com/prometheus/client_golang" - "github.com/hashicorp/go-plugin" - "github.com/hashicorp/go-hclog" - - "github.com/go-redis/redis/v8" + - "github.com/redis/go-redis/v9" - "github.com/getsentry/sentry-go" - "github.com/spf13/cast" - "github.com/go-co-op/gocron" diff --git a/go.mod b/go.mod index 16f0c72..c39df0a 100644 --- a/go.mod +++ b/go.mod @@ -1,23 +1,23 @@ module github.com/gatewayd-io/gatewayd-plugin-cache -go 1.24 +go 1.24.0 require ( - github.com/alicebob/miniredis/v2 v2.35.0 - github.com/gatewayd-io/gatewayd v0.10.2 - github.com/gatewayd-io/gatewayd-plugin-sdk v0.4.3 - github.com/getsentry/sentry-go v0.35.0 + github.com/alicebob/miniredis/v2 v2.36.1 + github.com/gatewayd-io/gatewayd v0.11.0 + github.com/gatewayd-io/gatewayd-plugin-sdk v0.4.4 + github.com/getsentry/sentry-go v0.42.0 github.com/go-co-op/gocron v1.37.0 - github.com/go-redis/redis/v8 v8.11.5 github.com/hashicorp/go-hclog v1.6.3 - github.com/hashicorp/go-plugin v1.6.3 - github.com/jackc/pgx/v5 v5.7.5 - github.com/prometheus/client_golang v1.23.0 - github.com/spf13/cast v1.9.2 - github.com/stretchr/testify v1.10.0 + github.com/hashicorp/go-plugin v1.7.0 + github.com/jackc/pgx/v5 v5.8.0 + github.com/prometheus/client_golang v1.23.2 + github.com/redis/go-redis/v9 v9.18.0 + github.com/spf13/cast v1.10.0 + github.com/stretchr/testify v1.11.1 github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04 - google.golang.org/grpc v1.74.2 - google.golang.org/protobuf v1.36.7 + google.golang.org/grpc v1.79.1 + google.golang.org/protobuf v1.36.11 ) require ( @@ -25,35 +25,35 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/expr-lang/expr v1.17.5 // indirect + github.com/expr-lang/expr v1.17.8 // indirect github.com/fatih/color v1.18.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.8 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/run v1.1.0 // indirect - github.com/onsi/gomega v1.34.1 // indirect - github.com/pganalyze/pg_query_go/v6 v6.1.0 // indirect + github.com/pganalyze/pg_query_go/v6 v6.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/procfs v0.16.1 // indirect - github.com/redis/go-redis/v9 v9.12.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/zerolog v1.34.0 // indirect github.com/tetratelabs/wazero v1.9.0 // indirect github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 // indirect github.com/wasilibs/wazero-helpers v0.0.0-20250123031827-cd30c44769bb // indirect github.com/yuin/gopher-lua v1.1.1 // indirect + go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect go.uber.org/atomic v1.11.0 // indirect - golang.org/x/net v0.40.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.34.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 80bfea2..a6453b8 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,13 @@ -github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= -github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= +github.com/alicebob/miniredis/v2 v2.36.1 h1:Dvc5oAnNOr7BIfPn7tF269U8DvRW1dBG2D5n0WrfYMI= +github.com/alicebob/miniredis/v2 v2.36.1/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= -github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -17,21 +17,19 @@ 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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/expr-lang/expr v1.17.5 h1:i1WrMvcdLF249nSNlpQZN1S6NXuW9WaOfF5tPi3aw3k= -github.com/expr-lang/expr v1.17.5/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= +github.com/expr-lang/expr v1.17.8 h1:W1loDTT+0PQf5YteHSTpju2qfUfNoBt4yw9+wOEU9VM= +github.com/expr-lang/expr v1.17.8/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/gatewayd-io/gatewayd v0.10.2 h1:UpBjJhhzx+mJpFPsnerQVGGDzWouSekgmhwzVgCgrTc= -github.com/gatewayd-io/gatewayd v0.10.2/go.mod h1:hyRUqTcby5tnc+UfysxLAni25ltJ+5GG9gFDlhEON5o= -github.com/gatewayd-io/gatewayd-plugin-sdk v0.4.3 h1:ru5NvbZWMLjNUmb4G4BKlN8jARYp5f7Ijab4BdwNDr8= -github.com/gatewayd-io/gatewayd-plugin-sdk v0.4.3/go.mod h1:Dmkpyfcwb4MIcJHHLs/xQrz9JUvUUmegqpoGS6kqSb0= -github.com/getsentry/sentry-go v0.35.0 h1:+FJNlnjJsZMG3g0/rmmP7GiKjQoUF5EXfEtBwtPtkzY= -github.com/getsentry/sentry-go v0.35.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE= +github.com/gatewayd-io/gatewayd v0.11.0 h1:2VxHNhR0tHmtPy45fRbWQIJWqJJBa3RaqmeMtqVqxtQ= +github.com/gatewayd-io/gatewayd v0.11.0/go.mod h1:GMMoWCYIcAG0r0RDKMdh4STMI9azp7GcC0b1MHEIr3A= +github.com/gatewayd-io/gatewayd-plugin-sdk v0.4.4 h1:/XEYjRuboxDmv5u6k26H7jwaZ0OyOIS57OZ6IZnUphM= +github.com/gatewayd-io/gatewayd-plugin-sdk v0.4.4/go.mod h1:ahFlwwQY2+1TaEyWaGorQR6sHtBVxiDpjdDGK6givN4= +github.com/getsentry/sentry-go v0.42.0 h1:eeFMACuZTbUQf90RE8dE4tXeSe4CZyfvR1MBL7RLEt8= +github.com/getsentry/sentry-go v0.42.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s= github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -40,36 +38,34 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= -github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.8 h1:NpbJl/eVbvrGE0MJ6X16X9SAifesl6Fwxg/YmCvubRI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.8/go.mod h1:mi7YA+gCzVem12exXy46ZespvGtX/lZmD/RLnQhVW7U= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= -github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= +github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA= +github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= 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/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= -github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= -github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= +github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= +github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= +github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -94,16 +90,10 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= -github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= -github.com/pganalyze/pg_query_go/v6 v6.1.0 h1:jG5ZLhcVgL1FAw4C/0VNQaVmX1SUJx71wBGdtTtBvls= -github.com/pganalyze/pg_query_go/v6 v6.1.0/go.mod h1:nvTHIuoud6e1SfrUaFwHqT0i4b5Nr+1rPWVds3B5+50= +github.com/pganalyze/pg_query_go/v6 v6.2.2 h1:O0L6zMC226R82RF3X5n0Ki6HjytDsoAzuzp4ATVAHNo= +github.com/pganalyze/pg_query_go/v6 v6.2.2/go.mod h1:Cn6+j4870kJz3iYNsb0VsNG04vpSWgEvBwc590J4qD0= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -111,27 +101,27 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= -github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -github.com/redis/go-redis/v9 v9.12.0 h1:XlVPGlflh4nxfhsNXPA8Qp6EmEfTo0rp8oaBzPipXnU= -github.com/redis/go-redis/v9 v9.12.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= +github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= -github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= -github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -140,8 +130,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 h1:mJdDDPblDfPe7z7go8Dvv1AJQDI3eQ/5xith3q2mFlo= @@ -150,31 +140,31 @@ github.com/wasilibs/wazero-helpers v0.0.0-20250123031827-cd30c44769bb h1:gQ+ZV4w github.com/wasilibs/wazero-helpers v0.0.0-20250123031827-cd30c44769bb/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04 h1:qXafrlZL1WsJW5OokjraLLRURHiw0OzKHD/RNdspp4w= github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04/go.mod h1:FiwNQxz6hGoNFBC4nIx+CxZhI3nne5RmIOlT/MXcSD4= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= +go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= +go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= +go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= +go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= +go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= 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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -183,28 +173,25 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ= -google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= -google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= +google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 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/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 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= diff --git a/main.go b/main.go index c4adee0..520638a 100644 --- a/main.go +++ b/main.go @@ -3,8 +3,10 @@ package main import ( "context" "flag" + "io" "log" "os" + "sync" "github.com/gatewayd-io/gatewayd-plugin-cache/plugin" sdkConfig "github.com/gatewayd-io/gatewayd-plugin-sdk/config" @@ -14,17 +16,33 @@ import ( v1 "github.com/gatewayd-io/gatewayd-plugin-sdk/plugin/v1" apiV1 "github.com/gatewayd-io/gatewayd/api/v1" "github.com/getsentry/sentry-go" - "github.com/go-redis/redis/v8" "github.com/hashicorp/go-hclog" goplugin "github.com/hashicorp/go-plugin" + "github.com/redis/go-redis/v9" "github.com/spf13/cast" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) +// handleStartupError logs an error and exits if ExitOnStartupError is set, +// closing any provided resources before exit. +func handleStartupError( + logger hclog.Logger, exitOnError bool, msg string, err error, closers ...io.Closer, +) { + logger.Error(msg, "error", err) + if exitOnError { + logger.Info("Exiting due to startup error") + for _, c := range closers { + if c != nil { + c.Close() + } + } + os.Exit(1) + } +} + func main() { sentryDSN := sdkConfig.GetEnv("SENTRY_DSN", "") - // Initialize Sentry SDK err := sentry.Init(sentry.ClientOptions{ Dsn: sentryDSN, TracesSampleRate: 1.0, @@ -33,7 +51,6 @@ func main() { log.Fatalf("Failed to initialize Sentry SDK: %s", err.Error()) } - // Parse command line flags, passed by GatewayD via the plugin config logLevel := flag.String("log-level", "info", "Log level") flag.Parse() @@ -45,72 +62,73 @@ func main() { }) pluginInstance := plugin.NewCachePlugin(plugin.Plugin{ - Logger: logger, + Logger: logger, + WaitGroup: &sync.WaitGroup{}, }) //nolint:nestif if cfg := cast.ToStringMap(plugin.PluginConfig["config"]); cfg != nil { + pluginInstance.Impl.ExitOnStartupError = cast.ToBool(cfg["exitOnStartupError"]) + + pluginInstance.Impl.RedisURL = cast.ToString(cfg["redisURL"]) + if pluginInstance.Impl.RedisURL == "" { + logger.Warn("redisURL is empty, defaulting to redis://localhost:6379") + pluginInstance.Impl.RedisURL = "redis://localhost:6379" + } + + pluginInstance.Impl.Expiry = cast.ToDuration(cfg["expiry"]) + if pluginInstance.Impl.Expiry <= 0 { + logger.Warn("expiry is invalid or unset, defaulting to 1h") + pluginInstance.Impl.Expiry = cast.ToDuration("1h") + } + + pluginInstance.Impl.DefaultDBName = cast.ToString(cfg["defaultDBName"]) + + pluginInstance.Impl.ScanCount = cast.ToInt64(cfg["scanCount"]) + if pluginInstance.Impl.ScanCount <= 0 { + logger.Warn("scanCount is invalid or unset, defaulting to 1000") + pluginInstance.Impl.ScanCount = 1000 + } + metricsConfig := metrics.NewMetricsConfig(cfg) if metricsConfig != nil && metricsConfig.Enabled { go metrics.ExposeMetrics(metricsConfig, logger) } - // Initialize the API client to get proxies from GatewayD. apiGRPCAddress := cast.ToString(cfg["apiGRPCAddress"]) apiClientConn, err := grpc.NewClient( apiGRPCAddress, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil || apiClientConn == nil { - logger.Error("Failed to initialize API client", "error", err) - if pluginInstance.Impl.ExitOnStartupError { - logger.Info("Exiting due to startup error") - if apiClientConn != nil { - apiClientConn.Close() - } - os.Exit(1) - } + handleStartupError( + logger, pluginInstance.Impl.ExitOnStartupError, + "Failed to initialize API client", err, apiClientConn) } defer apiClientConn.Close() pluginInstance.Impl.APIClient = apiV1.NewGatewayDAdminAPIServiceClient(apiClientConn) cacheBufferSize := cast.ToUint(cfg["cacheBufferSize"]) if cacheBufferSize <= 0 { - cacheBufferSize = 100 // default value + cacheBufferSize = 100 } pluginInstance.Impl.UpdateCacheChannel = make(chan *v1.Struct, cacheBufferSize) + pluginInstance.Impl.WaitGroup.Add(1) go pluginInstance.Impl.UpdateCache(context.Background()) - pluginInstance.Impl.RedisURL = cast.ToString(cfg["redisURL"]) - pluginInstance.Impl.Expiry = cast.ToDuration(cfg["expiry"]) - pluginInstance.Impl.DefaultDBName = cast.ToString(cfg["defaultDBName"]) - pluginInstance.Impl.ScanCount = cast.ToInt64(cfg["scanCount"]) - pluginInstance.Impl.ExitOnStartupError = cast.ToBool(cfg["exitOnStartupError"]) - redisConfig, err := redis.ParseURL(pluginInstance.Impl.RedisURL) if err != nil { - logger.Error("Failed to parse Redis URL", "error", err) - if pluginInstance.Impl.ExitOnStartupError { - logger.Info("Exiting due to startup error") - if apiClientConn != nil { - apiClientConn.Close() - } - os.Exit(1) //nolint:gocritic - } + handleStartupError( + logger, pluginInstance.Impl.ExitOnStartupError, + "Failed to parse Redis URL", err, apiClientConn) } pluginInstance.Impl.RedisClient = redis.NewClient(redisConfig) - // Ping the Redis server to check if it is available. _, err = pluginInstance.Impl.RedisClient.Ping(context.Background()).Result() if err != nil { - logger.Error("Failed to ping Redis server", "error", err) - if pluginInstance.Impl.ExitOnStartupError { - logger.Info("Exiting due to startup error") - if apiClientConn != nil { - apiClientConn.Close() - } - os.Exit(1) - } + handleStartupError( + logger, pluginInstance.Impl.ExitOnStartupError, + "Failed to ping Redis server", err, apiClientConn) } pluginInstance.Impl.PeriodicInvalidatorEnabled = cast.ToBool( @@ -120,13 +138,17 @@ func main() { pluginInstance.Impl.PeriodicInvalidatorInterval = cast.ToDuration( cfg["periodicInvalidatorInterval"]) - // Start the periodic invalidator. if pluginInstance.Impl.PeriodicInvalidatorEnabled { pluginInstance.Impl.PeriodicInvalidator() } } - defer close(pluginInstance.Impl.UpdateCacheChannel) + defer func() { + if pluginInstance.Impl.UpdateCacheChannel != nil { + close(pluginInstance.Impl.UpdateCacheChannel) + pluginInstance.Impl.WaitGroup.Wait() + } + }() goplugin.Serve(&goplugin.ServeConfig{ HandshakeConfig: goplugin.HandshakeConfig{ diff --git a/plugin/metrics.go b/plugin/metrics.go index 2d2ef05..69f4ae6 100644 --- a/plugin/metrics.go +++ b/plugin/metrics.go @@ -63,4 +63,9 @@ var ( Name: "cache_scan_keys_total", Help: "The total number of cache scan keys", }) + CacheErrorsCounter = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: metrics.Namespace, + Name: "cache_errors_total", + Help: "The total number of Redis operation errors", + }) ) diff --git a/plugin/plugin.go b/plugin/plugin.go index 9966483..346f2cf 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -4,6 +4,7 @@ import ( "context" "encoding/base64" "strings" + "sync" "time" sdkAct "github.com/gatewayd-io/gatewayd-plugin-sdk/act" @@ -11,9 +12,9 @@ import ( sdkPlugin "github.com/gatewayd-io/gatewayd-plugin-sdk/plugin" v1 "github.com/gatewayd-io/gatewayd-plugin-sdk/plugin/v1" apiV1 "github.com/gatewayd-io/gatewayd/api/v1" - goRedis "github.com/go-redis/redis/v8" "github.com/hashicorp/go-hclog" goplugin "github.com/hashicorp/go-plugin" + goRedis "github.com/redis/go-redis/v9" "github.com/spf13/cast" "google.golang.org/grpc" ) @@ -35,6 +36,7 @@ type Plugin struct { ExitOnStartupError bool UpdateCacheChannel chan *v1.Struct + WaitGroup *sync.WaitGroup // Periodic invalidator configuration. PeriodicInvalidatorEnabled bool @@ -111,7 +113,7 @@ func (p *Plugin) OnTrafficFromClient( if database == "" { database, err = p.RedisClient.Get(ctx, client["remote"]).Result() if err != nil { - CacheMissesCounter.Inc() + CacheErrorsCounter.Inc() p.Logger.Debug("Failed to get cache", "error", err) } CacheGetsCounter.Inc() @@ -150,7 +152,6 @@ func (p *Plugin) OnTrafficFromClient( // Check if the query is cached. response, err := p.RedisClient.Get(ctx, cacheKey).Bytes() if err != nil { - CacheMissesCounter.Inc() p.Logger.Debug("Failed to get cached response", "error", err) } CacheGetsCounter.Inc() @@ -170,8 +171,7 @@ func (p *Plugin) OnTrafficFromClient( }).ToMap(), }) if err != nil { - CacheMissesCounter.Inc() - // This should never happen, but log the error just in case. + CacheErrorsCounter.Inc() p.Logger.Error("Failed to create signals", "error", err) } else { CacheHitsCounter.Inc() @@ -195,6 +195,7 @@ func IsCacheNeeded(upperQuery string) bool { } func (p *Plugin) UpdateCache(ctx context.Context) { + defer p.WaitGroup.Done() for { serverResponse, ok := <-p.UpdateCacheChannel if !ok { @@ -230,7 +231,7 @@ func (p *Plugin) UpdateCache(ctx context.Context) { if client != nil && client["remote"] != "" { database, err = p.RedisClient.Get(ctx, client["remote"]).Result() if err != nil { - CacheMissesCounter.Inc() + CacheErrorsCounter.Inc() p.Logger.Debug("Failed to get cached response", "error", err) } CacheGetsCounter.Inc() @@ -245,42 +246,47 @@ func (p *Plugin) UpdateCache(ctx context.Context) { "Skipping cache") p.Logger.Debug("Consider setting the database name in the " + "plugin config or disabling the plugin if you don't need it") - return + continue } cacheKey := strings.Join([]string{server["remote"], database, string(request)}, ":") - if errorResponse == "" && rowDescription != "" && dataRow != nil && len(dataRow) > 0 && IsCacheNeeded(cacheKey) { - // The request was successful and the response contains data. Cache the response. - if err := p.RedisClient.Set(ctx, cacheKey, response, p.Expiry).Err(); err != nil { - CacheMissesCounter.Inc() - p.Logger.Debug("Failed to set cache", "error", err) - } - CacheSetsCounter.Inc() + if errorResponse != "" || rowDescription == "" || dataRow == nil || len(dataRow) == 0 { + continue + } - // Cache the query as well. - query, err := postgres.GetQueryFromRequest(request) - if err != nil { - p.Logger.Debug("Failed to get query from request", "error", err) - return - } + query, err := postgres.GetQueryFromRequest(request) + if err != nil { + p.Logger.Debug("Failed to get query from request", "error", err) + continue + } - tables, err := postgres.GetTablesFromQuery(query) - if err != nil { - p.Logger.Debug("Failed to get tables from query", "error", err) - return - } + if !IsCacheNeeded(strings.ToUpper(query)) { + continue + } - // Cache the table(s) used in each cached request. This is used to invalidate - // the cache when a rows is inserted, updated or deleted into that table. - for _, table := range tables { - requestQueryCacheKey := strings.Join([]string{table, cacheKey}, ":") - if err := p.RedisClient.Set( - ctx, requestQueryCacheKey, "", p.Expiry).Err(); err != nil { - CacheMissesCounter.Inc() - p.Logger.Debug("Failed to set cache", "error", err) - } - CacheSetsCounter.Inc() + // The request was successful and the response contains data. Cache the response. + if err := p.RedisClient.Set(ctx, cacheKey, response, p.Expiry).Err(); err != nil { + CacheErrorsCounter.Inc() + p.Logger.Debug("Failed to set cache", "error", err) + } + CacheSetsCounter.Inc() + + tables, err := postgres.GetTablesFromQuery(query) + if err != nil { + p.Logger.Debug("Failed to get tables from query", "error", err) + continue + } + + // Cache the table(s) used in each cached request. This is used to invalidate + // the cache when a rows is inserted, updated or deleted into that table. + for _, table := range tables { + requestQueryCacheKey := strings.Join([]string{table, cacheKey}, ":") + if err := p.RedisClient.Set( + ctx, requestQueryCacheKey, "", p.Expiry).Err(); err != nil { + CacheErrorsCounter.Inc() + p.Logger.Debug("Failed to set cache", "error", err) } + CacheSetsCounter.Inc() } } } @@ -300,7 +306,7 @@ func (p *Plugin) OnClosed(ctx context.Context, req *v1.Struct) (*v1.Struct, erro if client != nil { if err := p.RedisClient.Del(ctx, client["remote"]).Err(); err != nil { p.Logger.Debug("Failed to delete cache", "error", err) - CacheMissesCounter.Inc() + CacheErrorsCounter.Inc() } p.Logger.Debug("Client closed", "client", client["remote"]) CacheDeletesCounter.Inc() @@ -346,7 +352,7 @@ func (p *Plugin) invalidateDML(ctx context.Context, query string) { for { scanResult := p.RedisClient.Scan(ctx, cursor, table+":*", p.ScanCount) if scanResult.Err() != nil { - CacheMissesCounter.Inc() + CacheErrorsCounter.Inc() p.Logger.Debug("Failed to scan keys", "error", scanResult.Err()) break } @@ -358,8 +364,8 @@ func (p *Plugin) invalidateDML(ctx context.Context, query string) { CacheScanKeysCounter.Add(float64(len(keys))) for _, tableKey := range keys { // Invalidate the cache for the table. - cachedRespnseKey := strings.TrimPrefix(tableKey, table+":") - pipeline.Del(ctx, cachedRespnseKey) + cachedResponseKey := strings.TrimPrefix(tableKey, table+":") + pipeline.Del(ctx, cachedResponseKey) // Invalidate the table cache key itself. pipeline.Del(ctx, tableKey) } @@ -376,7 +382,7 @@ func (p *Plugin) invalidateDML(ctx context.Context, query string) { for _, res := range result { if res.Err() != nil { - CacheMissesCounter.Inc() + CacheErrorsCounter.Inc() } else { CacheDeletesCounter.Inc() } @@ -416,7 +422,7 @@ func (p *Plugin) getDBFromStartupMessage( startupMsgParams["database"], time.Duration(0), ).Err(); err != nil { - CacheMissesCounter.Inc() + CacheErrorsCounter.Inc() p.Logger.Debug("Failed to set cache", "error", err) } CacheSetsCounter.Inc() diff --git a/plugin/plugin_test.go b/plugin/plugin_test.go index 3d39250..5b9ff93 100644 --- a/plugin/plugin_test.go +++ b/plugin/plugin_test.go @@ -3,17 +3,19 @@ package plugin import ( "context" "encoding/base64" + "encoding/json" "os" "sync" "testing" + "time" miniredis "github.com/alicebob/miniredis/v2" sdkAct "github.com/gatewayd-io/gatewayd-plugin-sdk/act" "github.com/gatewayd-io/gatewayd-plugin-sdk/logging" v1 "github.com/gatewayd-io/gatewayd-plugin-sdk/plugin/v1" - "github.com/go-redis/redis/v8" "github.com/hashicorp/go-hclog" pgproto3 "github.com/jackc/pgx/v5/pgproto3" + "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" ) @@ -25,7 +27,7 @@ func testQueryRequest() (string, []byte) { return query, queryBytes } -func testQueryRequestWithDateFucntion() (string, []byte) { +func testQueryRequestWithDateFunction() (string, []byte) { query := `SELECT user_id, username, @@ -76,15 +78,11 @@ func Test_Plugin(t *testing.T) { RedisURL: redisURL, RedisClient: redisClient, UpdateCacheChannel: updateCacheChannel, + WaitGroup: &sync.WaitGroup{}, }) - // Use a WaitGroup to wait for the goroutine to finish - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - p.Impl.UpdateCache(context.Background()) - }() + p.Impl.WaitGroup.Add(1) + go p.Impl.UpdateCache(context.Background()) assert.NotNil(t, p) @@ -178,9 +176,8 @@ func Test_Plugin(t *testing.T) { assert.NotNil(t, result) assert.Equal(t, result, resp) - // Close the channel and wait for the cache updater to return gracefully close(updateCacheChannel) - wg.Wait() + p.Impl.WaitGroup.Wait() // Check that the query and response was cached. cachedResponse, err := redisClient.Get( @@ -216,15 +213,11 @@ func TestPluginDateFunctionInQuery(t *testing.T) { RedisURL: redisURL, RedisClient: redisClient, UpdateCacheChannel: cacheUpdateChannel, + WaitGroup: &sync.WaitGroup{}, }) - // Use a WaitGroup to wait for the goroutine to finish. - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - plugin.Impl.UpdateCache(context.Background()) - }() + plugin.Impl.WaitGroup.Add(1) + go plugin.Impl.UpdateCache(context.Background()) // Test the plugin's OnTrafficFromClient method with a StartupMessage. clientArgs := map[string]interface{}{ @@ -243,7 +236,7 @@ func TestPluginDateFunctionInQuery(t *testing.T) { plugin.Impl.OnTrafficFromClient(context.Background(), clientRequest) // Test the plugin's OnTrafficFromServer method with a query request. - _, queryRequest := testQueryRequestWithDateFucntion() + _, queryRequest := testQueryRequestWithDateFunction() queryResponse, err := base64.StdEncoding.DecodeString("VAAAABsAAWlkAAAAQAQAAQAAABcABP////8AAEQAAAALAAEAAAABMUMAAAANU0VMRUNUIDEAWgAAAAVJ") assert.Nil(t, err) queryArgs := map[string]interface{}{ @@ -262,10 +255,200 @@ func TestPluginDateFunctionInQuery(t *testing.T) { serverRequest, err := v1.NewStruct(queryArgs) plugin.Impl.OnTrafficFromServer(context.Background(), serverRequest) - // Close the channel and wait for the cache updater to return gracefully. close(cacheUpdateChannel) - wg.Wait() + plugin.Impl.WaitGroup.Wait() keys, _ := redisClient.Keys(context.Background(), "*").Result() assert.Equal(t, 1, len(keys)) // Only one key (representing the database name) should be present. } + +func newTestPlugin(t *testing.T) (*CachePlugin, *redis.Client) { + t.Helper() + redisServer := miniredis.RunT(t) + redisURL := "redis://" + redisServer.Addr() + "/0" + redisConfig, _ := redis.ParseURL(redisURL) + redisClient := redis.NewClient(redisConfig) + + logger := hclog.New(&hclog.LoggerOptions{ + Level: logging.GetLogLevel("error"), + Output: os.Stdout, + }) + p := NewCachePlugin(Plugin{ + Logger: logger, + RedisURL: redisURL, + RedisClient: redisClient, + Expiry: time.Hour, + ScanCount: 1000, + UpdateCacheChannel: make(chan *v1.Struct, 10), + WaitGroup: &sync.WaitGroup{}, + }) + return p, redisClient +} + +func TestIsCacheNeeded(t *testing.T) { + tests := []struct { + name string + query string + want bool + }{ + {"plain SELECT", "SELECT * FROM USERS", true}, + {"contains NOW()", "SELECT NOW(), ID FROM USERS", false}, + {"contains CURRENT_DATE", "SELECT ID FROM USERS WHERE CREATED_AT >= CURRENT_DATE", false}, + {"contains CURRENT_TIMESTAMP", "SELECT CURRENT_TIMESTAMP", false}, + {"contains CLOCK_TIMESTAMP()", "SELECT CLOCK_TIMESTAMP()", false}, + {"contains LOCALTIME", "SELECT LOCALTIME", false}, + {"contains LOCALTIMESTAMP", "SELECT LOCALTIMESTAMP", false}, + {"contains AGE()", "SELECT AGE(TIMESTAMP '2001-04-10')", false}, + {"contains STATEMENT_TIMESTAMP", "SELECT STATEMENT_TIMESTAMP()", false}, + {"contains TIMEOFDAY", "SELECT TIMEOFDAY()", false}, + {"contains TRANSACTION_TIMESTAMP", "SELECT TRANSACTION_TIMESTAMP()", false}, + {"INSERT query", "INSERT INTO USERS VALUES (1)", true}, + {"empty query", "", true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, IsCacheNeeded(tt.query)) + }) + } +} + +func TestOnClosed(t *testing.T) { + p, redisClient := newTestPlugin(t) + ctx := context.Background() + + // Simulate a stored client-to-database mapping. + redisClient.Set(ctx, "localhost:45320", "postgres", 0) + val := redisClient.Get(ctx, "localhost:45320").Val() + assert.Equal(t, "postgres", val) + + // Call OnClosed to clean up. + args := map[string]interface{}{ + "client": map[string]interface{}{ + "local": "localhost:15432", + "remote": "localhost:45320", + }, + } + req, err := v1.NewStruct(args) + assert.Nil(t, err) + + result, err := p.Impl.OnClosed(ctx, req) + assert.Nil(t, err) + assert.NotNil(t, result) + + // The client key should be deleted. + val = redisClient.Get(ctx, "localhost:45320").Val() + assert.Equal(t, "", val) +} + +func TestOnClosedNilClient(t *testing.T) { + p, _ := newTestPlugin(t) + ctx := context.Background() + + args := map[string]interface{}{} + req, err := v1.NewStruct(args) + assert.Nil(t, err) + + result, err := p.Impl.OnClosed(ctx, req) + assert.Nil(t, err) + assert.NotNil(t, result) +} + +func TestInvalidateDML(t *testing.T) { + p, redisClient := newTestPlugin(t) + ctx := context.Background() + + _, request := testQueryRequest() + cacheKey := "localhost:5432:postgres:" + string(request) + tableKey := "users:" + cacheKey + + // Pre-populate cache entries (simulating cached SELECT response + table index). + redisClient.Set(ctx, cacheKey, "cached-response-data", time.Hour) + redisClient.Set(ctx, tableKey, "", time.Hour) + + val := redisClient.Get(ctx, cacheKey).Val() + assert.Equal(t, "cached-response-data", val) + + // Build a base64-encoded query message for an INSERT query. + insertQuery := map[string]string{"String": "INSERT INTO users VALUES (1)"} + queryJSON, err := json.Marshal(insertQuery) + assert.Nil(t, err) + encodedQuery := base64.StdEncoding.EncodeToString(queryJSON) + + p.Impl.invalidateDML(ctx, encodedQuery) + + // Both the cached response and the table index key should be deleted. + val = redisClient.Get(ctx, cacheKey).Val() + assert.Equal(t, "", val) + val = redisClient.Get(ctx, tableKey).Val() + assert.Equal(t, "", val) +} + +func TestInvalidateDMLSelectIgnored(t *testing.T) { + p, redisClient := newTestPlugin(t) + ctx := context.Background() + + // Pre-populate a cache entry. + redisClient.Set(ctx, "test-key", "test-value", time.Hour) + + selectQuery := map[string]string{"String": "SELECT * FROM users"} + queryJSON, _ := json.Marshal(selectQuery) + encodedQuery := base64.StdEncoding.EncodeToString(queryJSON) + + p.Impl.invalidateDML(ctx, encodedQuery) + + // SELECT queries should not trigger invalidation. + val := redisClient.Get(ctx, "test-key").Val() + assert.Equal(t, "test-value", val) +} + +func TestUpdateCacheContinuesOnError(t *testing.T) { + p, redisClient := newTestPlugin(t) + ctx := context.Background() + + p.Impl.WaitGroup.Add(1) + go p.Impl.UpdateCache(ctx) + + // Send a message with empty database (should trigger continue, not kill goroutine). + badArgs := map[string]interface{}{ + "request": []byte{}, + "response": []byte{}, + "client": map[string]interface{}{ + "remote": "unknown:99999", + }, + "server": map[string]interface{}{ + "remote": "localhost:5432", + }, + } + badResp, _ := v1.NewStruct(badArgs) + p.Impl.UpdateCacheChannel <- badResp + + // Now send a valid message. If the goroutine survived, this will be processed. + _, request := testQueryRequest() + response, _ := base64.StdEncoding.DecodeString( + "VAAAABsAAWlkAAAAQAQAAQAAABcABP////8AAEQAAAALAAEAAAABMUMAAAANU0VMRUNUIDEAWgAAAAVJ") + + // Set up the database mapping so UpdateCache can find it. + redisClient.Set(ctx, "localhost:45320", "postgres", 0) + + goodArgs := map[string]interface{}{ + "request": request, + "response": response, + "client": map[string]interface{}{ + "remote": "localhost:45320", + }, + "server": map[string]interface{}{ + "remote": "localhost:5432", + }, + } + goodResp, _ := v1.NewStruct(goodArgs) + p.Impl.UpdateCacheChannel <- goodResp + + close(p.Impl.UpdateCacheChannel) + p.Impl.WaitGroup.Wait() + + // The valid message should have been cached (goroutine survived the error). + cachedResponse, err := redisClient.Get( + ctx, "localhost:5432:postgres:"+string(request)).Bytes() + assert.Nil(t, err) + assert.Equal(t, response, cachedResponse) +}