From 25de383540265f10367d63de2a9632a0e9ffd30a Mon Sep 17 00:00:00 2001 From: ying-jeanne <74549700+ying-jeanne@users.noreply.github.com> Date: Tue, 16 Aug 2022 13:17:14 -0500 Subject: [PATCH] Chore: Replace xorm with sqlx (#52575) * Change of sqlstore to use sqlx * Use sqlx in the playlist store * Refectory of the interface * update playlist service * go mod tidy * some refectory on interface * fix kyle --- go.mod | 3 +- go.sum | 25 +- pkg/services/playlist/model.go | 25 +- .../playlist/playlistimpl/playlist.go | 10 +- .../playlist/playlistimpl/sqlx_store.go | 201 ++++++++++++++++ .../playlist/playlistimpl/sqlx_store_test.go | 13 ++ pkg/services/playlist/playlistimpl/store.go | 210 ----------------- .../playlist/playlistimpl/store_test.go | 64 ++++-- .../playlist/playlistimpl/xorm_store.go | 216 ++++++++++++++++++ .../playlist/playlistimpl/xorm_store_test.go | 13 ++ pkg/services/sqlstore/db/db.go | 2 + pkg/services/sqlstore/mockstore/mockstore.go | 5 + pkg/services/sqlstore/session/session.go | 112 +++++++++ pkg/services/sqlstore/sqlstore.go | 12 +- pkg/services/sqlstore/sqlutil/sqlutil.go | 5 +- pkg/services/sqlstore/store.go | 2 + 16 files changed, 652 insertions(+), 266 deletions(-) create mode 100644 pkg/services/playlist/playlistimpl/sqlx_store.go create mode 100644 pkg/services/playlist/playlistimpl/sqlx_store_test.go create mode 100644 pkg/services/playlist/playlistimpl/xorm_store.go create mode 100644 pkg/services/playlist/playlistimpl/xorm_store_test.go create mode 100644 pkg/services/sqlstore/session/session.go diff --git a/go.mod b/go.mod index 6b7dbad8fa0..613cd2d427c 100644 --- a/go.mod +++ b/go.mod @@ -244,12 +244,11 @@ require ( github.com/blugelabs/bluge v0.1.9 github.com/blugelabs/bluge_segment_api v0.2.0 github.com/getkin/kin-openapi v0.94.0 - github.com/go-git/go-git v4.7.0+incompatible github.com/golang-migrate/migrate/v4 v4.7.0 - github.com/google/go-github/v31 v31.0.0 github.com/google/go-github/v45 v45.2.0 github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f github.com/grafana/thema v0.0.0-20220726124731-b8017e278cc1 + github.com/jmoiron/sqlx v1.3.5 go.etcd.io/etcd/api/v3 v3.5.4 go.opentelemetry.io/contrib/propagators/jaeger v1.6.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.6.3 diff --git a/go.sum b/go.sum index b5e37f9f6ff..78878c2ca6f 100644 --- a/go.sum +++ b/go.sum @@ -286,7 +286,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/alecthomas/units v0.0.0-20210912230133-d1bdfacee922 h1:8ypNbf5sd3Sm3cKJ9waOGoQv6dKAFiFty9L6NP1AqJ4= github.com/alecthomas/units v0.0.0-20210912230133-d1bdfacee922/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= @@ -876,8 +875,6 @@ github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4u github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-git v4.7.0+incompatible h1:+W9rgGY4DOKKdX2x6HxSR7HNeTxqiKrOvKnuittYVdA= -github.com/go-git/go-git v4.7.0+incompatible/go.mod h1:6+421e08gnZWn30y26Vchf7efgYLe4dl5OQbBSUXShE= github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= @@ -891,7 +888,6 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/kit v0.11.0 h1:IGmIEl7aHTYh6E2HlT+ptILBotjo4xl8PMDl852etiI= github.com/go-kit/kit v0.11.0/go.mod h1:73/6Ixaufkvb5Osvkls8C79vuQ49Ba1rUEUYNSf+FUw= -github.com/go-kit/log v0.1.0 h1:DGJh0Sm43HbOeYDNnVZFl8BvcYVvjD5bqYJvp0REbwQ= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.0 h1:7i2K3eKTos3Vc0enKCfnVcgHh2olr/MyfboYq7cAcFw= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= @@ -923,7 +919,6 @@ github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgT github.com/go-openapi/analysis v0.19.14/go.mod h1:zN0kY6i38wo2LQOwltVyMk61bqlqOm86n1/Iszo8F8Y= github.com/go-openapi/analysis v0.19.16/go.mod h1:GLInF007N83Ad3m8a/CbQ5TPzdnGT7workfHwuVjNVk= github.com/go-openapi/analysis v0.20.0/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og= -github.com/go-openapi/analysis v0.20.1 h1:zdVbw8yoD4SWZeq+cWdGgquaB0W4VrsJvDJHJND/Ktc= github.com/go-openapi/analysis v0.20.1/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og= github.com/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P1wLJU= github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= @@ -937,7 +932,6 @@ github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpX github.com/go-openapi/errors v0.19.7/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.20.0 h1:Sxpo9PjEHDzhs3FbnGNonvDgWcMW2U7wGTcDDSFSceM= github.com/go-openapi/errors v0.20.0/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8= github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= @@ -970,7 +964,6 @@ github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2e github.com/go-openapi/loads v0.19.6/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc= github.com/go-openapi/loads v0.19.7/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc= github.com/go-openapi/loads v0.20.0/go.mod h1:2LhKquiE513rN5xC6Aan6lYOSddlL8Mp20AW9kpviM4= -github.com/go-openapi/loads v0.20.2 h1:z5p5Xf5wujMxS1y8aP+vxwW5qYT2zdJBbXKmQUG3lcc= github.com/go-openapi/loads v0.20.2/go.mod h1:hTVUotJ+UonAMMZsvakEgmWKgtulweO9vYP2bQYKA/o= github.com/go-openapi/loads v0.21.1 h1:Wb3nVZpdEzDTcly8S4HMkey6fjARRzb7iEaySimlDW0= github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g= @@ -984,7 +977,6 @@ github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiS github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk= github.com/go-openapi/runtime v0.19.26/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M= github.com/go-openapi/runtime v0.19.28/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M= -github.com/go-openapi/runtime v0.19.29 h1:5IIvCaIDbxetN674vX9eOxvoZ9mYGQ16fV1Q0VSG+NA= github.com/go-openapi/runtime v0.19.29/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M= github.com/go-openapi/runtime v0.23.1 h1:/Drg9R96eMmgKJHVWZADz78XbE39/6QiIiB45mc+epo= github.com/go-openapi/runtime v0.23.1/go.mod h1:AKurw9fNre+h3ELZfk6ILsfvPN+bvvlaU/M9q/r9hpk= @@ -1015,7 +1007,6 @@ github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk github.com/go-openapi/strfmt v0.19.11/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc= github.com/go-openapi/strfmt v0.20.0/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc= github.com/go-openapi/strfmt v0.20.1/go.mod h1:43urheQI9dNtE5lTZQfuFJvjYJKPrxicATpEfZwHUNk= -github.com/go-openapi/strfmt v0.20.2 h1:6XZL+fF4VZYFxKQGLAUB358hOrRh/wS51uWEtlONADE= github.com/go-openapi/strfmt v0.20.2/go.mod h1:43urheQI9dNtE5lTZQfuFJvjYJKPrxicATpEfZwHUNk= github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= @@ -1047,7 +1038,6 @@ github.com/go-openapi/validate v0.19.12/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0 github.com/go-openapi/validate v0.19.14/go.mod h1:PdGrHe0rp6MG3A1SrAY/rIHATqzJEEhohGE1atLkBEQ= github.com/go-openapi/validate v0.19.15/go.mod h1:tbn/fdOwYHgrhPBzidZfJC2MIVvs9GA7monOmWBbeCI= github.com/go-openapi/validate v0.20.1/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE9E4k54HpKcJ0= -github.com/go-openapi/validate v0.20.2 h1:AhqDegYV3J3iQkMPJSXkvzymHKMTw0BST3RK3hTT4ts= github.com/go-openapi/validate v0.20.2/go.mod h1:e7OJoKNgd0twXZwIn0A43tHbvIcr/rZIVCbJBpTUoY0= github.com/go-openapi/validate v0.21.0 h1:+Wqk39yKOhfpLqNLEC0/eViCkzM5FVXVqrvt526+wcI= github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= @@ -1071,7 +1061,6 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= @@ -1123,7 +1112,6 @@ github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -1240,8 +1228,6 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8 github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-github/v31 v31.0.0 h1:JJUxlP9lFK+ziXKimTCprajMApV1ecWD4NB6CCb0plo= -github.com/google/go-github/v31 v31.0.0/go.mod h1:NQPZol8/1sMoWYGN2yaALIBytu17gAWfhbweiEed3pM= github.com/google/go-github/v45 v45.2.0 h1:5oRLszbrkvxDDqBCNj2hjDZMKmvexaZ1xw/FCD+K3FI= github.com/google/go-github/v45 v45.2.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -1481,8 +1467,6 @@ github.com/hashicorp/memberlist v0.2.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/memberlist v0.2.3/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/memberlist v0.2.4/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.3.0 h1:8+567mCcFDnS5ADl7lrpxPMWiFCElyUEeW0gtj34fMA= -github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/memberlist v0.3.1 h1:MXgUXLqva1QvpVEDQW1IQLG0wivQAtmFlHRQ+1vWZfM= github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= @@ -1622,6 +1606,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -1822,6 +1808,7 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA= github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= @@ -2092,8 +2079,6 @@ github.com/prometheus/alertmanager v0.21.1-0.20210422101724-8176f78a70e1/go.mod github.com/prometheus/alertmanager v0.22.2/go.mod h1:rYinOWxFuCnNssc3iOjn2oMTlhLaPcUuqV5yk5JKUAE= github.com/prometheus/alertmanager v0.23.0/go.mod h1:0MLTrjQI8EuVmvykEhcfr/7X0xmaDAZrqMgxIq3OXHk= github.com/prometheus/alertmanager v0.23.1-0.20210914172521-e35efbddb66a/go.mod h1:U7pGu+z7A9ZKhK8lq1MvIOp5GdVlZjwOYk+S0h3LSbA= -github.com/prometheus/alertmanager v0.23.1-0.20211116083607-e2a10119aaf7 h1:OMwDo53awRp+UzaBrwmVC7HJiAMYP/niBJfKcGpPiac= -github.com/prometheus/alertmanager v0.23.1-0.20211116083607-e2a10119aaf7/go.mod h1:1UH4XA4DAXzsvofKVzcXmC0mqt6Y8BZP9JcQWKDmbFc= github.com/prometheus/alertmanager v0.24.0 h1:HBWR3lk4uy3ys+naDZthDdV7yEsxpaNeZuUS+hJgrOw= github.com/prometheus/alertmanager v0.24.0/go.mod h1:r6fy/D7FRuZh5YbnX6J3MBY0eI4Pb5yPYS7/bPSXXqI= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -2159,8 +2144,6 @@ github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57J github.com/prometheus/exporter-toolkit v0.5.0/go.mod h1:OCkM4805mmisBhLmVFw858QYi3v0wKdY6/UxrT0pZVg= github.com/prometheus/exporter-toolkit v0.5.1/go.mod h1:OCkM4805mmisBhLmVFw858QYi3v0wKdY6/UxrT0pZVg= github.com/prometheus/exporter-toolkit v0.6.1/go.mod h1:ZUBIj498ePooX9t/2xtDjeQYwvRpiPP2lh5u4iblj2g= -github.com/prometheus/exporter-toolkit v0.7.0 h1:XtYeVeeC5daG4txbc9+mieKq+/AK4gtIBLl9Mulrjnk= -github.com/prometheus/exporter-toolkit v0.7.0/go.mod h1:ZUBIj498ePooX9t/2xtDjeQYwvRpiPP2lh5u4iblj2g= github.com/prometheus/exporter-toolkit v0.7.1 h1:c6RXaK8xBVercEeUQ4tRNL8UGWzDHfvj9dseo1FcK1Y= github.com/prometheus/exporter-toolkit v0.7.1/go.mod h1:ZUBIj498ePooX9t/2xtDjeQYwvRpiPP2lh5u4iblj2g= github.com/prometheus/node_exporter v1.0.0-rc.0.0.20200428091818-01054558c289 h1:dTUS1vaLWq+Y6XKOTnrFpoVsQKLCbCp1OLj24TDi7oM= @@ -2559,8 +2542,6 @@ go.mongodb.org/mongo-driver v1.4.4/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4S go.mongodb.org/mongo-driver v1.4.6/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw= go.mongodb.org/mongo-driver v1.5.2/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw= -go.mongodb.org/mongo-driver v1.7.0 h1:hHrvOBWlWB2c7+8Gh/Xi5jj82AgidK/t7KVXBZ+IyUA= -go.mongodb.org/mongo-driver v1.7.0/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8= go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= go.mongodb.org/mongo-driver v1.8.3 h1:TDKlTkGDKm9kkJVUOAXDK5/fkqKHJVwYQSpoRfB43R4= diff --git a/pkg/services/playlist/model.go b/pkg/services/playlist/model.go index b310edba74c..d65e47479a7 100644 --- a/pkg/services/playlist/model.go +++ b/pkg/services/playlist/model.go @@ -13,11 +13,11 @@ var ( // Playlist model type Playlist struct { - Id int64 `json:"id"` - UID string `json:"uid" xorm:"uid"` - Name string `json:"name"` - Interval string `json:"interval"` - OrgId int64 `json:"-"` + Id int64 `json:"id,omitempty" db:"id"` + UID string `json:"uid" xorm:"uid" db:"uid"` + Name string `json:"name" db:"name"` + Interval string `json:"interval" db:"interval"` + OrgId int64 `json:"-" db:"org_id"` } type PlaylistDTO struct { @@ -39,12 +39,12 @@ type PlaylistItemDTO struct { } type PlaylistItem struct { - Id int64 - PlaylistId int64 - Type string - Value string - Order int - Title string + Id int64 `db:"id"` + PlaylistId int64 `db:"playlist_id"` + Type string `db:"type"` + Value string `db:"value"` + Order int `db:"order"` + Title string `db:"title"` } type Playlists []*Playlist @@ -65,8 +65,7 @@ type CreatePlaylistCommand struct { Name string `json:"name" binding:"Required"` Interval string `json:"interval"` Items []PlaylistItemDTO `json:"items"` - - OrgId int64 `json:"-"` + OrgId int64 `json:"-"` } type DeletePlaylistCommand struct { diff --git a/pkg/services/playlist/playlistimpl/playlist.go b/pkg/services/playlist/playlistimpl/playlist.go index de2a8588051..6646679005d 100644 --- a/pkg/services/playlist/playlistimpl/playlist.go +++ b/pkg/services/playlist/playlistimpl/playlist.go @@ -5,13 +5,21 @@ import ( "github.com/grafana/grafana/pkg/services/playlist" "github.com/grafana/grafana/pkg/services/sqlstore/db" + "github.com/grafana/grafana/pkg/setting" ) type Service struct { store store } -func ProvideService(db db.DB) playlist.Service { +func ProvideService(db db.DB, cfg *setting.Cfg) playlist.Service { + if cfg.IsFeatureToggleEnabled("newDBLibrary") { + return &Service{ + store: &sqlxStore{ + sess: db.GetSqlxSession(), + }, + } + } return &Service{ store: &sqlStore{ db: db, diff --git a/pkg/services/playlist/playlistimpl/sqlx_store.go b/pkg/services/playlist/playlistimpl/sqlx_store.go new file mode 100644 index 00000000000..230efb54bd6 --- /dev/null +++ b/pkg/services/playlist/playlistimpl/sqlx_store.go @@ -0,0 +1,201 @@ +package playlistimpl + +import ( + "context" + "database/sql" + "errors" + + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/playlist" + "github.com/grafana/grafana/pkg/services/sqlstore/session" +) + +type sqlxStore struct { + sess *session.SessionDB +} + +func (s *sqlxStore) Insert(ctx context.Context, cmd *playlist.CreatePlaylistCommand) (*playlist.Playlist, error) { + p := playlist.Playlist{} + var err error + uid, err := newGenerateAndValidateNewPlaylistUid(ctx, s.sess, cmd.OrgId) + if err != nil { + return nil, err + } + + p = playlist.Playlist{ + Name: cmd.Name, + Interval: cmd.Interval, + OrgId: cmd.OrgId, + UID: uid, + } + + err = s.sess.WithTransaction(ctx, func(tx *session.SessionTx) error { + query := `INSERT INTO playlist (name, "interval", org_id, uid) VALUES (?, ?, ?, ?)` + var err error + p.Id, err = tx.ExecWithReturningId(ctx, query, p.Name, p.Interval, p.OrgId, p.UID) + if err != nil { + return err + } + + if len(cmd.Items) > 0 { + playlistItems := make([]playlist.PlaylistItem, 0) + for _, item := range cmd.Items { + playlistItems = append(playlistItems, playlist.PlaylistItem{ + PlaylistId: p.Id, + Type: item.Type, + Value: item.Value, + Order: item.Order, + Title: item.Title, + }) + } + query := `INSERT INTO playlist_item (playlist_id, type, value, title, "order") VALUES (:playlist_id, :type, :value, :title, :order)` + _, err = tx.NamedExec(ctx, query, playlistItems) + if err != nil { + return err + } + } + return nil + }) + + return &p, err +} + +func (s *sqlxStore) Update(ctx context.Context, cmd *playlist.UpdatePlaylistCommand) (*playlist.PlaylistDTO, error) { + dto := playlist.PlaylistDTO{} + + // Get the id of playlist to be updated with orgId and UID + existingPlaylist, err := s.Get(ctx, &playlist.GetPlaylistByUidQuery{UID: cmd.UID, OrgId: cmd.OrgId}) + if err != nil { + return nil, err + } + + // Create object to be update to + p := playlist.Playlist{ + Id: existingPlaylist.Id, + UID: cmd.UID, + OrgId: cmd.OrgId, + Name: cmd.Name, + Interval: cmd.Interval, + } + + err = s.sess.WithTransaction(ctx, func(tx *session.SessionTx) error { + query := `UPDATE playlist SET uid=:uid, org_id=:org_id, name=:name, "interval"=:interval WHERE id=:id` + _, err = tx.NamedExec(ctx, query, p) + if err != nil { + return err + } + + if _, err = tx.Exec(ctx, "DELETE FROM playlist_item WHERE playlist_id = ?", p.Id); err != nil { + return err + } + + playlistItems := make([]playlist.PlaylistItem, 0) + + for index, item := range cmd.Items { + playlistItems = append(playlistItems, playlist.PlaylistItem{ + PlaylistId: p.Id, + Type: item.Type, + Value: item.Value, + Order: index + 1, + Title: item.Title, + }) + } + query = `INSERT INTO playlist_item (playlist_id, type, value, title, "order") VALUES (:playlist_id, :type, :value, :title, :order)` + _, err = tx.NamedExec(ctx, query, playlistItems) + return err + }) + + return &dto, err +} + +func (s *sqlxStore) Get(ctx context.Context, query *playlist.GetPlaylistByUidQuery) (*playlist.Playlist, error) { + if query.UID == "" || query.OrgId == 0 { + return nil, playlist.ErrCommandValidationFailed + } + + p := playlist.Playlist{} + err := s.sess.Get(ctx, &p, "SELECT * FROM playlist WHERE uid=? AND org_id=?", query.UID, query.OrgId) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, playlist.ErrPlaylistNotFound + } + return nil, err + } + return &p, err +} + +func (s *sqlxStore) Delete(ctx context.Context, cmd *playlist.DeletePlaylistCommand) error { + if cmd.UID == "" || cmd.OrgId == 0 { + return playlist.ErrCommandValidationFailed + } + + p := playlist.Playlist{} + if err := s.sess.Get(ctx, &p, "SELECT * FROM playlist WHERE uid=? AND org_id=?", cmd.UID, cmd.OrgId); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil + } + return err + } + + err := s.sess.WithTransaction(ctx, func(tx *session.SessionTx) error { + if _, err := tx.Exec(ctx, "DELETE FROM playlist WHERE uid = ? and org_id = ?", cmd.UID, cmd.OrgId); err != nil { + return err + } + + if _, err := tx.Exec(ctx, "DELETE FROM playlist_item WHERE playlist_id = ?", p.Id); err != nil { + return err + } + return nil + }) + + return err +} + +func (s *sqlxStore) List(ctx context.Context, query *playlist.GetPlaylistsQuery) (playlist.Playlists, error) { + playlists := make(playlist.Playlists, 0) + if query.OrgId == 0 { + return playlists, playlist.ErrCommandValidationFailed + } + + var err error + if query.Name == "" { + err = s.sess.Select( + ctx, &playlists, "SELECT * FROM playlist WHERE org_id = ? LIMIT ?", query.OrgId, query.Limit) + } else { + err = s.sess.Select( + ctx, &playlists, "SELECT * FROM playlist WHERE org_id = ? AND name LIKE ? LIMIT ?", query.OrgId, "%"+query.Name+"%", query.Limit) + } + return playlists, err +} + +func (s *sqlxStore) GetItems(ctx context.Context, query *playlist.GetPlaylistItemsByUidQuery) ([]playlist.PlaylistItem, error) { + var playlistItems = make([]playlist.PlaylistItem, 0) + if query.PlaylistUID == "" || query.OrgId == 0 { + return playlistItems, models.ErrCommandValidationFailed + } + + var p = playlist.Playlist{} + err := s.sess.Get(ctx, &p, "SELECT * FROM playlist WHERE uid=? AND org_id=?", query.PlaylistUID, query.OrgId) + if err != nil { + return playlistItems, err + } + + err = s.sess.Select(ctx, &playlistItems, "SELECT * FROM playlist_item WHERE playlist_id=?", p.Id) + return playlistItems, err +} + +func newGenerateAndValidateNewPlaylistUid(ctx context.Context, sess *session.SessionDB, orgId int64) (string, error) { + for i := 0; i < 3; i++ { + uid := generateNewUid() + p := playlist.Playlist{OrgId: orgId, UID: uid} + err := sess.Get(ctx, &p, "SELECT * FROM playlist WHERE uid=? AND org_id=?", uid, orgId) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return uid, nil + } + return "", err + } + } + + return "", models.ErrPlaylistFailedGenerateUniqueUid +} diff --git a/pkg/services/playlist/playlistimpl/sqlx_store_test.go b/pkg/services/playlist/playlistimpl/sqlx_store_test.go new file mode 100644 index 00000000000..efa7fd218d3 --- /dev/null +++ b/pkg/services/playlist/playlistimpl/sqlx_store_test.go @@ -0,0 +1,13 @@ +package playlistimpl + +import ( + "testing" + + "github.com/grafana/grafana/pkg/services/sqlstore" +) + +func TestIntegrationSQLxPlaylistDataAccess(t *testing.T) { + testIntegrationPlaylistDataAccess(t, func(ss *sqlstore.SQLStore) store { + return &sqlxStore{sess: ss.GetSqlxSession()} + }) +} diff --git a/pkg/services/playlist/playlistimpl/store.go b/pkg/services/playlist/playlistimpl/store.go index 121e3144ffc..e3f86109efe 100644 --- a/pkg/services/playlist/playlistimpl/store.go +++ b/pkg/services/playlist/playlistimpl/store.go @@ -3,11 +3,7 @@ package playlistimpl import ( "context" - "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/playlist" - "github.com/grafana/grafana/pkg/services/sqlstore" - "github.com/grafana/grafana/pkg/services/sqlstore/db" - "github.com/grafana/grafana/pkg/util" ) type store interface { @@ -18,209 +14,3 @@ type store interface { List(context.Context, *playlist.GetPlaylistsQuery) (playlist.Playlists, error) Update(context.Context, *playlist.UpdatePlaylistCommand) (*playlist.PlaylistDTO, error) } - -type sqlStore struct { - db db.DB -} - -func (s *sqlStore) Insert(ctx context.Context, cmd *playlist.CreatePlaylistCommand) (*playlist.Playlist, error) { - p := playlist.Playlist{} - err := s.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { - uid, err := generateAndValidateNewPlaylistUid(sess, cmd.OrgId) - if err != nil { - return err - } - - p = playlist.Playlist{ - Name: cmd.Name, - Interval: cmd.Interval, - OrgId: cmd.OrgId, - UID: uid, - } - - _, err = sess.Insert(&p) - if err != nil { - return err - } - - playlistItems := make([]playlist.PlaylistItem, 0) - for _, item := range cmd.Items { - playlistItems = append(playlistItems, playlist.PlaylistItem{ - PlaylistId: p.Id, - Type: item.Type, - Value: item.Value, - Order: item.Order, - Title: item.Title, - }) - } - - _, err = sess.Insert(&playlistItems) - - return err - }) - return &p, err -} - -func (s *sqlStore) Update(ctx context.Context, cmd *playlist.UpdatePlaylistCommand) (*playlist.PlaylistDTO, error) { - dto := playlist.PlaylistDTO{} - err := s.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { - p := playlist.Playlist{ - UID: cmd.UID, - OrgId: cmd.OrgId, - Name: cmd.Name, - Interval: cmd.Interval, - } - - existingPlaylist := playlist.Playlist{UID: cmd.UID, OrgId: cmd.OrgId} - _, err := sess.Get(&existingPlaylist) - if err != nil { - return err - } - p.Id = existingPlaylist.Id - - dto = playlist.PlaylistDTO{ - - Id: p.Id, - UID: p.UID, - OrgId: p.OrgId, - Name: p.Name, - Interval: p.Interval, - } - - _, err = sess.Where("id=?", p.Id).Cols("name", "interval").Update(&p) - if err != nil { - return err - } - - rawSQL := "DELETE FROM playlist_item WHERE playlist_id = ?" - _, err = sess.Exec(rawSQL, p.Id) - - if err != nil { - return err - } - - playlistItems := make([]models.PlaylistItem, 0) - - for index, item := range cmd.Items { - playlistItems = append(playlistItems, models.PlaylistItem{ - PlaylistId: p.Id, - Type: item.Type, - Value: item.Value, - Order: index + 1, - Title: item.Title, - }) - } - - _, err = sess.Insert(&playlistItems) - return err - }) - return &dto, err -} - -func (s *sqlStore) Get(ctx context.Context, query *playlist.GetPlaylistByUidQuery) (*playlist.Playlist, error) { - if query.UID == "" || query.OrgId == 0 { - return nil, playlist.ErrCommandValidationFailed - } - - p := playlist.Playlist{} - err := s.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { - p = playlist.Playlist{UID: query.UID, OrgId: query.OrgId} - exists, err := sess.Get(&p) - if !exists { - return playlist.ErrPlaylistNotFound - } - - return err - }) - return &p, err -} - -func (s *sqlStore) Delete(ctx context.Context, cmd *playlist.DeletePlaylistCommand) error { - if cmd.UID == "" || cmd.OrgId == 0 { - return playlist.ErrCommandValidationFailed - } - - return s.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { - playlist := playlist.Playlist{UID: cmd.UID, OrgId: cmd.OrgId} - _, err := sess.Get(&playlist) - if err != nil { - return err - } - - var rawPlaylistSQL = "DELETE FROM playlist WHERE uid = ? and org_id = ?" - _, err = sess.Exec(rawPlaylistSQL, cmd.UID, cmd.OrgId) - if err != nil { - return err - } - - var rawItemSQL = "DELETE FROM playlist_item WHERE playlist_id = ?" - _, err = sess.Exec(rawItemSQL, playlist.Id) - - return err - }) -} - -func (s *sqlStore) List(ctx context.Context, query *playlist.GetPlaylistsQuery) (playlist.Playlists, error) { - playlists := make(playlist.Playlists, 0) - if query.OrgId == 0 { - return playlists, playlist.ErrCommandValidationFailed - } - - err := s.db.WithDbSession(ctx, func(dbSess *sqlstore.DBSession) error { - sess := dbSess.Limit(query.Limit) - - if query.Name != "" { - sess.Where("name LIKE ?", "%"+query.Name+"%") - } - - sess.Where("org_id = ?", query.OrgId) - err := sess.Find(&playlists) - - return err - }) - return playlists, err -} - -func (s *sqlStore) GetItems(ctx context.Context, query *playlist.GetPlaylistItemsByUidQuery) ([]playlist.PlaylistItem, error) { - var playlistItems = make([]playlist.PlaylistItem, 0) - err := s.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { - if query.PlaylistUID == "" || query.OrgId == 0 { - return models.ErrCommandValidationFailed - } - - // getQuery the playlist Id - getQuery := &playlist.GetPlaylistByUidQuery{UID: query.PlaylistUID, OrgId: query.OrgId} - p, err := s.Get(ctx, getQuery) - if err != nil { - return err - } - - err = sess.Where("playlist_id=?", p.Id).Find(&playlistItems) - - return err - }) - return playlistItems, err -} - -// generateAndValidateNewPlaylistUid generates a playlistUID and verifies that -// the uid isn't already in use. This is deliberately overly cautious, since users -// can also specify playlist uids during provisioning. -func generateAndValidateNewPlaylistUid(sess *sqlstore.DBSession, orgId int64) (string, error) { - for i := 0; i < 3; i++ { - uid := generateNewUid() - - playlist := models.Playlist{OrgId: orgId, UID: uid} - exists, err := sess.Get(&playlist) - if err != nil { - return "", err - } - - if !exists { - return uid, nil - } - } - - return "", models.ErrPlaylistFailedGenerateUniqueUid -} - -var generateNewUid func() string = util.GenerateShortUID diff --git a/pkg/services/playlist/playlistimpl/store_test.go b/pkg/services/playlist/playlistimpl/store_test.go index 5d63b3a9f54..16d2f9a5ff8 100644 --- a/pkg/services/playlist/playlistimpl/store_test.go +++ b/pkg/services/playlist/playlistimpl/store_test.go @@ -9,12 +9,15 @@ import ( "github.com/stretchr/testify/require" ) -func TestIntegrationPlaylistDataAccess(t *testing.T) { +type getStore func(*sqlstore.SQLStore) store + +func testIntegrationPlaylistDataAccess(t *testing.T, fn getStore) { if testing.Short() { t.Skip("skipping integration test") } + ss := sqlstore.InitTestDB(t) - playlistStore := sqlStore{db: ss} + playlistStore := fn(ss) t.Run("Can create playlist", func(t *testing.T) { items := []playlist.PlaylistItemDTO{ @@ -26,18 +29,18 @@ func TestIntegrationPlaylistDataAccess(t *testing.T) { require.NoError(t, err) uid := p.UID + t.Run("Can get playlist", func(t *testing.T) { + get := &playlist.GetPlaylistByUidQuery{UID: uid, OrgId: 1} + pl, err := playlistStore.Get(context.Background(), get) + require.NoError(t, err) + require.Equal(t, p.Id, pl.Id) + }) + t.Run("Can get playlist items", func(t *testing.T) { get := &playlist.GetPlaylistItemsByUidQuery{PlaylistUID: uid, OrgId: 1} storedPlaylistItems, err := playlistStore.GetItems(context.Background(), get) require.NoError(t, err) - require.Equal(t, len(storedPlaylistItems), len(items)) - }) - - t.Run("Get playlist that doesn't exist", func(t *testing.T) { - get := &playlist.GetPlaylistByUidQuery{UID: "unknown", OrgId: 1} - _, err := playlistStore.Get(context.Background(), get) - require.Error(t, err) - require.ErrorIs(t, err, playlist.ErrPlaylistNotFound) + require.Equal(t, len(items), len(storedPlaylistItems)) }) t.Run("Can update playlist", func(t *testing.T) { @@ -56,14 +59,49 @@ func TestIntegrationPlaylistDataAccess(t *testing.T) { require.NoError(t, err) getQuery := playlist.GetPlaylistByUidQuery{UID: uid, OrgId: 1} - p, err := playlistStore.Get(context.Background(), &getQuery) + _, err := playlistStore.Get(context.Background(), &getQuery) require.Error(t, err) - require.Equal(t, uid, p.UID, "playlist should've been removed") require.ErrorIs(t, err, playlist.ErrPlaylistNotFound) }) }) - t.Run("Delete playlist that doesn't exist", func(t *testing.T) { + t.Run("Search playlist", func(t *testing.T) { + items := []playlist.PlaylistItemDTO{ + {Title: "graphite", Value: "graphite", Type: "dashboard_by_tag"}, + {Title: "Backend response times", Value: "3", Type: "dashboard_by_id"}, + } + pl1 := playlist.CreatePlaylistCommand{Name: "NYC office", Interval: "10m", OrgId: 1, Items: items} + pl2 := playlist.CreatePlaylistCommand{Name: "NICE office", Interval: "10m", OrgId: 1, Items: items} + pl3 := playlist.CreatePlaylistCommand{Name: "NICE office", Interval: "10m", OrgId: 2, Items: items} + _, err := playlistStore.Insert(context.Background(), &pl1) + require.NoError(t, err) + _, err = playlistStore.Insert(context.Background(), &pl2) + require.NoError(t, err) + _, err = playlistStore.Insert(context.Background(), &pl3) + require.NoError(t, err) + + t.Run("With Org ID", func(t *testing.T) { + qr := playlist.GetPlaylistsQuery{Limit: 100, OrgId: 1} + res, err := playlistStore.List(context.Background(), &qr) + + require.NoError(t, err) + require.Equal(t, 2, len(res)) + }) + t.Run("With Limit", func(t *testing.T) { + qr := playlist.GetPlaylistsQuery{Limit: 1, Name: "office", OrgId: 1} + res, err := playlistStore.List(context.Background(), &qr) + require.NoError(t, err) + require.Equal(t, 1, len(res)) + }) + t.Run("With Org ID and Name", func(t *testing.T) { + qr := playlist.GetPlaylistsQuery{Limit: 100, Name: "office", OrgId: 1} + res, err := playlistStore.List(context.Background(), &qr) + require.NoError(t, err) + require.Equal(t, 2, len(res)) + }) + }) + + t.Run("Delete playlist that doesn't exist, should not return error", func(t *testing.T) { deleteQuery := playlist.DeletePlaylistCommand{UID: "654312", OrgId: 1} err := playlistStore.Delete(context.Background(), &deleteQuery) require.NoError(t, err) diff --git a/pkg/services/playlist/playlistimpl/xorm_store.go b/pkg/services/playlist/playlistimpl/xorm_store.go new file mode 100644 index 00000000000..1a51bd25b51 --- /dev/null +++ b/pkg/services/playlist/playlistimpl/xorm_store.go @@ -0,0 +1,216 @@ +package playlistimpl + +import ( + "context" + + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/playlist" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/sqlstore/db" + "github.com/grafana/grafana/pkg/util" +) + +type sqlStore struct { + db db.DB +} + +func (s *sqlStore) Insert(ctx context.Context, cmd *playlist.CreatePlaylistCommand) (*playlist.Playlist, error) { + p := playlist.Playlist{} + err := s.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { + uid, err := generateAndValidateNewPlaylistUid(sess, cmd.OrgId) + if err != nil { + return err + } + + p = playlist.Playlist{ + Name: cmd.Name, + Interval: cmd.Interval, + OrgId: cmd.OrgId, + UID: uid, + } + + _, err = sess.Insert(&p) + if err != nil { + return err + } + + playlistItems := make([]playlist.PlaylistItem, 0) + for _, item := range cmd.Items { + playlistItems = append(playlistItems, playlist.PlaylistItem{ + PlaylistId: p.Id, + Type: item.Type, + Value: item.Value, + Order: item.Order, + Title: item.Title, + }) + } + + _, err = sess.Insert(&playlistItems) + + return err + }) + return &p, err +} + +func (s *sqlStore) Update(ctx context.Context, cmd *playlist.UpdatePlaylistCommand) (*playlist.PlaylistDTO, error) { + dto := playlist.PlaylistDTO{} + err := s.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { + p := playlist.Playlist{ + UID: cmd.UID, + OrgId: cmd.OrgId, + Name: cmd.Name, + Interval: cmd.Interval, + } + + existingPlaylist := playlist.Playlist{UID: cmd.UID, OrgId: cmd.OrgId} + _, err := sess.Get(&existingPlaylist) + if err != nil { + return err + } + p.Id = existingPlaylist.Id + + dto = playlist.PlaylistDTO{ + + Id: p.Id, + UID: p.UID, + OrgId: p.OrgId, + Name: p.Name, + Interval: p.Interval, + } + + _, err = sess.Where("id=?", p.Id).Cols("name", "interval").Update(&p) + if err != nil { + return err + } + + rawSQL := "DELETE FROM playlist_item WHERE playlist_id = ?" + _, err = sess.Exec(rawSQL, p.Id) + + if err != nil { + return err + } + + playlistItems := make([]models.PlaylistItem, 0) + + for index, item := range cmd.Items { + playlistItems = append(playlistItems, models.PlaylistItem{ + PlaylistId: p.Id, + Type: item.Type, + Value: item.Value, + Order: index + 1, + Title: item.Title, + }) + } + + _, err = sess.Insert(&playlistItems) + return err + }) + return &dto, err +} + +func (s *sqlStore) Get(ctx context.Context, query *playlist.GetPlaylistByUidQuery) (*playlist.Playlist, error) { + if query.UID == "" || query.OrgId == 0 { + return nil, playlist.ErrCommandValidationFailed + } + + p := playlist.Playlist{} + err := s.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + p = playlist.Playlist{UID: query.UID, OrgId: query.OrgId} + exists, err := sess.Get(&p) + if !exists { + return playlist.ErrPlaylistNotFound + } + + return err + }) + return &p, err +} + +func (s *sqlStore) Delete(ctx context.Context, cmd *playlist.DeletePlaylistCommand) error { + if cmd.UID == "" || cmd.OrgId == 0 { + return playlist.ErrCommandValidationFailed + } + + return s.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { + playlist := playlist.Playlist{UID: cmd.UID, OrgId: cmd.OrgId} + _, err := sess.Get(&playlist) + if err != nil { + return err + } + + var rawPlaylistSQL = "DELETE FROM playlist WHERE uid = ? and org_id = ?" + _, err = sess.Exec(rawPlaylistSQL, cmd.UID, cmd.OrgId) + if err != nil { + return err + } + + var rawItemSQL = "DELETE FROM playlist_item WHERE playlist_id = ?" + _, err = sess.Exec(rawItemSQL, playlist.Id) + + return err + }) +} + +func (s *sqlStore) List(ctx context.Context, query *playlist.GetPlaylistsQuery) (playlist.Playlists, error) { + playlists := make(playlist.Playlists, 0) + if query.OrgId == 0 { + return playlists, playlist.ErrCommandValidationFailed + } + + err := s.db.WithDbSession(ctx, func(dbSess *sqlstore.DBSession) error { + sess := dbSess.Limit(query.Limit) + + if query.Name != "" { + sess.Where("name LIKE ?", "%"+query.Name+"%") + } + + sess.Where("org_id = ?", query.OrgId) + err := sess.Find(&playlists) + + return err + }) + return playlists, err +} + +func (s *sqlStore) GetItems(ctx context.Context, query *playlist.GetPlaylistItemsByUidQuery) ([]playlist.PlaylistItem, error) { + var playlistItems = make([]playlist.PlaylistItem, 0) + if query.PlaylistUID == "" || query.OrgId == 0 { + return playlistItems, models.ErrCommandValidationFailed + } + err := s.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + // getQuery the playlist Id + getQuery := &playlist.GetPlaylistByUidQuery{UID: query.PlaylistUID, OrgId: query.OrgId} + p, err := s.Get(ctx, getQuery) + if err != nil { + return err + } + + err = sess.Where("playlist_id=?", p.Id).Find(&playlistItems) + + return err + }) + return playlistItems, err +} + +// generateAndValidateNewPlaylistUid generates a playlistUID and verifies that +// the uid isn't already in use. This is deliberately overly cautious, since users +// can also specify playlist uids during provisioning. +func generateAndValidateNewPlaylistUid(sess *sqlstore.DBSession, orgId int64) (string, error) { + for i := 0; i < 3; i++ { + uid := generateNewUid() + + playlist := models.Playlist{OrgId: orgId, UID: uid} + exists, err := sess.Get(&playlist) + if err != nil { + return "", err + } + + if !exists { + return uid, nil + } + } + + return "", models.ErrPlaylistFailedGenerateUniqueUid +} + +var generateNewUid func() string = util.GenerateShortUID diff --git a/pkg/services/playlist/playlistimpl/xorm_store_test.go b/pkg/services/playlist/playlistimpl/xorm_store_test.go new file mode 100644 index 00000000000..ef9a53e06d6 --- /dev/null +++ b/pkg/services/playlist/playlistimpl/xorm_store_test.go @@ -0,0 +1,13 @@ +package playlistimpl + +import ( + "testing" + + "github.com/grafana/grafana/pkg/services/sqlstore" +) + +func TestIntegrationXormPlaylistDataAccess(t *testing.T) { + testIntegrationPlaylistDataAccess(t, func(ss *sqlstore.SQLStore) store { + return &sqlStore{db: ss} + }) +} diff --git a/pkg/services/sqlstore/db/db.go b/pkg/services/sqlstore/db/db.go index 81ce357f032..6e0cc94bf6a 100644 --- a/pkg/services/sqlstore/db/db.go +++ b/pkg/services/sqlstore/db/db.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + "github.com/grafana/grafana/pkg/services/sqlstore/session" ) type DB interface { @@ -12,4 +13,5 @@ type DB interface { WithDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error NewSession(ctx context.Context) *sqlstore.DBSession GetDialect() migrator.Dialect + GetSqlxSession() *session.SessionDB } diff --git a/pkg/services/sqlstore/mockstore/mockstore.go b/pkg/services/sqlstore/mockstore/mockstore.go index 5a1bbdca276..18233ea088a 100644 --- a/pkg/services/sqlstore/mockstore/mockstore.go +++ b/pkg/services/sqlstore/mockstore/mockstore.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/services/apikey" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + "github.com/grafana/grafana/pkg/services/sqlstore/session" "github.com/grafana/grafana/pkg/services/user" ) @@ -458,3 +459,7 @@ func (m *SQLStoreMock) IsAdminOfTeams(ctx context.Context, query *models.IsAdmin func (m *SQLStoreMock) GetAPIKeyByHash(ctx context.Context, hash string) (*apikey.APIKey, error) { return nil, m.ExpectedError } + +func (m *SQLStoreMock) GetSqlxSession() *session.SessionDB { + return nil +} diff --git a/pkg/services/sqlstore/session/session.go b/pkg/services/sqlstore/session/session.go new file mode 100644 index 00000000000..fdadf5f55c8 --- /dev/null +++ b/pkg/services/sqlstore/session/session.go @@ -0,0 +1,112 @@ +package session + +import ( + "context" + "database/sql" + "fmt" + + "github.com/jmoiron/sqlx" +) + +type Session interface { + Get(ctx context.Context, dest interface{}, query string, args ...interface{}) error + Exec(ctx context.Context, query string, args ...interface{}) (sql.Result, error) +} + +type SessionDB struct { + sqlxdb *sqlx.DB +} + +func GetSession(sqlxdb *sqlx.DB) *SessionDB { + return &SessionDB{sqlxdb: sqlxdb} +} + +func (gs *SessionDB) Get(ctx context.Context, dest interface{}, query string, args ...interface{}) error { + return gs.sqlxdb.GetContext(ctx, dest, gs.sqlxdb.Rebind(query), args...) +} + +func (gs *SessionDB) Select(ctx context.Context, dest interface{}, query string, args ...interface{}) error { + return gs.sqlxdb.SelectContext(ctx, dest, gs.sqlxdb.Rebind(query), args...) +} + +func (gs *SessionDB) Exec(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { + return gs.sqlxdb.ExecContext(ctx, gs.sqlxdb.Rebind(query), args...) +} + +func (gs *SessionDB) driverName() string { + return gs.sqlxdb.DriverName() +} + +func (gs *SessionDB) Beginx() (*SessionTx, error) { + tx, err := gs.sqlxdb.Beginx() + return &SessionTx{sqlxtx: tx}, err +} + +func (gs *SessionDB) WithTransaction(ctx context.Context, callback func(*SessionTx) error) error { + tx, err := gs.Beginx() + if err != nil { + return err + } + err = callback(tx) + if err != nil { + if rbErr := tx.sqlxtx.Rollback(); rbErr != nil { + return fmt.Errorf("tx err: %v, rb err: %v", err, rbErr) + } + return err + } + return tx.sqlxtx.Commit() +} + +func (gs *SessionDB) ExecWithReturningId(ctx context.Context, query string, args ...interface{}) (int64, error) { + return execWithReturningId(ctx, gs.driverName(), query, gs, args...) +} + +type SessionTx struct { + sqlxtx *sqlx.Tx +} + +func (gtx *SessionTx) NamedExec(ctx context.Context, query string, arg interface{}) (sql.Result, error) { + return gtx.sqlxtx.NamedExecContext(ctx, gtx.sqlxtx.Rebind(query), arg) +} + +func (gtx *SessionTx) Exec(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { + return gtx.sqlxtx.ExecContext(ctx, gtx.sqlxtx.Rebind(query), args...) +} + +func (gtx *SessionTx) Get(ctx context.Context, dest interface{}, query string, args ...interface{}) error { + return gtx.sqlxtx.GetContext(ctx, dest, gtx.sqlxtx.Rebind(query), args...) +} + +func (gtx *SessionTx) driverName() string { + return gtx.sqlxtx.DriverName() +} + +func (gtx *SessionTx) ExecWithReturningId(ctx context.Context, query string, args ...interface{}) (int64, error) { + return execWithReturningId(ctx, gtx.driverName(), query, gtx, args...) +} + +func execWithReturningId(ctx context.Context, driverName string, query string, sess Session, args ...interface{}) (int64, error) { + supported := false + var id int64 + if driverName == "postgres" { + query = fmt.Sprintf("%s RETURNING id", query) + supported = true + } + if supported { + err := sess.Get(ctx, &id, query, args...) + if err != nil { + return id, err + } + return id, nil + } else { + res, err := sess.Exec(ctx, query, args...) + if err != nil { + return id, err + } + id, err = res.LastInsertId() + if err != nil { + return id, err + } + } + return id, nil +} diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 0306226fc2b..fed644070d8 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -12,6 +12,7 @@ import ( "time" "github.com/go-sql-driver/mysql" + "github.com/jmoiron/sqlx" _ "github.com/lib/pq" "github.com/prometheus/client_golang/prometheus" "xorm.io/xorm" @@ -27,6 +28,7 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/sqlstore/migrations" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + "github.com/grafana/grafana/pkg/services/sqlstore/session" "github.com/grafana/grafana/pkg/services/sqlstore/sqlutil" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" @@ -44,6 +46,7 @@ type ContextSessionKey struct{} type SQLStore struct { Cfg *setting.Cfg + sqlxsession *session.SessionDB CacheService *localcache.CacheService bus bus.Bus @@ -183,6 +186,13 @@ func (ss *SQLStore) Bus() bus.Bus { return ss.bus } +func (ss *SQLStore) GetSqlxSession() *session.SessionDB { + if ss.sqlxsession == nil { + ss.sqlxsession = session.GetSession(sqlx.NewDb(ss.engine.DB().DB, ss.GetDialect().DriverName())) + } + return ss.sqlxsession +} + func (ss *SQLStore) ensureMainOrgAndAdminUser() error { ctx := context.Background() err := ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { @@ -285,7 +295,7 @@ func (ss *SQLStore) buildConnectionString() (string, error) { cnnstr += fmt.Sprintf("&tx_isolation=%s", val) } - if ss.Cfg.IsFeatureToggleEnabled("mysqlAnsiQuotes") { + if ss.Cfg.IsFeatureToggleEnabled("mysqlAnsiQuotes") || ss.Cfg.IsFeatureToggleEnabled("newDBLibrary") { cnnstr += "&sql_mode='ANSI_QUOTES'" } diff --git a/pkg/services/sqlstore/sqlutil/sqlutil.go b/pkg/services/sqlstore/sqlutil/sqlutil.go index 240aa1470f2..18da1fda0a4 100644 --- a/pkg/services/sqlstore/sqlutil/sqlutil.go +++ b/pkg/services/sqlstore/sqlutil/sqlutil.go @@ -28,10 +28,7 @@ func MySQLTestDB() TestDB { if port == "" { port = "3306" } - conn_str := fmt.Sprintf("grafana:password@tcp(%s:%s)/grafana_tests?collation=utf8mb4_unicode_ci", host, port) - if _, present := os.LookupEnv("MYSQL_ANSI_QUOTES"); present { - conn_str += "&sql_mode='ANSI_QUOTES'" - } + conn_str := fmt.Sprintf("grafana:password@tcp(%s:%s)/grafana_tests?collation=utf8mb4_unicode_ci&sql_mode='ANSI_QUOTES'", host, port) return TestDB{ DriverName: "mysql", ConnStr: conn_str, diff --git a/pkg/services/sqlstore/store.go b/pkg/services/sqlstore/store.go index b760381234a..e7cfa4c265e 100644 --- a/pkg/services/sqlstore/store.go +++ b/pkg/services/sqlstore/store.go @@ -6,6 +6,7 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + "github.com/grafana/grafana/pkg/services/sqlstore/session" "github.com/grafana/grafana/pkg/services/user" ) @@ -77,4 +78,5 @@ type Store interface { GetDBHealthQuery(ctx context.Context, query *models.GetDBHealthQuery) error SearchOrgs(ctx context.Context, query *models.SearchOrgsQuery) error IsAdminOfTeams(ctx context.Context, query *models.IsAdminOfTeamsQuery) error + GetSqlxSession() *session.SessionDB }