mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-11 22:49:54 +00:00
Add MySQL REPL to the web UI (#57169)
This commit is contained in:
@@ -9,3 +9,5 @@ docs/theme/js/theme.js linguist-vendored=false
|
|||||||
*_pb.ts linguist-generated
|
*_pb.ts linguist-generated
|
||||||
*_pb.grpc-*.ts linguist-generated
|
*_pb.grpc-*.ts linguist-generated
|
||||||
*_pb.client.ts linguist-generated
|
*_pb.client.ts linguist-generated
|
||||||
|
|
||||||
|
*.golden -text
|
||||||
|
|||||||
@@ -206,6 +206,8 @@ require (
|
|||||||
github.com/spiffe/aws-spiffe-workload-helper v0.0.4
|
github.com/spiffe/aws-spiffe-workload-helper v0.0.4
|
||||||
github.com/spiffe/go-spiffe/v2 v2.5.0
|
github.com/spiffe/go-spiffe/v2 v2.5.0
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
|
github.com/testcontainers/testcontainers-go v0.37.0
|
||||||
|
github.com/testcontainers/testcontainers-go/modules/mysql v0.37.0
|
||||||
github.com/tidwall/pretty v1.2.1
|
github.com/tidwall/pretty v1.2.1
|
||||||
github.com/tidwall/sjson v1.2.5
|
github.com/tidwall/sjson v1.2.5
|
||||||
github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb
|
github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb
|
||||||
@@ -337,6 +339,7 @@ require (
|
|||||||
github.com/containerd/log v0.1.0 // indirect
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
github.com/containerd/platforms v0.2.1 // indirect
|
github.com/containerd/platforms v0.2.1 // indirect
|
||||||
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
|
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||||
github.com/crewjam/httperr v0.2.0 // indirect
|
github.com/crewjam/httperr v0.2.0 // indirect
|
||||||
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect
|
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||||
@@ -454,6 +457,7 @@ require (
|
|||||||
github.com/lithammer/dedent v1.1.0 // indirect
|
github.com/lithammer/dedent v1.1.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.10 // indirect
|
||||||
github.com/mailgun/errors v0.4.0 // indirect
|
github.com/mailgun/errors v0.4.0 // indirect
|
||||||
github.com/mailru/easyjson v0.9.0 // indirect
|
github.com/mailru/easyjson v0.9.0 // indirect
|
||||||
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
|
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
|
||||||
@@ -469,11 +473,17 @@ require (
|
|||||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
|
github.com/moby/go-archive v0.1.0 // indirect
|
||||||
|
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||||
github.com/moby/spdystream v0.5.0 // indirect
|
github.com/moby/spdystream v0.5.0 // indirect
|
||||||
|
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||||
|
github.com/moby/sys/user v0.4.0 // indirect
|
||||||
|
github.com/moby/sys/userns v0.1.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.0 // indirect
|
github.com/montanaflynn/stats v0.7.0 // indirect
|
||||||
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/mtibben/percent v0.2.1 // indirect
|
github.com/mtibben/percent v0.2.1 // indirect
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
|
|||||||
@@ -653,8 +653,8 @@ github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMb
|
|||||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
|
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
|
||||||
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
|
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
|
||||||
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
|
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||||
github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg=
|
github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg=
|
||||||
github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d/go.mod h1:XNqJ7hv2kY++g8XEHREpi+JqZo3+0l+CH2egBVN4yqM=
|
github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d/go.mod h1:XNqJ7hv2kY++g8XEHREpi+JqZo3+0l+CH2egBVN4yqM=
|
||||||
github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.14.0 h1:kcnfY4vljxXliXDBrA9K9lwF8IoEZ4Up6Eg9kWTIm28=
|
github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.14.0 h1:kcnfY4vljxXliXDBrA9K9lwF8IoEZ4Up6Eg9kWTIm28=
|
||||||
@@ -1111,6 +1111,8 @@ github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7
|
|||||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
@@ -1806,6 +1808,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2
|
|||||||
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
||||||
github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
||||||
github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
|
github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
|
||||||
|
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||||
|
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mailgun/errors v0.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8=
|
github.com/mailgun/errors v0.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8=
|
||||||
github.com/mailgun/errors v0.4.0/go.mod h1:xGBaaKdEdQT0/FhwvoXv4oBaqqmVZz9P1XEnvD/onc0=
|
github.com/mailgun/errors v0.4.0/go.mod h1:xGBaaKdEdQT0/FhwvoXv4oBaqqmVZz9P1XEnvD/onc0=
|
||||||
github.com/mailgun/mailgun-go/v4 v4.23.0 h1:jPEMJzzin2s7lvehcfv/0UkyBu18GvcURPr2+xtZRbk=
|
github.com/mailgun/mailgun-go/v4 v4.23.0 h1:jPEMJzzin2s7lvehcfv/0UkyBu18GvcURPr2+xtZRbk=
|
||||||
@@ -1873,12 +1877,20 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
|
|||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
|
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
||||||
|
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
|
||||||
|
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||||
|
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||||
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
|
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
|
||||||
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
||||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||||
|
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||||
|
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||||
|
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||||
|
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
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/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@@ -2191,6 +2203,10 @@ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDd
|
|||||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
|
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
|
||||||
github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/Yc7nM=
|
github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/Yc7nM=
|
||||||
github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
|
github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
|
||||||
|
github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg=
|
||||||
|
github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM=
|
||||||
|
github.com/testcontainers/testcontainers-go/modules/mysql v0.37.0 h1:LqUos1oR5iuuzorFnSvxsHNdYdCHB/DfI82CuT58wbI=
|
||||||
|
github.com/testcontainers/testcontainers-go/modules/mysql v0.37.0/go.mod h1:vHEEHx5Kf+uq5hveaVAMrTzPY8eeRZcKcl23MRw5Tkc=
|
||||||
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
|
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
|
||||||
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
|
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
|
||||||
github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI=
|
github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI=
|
||||||
@@ -3189,8 +3205,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||||
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
||||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
||||||
helm.sh/helm/v3 v3.18.4 h1:pNhnHM3nAmDrxz6/UC+hfjDY4yeDATQCka2/87hkZXQ=
|
helm.sh/helm/v3 v3.18.4 h1:pNhnHM3nAmDrxz6/UC+hfjDY4yeDATQCka2/87hkZXQ=
|
||||||
|
|||||||
@@ -1006,6 +1006,8 @@ github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7
|
|||||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
@@ -1059,6 +1061,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY=
|
github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY=
|
||||||
github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
|
github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
|
||||||
|
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||||
|
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/elimity-com/scim v0.0.0-20240320110924-172bf2aee9c8 h1:0+BTyxIYgiVAry/P5s8R4dYuLkhB9Nhso8ogFWNr4IQ=
|
github.com/elimity-com/scim v0.0.0-20240320110924-172bf2aee9c8 h1:0+BTyxIYgiVAry/P5s8R4dYuLkhB9Nhso8ogFWNr4IQ=
|
||||||
github.com/elimity-com/scim v0.0.0-20240320110924-172bf2aee9c8/go.mod h1:JkjcmqbLW+khwt2fmBPJFBhx2zGZ8XobRZ+O0VhlwWo=
|
github.com/elimity-com/scim v0.0.0-20240320110924-172bf2aee9c8/go.mod h1:JkjcmqbLW+khwt2fmBPJFBhx2zGZ8XobRZ+O0VhlwWo=
|
||||||
github.com/emicklei/go-restful/v3 v3.11.3 h1:yagOQz/38xJmcNeZJtrUcKjkHRltIaIFXKWeG1SkWGE=
|
github.com/emicklei/go-restful/v3 v3.11.3 h1:yagOQz/38xJmcNeZJtrUcKjkHRltIaIFXKWeG1SkWGE=
|
||||||
@@ -1166,6 +1170,8 @@ 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-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
|
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
|
||||||
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
|
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
|
||||||
|
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-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
|
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
|
||||||
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
|
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
|
||||||
github.com/go-openapi/errors v0.22.1 h1:kslMRRnK7NCb/CvR1q1VWuEQCEIsBGn5GgKD9e+HYhU=
|
github.com/go-openapi/errors v0.22.1 h1:kslMRRnK7NCb/CvR1q1VWuEQCEIsBGn5GgKD9e+HYhU=
|
||||||
@@ -1710,9 +1716,13 @@ 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.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
|
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
|
||||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
|
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
|
||||||
|
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/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
||||||
github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
||||||
github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
|
github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
|
||||||
|
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||||
|
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||||
github.com/mark3labs/mcp-go v0.36.0 h1:rIZaijrRYPeSbJG8/qNDe0hWlGrCJ7FWHNMz2SQpTis=
|
github.com/mark3labs/mcp-go v0.36.0 h1:rIZaijrRYPeSbJG8/qNDe0hWlGrCJ7FWHNMz2SQpTis=
|
||||||
@@ -1767,8 +1777,18 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
|
|||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
|
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
||||||
|
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
|
||||||
|
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||||
|
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||||
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
|
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
|
||||||
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
||||||
|
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||||
|
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||||
|
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||||
|
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||||
|
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||||
|
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
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/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@@ -1781,6 +1801,8 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod
|
|||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||||
github.com/montanaflynn/stats v0.7.0 h1:r3y12KyNxj/Sb/iOE46ws+3mS1+MZca1wlHQFPsY/JU=
|
github.com/montanaflynn/stats v0.7.0 h1:r3y12KyNxj/Sb/iOE46ws+3mS1+MZca1wlHQFPsY/JU=
|
||||||
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||||
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
|
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
|
||||||
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
|
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
|
||||||
github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM=
|
github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM=
|
||||||
@@ -1878,6 +1900,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
|||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
|
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
|
||||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
|
github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
|
||||||
github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
|
github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
|
||||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||||
@@ -1953,6 +1977,9 @@ github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df h1:S77Pf5fIG
|
|||||||
github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df/go.mod h1:dcuzJZ83w/SqN9k4eQqwKYMgmKWzg/KzJAURBhRL1tc=
|
github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df/go.mod h1:dcuzJZ83w/SqN9k4eQqwKYMgmKWzg/KzJAURBhRL1tc=
|
||||||
github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=
|
github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=
|
||||||
github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE=
|
github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE=
|
||||||
|
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=
|
||||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
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.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
@@ -2035,6 +2062,10 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
|||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg=
|
||||||
|
github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM=
|
||||||
|
github.com/testcontainers/testcontainers-go/modules/mysql v0.37.0 h1:LqUos1oR5iuuzorFnSvxsHNdYdCHB/DfI82CuT58wbI=
|
||||||
|
github.com/testcontainers/testcontainers-go/modules/mysql v0.37.0/go.mod h1:vHEEHx5Kf+uq5hveaVAMrTzPY8eeRZcKcl23MRw5Tkc=
|
||||||
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
|
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
|
||||||
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
|
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
|
||||||
github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI=
|
github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI=
|
||||||
@@ -2062,6 +2093,10 @@ github.com/tink-crypto/tink-go/v2 v2.4.0 h1:8VPZeZI4EeZ8P/vB6SIkhlStrJfivTJn+cQ4
|
|||||||
github.com/tink-crypto/tink-go/v2 v2.4.0/go.mod h1:l//evrF2Y3MjdbpNDNGnKgCpo5zSmvUvnQ4MU+yE2sw=
|
github.com/tink-crypto/tink-go/v2 v2.4.0/go.mod h1:l//evrF2Y3MjdbpNDNGnKgCpo5zSmvUvnQ4MU+yE2sw=
|
||||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0=
|
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0=
|
||||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs=
|
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
|
||||||
|
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
|
||||||
|
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||||
github.com/transparency-dev/formats v0.0.0-20250421220931-bb8ad4d07c26 h1:YTbkeFbzcer+42bIgo6Za2194nKwhZPgaZKsP76QffE=
|
github.com/transparency-dev/formats v0.0.0-20250421220931-bb8ad4d07c26 h1:YTbkeFbzcer+42bIgo6Za2194nKwhZPgaZKsP76QffE=
|
||||||
github.com/transparency-dev/formats v0.0.0-20250421220931-bb8ad4d07c26/go.mod h1:ODywn0gGarHMMdSkWT56ULoK8Hk71luOyRseKek9COw=
|
github.com/transparency-dev/formats v0.0.0-20250421220931-bb8ad4d07c26/go.mod h1:ODywn0gGarHMMdSkWT56ULoK8Hk71luOyRseKek9COw=
|
||||||
github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4=
|
github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4=
|
||||||
@@ -2120,6 +2155,8 @@ github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUei
|
|||||||
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
|
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
|
||||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
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/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms=
|
github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms=
|
||||||
github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
|
github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
|
||||||
github.com/zclconf/go-cty v1.16.3 h1:osr++gw2T61A8KVYHoQiFbFd1Lh3JOCXc/jFLJXKTxk=
|
github.com/zclconf/go-cty v1.16.3 h1:osr++gw2T61A8KVYHoQiFbFd1Lh3JOCXc/jFLJXKTxk=
|
||||||
|
|||||||
@@ -1029,6 +1029,8 @@ github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7
|
|||||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
@@ -1082,6 +1084,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY=
|
github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY=
|
||||||
github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
|
github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
|
||||||
|
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||||
|
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/elimity-com/scim v0.0.0-20240320110924-172bf2aee9c8 h1:0+BTyxIYgiVAry/P5s8R4dYuLkhB9Nhso8ogFWNr4IQ=
|
github.com/elimity-com/scim v0.0.0-20240320110924-172bf2aee9c8 h1:0+BTyxIYgiVAry/P5s8R4dYuLkhB9Nhso8ogFWNr4IQ=
|
||||||
github.com/elimity-com/scim v0.0.0-20240320110924-172bf2aee9c8/go.mod h1:JkjcmqbLW+khwt2fmBPJFBhx2zGZ8XobRZ+O0VhlwWo=
|
github.com/elimity-com/scim v0.0.0-20240320110924-172bf2aee9c8/go.mod h1:JkjcmqbLW+khwt2fmBPJFBhx2zGZ8XobRZ+O0VhlwWo=
|
||||||
github.com/emicklei/go-restful/v3 v3.11.3 h1:yagOQz/38xJmcNeZJtrUcKjkHRltIaIFXKWeG1SkWGE=
|
github.com/emicklei/go-restful/v3 v3.11.3 h1:yagOQz/38xJmcNeZJtrUcKjkHRltIaIFXKWeG1SkWGE=
|
||||||
@@ -1198,6 +1202,8 @@ 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-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
|
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
|
||||||
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
|
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
|
||||||
|
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-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
|
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
|
||||||
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
|
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
|
||||||
github.com/go-openapi/errors v0.22.1 h1:kslMRRnK7NCb/CvR1q1VWuEQCEIsBGn5GgKD9e+HYhU=
|
github.com/go-openapi/errors v0.22.1 h1:kslMRRnK7NCb/CvR1q1VWuEQCEIsBGn5GgKD9e+HYhU=
|
||||||
@@ -1766,9 +1772,13 @@ 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.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
|
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
|
||||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
|
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
|
||||||
|
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/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
||||||
github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
|
||||||
github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
|
github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
|
||||||
|
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||||
|
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||||
github.com/mark3labs/mcp-go v0.36.0 h1:rIZaijrRYPeSbJG8/qNDe0hWlGrCJ7FWHNMz2SQpTis=
|
github.com/mark3labs/mcp-go v0.36.0 h1:rIZaijrRYPeSbJG8/qNDe0hWlGrCJ7FWHNMz2SQpTis=
|
||||||
@@ -1840,8 +1850,18 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
|
|||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
|
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
||||||
|
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
|
||||||
|
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||||
|
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||||
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
|
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
|
||||||
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
||||||
|
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||||
|
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||||
|
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||||
|
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||||
|
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||||
|
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
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/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@@ -1854,6 +1874,8 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod
|
|||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||||
github.com/montanaflynn/stats v0.7.0 h1:r3y12KyNxj/Sb/iOE46ws+3mS1+MZca1wlHQFPsY/JU=
|
github.com/montanaflynn/stats v0.7.0 h1:r3y12KyNxj/Sb/iOE46ws+3mS1+MZca1wlHQFPsY/JU=
|
||||||
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||||
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
|
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
|
||||||
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
|
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
|
||||||
github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM=
|
github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM=
|
||||||
@@ -1954,6 +1976,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
|
|||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
|
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
|
||||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
|
github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
|
||||||
github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
|
github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
|
||||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||||
@@ -2033,6 +2057,9 @@ github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df h1:S77Pf5fIG
|
|||||||
github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df/go.mod h1:dcuzJZ83w/SqN9k4eQqwKYMgmKWzg/KzJAURBhRL1tc=
|
github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df/go.mod h1:dcuzJZ83w/SqN9k4eQqwKYMgmKWzg/KzJAURBhRL1tc=
|
||||||
github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=
|
github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=
|
||||||
github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE=
|
github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE=
|
||||||
|
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=
|
||||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
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.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
@@ -2116,6 +2143,10 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
|||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg=
|
||||||
|
github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM=
|
||||||
|
github.com/testcontainers/testcontainers-go/modules/mysql v0.37.0 h1:LqUos1oR5iuuzorFnSvxsHNdYdCHB/DfI82CuT58wbI=
|
||||||
|
github.com/testcontainers/testcontainers-go/modules/mysql v0.37.0/go.mod h1:vHEEHx5Kf+uq5hveaVAMrTzPY8eeRZcKcl23MRw5Tkc=
|
||||||
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
|
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
|
||||||
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
|
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
|
||||||
github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI=
|
github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI=
|
||||||
@@ -2143,6 +2174,10 @@ github.com/tink-crypto/tink-go/v2 v2.4.0 h1:8VPZeZI4EeZ8P/vB6SIkhlStrJfivTJn+cQ4
|
|||||||
github.com/tink-crypto/tink-go/v2 v2.4.0/go.mod h1:l//evrF2Y3MjdbpNDNGnKgCpo5zSmvUvnQ4MU+yE2sw=
|
github.com/tink-crypto/tink-go/v2 v2.4.0/go.mod h1:l//evrF2Y3MjdbpNDNGnKgCpo5zSmvUvnQ4MU+yE2sw=
|
||||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0=
|
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0=
|
||||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs=
|
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
|
||||||
|
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
|
||||||
|
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||||
github.com/transparency-dev/formats v0.0.0-20250421220931-bb8ad4d07c26 h1:YTbkeFbzcer+42bIgo6Za2194nKwhZPgaZKsP76QffE=
|
github.com/transparency-dev/formats v0.0.0-20250421220931-bb8ad4d07c26 h1:YTbkeFbzcer+42bIgo6Za2194nKwhZPgaZKsP76QffE=
|
||||||
github.com/transparency-dev/formats v0.0.0-20250421220931-bb8ad4d07c26/go.mod h1:ODywn0gGarHMMdSkWT56ULoK8Hk71luOyRseKek9COw=
|
github.com/transparency-dev/formats v0.0.0-20250421220931-bb8ad4d07c26/go.mod h1:ODywn0gGarHMMdSkWT56ULoK8Hk71luOyRseKek9COw=
|
||||||
github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4=
|
github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4=
|
||||||
@@ -2205,6 +2240,8 @@ github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUei
|
|||||||
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
|
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
|
||||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
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/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms=
|
github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms=
|
||||||
github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
|
github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
|
||||||
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
||||||
|
|||||||
@@ -0,0 +1,280 @@
|
|||||||
|
// Teleport
|
||||||
|
// Copyright (C) 2025 Gravitational, Inc.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package repl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmp"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/go-mysql-org/go-mysql/mysql"
|
||||||
|
"github.com/gravitational/trace"
|
||||||
|
|
||||||
|
"github.com/gravitational/teleport"
|
||||||
|
"github.com/gravitational/teleport/lib/asciitable"
|
||||||
|
)
|
||||||
|
|
||||||
|
type commandManager struct {
|
||||||
|
byName map[string]*command
|
||||||
|
byShortcut map[rune]*command
|
||||||
|
// shortcutPrefix is the prefix for command shortcuts, e.g. \h for help.
|
||||||
|
shortcutPrefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
// command represents a command that can be executed in the REPL.
|
||||||
|
type command struct {
|
||||||
|
// name is the command's full name.
|
||||||
|
name string
|
||||||
|
// shortcut is the command's escaped single char name: \x.
|
||||||
|
shortcut rune
|
||||||
|
// description provides a user-friendly explanation of what the command
|
||||||
|
// does.
|
||||||
|
description string
|
||||||
|
// execFunc is the function to execute the command. The commands can either
|
||||||
|
// return a reply (that will be sent back to the client) as a string. It can
|
||||||
|
// terminate the REPL by returning bool on the second argument.
|
||||||
|
execFunc func(r *REPL, args string) (reply string, exit bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *command) checkAndSetDefaults() error {
|
||||||
|
if len(c.name) == 0 {
|
||||||
|
return trace.BadParameter("missing command name")
|
||||||
|
}
|
||||||
|
if c.shortcut == 0 {
|
||||||
|
return trace.BadParameter("missing command short name")
|
||||||
|
}
|
||||||
|
if len(c.description) == 0 {
|
||||||
|
return trace.BadParameter("missing command description")
|
||||||
|
}
|
||||||
|
if c.execFunc == nil {
|
||||||
|
return trace.BadParameter("missing command exec func")
|
||||||
|
}
|
||||||
|
c.name = normalizeCommandName(c.name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeCommandName(name string) string {
|
||||||
|
return strings.ToLower(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCommands() (*commandManager, error) {
|
||||||
|
c := &commandManager{
|
||||||
|
byName: make(map[string]*command),
|
||||||
|
byShortcut: make(map[rune]*command),
|
||||||
|
shortcutPrefix: `\`,
|
||||||
|
}
|
||||||
|
for _, cmd := range []*command{
|
||||||
|
{
|
||||||
|
name: "delimiter",
|
||||||
|
shortcut: 'd',
|
||||||
|
description: "Set statement delimiter. With no argument the delimiter resets to the default.",
|
||||||
|
execFunc: func(r *REPL, args string) (reply string, exit bool) {
|
||||||
|
delim := getArg(args)
|
||||||
|
if err := r.parser.lex.setDelimiter(delim); err != nil {
|
||||||
|
return err.Error(), false
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "help",
|
||||||
|
shortcut: 'h',
|
||||||
|
description: "Show the list of supported commands.",
|
||||||
|
execFunc: func(_ *REPL, _ string) (string, bool) {
|
||||||
|
return c.help(), false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "quit",
|
||||||
|
shortcut: 'q',
|
||||||
|
description: "Terminates the session.",
|
||||||
|
execFunc: func(_ *REPL, _ string) (string, bool) { return "", true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "status",
|
||||||
|
shortcut: 's',
|
||||||
|
description: "Get status information.",
|
||||||
|
execFunc: func(r *REPL, _ string) (string, bool) {
|
||||||
|
table := asciitable.MakeHeadlessTable(2)
|
||||||
|
table.AddRow([]string{"Teleport database:", r.route.ServiceName})
|
||||||
|
table.AddRow([]string{"Current database:", formatDatabaseName(r.route.Database)})
|
||||||
|
table.AddRow([]string{"Current user:", r.route.Username})
|
||||||
|
table.AddRow([]string{"Server version:", r.myConn.GetServerVersion()})
|
||||||
|
table.AddRow([]string{"Using delimiter:", r.parser.lex.delimiter()})
|
||||||
|
return table.String(), false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "teleport",
|
||||||
|
shortcut: 't',
|
||||||
|
description: "Show Teleport interactive shell information, such as execution limitations.",
|
||||||
|
execFunc: func(_ *REPL, _ string) (string, bool) {
|
||||||
|
var sb strings.Builder
|
||||||
|
for _, l := range descriptiveLimitations {
|
||||||
|
sb.WriteString("- " + strings.ReplaceAll(l, "\n", "\n ") + lineBreak)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Teleport MySQL interactive shell (v%s)\n\nLimitations: \n%s",
|
||||||
|
teleport.Version,
|
||||||
|
sb.String(),
|
||||||
|
), false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "use",
|
||||||
|
shortcut: 'u',
|
||||||
|
description: "Use another database.",
|
||||||
|
execFunc: func(r *REPL, args string) (string, bool) {
|
||||||
|
args = strings.TrimSpace(args)
|
||||||
|
if len(args) == 0 {
|
||||||
|
return "USE must be followed by a database name", false
|
||||||
|
}
|
||||||
|
dbName, err := getDatabaseName(r.myConn, args)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error(), false
|
||||||
|
}
|
||||||
|
if err := r.myConn.UseDB(dbName); err != nil {
|
||||||
|
return err.Error(), false
|
||||||
|
}
|
||||||
|
r.route.Database = dbName
|
||||||
|
return fmt.Sprintf(`Default database changed to %q`, dbName), false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
if err := cmd.checkAndSetDefaults(); err != nil {
|
||||||
|
return nil, trace.Wrap(err)
|
||||||
|
}
|
||||||
|
if err := c.add(cmd); err != nil {
|
||||||
|
return nil, trace.Wrap(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commandManager) add(cmd *command) error {
|
||||||
|
if _, ok := c.byName[cmd.name]; ok {
|
||||||
|
return trace.BadParameter("overlapping command name: %s", cmd.name)
|
||||||
|
}
|
||||||
|
c.byName[cmd.name] = cmd
|
||||||
|
if _, ok := c.byShortcut[cmd.shortcut]; ok {
|
||||||
|
return trace.BadParameter("overlapping command shortcut: %c", cmd.shortcut)
|
||||||
|
}
|
||||||
|
c.byShortcut[cmd.shortcut] = cmd
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findCommand tries to find a command at the start of a line. If a command name
|
||||||
|
// or short name matches, then it returns the command and the rest of the line.
|
||||||
|
// If no command is found and the line does not start with the short name prefix
|
||||||
|
// then the command and error returned will both be nil.
|
||||||
|
func (c *commandManager) findCommand(line string) (*command, string, error) {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if before, after, found := strings.Cut(line, c.shortcutPrefix); found && len(before) == 0 {
|
||||||
|
shortcut, width := utf8.DecodeRuneInString(after)
|
||||||
|
if width > 0 && shortcut != utf8.RuneError {
|
||||||
|
if cmd, ok := c.byShortcut[shortcut]; ok {
|
||||||
|
return cmd, after[1:], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, "", trace.Errorf("Unknown command.\n%v", c.help())
|
||||||
|
}
|
||||||
|
|
||||||
|
name, after, _ := strings.Cut(line, " ")
|
||||||
|
if cmd, ok := c.byName[normalizeCommandName(name)]; ok {
|
||||||
|
return cmd, after, nil
|
||||||
|
}
|
||||||
|
return nil, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commandManager) help() string {
|
||||||
|
var res strings.Builder
|
||||||
|
cmds := slices.Collect(maps.Values(c.byName))
|
||||||
|
slices.SortStableFunc(cmds, func(a, b *command) int {
|
||||||
|
return strings.Compare(a.name, b.name)
|
||||||
|
})
|
||||||
|
table := asciitable.MakeHeadlessTable(3)
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
short := fmt.Sprintf("%s%c", c.shortcutPrefix, cmd.shortcut)
|
||||||
|
row := []string{cmd.name, short, cmd.description}
|
||||||
|
table.AddRow(row)
|
||||||
|
}
|
||||||
|
table.WriteTo(&res)
|
||||||
|
res.WriteString(lineBreak)
|
||||||
|
return res.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getArg trims spaces from the line and returns the first space separated arg.
|
||||||
|
func getArg(line string) string {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
before, _, _ := strings.Cut(line, " ")
|
||||||
|
return before
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDatabaseName extracts a database name from args.
|
||||||
|
// Since USE is both a command (COM_INIT_DB) and a statement, as a client
|
||||||
|
// we have to parse the database argument for COM_INIT_DB.
|
||||||
|
// To handle quotation, we send the args to the server as a statement and then
|
||||||
|
// ask the server what the current database is.
|
||||||
|
func getDatabaseName(conn mysql.Executer, args string) (string, error) {
|
||||||
|
result, err := conn.Execute("USE " + args)
|
||||||
|
if err != nil {
|
||||||
|
return "", trace.Wrap(err)
|
||||||
|
}
|
||||||
|
result.Close()
|
||||||
|
return getCurrentDatabase(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCurrentDatabase(conn mysql.Executer) (string, error) {
|
||||||
|
result, err := conn.Execute("SELECT DATABASE() LIMIT 1")
|
||||||
|
if err != nil {
|
||||||
|
return "", trace.Wrap(err)
|
||||||
|
}
|
||||||
|
defer result.Close()
|
||||||
|
if result.Resultset == nil || len(result.Resultset.Values) == 0 || len(result.Resultset.Values[0]) == 0 {
|
||||||
|
return "", trace.NotFound("missing result")
|
||||||
|
}
|
||||||
|
val := result.Resultset.Values[0][0]
|
||||||
|
if val.Type == mysql.FieldValueTypeString {
|
||||||
|
return string(val.AsString()), nil
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatDatabaseName(name string) string {
|
||||||
|
return cmp.Or(name, "(none)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// descriptiveLimitations defines a user-friendly text containing the REPL
|
||||||
|
// limitations.
|
||||||
|
var descriptiveLimitations = []string{
|
||||||
|
`Query cancellation is not supported. Once a query is sent, its execution
|
||||||
|
can only be canceled by killing it from another session.
|
||||||
|
You can identify the query process ID with "SHOW PROCESSLIST" and then kill the
|
||||||
|
query with "KILL QUERY <pid>".
|
||||||
|
`,
|
||||||
|
// This limitation is due to our terminal emulator not fully supporting this
|
||||||
|
// shortcut's custom handler. Instead, it will close the terminal, leading
|
||||||
|
// to terminating the session. To avoid having users accidentally
|
||||||
|
// terminating their sessions, we're turning this off until we have a better
|
||||||
|
// solution and propose the behavior for it.
|
||||||
|
//
|
||||||
|
// This shortcut filtered out by the WebUI key handler.
|
||||||
|
"Pressing CTRL-C will have no effect in this shell.",
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
// Teleport
|
||||||
|
// Copyright (C) 2025 Gravitational, Inc.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package repl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-mysql-org/go-mysql/mysql"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/gravitational/teleport"
|
||||||
|
clientproto "github.com/gravitational/teleport/api/client/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommands(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
parser, err := newParser()
|
||||||
|
require.NoError(t, err)
|
||||||
|
commands := parser.commands
|
||||||
|
require.NotEmpty(t, commands.byName)
|
||||||
|
require.ElementsMatch(t,
|
||||||
|
slices.Collect(maps.Values(commands.byName)),
|
||||||
|
slices.Collect(maps.Values(commands.byShortcut)),
|
||||||
|
)
|
||||||
|
|
||||||
|
for name, cmd := range commands.byName {
|
||||||
|
require.NotNil(t, cmd)
|
||||||
|
require.NotEmpty(t, cmd.name)
|
||||||
|
require.NotZero(t, cmd.shortcut)
|
||||||
|
require.NotEmpty(t, cmd.description)
|
||||||
|
require.NotNil(t, cmd.execFunc)
|
||||||
|
|
||||||
|
require.Equal(t, cmd.name, name, "expected command to be registered by name")
|
||||||
|
byShortcut, ok := commands.byShortcut[cmd.shortcut]
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, cmd, byShortcut, "expected commands from name and shortcut indices to match")
|
||||||
|
}
|
||||||
|
|
||||||
|
repl := &REPL{
|
||||||
|
parser: parser,
|
||||||
|
myConn: &fakeMySQLConn{
|
||||||
|
exec: func(command string, args ...any) (*mysql.Result, error) {
|
||||||
|
resultSet, err := mysql.BuildSimpleTextResultset([]string{"current_database"}, [][]any{{"example"}})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resultSet.Values = make([][]mysql.FieldValue, 1)
|
||||||
|
resultSet.Values[0], err = resultSet.RowDatas[0].Parse(resultSet.Fields, false, nil)
|
||||||
|
return &mysql.Result{
|
||||||
|
Resultset: resultSet,
|
||||||
|
}, err
|
||||||
|
},
|
||||||
|
},
|
||||||
|
route: clientproto.RouteToDatabase{
|
||||||
|
ServiceName: "test-service",
|
||||||
|
Username: "test-user",
|
||||||
|
Database: "test-database",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
cmdName string
|
||||||
|
args string
|
||||||
|
expectExit bool
|
||||||
|
assertCommandReply require.ValueAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "delimiter command has an empty reply",
|
||||||
|
cmdName: "delimiter",
|
||||||
|
assertCommandReply: func(t require.TestingT, val any, _ ...any) {
|
||||||
|
require.Empty(t, val)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "delimiter fails regex",
|
||||||
|
cmdName: "delimiter",
|
||||||
|
args: "abc",
|
||||||
|
assertCommandReply: func(t require.TestingT, val any, _ ...any) {
|
||||||
|
require.Equal(t, `DELIMITER "abc" does not match regex used for validation "^(;|[/]{2}|[$]{2})$"`, val)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "help",
|
||||||
|
cmdName: "help",
|
||||||
|
assertCommandReply: func(t require.TestingT, val any, _ ...any) {
|
||||||
|
for name := range commands.byName {
|
||||||
|
require.Contains(t, val, name, `expected help command to include information about the %q command`, name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "quit",
|
||||||
|
cmdName: "quit",
|
||||||
|
expectExit: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "status",
|
||||||
|
cmdName: "status",
|
||||||
|
assertCommandReply: func(t require.TestingT, val any, _ ...any) {
|
||||||
|
require.Contains(t, val, "test-service", "expected session command to contain service name")
|
||||||
|
require.Contains(t, val, "test-user", "expected session command to contain username")
|
||||||
|
require.Contains(t, val, "test-database", "expected session command to contain database name")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "teleport",
|
||||||
|
cmdName: "teleport",
|
||||||
|
assertCommandReply: func(t require.TestingT, val any, _ ...any) {
|
||||||
|
require.Contains(t, val, teleport.Version, "expected teleport command to include current Teleport version")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "use requires an arg",
|
||||||
|
cmdName: "use",
|
||||||
|
assertCommandReply: func(t require.TestingT, val any, _ ...any) {
|
||||||
|
require.Equal(t, "USE must be followed by a database name", val)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "use with an unquoted argument",
|
||||||
|
cmdName: "use",
|
||||||
|
args: "example",
|
||||||
|
assertCommandReply: func(t require.TestingT, val any, _ ...any) {
|
||||||
|
require.Equal(t, `Default database changed to "example"`, val)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "use with a quoted argument",
|
||||||
|
cmdName: "use",
|
||||||
|
args: `"example"`,
|
||||||
|
assertCommandReply: func(t require.TestingT, val any, _ ...any) {
|
||||||
|
require.Equal(t, `Default database changed to "example"`, val)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
cmd, ok := commands.byName[test.cmdName]
|
||||||
|
require.True(t, ok, "expected command to be registered")
|
||||||
|
reply, exit := cmd.execFunc(repl, test.args)
|
||||||
|
if test.expectExit {
|
||||||
|
require.True(t, exit, "expected command to exit the REPL")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
test.assertCommandReply(t, reply)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeMySQLConn struct {
|
||||||
|
exec func(command string, args ...any) (*mysql.Result, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeMySQLConn) Execute(command string, args ...any) (*mysql.Result, error) {
|
||||||
|
return c.exec(command, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeMySQLConn) UseDB(dbName string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeMySQLConn) GetServerVersion() string {
|
||||||
|
return "1.2.3 Fake MySQL Server"
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
-- temporarily disable note warnings for test consistency
|
||||||
|
set session sql_notes = 0;
|
||||||
|
CREATE DATABASE IF NOT EXISTS all_data_types;
|
||||||
|
set session sql_notes = 1;
|
||||||
|
USE all_data_types;
|
||||||
|
|
||||||
|
-- without ANSI_QUOTES double quotes are literals
|
||||||
|
SET SESSION sql_mode='';
|
||||||
|
SELECT "thing";
|
||||||
|
-- with ANSI_QUOTES double quotes are identifiers, which is what we want when
|
||||||
|
-- selecting from the table below
|
||||||
|
SET SESSION sql_mode='ANSI_QUOTES';
|
||||||
|
SELECT "thing";
|
||||||
|
|
||||||
|
CREATE TABLE all_types_demo (
|
||||||
|
"c tinyint" TINYINT,
|
||||||
|
c_smallint SMALLINT,
|
||||||
|
c_mediumint MEDIUMINT,
|
||||||
|
c_int INT,
|
||||||
|
c_bigint BIGINT,
|
||||||
|
c_decimal DECIMAL(10,2),
|
||||||
|
c_float FLOAT,
|
||||||
|
c_double DOUBLE,
|
||||||
|
c_bit BIT(8),
|
||||||
|
c_bool BOOL,
|
||||||
|
c_date DATE,
|
||||||
|
c_time TIME,
|
||||||
|
c_datetime DATETIME,
|
||||||
|
c_timestamp TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
c_year YEAR,
|
||||||
|
c_char CHAR(5),
|
||||||
|
c_varchar VARCHAR(10),
|
||||||
|
c_binary BINARY(3),
|
||||||
|
c_varbinary VARBINARY(4),
|
||||||
|
c_tinyblob TINYBLOB,
|
||||||
|
c_blob BLOB,
|
||||||
|
c_mediumblob MEDIUMBLOB,
|
||||||
|
c_longblob LONGBLOB,
|
||||||
|
c_tinytext TINYTEXT,
|
||||||
|
c_text TEXT,
|
||||||
|
c_mediumtext MEDIUMTEXT,
|
||||||
|
c_longtext LONGTEXT,
|
||||||
|
c_enum ENUM('foo','bar','baz'),
|
||||||
|
c_set SET('a','b','c'),
|
||||||
|
c_geometry GEOMETRY,
|
||||||
|
c_point POINT,
|
||||||
|
c_linestring LINESTRING,
|
||||||
|
c_polygon POLYGON,
|
||||||
|
c_multipoint MULTIPOINT,
|
||||||
|
c_multilinestring MULTILINESTRING,
|
||||||
|
c_multipolygon MULTIPOLYGON,
|
||||||
|
c_geometrycoll GEOMETRYCOLLECTION,
|
||||||
|
c_json JSON
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO all_types_demo VALUES (
|
||||||
|
127,
|
||||||
|
32767,
|
||||||
|
8388607,
|
||||||
|
2147483647,
|
||||||
|
9223372036854775807,
|
||||||
|
12345.67,
|
||||||
|
3.14,
|
||||||
|
2.7182818284,
|
||||||
|
b'10101010',
|
||||||
|
1,
|
||||||
|
'2024-07-09',
|
||||||
|
'12:34:56',
|
||||||
|
'2024-07-09 12:34:56',
|
||||||
|
'2024-07-09 12:34:56',
|
||||||
|
2025,
|
||||||
|
'abcde',
|
||||||
|
'abcdefghij',
|
||||||
|
'abc',
|
||||||
|
'defg',
|
||||||
|
'a',
|
||||||
|
'blob',
|
||||||
|
'medblob',
|
||||||
|
'longblob',
|
||||||
|
'tiny',
|
||||||
|
'text',
|
||||||
|
'mediumtext',
|
||||||
|
'longtext',
|
||||||
|
'foo',
|
||||||
|
'a,b',
|
||||||
|
ST_GeomFromText('POINT(1 1)'),
|
||||||
|
ST_GeomFromText('POINT(2 2)'),
|
||||||
|
ST_GeomFromText('LINESTRING(0 0,1 1,2 2)'),
|
||||||
|
ST_GeomFromText('POLYGON((0 0,1 0,1 1,0 1,0 0))'),
|
||||||
|
ST_GeomFromText('MULTIPOINT((0 0),(1 1))'),
|
||||||
|
ST_GeomFromText('MULTILINESTRING((0 0,1 1),(2 2,3 3))'),
|
||||||
|
ST_GeomFromText('MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0)))'),
|
||||||
|
ST_GeomFromText('GEOMETRYCOLLECTION(POINT(1 2),LINESTRING(0 0,1 1))'),
|
||||||
|
JSON_OBJECT('x', 1, 'y', 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
SELECT "c tinyint", `c_smallint`, "c_mediumint" FROM `all_types_demo`;
|
||||||
|
SELECT c_int, c_bigint, c_decimal FROM all_types_demo;
|
||||||
|
SELECT c_float, c_double, c_bit FROM all_types_demo;
|
||||||
|
SELECT c_bool, c_date, c_time FROM all_types_demo;
|
||||||
|
SELECT c_datetime, c_timestamp, c_year FROM all_types_demo;
|
||||||
|
SELECT c_char, c_varchar, c_binary FROM all_types_demo;
|
||||||
|
SELECT c_varbinary, c_tinyblob, c_blob FROM all_types_demo;
|
||||||
|
SELECT "c_mediumblob",
|
||||||
|
`c_longblob`,
|
||||||
|
c_tinytext
|
||||||
|
FROM all_types_demo;
|
||||||
|
SELECT c_text, c_mediumtext, c_longtext FROM all_types_demo;
|
||||||
|
SELECT c_enum, c_set, ST_AsText(c_geometry) AS c_geometry FROM all_types_demo;
|
||||||
|
SELECT ST_AsText(c_point) AS c_point, ST_AsText(c_linestring) AS c_linestring, ST_AsText(c_polygon) AS c_polygon FROM all_types_demo;
|
||||||
|
SELECT ST_AsText(c_multipoint) AS c_multipoint, ST_AsText(c_multilinestring) AS c_multilinestring, ST_AsText(c_multipolygon) AS c_multipolygon FROM all_types_demo;
|
||||||
|
SELECT ST_AsText(c_geometrycoll) AS c_geometrycoll, c_json FROM all_types_demo;
|
||||||
|
DROP DATABASE all_data_types;
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
-- temporarily disable note warnings for test consistency
|
||||||
|
set session sql_notes = 0;
|
||||||
|
create database if not exists test_commands;
|
||||||
|
create database if not exists foo;
|
||||||
|
create database if not exists bar;
|
||||||
|
set session sql_notes = 1;
|
||||||
|
use test_commands;
|
||||||
|
|
||||||
|
-- leading spaces/comments before commands
|
||||||
|
/* just a comment */
|
||||||
|
use foo;
|
||||||
|
use foo
|
||||||
|
/*
|
||||||
|
llama
|
||||||
|
*/
|
||||||
|
use foo;
|
||||||
|
/*
|
||||||
|
llama
|
||||||
|
*/
|
||||||
|
use foo
|
||||||
|
-- llama
|
||||||
|
use foo;
|
||||||
|
-- llama
|
||||||
|
use foo
|
||||||
|
-- llama
|
||||||
|
use foo;
|
||||||
|
-- llama
|
||||||
|
use foo
|
||||||
|
/* llama */use foo;
|
||||||
|
/* llama */use foo
|
||||||
|
/* llama */ use foo;
|
||||||
|
/* llama */ use foo
|
||||||
|
/* llama */
|
||||||
|
use foo;
|
||||||
|
/* llama */
|
||||||
|
use foo
|
||||||
|
/* llama
|
||||||
|
*/use foo;
|
||||||
|
/* llama
|
||||||
|
*/use foo
|
||||||
|
/* llama
|
||||||
|
*/ use foo;
|
||||||
|
/* llama
|
||||||
|
*/ use foo
|
||||||
|
/* llama
|
||||||
|
-- llama
|
||||||
|
*/use foo;
|
||||||
|
/* llama
|
||||||
|
-- llama
|
||||||
|
*/use foo
|
||||||
|
/* llama
|
||||||
|
-- llama
|
||||||
|
*/ use foo;
|
||||||
|
/* llama
|
||||||
|
-- llama
|
||||||
|
*/ use foo
|
||||||
|
/* llama
|
||||||
|
-- */use foo;
|
||||||
|
/* llama
|
||||||
|
-- */use foo
|
||||||
|
/* llama
|
||||||
|
-- */ use foo;
|
||||||
|
/* llama
|
||||||
|
-- */ use foo
|
||||||
|
/* llama */
|
||||||
|
|
||||||
|
use foo;use bar; use foo
|
||||||
|
-- this isn't a command, but that should just print an error message and help info.
|
||||||
|
\xyz select 1
|
||||||
|
\xyz;select 1;
|
||||||
|
delimiter $$
|
||||||
|
help$$
|
||||||
|
\d //
|
||||||
|
\h
|
||||||
|
help
|
||||||
|
status
|
||||||
|
\s
|
||||||
|
teleport
|
||||||
|
\t
|
||||||
|
use foo
|
||||||
|
\u mysql//
|
||||||
|
-- reset the delimiter to default ;
|
||||||
|
\d
|
||||||
|
teleport;\s;\h
|
||||||
|
use `bar`;drop database foo;drop database bar;drop database test_commands;quit
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
-- temporarily disable note warnings for test consistency
|
||||||
|
set session sql_notes = 0;
|
||||||
|
DROP DATABASE IF EXISTS test_ddl_db;
|
||||||
|
CREATE DATABASE test_ddl_db;
|
||||||
|
set session sql_notes = 1;
|
||||||
|
-- this should produce a note level warning because it does exist
|
||||||
|
CREATE DATABASE if not exists test_ddl_db;
|
||||||
|
-- we clear the warning by selecting the warning count
|
||||||
|
select @@warning_count;
|
||||||
|
USE test_ddl_db;
|
||||||
|
|
||||||
|
CREATE TABLE test_table (
|
||||||
|
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
value VARCHAR(50) NOT NULL,
|
||||||
|
note VARCHAR(100) DEFAULT NULL
|
||||||
|
) ENGINE=InnoDB;
|
||||||
|
|
||||||
|
CREATE VIEW test_view AS (
|
||||||
|
SELECT id, value
|
||||||
|
FROM test_table
|
||||||
|
);
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE FUNCTION test_func(x INT) RETURNS INT DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
RETURN x * 2;
|
||||||
|
END $$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE PROCEDURE test_proc()
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO test_table(value) VALUES ('from proc');
|
||||||
|
END $$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
CALL test_proc();
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE TRIGGER test_trigger_before_insert
|
||||||
|
BEFORE INSERT ON test_table
|
||||||
|
FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
SET NEW.value = UPPER(NEW.value);
|
||||||
|
END $$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
CALL test_proc();
|
||||||
|
|
||||||
|
CREATE EVENT test_event
|
||||||
|
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 HOUR
|
||||||
|
DO INSERT INTO test_table(value) VALUES ('from event');
|
||||||
|
|
||||||
|
set session sql_notes = 0;
|
||||||
|
-- roles are not database scoped, let's disable note warnings in case some other
|
||||||
|
-- test has already created the role or user.
|
||||||
|
CREATE ROLE IF NOT EXISTS test_role;
|
||||||
|
DROP ROLE test_role;
|
||||||
|
|
||||||
|
CREATE USER IF NOT EXISTS test_user@'localhost'
|
||||||
|
IDENTIFIED BY 'some password'
|
||||||
|
ACCOUNT LOCK;
|
||||||
|
ALTER USER test_user@'localhost' PASSWORD EXPIRE;
|
||||||
|
DROP USER test_user@'localhost';
|
||||||
|
set session sql_notes = 1;
|
||||||
|
|
||||||
|
ALTER DATABASE test_ddl_db CHARACTER SET utf8mb4;
|
||||||
|
ALTER TABLE test_table ADD COLUMN note2 VARCHAR(100) DEFAULT NULL;
|
||||||
|
ALTER EVENT test_event DISABLE;
|
||||||
|
ALTER FUNCTION test_func COMMENT 'Test function';
|
||||||
|
ALTER PROCEDURE test_proc COMMENT 'Test procedure';
|
||||||
|
|
||||||
|
RENAME TABLE test_table
|
||||||
|
TO test_table_renamed;
|
||||||
|
SELECT * from test_table_renamed;
|
||||||
|
|
||||||
|
RENAME
|
||||||
|
TABLE test_table_renamed
|
||||||
|
TO test_table;
|
||||||
|
|
||||||
|
SELECT * from test_table;
|
||||||
|
|
||||||
|
-- there is no "test_server", this should make an error.
|
||||||
|
ALTER SERVER test_server OPTIONS (HOST '127.0.0.1');
|
||||||
|
|
||||||
|
DROP TRIGGER test_trigger_before_insert;
|
||||||
|
|
||||||
|
TRUNCATE TABLE test_table ;
|
||||||
|
DROP TABLE test_table;
|
||||||
|
|
||||||
|
DROP DATABASE test_ddl_db
|
||||||
|
;
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
-- with ANSI_QUOTES double quotes are identifiers
|
||||||
|
SET SESSION sql_mode='ANSI_QUOTES';
|
||||||
|
SELECT "thing";
|
||||||
|
-- without ANSI_QUOTES double quotes are literals
|
||||||
|
SET SESSION sql_mode='';
|
||||||
|
SELECT "thing";
|
||||||
|
select 1, -1,1.24, -1.24, 'foo', null, 'null';
|
||||||
|
select
|
||||||
|
1 as 'unsigned int',
|
||||||
|
-1 as 'negative int',
|
||||||
|
1.24 as "float",
|
||||||
|
-1.24 as 'negative float',
|
||||||
|
'foo' as 'string',
|
||||||
|
null as 'actual NULL',
|
||||||
|
"null" as 'string null';
|
||||||
|
|
||||||
|
select 1 as 'multiple rows of literals'
|
||||||
|
union
|
||||||
|
select -1
|
||||||
|
union
|
||||||
|
select 1.24
|
||||||
|
union
|
||||||
|
select -1.24
|
||||||
|
union
|
||||||
|
select 'foo'
|
||||||
|
union
|
||||||
|
select null
|
||||||
|
union
|
||||||
|
select 'null';
|
||||||
|
|
||||||
|
select 'a
|
||||||
|
b
|
||||||
|
c' as 'multiline-string';
|
||||||
|
|
||||||
|
select 'a
|
||||||
|
;
|
||||||
|
b
|
||||||
|
;
|
||||||
|
c' as 'multiline with delimiters';
|
||||||
|
|
||||||
|
/* foo bar */
|
||||||
|
select 1;
|
||||||
|
/* foo bar */select /*
|
||||||
|
baz */ 42;
|
||||||
|
|
||||||
|
delimiter $$
|
||||||
|
select '$$1$$'$$
|
||||||
|
select '$$;$$';$$
|
||||||
|
delimiter ;
|
||||||
|
select 1;
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
// Teleport
|
||||||
|
// Copyright (C) 2025 Gravitational, Inc.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package repl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmp"
|
||||||
|
"io"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/gravitational/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// lexer scans REPL input lines distinguishing between comments, strings,
|
||||||
|
// whitespace, delimiter, and normal query text.
|
||||||
|
type lexer struct {
|
||||||
|
delim string
|
||||||
|
inComment bool
|
||||||
|
inStringToken token
|
||||||
|
queryBuf strings.Builder
|
||||||
|
|
||||||
|
lineReader strings.Reader
|
||||||
|
line string
|
||||||
|
}
|
||||||
|
|
||||||
|
// clientDefaultStatementDelimiter is the default client-side delimiter between
|
||||||
|
// statements. This is the only delimiter that is recognized server-side, but it
|
||||||
|
// is sometimes necessary for the client to switch delimiters while typing at a
|
||||||
|
// REPL.
|
||||||
|
const clientDefaultStatementDelimiter = ";"
|
||||||
|
|
||||||
|
// delimiter returns the current client-side delimiter.
|
||||||
|
func (l *lexer) delimiter() string {
|
||||||
|
return cmp.Or(l.delim, clientDefaultStatementDelimiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validDelimiterRegex only allows somewhat sane choices for delimiters.
|
||||||
|
var validDelimiterRegex = regexp.MustCompile(`^(;|[/]{2}|[$]{2})$`)
|
||||||
|
|
||||||
|
// setDelimiter sets the client-side statement delimiter.
|
||||||
|
func (l *lexer) setDelimiter(delimiter string) error {
|
||||||
|
delimiter = strings.TrimSpace(delimiter)
|
||||||
|
if delimiter == "" {
|
||||||
|
l.delim = ""
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if validDelimiterRegex.MatchString(delimiter) {
|
||||||
|
l.delim = delimiter
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return trace.BadParameter("DELIMITER %q does not match regex used for validation %q",
|
||||||
|
delimiter, validDelimiterRegex.String(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeString writes a string to the query buffer.
|
||||||
|
func (l *lexer) writeString(s string) {
|
||||||
|
l.queryBuf.WriteString(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeRune writes a single rune to the query buffer.
|
||||||
|
func (l *lexer) writeRune(r rune) {
|
||||||
|
l.queryBuf.WriteRune(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getQuery builds the buffered query string and empties the query buffer.
|
||||||
|
func (l *lexer) getQuery() string {
|
||||||
|
out := l.queryBuf.String()
|
||||||
|
l.queryBuf.Reset()
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// isInQuery returns true if the lexer is reading inside of a MySQL query.
|
||||||
|
func (l *lexer) isInQuery() bool {
|
||||||
|
return l.queryBuf.Len() > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// isInComment returns true if the lexer is reading inside of a MySQL comment.
|
||||||
|
func (l *lexer) isInComment() bool {
|
||||||
|
return l.inComment
|
||||||
|
}
|
||||||
|
|
||||||
|
// isInString returns true if the lexer is reading inside of a MySQL string.
|
||||||
|
func (l *lexer) isInString() bool {
|
||||||
|
return len(l.inStringToken.text) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// inStringKind returns the kind of MySQL string the lexer is currently reading
|
||||||
|
// inside of, i.e., the opening/closing rune: "'`.
|
||||||
|
func (l *lexer) inStringKind() string {
|
||||||
|
return l.inStringToken.text
|
||||||
|
}
|
||||||
|
|
||||||
|
// isMultilineQuery returns true if the lexer is reading a multline query.
|
||||||
|
func (l *lexer) isMultilineQuery() bool {
|
||||||
|
return l.isInQuery() && strings.Contains(l.queryBuf.String(), lineBreak)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setCloseComment marks the start of a comment.
|
||||||
|
func (l *lexer) setOpenComment() {
|
||||||
|
l.inComment = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// setCloseComment marks the end of a comment.
|
||||||
|
func (l *lexer) setCloseComment() {
|
||||||
|
l.inComment = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// setCloseString marks the start of a MySQL string.
|
||||||
|
func (l *lexer) setOpenString(tok token) {
|
||||||
|
l.inStringToken = tok
|
||||||
|
}
|
||||||
|
|
||||||
|
// setCloseString closes the currently open string state.
|
||||||
|
func (l *lexer) setCloseString() {
|
||||||
|
l.inStringToken = token{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setLine sets the input line to read from.
|
||||||
|
func (l *lexer) setLine(line string) {
|
||||||
|
l.lineReader.Reset(line)
|
||||||
|
l.line = line
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEmpty returns whether the line has been completely read.
|
||||||
|
func (l *lexer) isEmpty() bool {
|
||||||
|
return l.lineReader.Len() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// readRune reads a single rune and returns true if there was no error.
|
||||||
|
func (l *lexer) readRune() (rune, bool) {
|
||||||
|
r, _, err := l.lineReader.ReadRune()
|
||||||
|
ok := err == nil
|
||||||
|
return r, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// unreadRune unreads the last rune that was read.
|
||||||
|
func (l *lexer) unreadRune() {
|
||||||
|
l.lineReader.UnreadRune()
|
||||||
|
}
|
||||||
|
|
||||||
|
// peekString returns the remainder of the line without advancing the reader.
|
||||||
|
func (l *lexer) peekString() string {
|
||||||
|
offset := len(l.line) - l.lineReader.Len()
|
||||||
|
return l.line[offset:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// advanceByRune advances the line reader by the given rune if and only if it
|
||||||
|
// matches.
|
||||||
|
func (l *lexer) advanceByRune(want rune) bool {
|
||||||
|
if r, ok := l.readRune(); ok {
|
||||||
|
if r == want {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
l.unreadRune()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// advanceByString advances the line reader by the given string if and only if
|
||||||
|
// it is a prefix of the remaining string.
|
||||||
|
func (l *lexer) advanceByDelimiter() bool {
|
||||||
|
return l.advanceByString(l.delimiter())
|
||||||
|
}
|
||||||
|
|
||||||
|
// advanceByString advances the line reader by the given string if and only if
|
||||||
|
// it is a prefix of the remaining string.
|
||||||
|
func (l *lexer) advanceByString(s string) bool {
|
||||||
|
if strings.HasPrefix(l.peekString(), s) {
|
||||||
|
l.lineReader.Seek(int64(len(s)), io.SeekCurrent)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan scans a token from the line reader.
|
||||||
|
func (l *lexer) scan() token {
|
||||||
|
r, ok := l.readRune()
|
||||||
|
if !ok {
|
||||||
|
return token{kind: tokenEOF}
|
||||||
|
}
|
||||||
|
|
||||||
|
if unicode.IsSpace(r) {
|
||||||
|
l.unreadRune()
|
||||||
|
return token{kind: tokenSpace, text: l.readMatching(unicode.IsSpace)}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r {
|
||||||
|
case '\\':
|
||||||
|
return token{kind: tokenBackslash, text: `\`}
|
||||||
|
case '\'':
|
||||||
|
return token{kind: tokenSingleQuote, text: "'"}
|
||||||
|
case '"':
|
||||||
|
return token{kind: tokenDoubleQuote, text: `"`}
|
||||||
|
case '`':
|
||||||
|
return token{kind: tokenBacktick, text: "`"}
|
||||||
|
case '-':
|
||||||
|
if l.advanceByRune('-') {
|
||||||
|
return token{kind: tokenSingleComment, text: "--"}
|
||||||
|
}
|
||||||
|
case '#':
|
||||||
|
return token{kind: tokenSingleComment, text: "#"}
|
||||||
|
case '/':
|
||||||
|
if l.advanceByRune('*') {
|
||||||
|
return token{kind: tokenOpenComment, text: "/*"}
|
||||||
|
}
|
||||||
|
case '*':
|
||||||
|
if l.advanceByRune('/') {
|
||||||
|
return token{kind: tokenCloseComment, text: "*/"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token{kind: tokenText, text: string(r)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readMatching reads runes matching matchFn into the query buffer.
|
||||||
|
func (l *lexer) readMatching(matchFn func(rune) bool) string {
|
||||||
|
var runes []rune
|
||||||
|
for {
|
||||||
|
r, ok := l.readRune()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !matchFn(r) {
|
||||||
|
l.unreadRune()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
runes = append(runes, r)
|
||||||
|
}
|
||||||
|
return string(runes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// acceptEscapedRune reads the next character following an escape backslash and
|
||||||
|
// saves it to the query buffer.
|
||||||
|
func (l *lexer) acceptEscapedRune() {
|
||||||
|
if r, ok := l.readRune(); ok {
|
||||||
|
l.writeRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// acceptString reads a MySQL string bounded by single-quotes, double-quotes, or
|
||||||
|
// backticks and saves it to the query buffer.
|
||||||
|
func (l *lexer) acceptString() {
|
||||||
|
for !l.isEmpty() {
|
||||||
|
tok := l.scan()
|
||||||
|
l.writeString(tok.text)
|
||||||
|
switch tok.kind {
|
||||||
|
case tokenBackslash:
|
||||||
|
l.acceptEscapedRune()
|
||||||
|
case l.inStringToken.kind:
|
||||||
|
// this must be a closing quote
|
||||||
|
l.setCloseString()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// discardRemaining discards the remainder of the line.
|
||||||
|
func (l *lexer) discardRemaining() {
|
||||||
|
l.lineReader.Reset("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// discardMatching discards runes matching matchFn.
|
||||||
|
func (l *lexer) discardMatching(matchFn func(rune) bool) {
|
||||||
|
for {
|
||||||
|
r, ok := l.readRune()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !matchFn(r) {
|
||||||
|
l.unreadRune()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// discardWhitespace discards leading whitespace.
|
||||||
|
func (l *lexer) discardWhitespace() {
|
||||||
|
l.discardMatching(unicode.IsSpace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// discardSingleLineComment discards the rest of the line following a comment.
|
||||||
|
func (l *lexer) discardSingleLineComment() {
|
||||||
|
l.discardRemaining()
|
||||||
|
}
|
||||||
|
|
||||||
|
// discardMultiLineComment discards everything inside of a multiline comment.
|
||||||
|
func (l *lexer) discardMultiLineComment() {
|
||||||
|
for !l.isEmpty() {
|
||||||
|
l.discardMatching(func(r rune) bool {
|
||||||
|
// shortcut: scan until we get a potential end of comment token
|
||||||
|
// note that escapes don't work in comments, so we don't check for them
|
||||||
|
return r != '*'
|
||||||
|
})
|
||||||
|
if tok := l.scan(); tok.kind == tokenCloseComment {
|
||||||
|
l.setCloseComment()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
// Teleport
|
||||||
|
// Copyright (C) 2025 Gravitational, Inc.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package repl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDelimiter(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var lex lexer
|
||||||
|
require.Equal(t, clientDefaultStatementDelimiter, lex.delimiter())
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
args string
|
||||||
|
wantDelimiter string
|
||||||
|
wantErrContains string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "passing no arg is ok",
|
||||||
|
wantDelimiter: ";",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "passing the default delimiter is ok",
|
||||||
|
args: ";",
|
||||||
|
wantDelimiter: ";",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "passing two dollars is ok",
|
||||||
|
args: "$$",
|
||||||
|
wantDelimiter: "$$",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "passing two slashes is ok",
|
||||||
|
args: "//",
|
||||||
|
wantDelimiter: "//",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "passing too many delimiter runes is invalid",
|
||||||
|
args: ";;;;;;",
|
||||||
|
wantErrContains: `DELIMITER ";;;;;;" does not match regex used for validation "^(;|[/]{2}|[$]{2})$"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "passing rejected runes is invalid",
|
||||||
|
args: "x",
|
||||||
|
wantErrContains: `DELIMITER "x" does not match regex used for validation "^(;|[/]{2}|[$]{2})$"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "passing rejected runes is invalid",
|
||||||
|
args: "\\",
|
||||||
|
wantErrContains: `DELIMITER "\\" does not match regex used for validation "^(;|[/]{2}|[$]{2})$"`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
var lex lexer
|
||||||
|
err := lex.setDelimiter(test.args)
|
||||||
|
if test.wantErrContains != "" {
|
||||||
|
require.ErrorContains(t, err, test.wantErrContains)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, test.wantDelimiter, lex.delimiter())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLexer_setLine(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var l lexer
|
||||||
|
l.setLine("SELECT * FROM t;")
|
||||||
|
require.Equal(t, "SELECT * FROM t;", l.peekString())
|
||||||
|
require.False(t, l.isEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLexer_advanceByRune(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var l lexer
|
||||||
|
l.setLine("abc")
|
||||||
|
require.True(t, l.advanceByRune('a'), "should advance on matching rune")
|
||||||
|
require.Equal(t, "bc", l.peekString())
|
||||||
|
require.False(t, l.advanceByRune('z'), "should not advance on non-matching rune")
|
||||||
|
require.Equal(t, "bc", l.peekString())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLexer_advanceByString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var l lexer
|
||||||
|
l.setLine("foobar")
|
||||||
|
require.True(t, l.advanceByString("foo"), "should advance by matching prefix")
|
||||||
|
require.Equal(t, "bar", l.peekString())
|
||||||
|
require.False(t, l.advanceByString("baz"), "should not advance if prefix doesn't match")
|
||||||
|
require.Equal(t, "bar", l.peekString())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLexer_advanceByDelimiter(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var l lexer
|
||||||
|
l.setDelimiter(";")
|
||||||
|
l.setLine("; SELECT")
|
||||||
|
require.True(t, l.advanceByDelimiter())
|
||||||
|
require.Equal(t, " SELECT", l.peekString())
|
||||||
|
require.False(t, l.advanceByDelimiter())
|
||||||
|
require.Equal(t, " SELECT", l.peekString())
|
||||||
|
l.setLine("//")
|
||||||
|
l.setDelimiter("//")
|
||||||
|
require.True(t, l.advanceByDelimiter())
|
||||||
|
require.Empty(t, l.peekString())
|
||||||
|
require.False(t, l.advanceByDelimiter())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLexer_getQuery(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var l lexer
|
||||||
|
l.writeString("hello")
|
||||||
|
l.writeRune(' ')
|
||||||
|
l.writeString("world!")
|
||||||
|
require.Equal(t, "hello world!", l.getQuery())
|
||||||
|
require.Empty(t, l.getQuery(), "getQuery should reset buffer")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLexer_unread(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var l lexer
|
||||||
|
l.setLine("xy")
|
||||||
|
r, ok := l.readRune()
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, 'x', r)
|
||||||
|
l.unreadRune()
|
||||||
|
|
||||||
|
r, ok = l.readRune()
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, 'x', r)
|
||||||
|
|
||||||
|
r, ok = l.readRune()
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, 'y', r)
|
||||||
|
|
||||||
|
_, ok = l.readRune()
|
||||||
|
require.False(t, ok, "should be false at end of input")
|
||||||
|
l.unreadRune()
|
||||||
|
_, ok = l.readRune()
|
||||||
|
require.False(t, ok, "cant unread eof")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLexer_readMatching(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var l lexer
|
||||||
|
l.setLine("123abcXYZ")
|
||||||
|
require.Equal(t, "123", l.readMatching(unicode.IsDigit))
|
||||||
|
require.Equal(t, "abc", l.readMatching(unicode.IsLower))
|
||||||
|
require.Equal(t, "XYZ", l.readMatching(unicode.IsUpper))
|
||||||
|
require.Empty(t, l.readMatching(unicode.IsDigit))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLexer_acceptEscapedRune(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var l lexer
|
||||||
|
l.setLine(`\xyz`)
|
||||||
|
tok := l.scan()
|
||||||
|
require.Equal(t, tokenBackslash, tok.kind)
|
||||||
|
l.writeString(tok.text)
|
||||||
|
l.acceptEscapedRune()
|
||||||
|
require.Equal(t, "yz", l.peekString())
|
||||||
|
require.Equal(t, `\x`, l.getQuery())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLexer_acceptString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var l lexer
|
||||||
|
l.setLine(`"inside string "; SELECT`)
|
||||||
|
tok := l.scan()
|
||||||
|
require.Equal(t, tokenDoubleQuote, tok.kind)
|
||||||
|
l.writeString(tok.text)
|
||||||
|
l.setOpenString(tok)
|
||||||
|
l.acceptString()
|
||||||
|
require.Equal(t, "; SELECT", l.peekString())
|
||||||
|
require.Equal(t, `"inside string "`, l.getQuery())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLexer_discardRemaining(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var l lexer
|
||||||
|
l.setLine("DROP TABLE test;")
|
||||||
|
l.discardRemaining()
|
||||||
|
require.True(t, l.isEmpty())
|
||||||
|
require.Empty(t, l.peekString())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLexer_discardMatching(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
matchFn func(rune) bool
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "discard digits",
|
||||||
|
input: "123abc",
|
||||||
|
matchFn: unicode.IsDigit,
|
||||||
|
expected: "abc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "discard lowercase",
|
||||||
|
input: "abcDEF",
|
||||||
|
matchFn: unicode.IsLower,
|
||||||
|
expected: "DEF",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nothing to discard",
|
||||||
|
input: "ABC123",
|
||||||
|
matchFn: unicode.IsLower,
|
||||||
|
expected: "ABC123",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
var l lexer
|
||||||
|
l.setLine(tc.input)
|
||||||
|
l.discardMatching(tc.matchFn)
|
||||||
|
require.Equal(t, tc.expected, l.peekString())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLexer_discardWhitespace(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "leading whitespace only",
|
||||||
|
input: " SELECT",
|
||||||
|
expected: "SELECT",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no whitespace",
|
||||||
|
input: "SELECT",
|
||||||
|
expected: "SELECT",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "whitespace and tabs",
|
||||||
|
input: "\t \n abc",
|
||||||
|
expected: "abc",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
var l lexer
|
||||||
|
l.setLine(tc.input)
|
||||||
|
l.discardWhitespace()
|
||||||
|
require.Equal(t, tc.expected, l.peekString())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLexer_discardSingleLineComment(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var l lexer
|
||||||
|
l.setLine("-- This is a comment")
|
||||||
|
l.discardSingleLineComment()
|
||||||
|
require.True(t, l.isEmpty())
|
||||||
|
require.Empty(t, l.peekString())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLexer_discardMultiLineComment(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var l lexer
|
||||||
|
l.setLine("inside comment */ SELECT 1;")
|
||||||
|
l.setOpenComment()
|
||||||
|
l.discardMultiLineComment()
|
||||||
|
require.False(t, l.isInComment(), "should have exited comment mode")
|
||||||
|
remaining := l.peekString()
|
||||||
|
require.Equal(t, " SELECT 1;", remaining)
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
// Teleport
|
||||||
|
// Copyright (C) 2025 Gravitational, Inc.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package repl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"iter"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gravitational/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newParser() (*parser, error) {
|
||||||
|
commands, err := newCommands()
|
||||||
|
if err != nil {
|
||||||
|
return nil, trace.Wrap(err)
|
||||||
|
}
|
||||||
|
return &parser{commands: commands}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parser parses commands and queries given one or more lines of input.
|
||||||
|
type parser struct {
|
||||||
|
commands *commandManager
|
||||||
|
lex lexer
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse returns an iterator over the input line yielding evaluators for each
|
||||||
|
// command or query found.
|
||||||
|
func (p *parser) parse(line string) iter.Seq[evaluator] {
|
||||||
|
p.lex.setLine(line)
|
||||||
|
return func(yield func(evaluator) bool) {
|
||||||
|
defer func() {
|
||||||
|
if p.lex.isEmpty() && p.lex.isInQuery() {
|
||||||
|
// must be a multiline query, so insert a linebreak in the query buf
|
||||||
|
p.lex.writeString(lineBreak)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for !p.lex.isEmpty() {
|
||||||
|
if p.lex.isInComment() {
|
||||||
|
// dont preserve comments
|
||||||
|
p.lex.discardMultiLineComment()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.lex.isInQuery() {
|
||||||
|
// not in a comment or query: is the rest of the line a command?
|
||||||
|
if cmd := p.tryParseCommand(); cmd != nil {
|
||||||
|
if !yield(cmd) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.lex.isInString() {
|
||||||
|
p.lex.acceptString()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// not in comment nor string: do we have a command/query delimiter?
|
||||||
|
if cmdOrQuery := p.tryParseQuery(); cmdOrQuery != nil {
|
||||||
|
if !yield(cmdOrQuery) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// not in comment nor string and no delimiter found: scan text
|
||||||
|
tok := p.lex.scan()
|
||||||
|
switch tok.kind {
|
||||||
|
case tokenSingleComment:
|
||||||
|
// dont preserve comments
|
||||||
|
p.lex.discardSingleLineComment()
|
||||||
|
case tokenOpenComment:
|
||||||
|
p.lex.setOpenComment()
|
||||||
|
p.lex.discardMultiLineComment()
|
||||||
|
case tokenBackslash:
|
||||||
|
p.lex.writeString(tok.text)
|
||||||
|
p.lex.acceptEscapedRune()
|
||||||
|
case tokenSingleQuote, tokenDoubleQuote, tokenBacktick:
|
||||||
|
p.lex.writeString(tok.text)
|
||||||
|
p.lex.setOpenString(tok)
|
||||||
|
p.lex.acceptString()
|
||||||
|
default:
|
||||||
|
p.lex.writeString(tok.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryParseCommand tries to parse a command and its args on the current line.
|
||||||
|
// If the line contains the current delimiter and the command is not the special
|
||||||
|
// DELIMITER command, then give up and let the query parser handle it.
|
||||||
|
func (p *parser) tryParseCommand() evaluator {
|
||||||
|
p.lex.discardWhitespace()
|
||||||
|
line := p.lex.peekString()
|
||||||
|
cmd, args, err := p.commands.findCommand(line)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
if strings.Contains(line, p.lex.delimiter()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
p.lex.discardRemaining()
|
||||||
|
return errorEvaluator{err: err}
|
||||||
|
case cmd != nil:
|
||||||
|
if cmd.name != "delimiter" && strings.Contains(line, p.lex.delimiter()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
p.lex.discardRemaining()
|
||||||
|
return commandEvaluator{cmd: cmd, args: args}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryParseQuery looks for a delimiter in the input. If it finds one and the
|
||||||
|
// query buffer is all from one line, then it first looks for a command in the
|
||||||
|
// query buffer. If there is no command or the query buffer is multiline, then
|
||||||
|
// it returns a query evaluator.
|
||||||
|
func (p *parser) tryParseQuery() evaluator {
|
||||||
|
if p.lex.advanceByDelimiter() {
|
||||||
|
query := p.lex.getQuery()
|
||||||
|
if !p.lex.isMultilineQuery() || strings.HasPrefix(query, p.commands.shortcutPrefix) {
|
||||||
|
cmd, args, err := p.commands.findCommand(query)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
// don't discard the rest of the line, since we found the
|
||||||
|
// command in the query buffer, unlike the command parser which
|
||||||
|
// looks ahead in the current line for a command
|
||||||
|
return errorEvaluator{err: err}
|
||||||
|
case cmd != nil:
|
||||||
|
return commandEvaluator{cmd: cmd, args: args}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return queryEvaluator(query)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type evaluator interface {
|
||||||
|
eval(r *REPL) (reply string, exit bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// queryEvaluator executes a single query and formats the response as a reply
|
||||||
|
// to the REPL client.
|
||||||
|
type queryEvaluator string
|
||||||
|
|
||||||
|
func (query queryEvaluator) eval(r *REPL) (string, bool) {
|
||||||
|
start := time.Now()
|
||||||
|
result, err := r.myConn.Execute(string(query))
|
||||||
|
if err != nil {
|
||||||
|
return errorReplyPrefix + err.Error(), false
|
||||||
|
}
|
||||||
|
defer result.Close()
|
||||||
|
if r.disableQueryTimings {
|
||||||
|
return formatResult(result, nil), false
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
return formatResult(result, &elapsed), false
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandEvaluator executes a command.
|
||||||
|
type commandEvaluator struct {
|
||||||
|
cmd *command
|
||||||
|
args string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e commandEvaluator) eval(r *REPL) (string, bool) {
|
||||||
|
return e.cmd.execFunc(r, e.args)
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorEvaluator struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorEvaluator formats an error as a reply to the REPL client.
|
||||||
|
func (e errorEvaluator) eval(_ *REPL) (string, bool) {
|
||||||
|
return e.err.Error(), false
|
||||||
|
}
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
// Teleport
|
||||||
|
// Copyright (C) 2025 Gravitational, Inc.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package repl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmp"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"iter"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-mysql-org/go-mysql/client"
|
||||||
|
"github.com/go-mysql-org/go-mysql/mysql"
|
||||||
|
"github.com/gravitational/trace"
|
||||||
|
"golang.org/x/term"
|
||||||
|
|
||||||
|
"github.com/gravitational/teleport"
|
||||||
|
clientproto "github.com/gravitational/teleport/api/client/proto"
|
||||||
|
"github.com/gravitational/teleport/lib/asciitable"
|
||||||
|
dbrepl "github.com/gravitational/teleport/lib/client/db/repl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// REPL implements [dbrepl.REPLInstance] for MySQL.
|
||||||
|
type REPL struct {
|
||||||
|
client io.ReadWriteCloser
|
||||||
|
serverConn net.Conn
|
||||||
|
route clientproto.RouteToDatabase
|
||||||
|
term *term.Terminal
|
||||||
|
parser *parser
|
||||||
|
myConn mysqlConn
|
||||||
|
|
||||||
|
// teleportVersion is used in golden tests to fake the current Teleport
|
||||||
|
// version and prevent test failures when the real version is incremented.
|
||||||
|
teleportVersion string
|
||||||
|
|
||||||
|
// testPassword is normally blank, only used in tests where the REPL connects
|
||||||
|
// directly to a MySQL instance without Teleport proxying.
|
||||||
|
testPassword string
|
||||||
|
// disableQueryTimings is used in golden tests to disable query timings for
|
||||||
|
// test consistency.
|
||||||
|
disableQueryTimings bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type mysqlConn interface {
|
||||||
|
Execute(command string, args ...any) (*mysql.Result, error)
|
||||||
|
UseDB(dbName string) error
|
||||||
|
GetServerVersion() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New implements [dbrepl.REPLNewFunc].
|
||||||
|
func New(_ context.Context, cfg *dbrepl.NewREPLConfig) (dbrepl.REPLInstance, error) {
|
||||||
|
parser, err := newParser()
|
||||||
|
if err != nil {
|
||||||
|
return nil, trace.Wrap(err)
|
||||||
|
}
|
||||||
|
return &REPL{
|
||||||
|
client: cfg.Client,
|
||||||
|
serverConn: cfg.ServerConn,
|
||||||
|
route: cfg.Route,
|
||||||
|
term: term.NewTerminal(cfg.Client, ""),
|
||||||
|
parser: parser,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run starts and run the PostgreSQL REPL session. The provided context is used
|
||||||
|
// to interrupt the execution and clean up resources.
|
||||||
|
func (r *REPL) Run(ctx context.Context) error {
|
||||||
|
dialer := func(context.Context, string, string) (net.Conn, error) {
|
||||||
|
return r.serverConn, nil
|
||||||
|
}
|
||||||
|
const hostnamePlaceholder = "repl"
|
||||||
|
myConn, err := client.ConnectWithDialer(ctx, "tcp", hostnamePlaceholder,
|
||||||
|
r.route.GetUsername(),
|
||||||
|
r.testPassword,
|
||||||
|
r.route.GetDatabase(),
|
||||||
|
dialer,
|
||||||
|
withClientCapabilities(
|
||||||
|
mysql.CLIENT_MULTI_RESULTS,
|
||||||
|
mysql.CLIENT_MULTI_STATEMENTS,
|
||||||
|
),
|
||||||
|
func(c *client.Conn) error {
|
||||||
|
c.SetAttributes(map[string]string{clientNameParamName: clientNameParamValue})
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return trace.ConnectionProblem(err, "Unable to connect to database: %v", err)
|
||||||
|
}
|
||||||
|
r.myConn = myConn
|
||||||
|
defer func() {
|
||||||
|
_ = myConn.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// term.Terminal blocks reads/writes without respecting the context. The
|
||||||
|
// only thing that unblocks it is closing the underlaying connection (in
|
||||||
|
// our case r.client). On this goroutine we only watch for context
|
||||||
|
// cancelation and close the connection. This will unblocks all terminal
|
||||||
|
// reads/writes.
|
||||||
|
stop := context.AfterFunc(ctx, func() {
|
||||||
|
_ = r.client.Close()
|
||||||
|
})
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
if err := r.presentBanner(); err != nil {
|
||||||
|
return trace.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
line, err := r.readLine()
|
||||||
|
if err != nil {
|
||||||
|
return trace.Wrap(rewriteTermError(ctx, err))
|
||||||
|
}
|
||||||
|
for reply, exit := range r.eval(line) {
|
||||||
|
if exit {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := r.print(reply); err != nil {
|
||||||
|
return trace.Wrap(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withClientCapabilities(caps ...uint32) client.Option {
|
||||||
|
return func(conn *client.Conn) error {
|
||||||
|
for _, cap := range caps {
|
||||||
|
conn.SetCapability(cap)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *REPL) presentBanner() error {
|
||||||
|
_, err := fmt.Fprintf(
|
||||||
|
r.term,
|
||||||
|
`Teleport MySQL interactive shell (v%s)
|
||||||
|
Connected to instance %q as user %q.
|
||||||
|
Type 'help' or '\h' for help.
|
||||||
|
|
||||||
|
`,
|
||||||
|
cmp.Or(r.teleportVersion, teleport.Version),
|
||||||
|
r.route.GetServiceName(),
|
||||||
|
r.route.GetUsername())
|
||||||
|
return trace.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *REPL) readLine() (string, error) {
|
||||||
|
r.term.SetPrompt(r.getPrompt())
|
||||||
|
return r.term.ReadLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *REPL) getPrompt() string {
|
||||||
|
dbName := formatDatabaseName(r.route.Database)
|
||||||
|
var suffix string
|
||||||
|
switch {
|
||||||
|
case r.parser.lex.isInComment():
|
||||||
|
suffix = "/*"
|
||||||
|
case r.parser.lex.isInString():
|
||||||
|
suffix = r.parser.lex.inStringKind()
|
||||||
|
case r.parser.lex.isInQuery():
|
||||||
|
suffix = "-"
|
||||||
|
default:
|
||||||
|
suffix = dbName
|
||||||
|
}
|
||||||
|
padding := len(dbName)
|
||||||
|
// pad the suffix with leading spaces until it is len(dbName), for example:
|
||||||
|
// default: "mysql>"
|
||||||
|
// comment: " */>"
|
||||||
|
// string: " '>"
|
||||||
|
// query: " ->"
|
||||||
|
return fmt.Sprintf("%*s> ", padding, suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *REPL) eval(line string) iter.Seq2[string, bool] {
|
||||||
|
return func(yield func(string, bool) bool) {
|
||||||
|
for evaluator := range r.parser.parse(line) {
|
||||||
|
if !yield(evaluator.eval(r)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// print writes a reply to the REPL client.
|
||||||
|
func (r *REPL) print(reply string) error {
|
||||||
|
reply = strings.TrimSpace(reply)
|
||||||
|
if reply == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err := r.term.Write([]byte(lineBreak + reply + lineBreak + lineBreak))
|
||||||
|
return trace.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewriteTermError changes the term.Terminal error to match caller expectations.
|
||||||
|
func rewriteTermError(ctx context.Context, err error) error {
|
||||||
|
// When context is canceled it will immediately lead read/write errors due
|
||||||
|
// to the closed connection. For this cases we return the context error.
|
||||||
|
if ctx.Err() != nil && (errors.Is(err, io.ErrClosedPipe) || errors.Is(err, net.ErrClosed)) {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatResult formats a query result.
|
||||||
|
func formatResult(result *mysql.Result, elapsed *time.Duration) string {
|
||||||
|
var out string
|
||||||
|
if result.Resultset != nil {
|
||||||
|
out = formatResultset(result.Resultset)
|
||||||
|
} else {
|
||||||
|
out = fmt.Sprintf("%v warnings, %v rows affected", result.Warnings, result.AffectedRows)
|
||||||
|
}
|
||||||
|
if elapsed != nil {
|
||||||
|
return fmt.Sprintf("%s (%.2f sec)", out, elapsed.Seconds())
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatResultset formats a query result that contains rows.
|
||||||
|
func formatResultset(set *mysql.Resultset) string {
|
||||||
|
if set == nil || len(set.Values) == 0 {
|
||||||
|
return "Empty set"
|
||||||
|
}
|
||||||
|
var sb strings.Builder
|
||||||
|
columns := make([]string, 0, len(set.Fields))
|
||||||
|
for _, f := range set.Fields {
|
||||||
|
columns = append(columns, string(f.Name))
|
||||||
|
}
|
||||||
|
table := asciitable.MakeTable(columns)
|
||||||
|
for _, rowValues := range set.Values {
|
||||||
|
row := make([]string, 0, len(rowValues))
|
||||||
|
for _, val := range rowValues {
|
||||||
|
row = append(row, val.String())
|
||||||
|
}
|
||||||
|
table.AddRow(row)
|
||||||
|
}
|
||||||
|
table.WriteTo(&sb)
|
||||||
|
sb.WriteString(lineBreak)
|
||||||
|
|
||||||
|
if numRows := len(set.Values); numRows == 1 {
|
||||||
|
sb.WriteString("1 row in set")
|
||||||
|
} else {
|
||||||
|
// pluralize 0 or multiple rows
|
||||||
|
fmt.Fprintf(&sb, "%d rows in set", numRows)
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// lineBreak represents a line break on the REPL.
|
||||||
|
lineBreak = "\n"
|
||||||
|
// errorReplyPrefix is the prefix presented when there is a execution error.
|
||||||
|
errorReplyPrefix = "ERR "
|
||||||
|
// clientNameParamName defines the application name parameter name.
|
||||||
|
//
|
||||||
|
// https://dev.mysql.com/doc/refman/8.0/en/performance-schema-connection-attribute-tables.html
|
||||||
|
clientNameParamName = "_client_name"
|
||||||
|
// clientNameParamValue defines the application name parameter value.
|
||||||
|
clientNameParamValue = "teleport-repl"
|
||||||
|
)
|
||||||
@@ -0,0 +1,455 @@
|
|||||||
|
// Teleport
|
||||||
|
// Copyright (C) 2025 Gravitational, Inc.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package repl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"cmp"
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
_ "embed"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-mysql-org/go-mysql/mysql"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/testcontainers/testcontainers-go"
|
||||||
|
mysqlcontainer "github.com/testcontainers/testcontainers-go/modules/mysql"
|
||||||
|
"golang.org/x/term"
|
||||||
|
|
||||||
|
clientproto "github.com/gravitational/teleport/api/client/proto"
|
||||||
|
dbrepl "github.com/gravitational/teleport/lib/client/db/repl"
|
||||||
|
"github.com/gravitational/teleport/lib/defaults"
|
||||||
|
"github.com/gravitational/teleport/lib/utils/testutils/golden"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testSrv *testServer
|
||||||
|
var testSrvMu sync.Mutex
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
code := m.Run()
|
||||||
|
|
||||||
|
// The "Ryuk" container will cleanup everything anyhow, but only after 10s
|
||||||
|
// without any client connections.
|
||||||
|
// We can speed up container cleanup after a -count=N run, e.g., a flaky
|
||||||
|
// test detector run, by terminating them immediately after tests have run.
|
||||||
|
// https://github.com/testcontainers/moby-ryuk
|
||||||
|
testSrvMu.Lock()
|
||||||
|
defer testSrvMu.Unlock()
|
||||||
|
// if we don't clean up after 10 seconds, RYUK will get them anyway.
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// ignore errors, Ryuk or worst case VM termination in CI will deal with it
|
||||||
|
_ = testSrv.container.Terminate(ctx)
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestREPL(t *testing.T) {
|
||||||
|
testSrv := newTestServer(t)
|
||||||
|
route := clientproto.RouteToDatabase{
|
||||||
|
ServiceName: "mysql-test-container",
|
||||||
|
Protocol: defaults.ProtocolMySQL,
|
||||||
|
Username: testSrv.username,
|
||||||
|
// Pass a blank database name, as if a user who didn't specify one.
|
||||||
|
// The REPL should handle this case gracefully.
|
||||||
|
Database: "",
|
||||||
|
}
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ddl",
|
||||||
|
input: testDDL,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all data types",
|
||||||
|
input: testDataTypes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "literals",
|
||||||
|
input: testLiterals,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "commands",
|
||||||
|
input: testCommands,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
// the term only recognizes line breaks with a carriage-return
|
||||||
|
const crlf = "\r\n"
|
||||||
|
input := strings.ReplaceAll(test.input, "\n", crlf) + crlf
|
||||||
|
recorder := newRecordingClient(t, io.NopCloser(strings.NewReader(input)))
|
||||||
|
testREPL, err := New(t.Context(), &dbrepl.NewREPLConfig{
|
||||||
|
Client: recorder,
|
||||||
|
ServerConn: testSrv.connectTCP(t, 10*time.Second),
|
||||||
|
Route: route,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
testREPL.(*REPL).testPassword = testSrv.password
|
||||||
|
testREPL.(*REPL).disableQueryTimings = true
|
||||||
|
err = testREPL.Run(t.Context())
|
||||||
|
require.NoError(t, err)
|
||||||
|
goldenName := strings.ReplaceAll(test.name, " ", "-")
|
||||||
|
if golden.ShouldSet() {
|
||||||
|
golden.SetNamed(t, goldenName, recorder.buf.Bytes())
|
||||||
|
}
|
||||||
|
require.Equal(t, string(golden.GetNamed(t, goldenName)), recorder.buf.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed fixtures/ddl.sql
|
||||||
|
testDDL string
|
||||||
|
//go:embed fixtures/all-data-types.sql
|
||||||
|
testDataTypes string
|
||||||
|
//go:embed fixtures/literals.sql
|
||||||
|
testLiterals string
|
||||||
|
//go:embed fixtures/commands.sql
|
||||||
|
testCommands string
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetPrompt(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
inQuery bool
|
||||||
|
inComment bool
|
||||||
|
inStringKind string
|
||||||
|
database string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "no default database",
|
||||||
|
want: "(none)> ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "default",
|
||||||
|
database: "llama",
|
||||||
|
want: "llama> ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "in a query",
|
||||||
|
inQuery: true,
|
||||||
|
database: "llama",
|
||||||
|
want: " -> ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "in a comment",
|
||||||
|
inComment: true,
|
||||||
|
database: "llama",
|
||||||
|
want: " /*> ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "in a quote string",
|
||||||
|
inStringKind: "'",
|
||||||
|
database: "llama",
|
||||||
|
want: " '> ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "in a double quote string",
|
||||||
|
inStringKind: `"`,
|
||||||
|
database: "llama",
|
||||||
|
want: ` "> `,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "in a backtick string",
|
||||||
|
inStringKind: "`",
|
||||||
|
database: "llama",
|
||||||
|
want: " `> ",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
r := &REPL{
|
||||||
|
route: clientproto.RouteToDatabase{
|
||||||
|
Database: test.database,
|
||||||
|
},
|
||||||
|
parser: &parser{
|
||||||
|
lex: lexer{
|
||||||
|
inComment: test.inComment,
|
||||||
|
inStringToken: token{text: test.inStringKind},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
teleportVersion: "v19.0.0-dev",
|
||||||
|
}
|
||||||
|
if test.inQuery {
|
||||||
|
r.parser.lex.queryBuf.WriteString("select")
|
||||||
|
}
|
||||||
|
require.Equal(t, test.want, r.getPrompt())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzEval(f *testing.F) {
|
||||||
|
parser, err := newParser()
|
||||||
|
require.NoError(f, err)
|
||||||
|
|
||||||
|
for _, cmd := range parser.commands.byName {
|
||||||
|
f.Add(cmd.name)
|
||||||
|
f.Add(parser.commands.shortcutPrefix + string(cmd.shortcut))
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Add("") // eof
|
||||||
|
f.Add(" ") // space
|
||||||
|
|
||||||
|
// MySQL string literal quotes
|
||||||
|
f.Add("'")
|
||||||
|
f.Add(`"`)
|
||||||
|
f.Add("`")
|
||||||
|
|
||||||
|
// single line comments
|
||||||
|
f.Add("--")
|
||||||
|
f.Add("#")
|
||||||
|
|
||||||
|
// multiline comments
|
||||||
|
f.Add("/*")
|
||||||
|
f.Add("*/")
|
||||||
|
f.Add("*")
|
||||||
|
|
||||||
|
// valid delimiters
|
||||||
|
f.Add(";")
|
||||||
|
f.Add("$")
|
||||||
|
f.Add("/")
|
||||||
|
|
||||||
|
// some statement syntax
|
||||||
|
f.Add("select")
|
||||||
|
f.Add("create")
|
||||||
|
f.Add("drop")
|
||||||
|
f.Add("replace")
|
||||||
|
f.Add("alter")
|
||||||
|
f.Add("(")
|
||||||
|
f.Add(")")
|
||||||
|
|
||||||
|
// multiline
|
||||||
|
f.Add("\n")
|
||||||
|
|
||||||
|
f.Fuzz(func(t *testing.T, line string) {
|
||||||
|
repl := &REPL{
|
||||||
|
myConn: &fakeMySQLConn{
|
||||||
|
exec: func(command string, args ...any) (*mysql.Result, error) {
|
||||||
|
return nil, io.EOF
|
||||||
|
},
|
||||||
|
},
|
||||||
|
parser: parser,
|
||||||
|
}
|
||||||
|
require.NotPanics(t, func() {
|
||||||
|
for line := range strings.SplitSeq(line, "\n") {
|
||||||
|
for _, exit := range repl.eval(line) {
|
||||||
|
if exit {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInteractively(t *testing.T) {
|
||||||
|
// You can get an interactive REPL to experiment with like this:
|
||||||
|
// $ export MYSQL_TEST_REPL_INTERACTIVELY=1
|
||||||
|
// $ go test -c -o ./test ./lib/client/db/mysql/repl
|
||||||
|
// $ ./test -test.v -test.count=1 -test.run TestInteractively
|
||||||
|
if os.Getenv("MYSQL_TEST_REPL_INTERACTIVELY") == "" {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
testSrv := newTestServer(t)
|
||||||
|
route := clientproto.RouteToDatabase{
|
||||||
|
ServiceName: "mysql-test-container",
|
||||||
|
Protocol: defaults.ProtocolMySQL,
|
||||||
|
Username: testSrv.username,
|
||||||
|
Database: testSrv.database,
|
||||||
|
Roles: []string{},
|
||||||
|
}
|
||||||
|
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer term.Restore(int(os.Stdin.Fd()), oldState)
|
||||||
|
recorder := newRecordingClient(t, os.Stdin, os.Stdout)
|
||||||
|
repl, err := New(t.Context(), &dbrepl.NewREPLConfig{
|
||||||
|
Client: recorder,
|
||||||
|
ServerConn: testSrv.connectTCP(t, 10*time.Second),
|
||||||
|
Route: route,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
repl.(*REPL).testPassword = testSrv.password
|
||||||
|
require.NoError(t, repl.Run(t.Context()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_formatResult(t *testing.T) {
|
||||||
|
resultRows, err := mysql.BuildSimpleTextResultset(
|
||||||
|
[]string{"one", "two", "three"},
|
||||||
|
[][]any{{1, 2.2, "3"}, {4, 5.5, "6"}, {7, 8.8, "8"}},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
noRows, err := mysql.BuildSimpleTextResultset(
|
||||||
|
[]string{"one", "two", "three"},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
input *mysql.Result
|
||||||
|
elapsed time.Duration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "result rows",
|
||||||
|
input: &mysql.Result{Resultset: resultRows},
|
||||||
|
elapsed: time.Millisecond * 1234,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "long elapsed time",
|
||||||
|
input: &mysql.Result{Resultset: resultRows},
|
||||||
|
elapsed: time.Millisecond * 123456,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty set",
|
||||||
|
input: &mysql.Result{Resultset: noRows},
|
||||||
|
elapsed: time.Millisecond * 42,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "nil set",
|
||||||
|
input: &mysql.Result{Warnings: 2, AffectedRows: 42},
|
||||||
|
elapsed: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
mustParseResult(t, test.input)
|
||||||
|
got := formatResult(test.input, &test.elapsed)
|
||||||
|
goldenName := strings.ReplaceAll(test.desc, " ", "-")
|
||||||
|
if golden.ShouldSet() {
|
||||||
|
golden.SetNamed(t, goldenName, []byte(got))
|
||||||
|
}
|
||||||
|
require.Equal(t, string(golden.GetNamed(t, goldenName)), got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustParseResult(t *testing.T, result *mysql.Result) {
|
||||||
|
t.Helper()
|
||||||
|
if result.Resultset == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result.Values = make([][]mysql.FieldValue, len(result.RowDatas))
|
||||||
|
for i := range result.RowDatas {
|
||||||
|
const isBinary = false
|
||||||
|
v, err := result.RowDatas[i].Parse(result.Fields, isBinary, result.Values[i])
|
||||||
|
result.Values[i] = v
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestServer(t *testing.T) *testServer {
|
||||||
|
t.Helper()
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
|
reuseName := cmp.Or(os.Getenv("MYSQL_TEST_SERVER_REUSE_CONTAINER_BY_NAME"), "default-mysql-test-server")
|
||||||
|
testSrvMu.Lock()
|
||||||
|
// hold the lock for the entire func to avoid parallel container requests
|
||||||
|
defer testSrvMu.Unlock()
|
||||||
|
if testSrv != nil {
|
||||||
|
return testSrv
|
||||||
|
}
|
||||||
|
|
||||||
|
user := cmp.Or(os.Getenv("MYSQL_TEST_SERVER_USER"), "root")
|
||||||
|
db := cmp.Or(os.Getenv("MYSQL_TEST_SERVER_DB"), "mysql")
|
||||||
|
pass := cmp.Or(os.Getenv("MYSQL_TEST_SERVER_PASS"), rand.Text())
|
||||||
|
opts := []testcontainers.ContainerCustomizer{
|
||||||
|
mysqlcontainer.WithDatabase(db),
|
||||||
|
mysqlcontainer.WithUsername(user),
|
||||||
|
mysqlcontainer.WithPassword(pass),
|
||||||
|
testcontainers.WithReuseByName(reuseName),
|
||||||
|
}
|
||||||
|
|
||||||
|
// MySQL 8.4.6 index digest
|
||||||
|
// https://docs.docker.com/dhi/core-concepts/digests/#multi-platform-images-and-manifests
|
||||||
|
// https://hub.docker.com/layers/library/mysql/8.4.6/images/sha256-9b9413211d004062e6d5be1118e1051773fe66d832ec9ebcae1c2fcce5af5f5b
|
||||||
|
const img = "mysql@sha256:d2c60b1b225c6d7845f0abdb596fc35c2d4122bcad6ec219588035a118f75d93"
|
||||||
|
container, err := mysqlcontainer.Run(ctx, img, opts...)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
host, err := container.Host(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mappedPort, err := container.MappedPort(ctx, "3306/tcp")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
srv := &testServer{
|
||||||
|
container: container,
|
||||||
|
host: host,
|
||||||
|
port: mappedPort.Port(),
|
||||||
|
username: user,
|
||||||
|
password: pass,
|
||||||
|
database: db,
|
||||||
|
}
|
||||||
|
testSrv = srv
|
||||||
|
return srv
|
||||||
|
}
|
||||||
|
|
||||||
|
// testServer is a MySQL server for tests.
|
||||||
|
type testServer struct {
|
||||||
|
// container is the container hosting the server.
|
||||||
|
container testcontainers.Container
|
||||||
|
// host is the MySQL connection endpoint host.
|
||||||
|
host string
|
||||||
|
// port is the MySQL connection endpoint port.
|
||||||
|
port string
|
||||||
|
// username is the database superuser.
|
||||||
|
username string
|
||||||
|
// password is the database superuser password.
|
||||||
|
password string
|
||||||
|
// database is a database schema provisioned in the database.
|
||||||
|
database string
|
||||||
|
}
|
||||||
|
|
||||||
|
// hostPort returns the server host:port.
|
||||||
|
func (s *testServer) hostPort() string {
|
||||||
|
return net.JoinHostPort(s.host, s.port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) connectTCP(t *testing.T, timeout time.Duration) net.Conn {
|
||||||
|
t.Helper()
|
||||||
|
conn, err := net.DialTimeout("tcp", s.hostPort(), timeout)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRecordingClient(t *testing.T, reader io.ReadCloser, writers ...io.Writer) *recordingClient {
|
||||||
|
t.Helper()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writers = append(writers, &buf)
|
||||||
|
return &recordingClient{
|
||||||
|
ReadCloser: reader,
|
||||||
|
Writer: io.MultiWriter(writers...),
|
||||||
|
buf: &buf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type recordingClient struct {
|
||||||
|
io.ReadCloser
|
||||||
|
io.Writer
|
||||||
|
buf *bytes.Buffer
|
||||||
|
}
|
||||||
+255
@@ -0,0 +1,255 @@
|
|||||||
|
Teleport MySQL interactive shell (v19.0.0-dev)
|
||||||
|
Connected to instance "mysql-test-container" as user "root".
|
||||||
|
Type 'help' or '\h' for help.
|
||||||
|
|
||||||
|
(none)> -- temporarily disable note warnings for test consistency
|
||||||
|
(none)> set session sql_notes = 0;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
(none)> CREATE DATABASE IF NOT EXISTS all_data_types;
|
||||||
|
|
||||||
|
0 warnings, 1 rows affected
|
||||||
|
|
||||||
|
(none)> set session sql_notes = 1;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
(none)> USE all_data_types;
|
||||||
|
|
||||||
|
Default database changed to "all_data_types"
|
||||||
|
|
||||||
|
all_data_types>
|
||||||
|
all_data_types> -- without ANSI_QUOTES double quotes are literals
|
||||||
|
all_data_types> SET SESSION sql_mode='';
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
all_data_types> SELECT "thing";
|
||||||
|
|
||||||
|
thing
|
||||||
|
-------
|
||||||
|
'thing'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
all_data_types> -- with ANSI_QUOTES double quotes are identifiers, which is what
|
||||||
|
we want when
|
||||||
|
all_data_types> -- selecting from the table below
|
||||||
|
all_data_types> SET SESSION sql_mode='ANSI_QUOTES';
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
all_data_types> SELECT "thing";
|
||||||
|
|
||||||
|
ERR ERROR 1054 (42S22): Unknown column 'thing' in 'field list'
|
||||||
|
|
||||||
|
all_data_types>
|
||||||
|
all_data_types> CREATE TABLE all_types_demo (
|
||||||
|
-> "c tinyint" TINYINT,
|
||||||
|
-> c_smallint SMALLINT,
|
||||||
|
-> c_mediumint MEDIUMINT,
|
||||||
|
-> c_int INT,
|
||||||
|
-> c_bigint BIGINT,
|
||||||
|
-> c_decimal DECIMAL(10,2),
|
||||||
|
-> c_float FLOAT,
|
||||||
|
-> c_double DOUBLE,
|
||||||
|
-> c_bit BIT(8),
|
||||||
|
-> c_bool BOOL,
|
||||||
|
-> c_date DATE,
|
||||||
|
-> c_time TIME,
|
||||||
|
-> c_datetime DATETIME,
|
||||||
|
-> c_timestamp TIMESTAMP NULL DEFAULT NULL,
|
||||||
|
-> c_year YEAR,
|
||||||
|
-> c_char CHAR(5),
|
||||||
|
-> c_varchar VARCHAR(10),
|
||||||
|
-> c_binary BINARY(3),
|
||||||
|
-> c_varbinary VARBINARY(4),
|
||||||
|
-> c_tinyblob TINYBLOB,
|
||||||
|
-> c_blob BLOB,
|
||||||
|
-> c_mediumblob MEDIUMBLOB,
|
||||||
|
-> c_longblob LONGBLOB,
|
||||||
|
-> c_tinytext TINYTEXT,
|
||||||
|
-> c_text TEXT,
|
||||||
|
-> c_mediumtext MEDIUMTEXT,
|
||||||
|
-> c_longtext LONGTEXT,
|
||||||
|
-> c_enum ENUM('foo','bar','baz'),
|
||||||
|
-> c_set SET('a','b','c'),
|
||||||
|
-> c_geometry GEOMETRY,
|
||||||
|
-> c_point POINT,
|
||||||
|
-> c_linestring LINESTRING,
|
||||||
|
-> c_polygon POLYGON,
|
||||||
|
-> c_multipoint MULTIPOINT,
|
||||||
|
-> c_multilinestring MULTILINESTRING,
|
||||||
|
-> c_multipolygon MULTIPOLYGON,
|
||||||
|
-> c_geometrycoll GEOMETRYCOLLECTION,
|
||||||
|
-> c_json JSON
|
||||||
|
-> );
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
all_data_types>
|
||||||
|
all_data_types> INSERT INTO all_types_demo VALUES (
|
||||||
|
-> 127,
|
||||||
|
-> 32767,
|
||||||
|
-> 8388607,
|
||||||
|
-> 2147483647,
|
||||||
|
-> 9223372036854775807,
|
||||||
|
-> 12345.67,
|
||||||
|
-> 3.14,
|
||||||
|
-> 2.7182818284,
|
||||||
|
-> b'10101010',
|
||||||
|
-> 1,
|
||||||
|
-> '2024-07-09',
|
||||||
|
-> '12:34:56',
|
||||||
|
-> '2024-07-09 12:34:56',
|
||||||
|
-> '2024-07-09 12:34:56',
|
||||||
|
-> 2025,
|
||||||
|
-> 'abcde',
|
||||||
|
-> 'abcdefghij',
|
||||||
|
-> 'abc',
|
||||||
|
-> 'defg',
|
||||||
|
-> 'a',
|
||||||
|
-> 'blob',
|
||||||
|
-> 'medblob',
|
||||||
|
-> 'longblob',
|
||||||
|
-> 'tiny',
|
||||||
|
-> 'text',
|
||||||
|
-> 'mediumtext',
|
||||||
|
-> 'longtext',
|
||||||
|
-> 'foo',
|
||||||
|
-> 'a,b',
|
||||||
|
-> ST_GeomFromText('POINT(1 1)'),
|
||||||
|
-> ST_GeomFromText('POINT(2 2)'),
|
||||||
|
-> ST_GeomFromText('LINESTRING(0 0,1 1,2 2)'),
|
||||||
|
-> ST_GeomFromText('POLYGON((0 0,1 0,1 1,0 1,0 0))'),
|
||||||
|
-> ST_GeomFromText('MULTIPOINT((0 0),(1 1))'),
|
||||||
|
-> ST_GeomFromText('MULTILINESTRING((0 0,1 1),(2 2,3 3))'),
|
||||||
|
-> ST_GeomFromText('MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0)))'),
|
||||||
|
-> ST_GeomFromText('GEOMETRYCOLLECTION(POINT(1 2),LINESTRING(0
|
||||||
|
0,1 1))'),
|
||||||
|
-> JSON_OBJECT('x', 1, 'y', 2)
|
||||||
|
-> );
|
||||||
|
|
||||||
|
0 warnings, 1 rows affected
|
||||||
|
|
||||||
|
all_data_types>
|
||||||
|
all_data_types> SELECT "c tinyint", `c_smallint`, "c_mediumint" FROM `all_types_
|
||||||
|
demo`;
|
||||||
|
|
||||||
|
c tinyint c_smallint c_mediumint
|
||||||
|
--------- ---------- -----------
|
||||||
|
127 32767 8388607
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
all_data_types> SELECT c_int, c_bigint, c_decimal FROM all_types_demo;
|
||||||
|
|
||||||
|
c_int c_bigint c_decimal
|
||||||
|
---------- ------------------- ----------
|
||||||
|
2147483647 9223372036854775807 '12345.67'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
all_data_types> SELECT c_float, c_double, c_bit FROM all_types_demo;
|
||||||
|
|
||||||
|
c_float c_double c_bit
|
||||||
|
------- ------------ -----
|
||||||
|
3.14 2.7182818284 'ª'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
all_data_types> SELECT c_bool, c_date, c_time FROM all_types_demo;
|
||||||
|
|
||||||
|
c_bool c_date c_time
|
||||||
|
------ ------------ ----------
|
||||||
|
1 '2024-07-09' '12:34:56'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
all_data_types> SELECT c_datetime, c_timestamp, c_year FROM all_types_demo;
|
||||||
|
|
||||||
|
c_datetime c_timestamp c_year
|
||||||
|
--------------------- --------------------- ------
|
||||||
|
'2024-07-09 12:34:56' '2024-07-09 12:34:56' 2025
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
all_data_types> SELECT c_char, c_varchar, c_binary FROM all_types_demo;
|
||||||
|
|
||||||
|
c_char c_varchar c_binary
|
||||||
|
------- ------------ --------
|
||||||
|
'abcde' 'abcdefghij' 'abc'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
all_data_types> SELECT c_varbinary, c_tinyblob, c_blob FROM all_types_demo;
|
||||||
|
|
||||||
|
c_varbinary c_tinyblob c_blob
|
||||||
|
----------- ---------- ------
|
||||||
|
'defg' 'a' 'blob'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
all_data_types> SELECT "c_mediumblob",
|
||||||
|
-> `c_longblob`,
|
||||||
|
-> c_tinytext
|
||||||
|
-> FROM all_types_demo;
|
||||||
|
|
||||||
|
c_mediumblob c_longblob c_tinytext
|
||||||
|
------------ ---------- ----------
|
||||||
|
'medblob' 'longblob' 'tiny'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
all_data_types> SELECT c_text, c_mediumtext, c_longtext FROM all_types_demo;
|
||||||
|
|
||||||
|
c_text c_mediumtext c_longtext
|
||||||
|
------ ------------ ----------
|
||||||
|
'text' 'mediumtext' 'longtext'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
all_data_types> SELECT c_enum, c_set, ST_AsText(c_geometry) AS c_geometry FROM a
|
||||||
|
ll_types_demo;
|
||||||
|
|
||||||
|
c_enum c_set c_geometry
|
||||||
|
------ ----- ------------
|
||||||
|
'foo' 'a,b' 'POINT(1 1)'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
all_data_types> SELECT ST_AsText(c_point) AS c_point, ST_AsText(c_linestring) AS
|
||||||
|
c_linestring, ST_AsText(c_polygon) AS c_polygon FROM all_types_demo;
|
||||||
|
|
||||||
|
c_point c_linestring c_polygon
|
||||||
|
------------ ------------------------- --------------------------------
|
||||||
|
'POINT(2 2)' 'LINESTRING(0 0,1 1,2 2)' 'POLYGON((0 0,1 0,1 1,0 1,0 0))'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
all_data_types> SELECT ST_AsText(c_multipoint) AS c_multipoint, ST_AsText(c_mult
|
||||||
|
ilinestring) AS c_multilinestring, ST_AsText(c_multipolygon) AS c_multipolygon F
|
||||||
|
ROM all_types_demo;
|
||||||
|
|
||||||
|
c_multipoint c_multilinestring c_multipolygon
|
||||||
|
------------------------- -------------------------------------- ---------------------------------------
|
||||||
|
'MULTIPOINT((0 0),(1 1))' 'MULTILINESTRING((0 0,1 1),(2 2,3 3))' 'MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0)))'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
all_data_types> SELECT ST_AsText(c_geometrycoll) AS c_geometrycoll, c_json FROM
|
||||||
|
all_types_demo;
|
||||||
|
|
||||||
|
c_geometrycoll c_json
|
||||||
|
---------------------------------------------------- ------------------
|
||||||
|
'GEOMETRYCOLLECTION(POINT(1 2),LINESTRING(0 0,1 1))' '{"x": 1, "y": 2}'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
all_data_types> DROP DATABASE all_data_types;
|
||||||
|
|
||||||
|
0 warnings, 1 rows affected
|
||||||
|
|
||||||
|
all_data_types>
|
||||||
|
all_data_types>
|
||||||
@@ -0,0 +1,325 @@
|
|||||||
|
Teleport MySQL interactive shell (v19.0.0-dev)
|
||||||
|
Connected to instance "mysql-test-container" as user "root".
|
||||||
|
Type 'help' or '\h' for help.
|
||||||
|
|
||||||
|
(none)> -- temporarily disable note warnings for test consistency
|
||||||
|
(none)> set session sql_notes = 0;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
(none)> create database if not exists test_commands;
|
||||||
|
|
||||||
|
0 warnings, 1 rows affected
|
||||||
|
|
||||||
|
(none)> create database if not exists foo;
|
||||||
|
|
||||||
|
0 warnings, 1 rows affected
|
||||||
|
|
||||||
|
(none)> create database if not exists bar;
|
||||||
|
|
||||||
|
0 warnings, 1 rows affected
|
||||||
|
|
||||||
|
(none)> set session sql_notes = 1;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
(none)> use test_commands;
|
||||||
|
|
||||||
|
Default database changed to "test_commands"
|
||||||
|
|
||||||
|
test_commands>
|
||||||
|
test_commands> -- leading spaces/comments before commands
|
||||||
|
test_commands> /* just a comment */
|
||||||
|
test_commands> use foo;
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> use foo
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /*
|
||||||
|
/*> llama
|
||||||
|
/*> */
|
||||||
|
foo> use foo;
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /*
|
||||||
|
/*> llama
|
||||||
|
/*> */
|
||||||
|
foo> use foo
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> -- llama
|
||||||
|
foo> use foo;
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> -- llama
|
||||||
|
foo> use foo
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> -- llama
|
||||||
|
foo> use foo;
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> -- llama
|
||||||
|
foo> use foo
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama */use foo;
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama */use foo
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama */ use foo;
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama */ use foo
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama */
|
||||||
|
foo> use foo;
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama */
|
||||||
|
foo> use foo
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama
|
||||||
|
/*> */use foo;
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama
|
||||||
|
/*> */use foo
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama
|
||||||
|
/*> */ use foo;
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama
|
||||||
|
/*> */ use foo
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama
|
||||||
|
/*> -- llama
|
||||||
|
/*> */use foo;
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama
|
||||||
|
/*> -- llama
|
||||||
|
/*> */use foo
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama
|
||||||
|
/*> -- llama
|
||||||
|
/*> */ use foo;
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama
|
||||||
|
/*> -- llama
|
||||||
|
/*> */ use foo
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama
|
||||||
|
/*> -- */use foo;
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama
|
||||||
|
/*> -- */use foo
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama
|
||||||
|
/*> -- */ use foo;
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama
|
||||||
|
/*> -- */ use foo
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> /* llama */
|
||||||
|
foo>
|
||||||
|
foo> use foo;use bar; use foo
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
|
||||||
|
Default database changed to "bar"
|
||||||
|
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> -- this isn't a command, but that should just print an error message and he
|
||||||
|
lp info.
|
||||||
|
foo> \xyz select 1
|
||||||
|
|
||||||
|
Unknown command.
|
||||||
|
delimiter \d Set statement delimiter. With no argument the delimiter resets to the default.
|
||||||
|
help \h Show the list of supported commands.
|
||||||
|
quit \q Terminates the session.
|
||||||
|
status \s Get status information.
|
||||||
|
teleport \t Show Teleport interactive shell information, such as execution limitations.
|
||||||
|
use \u Use another database.
|
||||||
|
|
||||||
|
foo> \xyz;select 1;
|
||||||
|
|
||||||
|
Unknown command.
|
||||||
|
delimiter \d Set statement delimiter. With no argument the delimiter resets to the default.
|
||||||
|
help \h Show the list of supported commands.
|
||||||
|
quit \q Terminates the session.
|
||||||
|
status \s Get status information.
|
||||||
|
teleport \t Show Teleport interactive shell information, such as execution limitations.
|
||||||
|
use \u Use another database.
|
||||||
|
|
||||||
|
|
||||||
|
1
|
||||||
|
-
|
||||||
|
1
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
foo> delimiter $$
|
||||||
|
foo> help$$
|
||||||
|
|
||||||
|
delimiter \d Set statement delimiter. With no argument the delimiter resets to the default.
|
||||||
|
help \h Show the list of supported commands.
|
||||||
|
quit \q Terminates the session.
|
||||||
|
status \s Get status information.
|
||||||
|
teleport \t Show Teleport interactive shell information, such as execution limitations.
|
||||||
|
use \u Use another database.
|
||||||
|
|
||||||
|
foo> \d //
|
||||||
|
foo> \h
|
||||||
|
|
||||||
|
delimiter \d Set statement delimiter. With no argument the delimiter resets to the default.
|
||||||
|
help \h Show the list of supported commands.
|
||||||
|
quit \q Terminates the session.
|
||||||
|
status \s Get status information.
|
||||||
|
teleport \t Show Teleport interactive shell information, such as execution limitations.
|
||||||
|
use \u Use another database.
|
||||||
|
|
||||||
|
foo> help
|
||||||
|
|
||||||
|
delimiter \d Set statement delimiter. With no argument the delimiter resets to the default.
|
||||||
|
help \h Show the list of supported commands.
|
||||||
|
quit \q Terminates the session.
|
||||||
|
status \s Get status information.
|
||||||
|
teleport \t Show Teleport interactive shell information, such as execution limitations.
|
||||||
|
use \u Use another database.
|
||||||
|
|
||||||
|
foo> status
|
||||||
|
|
||||||
|
Teleport database: mysql-test-container
|
||||||
|
Current database: foo
|
||||||
|
Current user: root
|
||||||
|
Server version: 8.4.6
|
||||||
|
Using delimiter: //
|
||||||
|
|
||||||
|
foo> \s
|
||||||
|
|
||||||
|
Teleport database: mysql-test-container
|
||||||
|
Current database: foo
|
||||||
|
Current user: root
|
||||||
|
Server version: 8.4.6
|
||||||
|
Using delimiter: //
|
||||||
|
|
||||||
|
foo> teleport
|
||||||
|
|
||||||
|
Teleport MySQL interactive shell (v19.0.0-dev)
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
- Query cancellation is not supported. Once a query is sent, its execution
|
||||||
|
can only be canceled by killing it from another session.
|
||||||
|
You can identify the query process ID with "SHOW PROCESSLIST" and then kill the
|
||||||
|
query with "KILL QUERY <pid>".
|
||||||
|
|
||||||
|
- Pressing CTRL-C will have no effect in this shell.
|
||||||
|
|
||||||
|
foo> \t
|
||||||
|
|
||||||
|
Teleport MySQL interactive shell (v19.0.0-dev)
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
- Query cancellation is not supported. Once a query is sent, its execution
|
||||||
|
can only be canceled by killing it from another session.
|
||||||
|
You can identify the query process ID with "SHOW PROCESSLIST" and then kill the
|
||||||
|
query with "KILL QUERY <pid>".
|
||||||
|
|
||||||
|
- Pressing CTRL-C will have no effect in this shell.
|
||||||
|
|
||||||
|
foo> use foo
|
||||||
|
|
||||||
|
Default database changed to "foo"
|
||||||
|
|
||||||
|
foo> \u mysql//
|
||||||
|
|
||||||
|
Default database changed to "mysql"
|
||||||
|
|
||||||
|
mysql> -- reset the delimiter to default ;
|
||||||
|
mysql> \d
|
||||||
|
mysql> teleport;\s;\h
|
||||||
|
|
||||||
|
Teleport MySQL interactive shell (v19.0.0-dev)
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
- Query cancellation is not supported. Once a query is sent, its execution
|
||||||
|
can only be canceled by killing it from another session.
|
||||||
|
You can identify the query process ID with "SHOW PROCESSLIST" and then kill the
|
||||||
|
query with "KILL QUERY <pid>".
|
||||||
|
|
||||||
|
- Pressing CTRL-C will have no effect in this shell.
|
||||||
|
|
||||||
|
|
||||||
|
Teleport database: mysql-test-container
|
||||||
|
Current database: mysql
|
||||||
|
Current user: root
|
||||||
|
Server version: 8.4.6
|
||||||
|
Using delimiter: ;
|
||||||
|
|
||||||
|
|
||||||
|
delimiter \d Set statement delimiter. With no argument the delimiter resets to the default.
|
||||||
|
help \h Show the list of supported commands.
|
||||||
|
quit \q Terminates the session.
|
||||||
|
status \s Get status information.
|
||||||
|
teleport \t Show Teleport interactive shell information, such as execution limitations.
|
||||||
|
use \u Use another database.
|
||||||
|
|
||||||
|
mysql> use `bar`;drop database foo;drop database bar;drop database test_commands
|
||||||
|
;quit
|
||||||
|
|
||||||
|
Default database changed to "bar"
|
||||||
|
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
Teleport MySQL interactive shell (v19.0.0-dev)
|
||||||
|
Connected to instance "mysql-test-container" as user "root".
|
||||||
|
Type 'help' or '\h' for help.
|
||||||
|
|
||||||
|
(none)> -- temporarily disable note warnings for test consistency
|
||||||
|
(none)> set session sql_notes = 0;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
(none)> DROP DATABASE IF EXISTS test_ddl_db;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
(none)> CREATE DATABASE test_ddl_db;
|
||||||
|
|
||||||
|
0 warnings, 1 rows affected
|
||||||
|
|
||||||
|
(none)> set session sql_notes = 1;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
(none)> -- this should produce a note level warning because it does exist
|
||||||
|
(none)> CREATE DATABASE if not exists test_ddl_db;
|
||||||
|
|
||||||
|
1 warnings, 1 rows affected
|
||||||
|
|
||||||
|
(none)> -- we clear the warning by selecting the warning count
|
||||||
|
(none)> select @@warning_count;
|
||||||
|
|
||||||
|
@@warning_count
|
||||||
|
---------------
|
||||||
|
1
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
(none)> USE test_ddl_db;
|
||||||
|
|
||||||
|
Default database changed to "test_ddl_db"
|
||||||
|
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> CREATE TABLE test_table (
|
||||||
|
-> id INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
-> value VARCHAR(50) NOT NULL,
|
||||||
|
-> note VARCHAR(100) DEFAULT NULL
|
||||||
|
-> ) ENGINE=InnoDB;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> CREATE VIEW test_view AS (
|
||||||
|
-> SELECT id, value
|
||||||
|
-> FROM test_table
|
||||||
|
-> );
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> DELIMITER $$
|
||||||
|
test_ddl_db> CREATE FUNCTION test_func(x INT) RETURNS INT DETERMINISTIC
|
||||||
|
-> BEGIN
|
||||||
|
-> RETURN x * 2;
|
||||||
|
-> END $$
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db> DELIMITER ;
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> DELIMITER $$
|
||||||
|
test_ddl_db> CREATE PROCEDURE test_proc()
|
||||||
|
-> BEGIN
|
||||||
|
-> INSERT INTO test_table(value) VALUES ('from proc');
|
||||||
|
-> END $$
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db> DELIMITER ;
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> CALL test_proc();
|
||||||
|
|
||||||
|
0 warnings, 1 rows affected
|
||||||
|
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> DELIMITER $$
|
||||||
|
test_ddl_db> CREATE TRIGGER test_trigger_before_insert
|
||||||
|
-> BEFORE INSERT ON test_table
|
||||||
|
-> FOR EACH ROW
|
||||||
|
-> BEGIN
|
||||||
|
-> SET NEW.value = UPPER(NEW.value);
|
||||||
|
-> END $$
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db> DELIMITER ;
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> CALL test_proc();
|
||||||
|
|
||||||
|
0 warnings, 1 rows affected
|
||||||
|
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> CREATE EVENT test_event
|
||||||
|
-> ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 HOUR
|
||||||
|
-> DO INSERT INTO test_table(value) VALUES ('from event');
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> set session sql_notes = 0;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db> -- roles are not database scoped, let's disable note warnings in ca
|
||||||
|
se some other
|
||||||
|
test_ddl_db> -- test has already created the role or user.
|
||||||
|
test_ddl_db> CREATE ROLE IF NOT EXISTS test_role;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db> DROP ROLE test_role;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> CREATE USER IF NOT EXISTS test_user@'localhost'
|
||||||
|
-> IDENTIFIED BY 'some password'
|
||||||
|
-> ACCOUNT LOCK;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db> ALTER USER test_user@'localhost' PASSWORD EXPIRE;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db> DROP USER test_user@'localhost';
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db> set session sql_notes = 1;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> ALTER DATABASE test_ddl_db CHARACTER SET utf8mb4;
|
||||||
|
|
||||||
|
0 warnings, 1 rows affected
|
||||||
|
|
||||||
|
test_ddl_db> ALTER TABLE test_table ADD COLUMN note2 VARCHAR(100) DEFAULT NULL;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db> ALTER EVENT test_event DISABLE;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db> ALTER FUNCTION test_func COMMENT 'Test function';
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db> ALTER PROCEDURE test_proc COMMENT 'Test procedure';
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> RENAME TABLE test_table
|
||||||
|
-> TO test_table_renamed;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db> SELECT * from test_table_renamed;
|
||||||
|
|
||||||
|
id value note note2
|
||||||
|
-- ----------- ---- -----
|
||||||
|
1 'from proc' NULL NULL
|
||||||
|
2 'FROM PROC' NULL NULL
|
||||||
|
|
||||||
|
2 rows in set
|
||||||
|
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> RENAME
|
||||||
|
-> TABLE test_table_renamed
|
||||||
|
-> TO test_table;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> SELECT * from test_table;
|
||||||
|
|
||||||
|
id value note note2
|
||||||
|
-- ----------- ---- -----
|
||||||
|
1 'from proc' NULL NULL
|
||||||
|
2 'FROM PROC' NULL NULL
|
||||||
|
|
||||||
|
2 rows in set
|
||||||
|
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> -- there is no "test_server", this should make an error.
|
||||||
|
test_ddl_db> ALTER SERVER test_server OPTIONS (HOST '127.0.0.1');
|
||||||
|
|
||||||
|
ERR ERROR 1477 (HY000): The foreign server name you are trying to reference does not exist. Data source error: test_server
|
||||||
|
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> DROP TRIGGER test_trigger_before_insert;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> TRUNCATE TABLE test_table ;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db> DROP TABLE test_table;
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db> DROP DATABASE test_ddl_db
|
||||||
|
-> ;
|
||||||
|
|
||||||
|
0 warnings, 1 rows affected
|
||||||
|
|
||||||
|
test_ddl_db>
|
||||||
|
test_ddl_db>
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
Teleport MySQL interactive shell (v19.0.0-dev)
|
||||||
|
Connected to instance "mysql-test-container" as user "root".
|
||||||
|
Type 'help' or '\h' for help.
|
||||||
|
|
||||||
|
(none)> -- with ANSI_QUOTES double quotes are identifiers
|
||||||
|
(none)> SET SESSION sql_mode='ANSI_QUOTES';
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
(none)> SELECT "thing";
|
||||||
|
|
||||||
|
ERR ERROR 1054 (42S22): Unknown column 'thing' in 'field list'
|
||||||
|
|
||||||
|
(none)> -- without ANSI_QUOTES double quotes are literals
|
||||||
|
(none)> SET SESSION sql_mode='';
|
||||||
|
|
||||||
|
0 warnings, 0 rows affected
|
||||||
|
|
||||||
|
(none)> SELECT "thing";
|
||||||
|
|
||||||
|
thing
|
||||||
|
-------
|
||||||
|
'thing'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
(none)> select 1, -1,1.24, -1.24, 'foo', null, 'null';
|
||||||
|
|
||||||
|
1 -1 1.24 -1.24 foo NULL null
|
||||||
|
- -- ------ ------- ----- ---- ------
|
||||||
|
1 -1 '1.24' '-1.24' 'foo' NULL 'null'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
(none)> select
|
||||||
|
-> 1 as 'unsigned int',
|
||||||
|
-> -1 as 'negative int',
|
||||||
|
-> 1.24 as "float",
|
||||||
|
-> -1.24 as 'negative float',
|
||||||
|
-> 'foo' as 'string',
|
||||||
|
-> null as 'actual NULL',
|
||||||
|
-> "null" as 'string null';
|
||||||
|
|
||||||
|
unsigned int negative int float negative float string actual NULL string null
|
||||||
|
------------ ------------ ------ -------------- ------ ----------- -----------
|
||||||
|
1 -1 '1.24' '-1.24' 'foo' NULL 'null'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
(none)>
|
||||||
|
(none)> select 1 as 'multiple rows of literals'
|
||||||
|
-> union
|
||||||
|
-> select -1
|
||||||
|
-> union
|
||||||
|
-> select 1.24
|
||||||
|
-> union
|
||||||
|
-> select -1.24
|
||||||
|
-> union
|
||||||
|
-> select 'foo'
|
||||||
|
-> union
|
||||||
|
-> select null
|
||||||
|
-> union
|
||||||
|
-> select 'null';
|
||||||
|
|
||||||
|
multiple rows of literals
|
||||||
|
-------------------------
|
||||||
|
'1'
|
||||||
|
'-1'
|
||||||
|
'1.24'
|
||||||
|
'-1.24'
|
||||||
|
'foo'
|
||||||
|
NULL
|
||||||
|
'null'
|
||||||
|
|
||||||
|
7 rows in set
|
||||||
|
|
||||||
|
(none)>
|
||||||
|
(none)> select 'a
|
||||||
|
'> b
|
||||||
|
'> c' as 'multiline-string';
|
||||||
|
|
||||||
|
multiline-string
|
||||||
|
----------------
|
||||||
|
'a
|
||||||
|
b
|
||||||
|
c'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
(none)>
|
||||||
|
(none)> select 'a
|
||||||
|
'> ;
|
||||||
|
'> b
|
||||||
|
'> ;
|
||||||
|
'> c' as 'multiline with delimiters';
|
||||||
|
|
||||||
|
multiline with delimiters
|
||||||
|
-------------------------
|
||||||
|
'a
|
||||||
|
;
|
||||||
|
b
|
||||||
|
;
|
||||||
|
c'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
(none)>
|
||||||
|
(none)> /* foo bar */
|
||||||
|
(none)> select 1;
|
||||||
|
|
||||||
|
1
|
||||||
|
-
|
||||||
|
1
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
(none)> /* foo bar */select /*
|
||||||
|
/*> baz */ 42;
|
||||||
|
|
||||||
|
42
|
||||||
|
--
|
||||||
|
42
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
(none)>
|
||||||
|
(none)> delimiter $$
|
||||||
|
(none)> select '$$1$$'$$
|
||||||
|
|
||||||
|
$$1$$
|
||||||
|
-------
|
||||||
|
'$$1$$'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
(none)> select '$$;$$';$$
|
||||||
|
|
||||||
|
$$;$$
|
||||||
|
-------
|
||||||
|
'$$;$$'
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
(none)> delimiter ;
|
||||||
|
(none)> select 1;
|
||||||
|
|
||||||
|
1
|
||||||
|
-
|
||||||
|
1
|
||||||
|
|
||||||
|
1 row in set
|
||||||
|
|
||||||
|
(none)>
|
||||||
|
(none)>
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
Empty set (0.04 sec)
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
one two three
|
||||||
|
--- --- -----
|
||||||
|
1 2.2 '3'
|
||||||
|
4 5.5 '6'
|
||||||
|
7 8.8 '8'
|
||||||
|
|
||||||
|
3 rows in set (123.46 sec)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
2 warnings, 42 rows affected (0.00 sec)
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
one two three
|
||||||
|
--- --- -----
|
||||||
|
1 2.2 '3'
|
||||||
|
4 5.5 '6'
|
||||||
|
7 8.8 '8'
|
||||||
|
|
||||||
|
3 rows in set (1.23 sec)
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// Teleport
|
||||||
|
// Copyright (C) 2025 Gravitational, Inc.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package repl
|
||||||
|
|
||||||
|
// token is a lexical token.
|
||||||
|
type token struct {
|
||||||
|
// kind is a token kind enum.
|
||||||
|
kind tokenKind
|
||||||
|
// text is the text that makes up the token.
|
||||||
|
text string
|
||||||
|
}
|
||||||
|
|
||||||
|
type tokenKind int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// EOF indicates end of token stream
|
||||||
|
tokenEOF tokenKind = iota
|
||||||
|
|
||||||
|
// whitespace
|
||||||
|
tokenSpace
|
||||||
|
|
||||||
|
// escape
|
||||||
|
tokenBackslash // \
|
||||||
|
|
||||||
|
// quotation syntax
|
||||||
|
tokenSingleQuote // '
|
||||||
|
tokenDoubleQuote // "
|
||||||
|
tokenBacktick // `
|
||||||
|
|
||||||
|
// single-line comments
|
||||||
|
tokenSingleComment // -- or #
|
||||||
|
|
||||||
|
// multi-line comment open/close
|
||||||
|
tokenOpenComment // /*
|
||||||
|
tokenCloseComment // */
|
||||||
|
|
||||||
|
// misc text that isn't special to us
|
||||||
|
tokenText
|
||||||
|
)
|
||||||
@@ -111,6 +111,7 @@ import (
|
|||||||
_ "github.com/gravitational/teleport/lib/backend/pgbk"
|
_ "github.com/gravitational/teleport/lib/backend/pgbk"
|
||||||
"github.com/gravitational/teleport/lib/bpf"
|
"github.com/gravitational/teleport/lib/bpf"
|
||||||
"github.com/gravitational/teleport/lib/cache"
|
"github.com/gravitational/teleport/lib/cache"
|
||||||
|
myrepl "github.com/gravitational/teleport/lib/client/db/mysql/repl"
|
||||||
pgrepl "github.com/gravitational/teleport/lib/client/db/postgres/repl"
|
pgrepl "github.com/gravitational/teleport/lib/client/db/postgres/repl"
|
||||||
dbrepl "github.com/gravitational/teleport/lib/client/db/repl"
|
dbrepl "github.com/gravitational/teleport/lib/client/db/repl"
|
||||||
"github.com/gravitational/teleport/lib/cloud"
|
"github.com/gravitational/teleport/lib/cloud"
|
||||||
@@ -1163,6 +1164,7 @@ func NewTeleport(cfg *servicecfg.Config) (_ *TeleportProcess, err error) {
|
|||||||
cfg.DatabaseREPLRegistry = dbrepl.NewREPLGetter(map[string]dbrepl.REPLNewFunc{
|
cfg.DatabaseREPLRegistry = dbrepl.NewREPLGetter(map[string]dbrepl.REPLNewFunc{
|
||||||
defaults.ProtocolPostgres: pgrepl.New,
|
defaults.ProtocolPostgres: pgrepl.New,
|
||||||
defaults.ProtocolCockroachDB: pgrepl.New,
|
defaults.ProtocolCockroachDB: pgrepl.New,
|
||||||
|
defaults.ProtocolMySQL: myrepl.New,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user