diff --git a/go.mod b/go.mod index 460a5ff5..05024af0 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,15 @@ go 1.26.1 require ( github.com/fsnotify/fsnotify v1.9.0 - github.com/go-sql-driver/mysql v1.8.0 - github.com/lib/pq v1.10.9 + github.com/go-sql-driver/mysql v1.9.3 + github.com/lib/pq v1.12.0 github.com/mark3labs/mcp-go v0.45.0 github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.9 github.com/testcontainers/testcontainers-go v0.41.0 golang.org/x/term v0.40.0 gopkg.in/yaml.v3 v3.0.1 - modernc.org/sqlite v1.30.1 + modernc.org/sqlite v1.48.0 ) require ( @@ -42,7 +42,6 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/jsonschema v0.13.0 // indirect github.com/klauspost/compress v1.18.2 // indirect @@ -58,7 +57,7 @@ require ( github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.2 // indirect github.com/morikuni/aec v1.0.0 // indirect - github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -76,21 +75,16 @@ require ( github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel v1.41.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect - go.opentelemetry.io/otel/metric v1.41.0 // indirect - go.opentelemetry.io/otel/trace v1.41.0 // indirect - go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.opentelemetry.io/otel v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect + go.opentelemetry.io/otel/metric v1.42.0 // indirect + go.opentelemetry.io/otel/sdk v1.42.0 // indirect + go.opentelemetry.io/otel/trace v1.42.0 // indirect + go.opentelemetry.io/proto/otlp v1.10.0 // indirect golang.org/x/crypto v0.48.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.41.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect - google.golang.org/protobuf v1.35.2 // indirect - modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect - modernc.org/libc v1.52.1 // indirect - modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.8.0 // indirect - modernc.org/strutil v1.2.0 // indirect - modernc.org/token v1.1.0 // indirect + golang.org/x/sys v0.42.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + modernc.org/libc v1.70.0 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect ) diff --git a/go.sum b/go.sum index 178359d5..3784136b 100644 --- a/go.sum +++ b/go.sum @@ -59,17 +59,17 @@ 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-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4= -github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/google/go-cmp v0.5.6/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/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= 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.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -83,8 +83,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo= +github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= @@ -113,8 +113,8 @@ github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= -github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -162,26 +162,26 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= -go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= +go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0 h1:inYW9ZhgqiDqh6BioM7DVHHzEGVq76Db5897WLGZ5Go= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0/go.mod h1:Izur+Wt8gClgMJqO/cZ8wdeeMryJ/xxiOVgFSSfpDTY= -go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= -go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= -go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8= -go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90= -go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= -go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= +go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= +go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= +go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= +go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= +go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= -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/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -189,25 +189,25 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= -google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +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.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= +google.golang.org/grpc v1.79.2/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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -216,29 +216,31 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk= -modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= -modernc.org/ccgo/v4 v4.17.10 h1:6wrtRozgrhCxieCeJh85QsxkX/2FFrT9hdaWPlbn4Zo= -modernc.org/ccgo/v4 v4.17.10/go.mod h1:0NBHgsqTTpm9cA5z2ccErvGZmtntSM9qD2kFAs6pjXM= -modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= -modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= -modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= -modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= -modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= -modernc.org/libc v1.52.1 h1:uau0VoiT5hnR+SpoWekCKbLqm7v6dhRL3hI+NQhgN3M= -modernc.org/libc v1.52.1/go.mod h1:HR4nVzFDSDizP620zcMCgjb1/8xk2lg5p/8yjfGv1IQ= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= -modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= -modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk= -modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU= -modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= -modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= +modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw= +modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= +modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw= +modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.48.0 h1:ElZyLop3Q2mHYk5IFPPXADejZrlHu7APbpB0sF78bq4= +modernc.org/sqlite v1.48.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/pkg/advisor/optimizer_test.go b/pkg/advisor/optimizer_test.go index f942ea6f..cb0a5358 100644 --- a/pkg/advisor/optimizer_test.go +++ b/pkg/advisor/optimizer_test.go @@ -1003,7 +1003,7 @@ func TestDefaultRules(t *testing.T) { func TestRuleMetadata(t *testing.T) { rules := DefaultRules() - expectedIDs := []string{ + expectedFirstIDs := []string{ "OPT-001", "OPT-002", "OPT-003", "OPT-004", "OPT-005", "OPT-006", "OPT-007", "OPT-008", "OPT-009", "OPT-010", "OPT-011", "OPT-012", @@ -1011,9 +1011,13 @@ func TestRuleMetadata(t *testing.T) { "OPT-017", "OPT-018", "OPT-019", "OPT-020", } - for i, rule := range rules { - if rule.ID() != expectedIDs[i] { - t.Errorf("rule %d: expected ID %q, got %q", i, expectedIDs[i], rule.ID()) + for i, expID := range expectedFirstIDs { + if i >= len(rules) { + t.Errorf("rule %d: expected ID %q but only %d rules registered", i, expID, len(rules)) + continue + } + if rules[i].ID() != expID { + t.Errorf("rule %d: expected ID %q, got %q", i, expID, rules[i].ID()) } } } diff --git a/pkg/transform/returning.go b/pkg/transform/returning.go new file mode 100644 index 00000000..df3a8382 --- /dev/null +++ b/pkg/transform/returning.go @@ -0,0 +1,82 @@ +// Copyright 2026 GoSQLX Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package transform + +import ( + "github.com/ajitpratap0/GoSQLX/pkg/sql/ast" +) + +// getReturning returns a pointer to the Returning slice for supported DML +// statements (INSERT, UPDATE, DELETE). Returns ErrUnsupportedStatement for +// SELECT or DDL statements. +func getReturning(stmt ast.Statement) (*[]ast.Expression, error) { + switch s := stmt.(type) { + case *ast.InsertStatement: + return &s.Returning, nil + case *ast.UpdateStatement: + return &s.Returning, nil + case *ast.DeleteStatement: + return &s.Returning, nil + default: + return nil, &ErrUnsupportedStatement{Transform: "RETURNING", Got: stmtTypeName(stmt)} + } +} + +// AddReturning returns a Rule that appends one or more column names to the +// RETURNING clause of an INSERT, UPDATE, or DELETE statement. This is the +// standard PostgreSQL extension for returning row data from DML operations. +// SQL Server users can achieve a similar result with the OUTPUT clause (not +// yet covered by this transform). +// +// If the statement already has a RETURNING clause the new columns are appended +// to the existing list. +// +// Returns ErrUnsupportedStatement for SELECT or DDL statements. +// +// Example: +// +// transform.Apply(stmt, transform.AddReturning("id", "created_at")) +func AddReturning(columns ...string) Rule { + return RuleFunc(func(stmt ast.Statement) error { + ret, err := getReturning(stmt) + if err != nil { + return err + } + for _, col := range columns { + *ret = append(*ret, &ast.Identifier{Name: col}) + } + return nil + }) +} + +// RemoveReturning returns a Rule that clears the entire RETURNING clause from +// an INSERT, UPDATE, or DELETE statement. If the clause is already empty the +// rule is a no-op (no error). +// +// Returns ErrUnsupportedStatement for SELECT or DDL statements. +// +// Example: +// +// transform.Apply(stmt, transform.RemoveReturning()) +func RemoveReturning() Rule { + return RuleFunc(func(stmt ast.Statement) error { + ret, err := getReturning(stmt) + if err != nil { + return err + } + *ret = nil + return nil + }) +} diff --git a/pkg/transform/returning_test.go b/pkg/transform/returning_test.go new file mode 100644 index 00000000..af0b2261 --- /dev/null +++ b/pkg/transform/returning_test.go @@ -0,0 +1,142 @@ +// Copyright 2026 GoSQLX Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package transform + +import ( + "testing" +) + +func TestAddReturning_OnInsert(t *testing.T) { + stmt := mustParse(t, "INSERT INTO users (name) VALUES ('alice')") + + err := Apply(stmt, AddReturning("id")) + if err != nil { + t.Fatalf("AddReturning on INSERT: %v", err) + } + + out := format(stmt) + assertContains(t, out, "RETURNING") + assertContains(t, out, "id") +} + +func TestAddReturning_OnUpdate(t *testing.T) { + stmt := mustParse(t, "UPDATE users SET status = 'active' WHERE id = 1") + + err := Apply(stmt, AddReturning("id", "updated_at")) + if err != nil { + t.Fatalf("AddReturning on UPDATE: %v", err) + } + + out := format(stmt) + assertContains(t, out, "RETURNING") + assertContains(t, out, "id") + assertContains(t, out, "updated_at") +} + +func TestAddReturning_OnDelete(t *testing.T) { + stmt := mustParse(t, "DELETE FROM users WHERE id = 1") + + err := Apply(stmt, AddReturning("id")) + if err != nil { + t.Fatalf("AddReturning on DELETE: %v", err) + } + + out := format(stmt) + assertContains(t, out, "RETURNING") + assertContains(t, out, "id") +} + +func TestAddReturning_MultipleColumns(t *testing.T) { + stmt := mustParse(t, "INSERT INTO orders (product_id, qty) VALUES (1, 5)") + + err := Apply(stmt, AddReturning("id", "created_at", "total")) + if err != nil { + t.Fatalf("AddReturning multiple columns: %v", err) + } + + out := format(stmt) + assertContains(t, out, "RETURNING") + assertContains(t, out, "id") + assertContains(t, out, "created_at") + assertContains(t, out, "total") +} + +func TestAddReturning_AppendsToPreviousReturning(t *testing.T) { + stmt := mustParse(t, "INSERT INTO users (name) VALUES ('alice')") + + // Add returning in two steps + _ = Apply(stmt, AddReturning("id")) + err := Apply(stmt, AddReturning("created_at")) + if err != nil { + t.Fatalf("second AddReturning: %v", err) + } + + out := format(stmt) + assertContains(t, out, "id") + assertContains(t, out, "created_at") +} + +func TestRemoveReturning_RemovesClause(t *testing.T) { + stmt := mustParse(t, "DELETE FROM users WHERE id = 1") + + _ = Apply(stmt, AddReturning("id")) + err := Apply(stmt, RemoveReturning()) + if err != nil { + t.Fatalf("RemoveReturning: %v", err) + } + + out := format(stmt) + assertNotContains(t, out, "RETURNING") +} + +func TestRemoveReturning_OnUpdate(t *testing.T) { + stmt := mustParse(t, "UPDATE users SET status = 'active'") + + _ = Apply(stmt, AddReturning("id", "status")) + err := Apply(stmt, RemoveReturning()) + if err != nil { + t.Fatalf("RemoveReturning on UPDATE: %v", err) + } + + out := format(stmt) + assertNotContains(t, out, "RETURNING") +} + +func TestRemoveReturning_WhenAlreadyEmpty_NoError(t *testing.T) { + stmt := mustParse(t, "DELETE FROM users WHERE active = false") + + err := Apply(stmt, RemoveReturning()) + if err != nil { + t.Fatalf("RemoveReturning on empty clause should not error: %v", err) + } +} + +func TestAddReturning_OnSelect_ReturnsError(t *testing.T) { + stmt := mustParse(t, "SELECT * FROM users") + + err := Apply(stmt, AddReturning("id")) + if err == nil { + t.Error("expected error applying AddReturning to SELECT statement") + } +} + +func TestRemoveReturning_OnSelect_ReturnsError(t *testing.T) { + stmt := mustParse(t, "SELECT * FROM users") + + err := Apply(stmt, RemoveReturning()) + if err == nil { + t.Error("expected error applying RemoveReturning to SELECT statement") + } +} diff --git a/pkg/transform/set.go b/pkg/transform/set.go new file mode 100644 index 00000000..81f33a4d --- /dev/null +++ b/pkg/transform/set.go @@ -0,0 +1,159 @@ +// Copyright 2026 GoSQLX Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package transform + +import ( + "fmt" + "strings" + + "github.com/ajitpratap0/GoSQLX/pkg/sql/ast" +) + +// identifierName extracts the column name string from an Expression that is +// expected to be an *ast.Identifier. Returns empty string for other types. +func identifierName(expr ast.Expression) string { + if id, ok := expr.(*ast.Identifier); ok { + return id.Name + } + return "" +} + +// parseValueExpr parses a SQL value expression string using the +// "SELECT * FROM _t WHERE _col = " trick, returning the right-hand +// side of the equality. This handles literals, function calls, identifiers, etc. +func parseValueExpr(valueSQL string) (ast.Expression, error) { + expr, err := parseCondition(fmt.Sprintf("_col = %s", valueSQL)) + if err != nil { + return nil, fmt.Errorf("parse value %q: %w", valueSQL, err) + } + if bin, ok := expr.(*ast.BinaryExpression); ok { + return bin.Right, nil + } + return expr, nil +} + +// AddSetClause returns a Rule that appends a new assignment to the SET clause +// of an UPDATE statement. If a column with the same name already exists (case- +// insensitive), its value is overwritten rather than duplicated. +// +// Parameters: +// - column: the column name to set +// - valueSQL: a SQL expression string for the new value (e.g., "'active'", "NOW()", "42") +// +// Returns ErrUnsupportedStatement for non-UPDATE statements. +// +// Example: +// +// transform.Apply(stmt, transform.AddSetClause("updated_at", "NOW()")) +func AddSetClause(column, valueSQL string) Rule { + return RuleFunc(func(stmt ast.Statement) error { + upd, ok := stmt.(*ast.UpdateStatement) + if !ok { + return &ErrUnsupportedStatement{Transform: "AddSetClause", Got: stmtTypeName(stmt)} + } + + valueExpr, err := parseValueExpr(valueSQL) + if err != nil { + return fmt.Errorf("AddSetClause: %w", err) + } + + // Replace existing assignment for the same column (case-insensitive). + for i, a := range upd.Assignments { + if strings.EqualFold(identifierName(a.Column), column) { + upd.Assignments[i].Value = valueExpr + return nil + } + } + + // Append a new assignment. + upd.Assignments = append(upd.Assignments, ast.UpdateExpression{ + Column: &ast.Identifier{Name: column}, + Value: valueExpr, + }) + return nil + }) +} + +// SetClause is an alias for AddSetClause. It sets the value of a column in the +// UPDATE SET clause, adding it if not present or replacing it if already there. +// +// Example: +// +// transform.Apply(stmt, transform.SetClause("status", "'active'")) +func SetClause(column, valueSQL string) Rule { + return AddSetClause(column, valueSQL) +} + +// RemoveSetClause returns a Rule that removes a column from the UPDATE SET clause. +// If the column is not found the statement is left unchanged (no error). +// The comparison is case-insensitive. +// +// Returns ErrUnsupportedStatement for non-UPDATE statements. +// +// Example: +// +// transform.Apply(stmt, transform.RemoveSetClause("internal_flag")) +func RemoveSetClause(column string) Rule { + return RuleFunc(func(stmt ast.Statement) error { + upd, ok := stmt.(*ast.UpdateStatement) + if !ok { + return &ErrUnsupportedStatement{Transform: "RemoveSetClause", Got: stmtTypeName(stmt)} + } + + filtered := upd.Assignments[:0] + for _, a := range upd.Assignments { + if !strings.EqualFold(identifierName(a.Column), column) { + filtered = append(filtered, a) + } + } + upd.Assignments = filtered + return nil + }) +} + +// ReplaceSetClause returns a Rule that completely replaces all SET assignments +// with the ones provided in the map. Keys are column names, values are SQL +// expression strings. This is useful for wholesale rewrites of the SET clause. +// +// Returns ErrUnsupportedStatement for non-UPDATE statements. +// +// Example: +// +// transform.Apply(stmt, transform.ReplaceSetClause(map[string]string{ +// "status": "'active'", +// "updated_at": "NOW()", +// })) +func ReplaceSetClause(assignments map[string]string) Rule { + return RuleFunc(func(stmt ast.Statement) error { + upd, ok := stmt.(*ast.UpdateStatement) + if !ok { + return &ErrUnsupportedStatement{Transform: "ReplaceSetClause", Got: stmtTypeName(stmt)} + } + + newAssignments := make([]ast.UpdateExpression, 0, len(assignments)) + for col, valueSQL := range assignments { + valueExpr, err := parseValueExpr(valueSQL) + if err != nil { + return fmt.Errorf("ReplaceSetClause: column %q: %w", col, err) + } + newAssignments = append(newAssignments, ast.UpdateExpression{ + Column: &ast.Identifier{Name: col}, + Value: valueExpr, + }) + } + upd.Assignments = newAssignments + return nil + }) +} diff --git a/pkg/transform/set_test.go b/pkg/transform/set_test.go new file mode 100644 index 00000000..cc1f602b --- /dev/null +++ b/pkg/transform/set_test.go @@ -0,0 +1,170 @@ +// Copyright 2026 GoSQLX Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package transform + +import ( + "testing" +) + +func TestAddSetClause_Basic(t *testing.T) { + stmt := mustParse(t, "UPDATE users SET name = 'old'") + + err := Apply(stmt, AddSetClause("status", "'active'")) + if err != nil { + t.Fatalf("AddSetClause: %v", err) + } + + out := format(stmt) + assertContains(t, out, "status") + assertContains(t, out, "active") +} + +func TestAddSetClause_ReplacesExisting(t *testing.T) { + stmt := mustParse(t, "UPDATE users SET name = 'old', status = 'inactive'") + + err := Apply(stmt, AddSetClause("name", "'new'")) + if err != nil { + t.Fatalf("AddSetClause: %v", err) + } + + out := format(stmt) + assertContains(t, out, "new") + assertNotContains(t, out, "old") + // status should still be present + assertContains(t, out, "status") +} + +func TestSetClause_ReplaceExisting(t *testing.T) { + stmt := mustParse(t, "UPDATE users SET name = 'old', status = 'active'") + + err := Apply(stmt, SetClause("name", "'new'")) + if err != nil { + t.Fatalf("SetClause: %v", err) + } + + out := format(stmt) + assertContains(t, out, "new") + assertNotContains(t, out, "old") +} + +func TestRemoveSetClause_RemovesColumn(t *testing.T) { + stmt := mustParse(t, "UPDATE users SET name = 'alice', status = 'active', age = 30") + + err := Apply(stmt, RemoveSetClause("status")) + if err != nil { + t.Fatalf("RemoveSetClause: %v", err) + } + + out := format(stmt) + assertNotContains(t, out, "status") + assertContains(t, out, "name") + assertContains(t, out, "age") +} + +func TestRemoveSetClause_NonExistentColumn_NoError(t *testing.T) { + stmt := mustParse(t, "UPDATE users SET name = 'alice'") + + err := Apply(stmt, RemoveSetClause("nonexistent")) + if err != nil { + t.Fatalf("RemoveSetClause on nonexistent column should not error: %v", err) + } + + out := format(stmt) + assertContains(t, out, "name") +} + +func TestReplaceSetClause_ReplacesAll(t *testing.T) { + stmt := mustParse(t, "UPDATE users SET name = 'old', status = 'x'") + + err := Apply(stmt, ReplaceSetClause(map[string]string{ + "email": "'user@example.com'", + })) + if err != nil { + t.Fatalf("ReplaceSetClause: %v", err) + } + + out := format(stmt) + assertNotContains(t, out, "name") + assertNotContains(t, out, "status") + assertContains(t, out, "email") +} + +func TestReplaceSetClause_MultipleColumns(t *testing.T) { + stmt := mustParse(t, "UPDATE orders SET status = 'pending'") + + err := Apply(stmt, ReplaceSetClause(map[string]string{ + "status": "'shipped'", + "updated_at": "NOW()", + })) + if err != nil { + t.Fatalf("ReplaceSetClause: %v", err) + } + + out := format(stmt) + assertContains(t, out, "status") + assertContains(t, out, "shipped") +} + +func TestAddSetClause_OnNonUpdate_ReturnsError(t *testing.T) { + stmt := mustParse(t, "DELETE FROM users WHERE id = 1") + + err := Apply(stmt, AddSetClause("name", "'x'")) + if err == nil { + t.Error("expected error applying AddSetClause to DELETE statement") + } +} + +func TestRemoveSetClause_OnNonUpdate_ReturnsError(t *testing.T) { + stmt := mustParse(t, "SELECT * FROM users") + + err := Apply(stmt, RemoveSetClause("name")) + if err == nil { + t.Error("expected error applying RemoveSetClause to SELECT statement") + } +} + +func TestReplaceSetClause_OnNonUpdate_ReturnsError(t *testing.T) { + stmt := mustParse(t, "INSERT INTO users (name) VALUES ('bob')") + + err := Apply(stmt, ReplaceSetClause(map[string]string{"name": "'x'"})) + if err == nil { + t.Error("expected error applying ReplaceSetClause to INSERT statement") + } +} + +func TestAddSetClause_CaseInsensitiveColumn(t *testing.T) { + stmt := mustParse(t, "UPDATE users SET Name = 'old'") + + err := Apply(stmt, AddSetClause("name", "'new'")) + if err != nil { + t.Fatalf("AddSetClause: %v", err) + } + + out := format(stmt) + assertContains(t, out, "new") + assertNotContains(t, out, "old") +} + +func TestAddSetClause_WithNumericValue(t *testing.T) { + stmt := mustParse(t, "UPDATE counters SET total = 0") + + err := Apply(stmt, AddSetClause("total", "42")) + if err != nil { + t.Fatalf("AddSetClause with numeric: %v", err) + } + + out := format(stmt) + assertContains(t, out, "42") +}