diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 0c10036bdd2..60d1418a526 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -173,6 +173,7 @@ Experimental features might be changed or removed without prior notice. | `newFolderPicker` | Enables the nested folder picker without having nested folders enabled | | `onPremToCloudMigrations` | In-development feature that will allow users to easily migrate their on-prem Grafana instances to Grafana Cloud. | | `promQLScope` | In-development feature that will allow injection of labels into prometheus queries. | +| `sqlExpressions` | Enables using SQL and DuckDB functions as Expressions. | | `nodeGraphDotLayout` | Changed the layout algorithm for the node graph | | `newPDFRendering` | New implementation for the dashboard to PDF rendering | | `kubernetesAggregator` | Enable grafana aggregator | diff --git a/go.mod b/go.mod index 0c8c0e11ae5..061044a1a84 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana -go 1.21 +go 1.21.0 // Override docker/docker to avoid: // go: github.com/drone-runners/drone-runner-docker@v1.8.2 requires @@ -92,6 +92,7 @@ require ( github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 // @grafana/alerting-squad-backend github.com/robfig/cron/v3 v3.0.1 // @grafana/backend-platform github.com/russellhaering/goxmldsig v1.4.0 // @grafana/backend-platform + github.com/scottlepp/go-duck v0.0.15 // @grafana/grafana-app-platform-squad github.com/stretchr/testify v1.8.4 // @grafana/backend-platform github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf // @grafana/backend-platform github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f // @grafana/backend-platform @@ -463,6 +464,9 @@ require github.com/spyzhov/ajson v0.9.0 // @grafana/grafana-app-platform-squad require github.com/fullstorydev/grpchan v1.1.1 // @grafana/backend-platform require ( + github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c // indirect + github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 // indirect + github.com/apache/thrift v0.18.1 // indirect github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4 // @grafana/grafana-app-platform-squad github.com/grafana/grafana/pkg/apiserver v0.0.0-20240226124929-648abdbd0ea4 // @grafana/grafana-app-platform-squad ) @@ -471,12 +475,17 @@ require ( github.com/bufbuild/protocompile v0.4.0 // indirect github.com/grafana/sqlds/v3 v3.2.0 // indirect github.com/jhump/protoreflect v1.15.1 // indirect + github.com/klauspost/asmfmt v1.3.2 // indirect + github.com/krasun/gosqlparser v1.0.5 // @grafana/grafana-app-platform-squad + github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect + github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mithrandie/csvq v1.17.10 // indirect github.com/mithrandie/csvq-driver v1.6.8 // indirect github.com/mithrandie/go-file/v2 v2.1.0 // indirect github.com/mithrandie/go-text v1.5.4 // indirect github.com/mithrandie/ternary v1.1.1 // indirect + github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 // @grafana/grafana-app-platform-squad ) // Use fork of crewjam/saml with fixes for some issues until changes get merged into upstream diff --git a/go.sum b/go.sum index 988032666be..a6aa605d076 100644 --- a/go.sum +++ b/go.sum @@ -1288,6 +1288,7 @@ github.com/GoogleCloudPlatform/cloudsql-proxy v1.29.0/go.mod h1:spvB9eLJH9dutlbP github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c h1:RGWPOewvKIROun94nF7v2cua9qP+thov/7M50KEoeSU= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= @@ -1354,6 +1355,8 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= github.com/apache/arrow/go/arrow v0.0.0-20210223225224-5bea62493d91/go.mod h1:c9sxoIT3YgLxH4UhLOCKaBlEojuMhVYpk4Ntv3opUTQ= +github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 h1:q4dksr6ICHXqG5hm0ZW5IHyeEJXoIJSOZeBLmWPNeIQ= +github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg= @@ -1362,10 +1365,14 @@ github.com/apache/arrow/go/v15 v15.0.0/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+ye github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= +github.com/apache/thrift v0.18.1 h1:lNhK/1nqjbwbiOPDBPFJVKxgDEGSepKuTh6OLiXW8kg= +github.com/apache/thrift v0.18.1/go.mod h1:rdQn/dCcDKEWjjylUeueum4vQEjG2v8v2PqriUnbr+I= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= +github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -2026,6 +2033,7 @@ github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl76 github.com/google/cel-go v0.17.7 h1:6ebJFzu1xO2n7TLtN+UBqShGBhlD85bhvglh5DpcfqQ= github.com/google/cel-go v0.17.7/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v23.5.26+incompatible h1:M9dgRyhJemaM4Sw8+66GHBu8ioaQmyPLg1b8VwK5WJg= github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= @@ -2447,10 +2455,12 @@ github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= @@ -2480,6 +2490,8 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/krasun/gosqlparser v1.0.5 h1:sHaexkxGb9NrAcjZ3mUs6u33iJ9qhR2fH7XrpZekMt8= +github.com/krasun/gosqlparser v1.0.5/go.mod h1:aXCTW1xnPl4qAaNROeqESauGJ8sqhoB4OFEIOVIDYI4= github.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -2586,7 +2598,9 @@ github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJys github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= @@ -2779,6 +2793,7 @@ github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= @@ -2920,6 +2935,9 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh github.com/scaleway/scaleway-sdk-go v1.0.0-beta.21 h1:yWfiTPwYxB0l5fGMhl/G+liULugVIHD9AU77iNLrURQ= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.21/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/scottlepp/go-duck v0.0.14 h1:fKrhE31OiwMJkS8byu0CWUfuWMfdxZYJNp5FUgHil4M= +github.com/scottlepp/go-duck v0.0.14/go.mod h1:GL+hHuKdueJRrFCduwBc7A7TQk+Tetc5BPXPVtduihY= +github.com/scottlepp/go-duck v0.0.15/go.mod h1:GL+hHuKdueJRrFCduwBc7A7TQk+Tetc5BPXPVtduihY= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= @@ -3085,6 +3103,8 @@ github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ= +github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY= github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= github.com/yalue/merged_fs v1.2.2 h1:vXHTpJBluJryju7BBpytr3PDIkzsPMpiEknxVGPhN/I= github.com/yalue/merged_fs v1.2.2/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M= @@ -3992,6 +4012,7 @@ google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxH google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210630183607-d20f26d13c79/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U= google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= diff --git a/go.work.sum b/go.work.sum index 57235ef9f2c..d8aa300bdfa 100644 --- a/go.work.sum +++ b/go.work.sum @@ -566,6 +566,7 @@ github.com/performancecopilot/speed/v4 v4.0.0 h1:VxEDCmdkfbQYDlcr/GC9YoN9PQ6p8ul github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/phpdave11/gofpdf v1.4.2 h1:KPKiIbfwbvC/wOncwhrpRdXVj2CZTCFlw4wnoyjtHfQ= github.com/phpdave11/gofpdi v1.0.13 h1:o61duiW8M9sMlkVXWlvP92sZJtGKENvW3VExs6dZukQ= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/profile v1.2.1 h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE= @@ -596,6 +597,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCL github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e h1:uO75wNGioszjmIzcY/tvdDYKRLVvzggtAmmJkn9j4GQ= +github.com/scottlepp/go-duck v0.0.15 h1:qrSF3pXlXAA4a7uxAfLYajqXLkeBjv8iW1wPdSfkMj0= +github.com/scottlepp/go-duck v0.0.15/go.mod h1:GL+hHuKdueJRrFCduwBc7A7TQk+Tetc5BPXPVtduihY= github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e/go.mod h1:tm/wZFQ8e24NYaBGIlnO2WGCAi67re4HHuOm0sftE/M= github.com/segmentio/parquet-go v0.0.0-20230427215636-d483faba23a5 h1:7CWCjaHrXSUCHrRhIARMGDVKdB82tnPAQMmANeflKOw= github.com/segmentio/parquet-go v0.0.0-20230427215636-d483faba23a5/go.mod h1:+J0xQnJjm8DuQUHBO7t57EnmPbstT6+b45+p3DC9k1Q= @@ -747,6 +750,8 @@ gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= +gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= +gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/telebot.v3 v3.2.1 h1:3I4LohaAyJBiivGmkfB+CiVu7QFOWkuZ4+KHgO/G3rs= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 48f8b5b5f95..0d8527653b8 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -171,6 +171,7 @@ export interface FeatureToggles { onPremToCloudMigrations?: boolean; alertingSaveStatePeriodic?: boolean; promQLScope?: boolean; + sqlExpressions?: boolean; nodeGraphDotLayout?: boolean; groupToNestedTableTransformation?: boolean; newPDFRendering?: boolean; diff --git a/pkg/expr/commands.go b/pkg/expr/commands.go index 32bc9b86361..1f8926102c4 100644 --- a/pkg/expr/commands.go +++ b/pkg/expr/commands.go @@ -324,6 +324,8 @@ const ( TypeClassicConditions // TypeThreshold is the CMDType for checking if a threshold has been crossed TypeThreshold + // TypeSQL is the CMDType for running SQL expressions + TypeSQL ) func (gt CommandType) String() string { @@ -336,6 +338,8 @@ func (gt CommandType) String() string { return "resample" case TypeClassicConditions: return "classic_conditions" + case TypeSQL: + return "sql" default: return "unknown" } @@ -354,6 +358,8 @@ func ParseCommandType(s string) (CommandType, error) { return TypeClassicConditions, nil case "threshold": return TypeThreshold, nil + case "sql": + return TypeSQL, nil default: return TypeUnknown, fmt.Errorf("'%v' is not a recognized expression type", s) } diff --git a/pkg/expr/graph.go b/pkg/expr/graph.go index 2b09143110b..49c90b6221b 100644 --- a/pkg/expr/graph.go +++ b/pkg/expr/graph.go @@ -75,6 +75,8 @@ func (dp *DataPipeline) execute(c context.Context, now time.Time, s *Service) (m executeDSNodesGrouped(c, now, vars, s, dsNodes) } + s.allowLongFrames = hasSqlExpression(*dp) + for _, node := range *dp { if groupByDSFlag && node.NodeType() == TypeDatasourceNode { continue // already executed via executeDSNodesGrouped @@ -266,6 +268,10 @@ func buildGraphEdges(dp *simple.DirectedGraph, registry map[string]Node) error { for _, neededVar := range cmdNode.Command.NeedsVars() { neededNode, ok := registry[neededVar] if !ok { + _, ok := cmdNode.Command.(*SQLCommand) + if ok { + continue + } return fmt.Errorf("unable to find dependent node '%v'", neededVar) } @@ -312,3 +318,37 @@ func GetCommandsFromPipeline[T Command](pipeline DataPipeline) []T { } return results } + +func hasSqlExpression(dp DataPipeline) bool { + for _, node := range dp { + if node.NodeType() == TypeCMDNode { + cmdNode := node.(*CMDNode) + _, ok := cmdNode.Command.(*SQLCommand) + if ok { + return true + } + } + } + return false +} + +// func graphHasSqlExpresssion(dp *simple.DirectedGraph) bool { +// node := dp.Nodes() +// for node.Next() { +// if cmdNode, ok := node.Node().(*CMDNode); ok { +// // res[dpNode.RefID()] = dpNode +// _, ok := cmdNode.Command.(*SQLCommand) +// if ok { +// return true +// } +// } +// // if node.NodeType() == TypeCMDNode { +// // cmdNode := node.(*CMDNode) +// // _, ok := cmdNode.Command.(*SQLCommand) +// // if ok { +// // return true +// // } +// // } +// } +// return false +// } diff --git a/pkg/expr/mathexp/parse/node.go b/pkg/expr/mathexp/parse/node.go index 41feeb70266..02c297dde96 100644 --- a/pkg/expr/mathexp/parse/node.go +++ b/pkg/expr/mathexp/parse/node.go @@ -405,6 +405,8 @@ const ( TypeVariantSet // TypeNoData is a no data response without a known data type. TypeNoData + // TypeTableData is a tabular data response. + TypeTableData ) // String returns a string representation of the ReturnType. @@ -422,6 +424,8 @@ func (f ReturnType) String() string { return "variant" case TypeNoData: return "noData" + case TypeTableData: + return "tableData" default: return "unknown" } diff --git a/pkg/expr/mathexp/types.go b/pkg/expr/mathexp/types.go index ca5069650b7..a5b5ba456a4 100644 --- a/pkg/expr/mathexp/types.go +++ b/pkg/expr/mathexp/types.go @@ -246,3 +246,48 @@ func (s NoData) New() NoData { func NewNoData() NoData { return NoData{data.NewFrame("no data")} } + +// TableData is an untyped no data response. +type TableData struct{ Frame *data.Frame } + +// Type returns the Value type and allows it to fulfill the Value interface. +func (s TableData) Type() parse.ReturnType { return parse.TypeTableData } + +// Value returns the actual value allows it to fulfill the Value interface. +func (s TableData) Value() any { return s } + +func (s TableData) GetLabels() data.Labels { return nil } + +func (s TableData) SetLabels(ls data.Labels) {} + +func (s TableData) GetMeta() any { + return s.Frame.Meta.Custom +} + +func (s TableData) SetMeta(v any) { + m := s.Frame.Meta + if m == nil { + m = &data.FrameMeta{} + s.Frame.SetMeta(m) + } + m.Custom = v +} + +func (s TableData) AddNotice(notice data.Notice) { + m := s.Frame.Meta + if m == nil { + m = &data.FrameMeta{} + s.Frame.SetMeta(m) + } + m.Notices = append(m.Notices, notice) +} + +func (s TableData) AsDataFrame() *data.Frame { return s.Frame } + +func (s TableData) New() TableData { + return NewTableData() +} + +func NewTableData() TableData { + return TableData{data.NewFrame("")} +} diff --git a/pkg/expr/models.go b/pkg/expr/models.go index 4480331a51f..dec7e3ce3a0 100644 --- a/pkg/expr/models.go +++ b/pkg/expr/models.go @@ -24,6 +24,9 @@ const ( // Threshold QueryTypeThreshold QueryType = "threshold" + + // SQL query via DuckDB + QueryTypeSQL QueryType = "sql" ) type MathQuery struct { @@ -69,6 +72,11 @@ type ClassicQuery struct { Conditions []classic.ConditionJSON `json:"conditions"` } +// SQLQuery requires the sqlExpression feature flag +type SQLExpression struct { + Expression string `json:"expression" jsonschema:"minLength=1,example=SELECT * FROM A LIMIT 1"` +} + //------------------------------- // Non-query commands //------------------------------- diff --git a/pkg/expr/nodes.go b/pkg/expr/nodes.go index 3f20267d71a..8ecf303cc4f 100644 --- a/pkg/expr/nodes.go +++ b/pkg/expr/nodes.go @@ -155,6 +155,8 @@ func buildCMDNode(rn *rawNode, toggles featuremgmt.FeatureToggles) (*CMDNode, er node.Command, err = classic.UnmarshalConditionsCmd(rn.Query, rn.RefID) case TypeThreshold: node.Command, err = UnmarshalThresholdCommand(rn, toggles) + case TypeSQL: + node.Command, err = UnmarshalSQLCommand(rn) default: return nil, fmt.Errorf("expression command type '%v' in expression '%v' not implemented", commandType, rn.RefID) } @@ -471,7 +473,8 @@ func convertDataFramesToResults(ctx context.Context, frames data.Frames, datasou logger.Warn("Ignoring InfluxDB data frame due to missing numeric fields") continue } - if schema.Type != data.TimeSeriesTypeWide { + + if schema.Type != data.TimeSeriesTypeWide && !s.allowLongFrames { return "", mathexp.Results{}, fmt.Errorf("input data must be a wide series but got type %s (input refid)", schema.Type) } filtered = append(filtered, frame) @@ -484,20 +487,29 @@ func convertDataFramesToResults(ctx context.Context, frames data.Frames, datasou maybeFixerFn := checkIfSeriesNeedToBeFixed(filtered, datasourceType) - vals := make([]mathexp.Value, 0, totalLen) - for _, frame := range filtered { - series, err := WideToMany(frame, maybeFixerFn) - if err != nil { - return "", mathexp.Results{}, err - } - for _, ser := range series { - vals = append(vals, ser) - } - } dataType := "single frame series" if len(filtered) > 1 { dataType = "multi frame series" } + + vals := make([]mathexp.Value, 0, totalLen) + for _, frame := range filtered { + schema := frame.TimeSeriesSchema() + if schema.Type == data.TimeSeriesTypeWide { + series, err := WideToMany(frame, maybeFixerFn) + if err != nil { + return "", mathexp.Results{}, err + } + for _, ser := range series { + vals = append(vals, ser) + } + } else { + v := mathexp.TableData{Frame: frame} + vals = append(vals, v) + dataType = "single frame" + } + } + return dataType, mathexp.Results{ Values: vals, }, nil diff --git a/pkg/expr/reader.go b/pkg/expr/reader.go index e5563bb95d7..b92e7044859 100644 --- a/pkg/expr/reader.go +++ b/pkg/expr/reader.go @@ -30,6 +30,7 @@ func NewExpressionQueryReader(features featuremgmt.FeatureToggles) (*ExpressionQ } // ReadQuery implements query.TypedQueryHandler. +// nolint:gocyclo func (h *ExpressionQueryReader) ReadQuery( // Properties that have been parsed off the same node common *rawNode, // common query.CommonQueryProperties @@ -102,6 +103,13 @@ func (h *ExpressionQueryReader) ReadQuery( eq.Command, err = classic.NewConditionCmd(common.RefID, q.Conditions) } + case QueryTypeSQL: + q := &SQLExpression{} + err = iter.ReadVal(q) + if err == nil { + eq.Command, err = NewSQLCommand(common.RefID, q.Expression, common.TimeRange) + } + case QueryTypeThreshold: q := &ThresholdQuery{} err = iter.ReadVal(q) diff --git a/pkg/expr/service.go b/pkg/expr/service.go index ffd7e3ffd66..978ee147211 100644 --- a/pkg/expr/service.go +++ b/pkg/expr/service.go @@ -63,8 +63,9 @@ type Service struct { pluginsClient backend.CallResourceHandler - tracer tracing.Tracer - metrics *metrics + tracer tracing.Tracer + metrics *metrics + allowLongFrames bool } type pluginContextProvider interface { diff --git a/pkg/expr/sql/parser.go b/pkg/expr/sql/parser.go new file mode 100644 index 00000000000..d5ea2d1f6be --- /dev/null +++ b/pkg/expr/sql/parser.go @@ -0,0 +1,99 @@ +package sql + +import ( + "errors" + "strings" + + parser "github.com/krasun/gosqlparser" + "github.com/xwb1989/sqlparser" +) + +// TablesList returns a list of tables for the sql statement +func TablesList(rawSQL string) ([]string, error) { + stmt, err := sqlparser.Parse(rawSQL) + if err != nil { + tables, err := parse(rawSQL) + if err != nil { + return parseTables(rawSQL) + } + return tables, nil + } + + tables := []string{} + switch kind := stmt.(type) { + case *sqlparser.Select: + for _, t := range kind.From { + buf := sqlparser.NewTrackedBuffer(nil) + t.Format(buf) + table := buf.String() + if table != "dual" { + tables = append(tables, buf.String()) + } + } + default: + return nil, errors.New("not a select statement") + } + return tables, nil +} + +// uses a simple tokenizer +func parse(rawSQL string) ([]string, error) { + query, err := parser.Parse(rawSQL) + if err != nil { + return nil, err + } + if query.GetType() == parser.StatementSelect { + sel, ok := query.(*parser.Select) + if ok { + return []string{sel.Table}, nil + } + } + return nil, err +} + +func parseTables(rawSQL string) ([]string, error) { + checkSql := strings.ToUpper(rawSQL) + if strings.HasPrefix(checkSql, "SELECT") || strings.HasPrefix(rawSQL, "WITH") { + tables := []string{} + tokens := strings.Split(rawSQL, " ") + checkNext := false + takeNext := false + for _, t := range tokens { + t = strings.ToUpper(t) + t = strings.TrimSpace(t) + + if takeNext { + tables = append(tables, t) + checkNext = false + takeNext = false + continue + } + if checkNext { + if strings.Contains(t, "(") { + checkNext = false + continue + } + if strings.Contains(t, ",") { + values := strings.Split(t, ",") + for _, v := range values { + v := strings.TrimSpace(v) + if v != "" { + tables = append(tables, v) + } else { + takeNext = true + break + } + } + continue + } + tables = append(tables, t) + checkNext = false + } + if t == "FROM" { + checkNext = true + } + } + return tables, nil + } + return nil, errors.New("not a select statement") +} diff --git a/pkg/expr/sql/parser_test.go b/pkg/expr/sql/parser_test.go new file mode 100644 index 00000000000..2c8e43681e6 --- /dev/null +++ b/pkg/expr/sql/parser_test.go @@ -0,0 +1,58 @@ +package sql + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParse(t *testing.T) { + sql := "select * from foo" + tables, err := parseTables((sql)) + assert.Nil(t, err) + + assert.Equal(t, "FOO", tables[0]) +} + +func TestParseWithComma(t *testing.T) { + sql := "select * from foo,bar" + tables, err := parseTables((sql)) + assert.Nil(t, err) + + assert.Equal(t, "FOO", tables[0]) + assert.Equal(t, "BAR", tables[1]) +} + +func TestParseWithCommas(t *testing.T) { + sql := "select * from foo,bar,baz" + tables, err := parseTables((sql)) + assert.Nil(t, err) + + assert.Equal(t, "FOO", tables[0]) + assert.Equal(t, "BAR", tables[1]) + assert.Equal(t, "BAZ", tables[2]) +} + +func TestArray(t *testing.T) { + sql := "SELECT array_value(1, 2, 3)" + tables, err := TablesList((sql)) + assert.Nil(t, err) + + assert.Equal(t, 0, len(tables)) +} + +func TestArray2(t *testing.T) { + sql := "SELECT array_value(1, 2, 3)[2]" + tables, err := TablesList((sql)) + assert.Nil(t, err) + + assert.Equal(t, 0, len(tables)) +} + +func TestXxx(t *testing.T) { + sql := "SELECT [3, 2, 1]::INT[3];" + tables, err := TablesList((sql)) + assert.Nil(t, err) + + assert.Equal(t, 0, len(tables)) +} diff --git a/pkg/expr/sql_command.go b/pkg/expr/sql_command.go new file mode 100644 index 00000000000..ce69bd550d5 --- /dev/null +++ b/pkg/expr/sql_command.go @@ -0,0 +1,107 @@ +package expr + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/scottlepp/go-duck/duck" + + "github.com/grafana/grafana/pkg/expr/mathexp" + "github.com/grafana/grafana/pkg/expr/sql" + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/util/errutil" +) + +// SQLCommand is an expression to run SQL over results +type SQLCommand struct { + query string + varsToQuery []string + timeRange TimeRange + refID string +} + +// NewSQLCommand creates a new SQLCommand. +func NewSQLCommand(refID, rawSQL string, tr TimeRange) (*SQLCommand, error) { + if rawSQL == "" { + return nil, errutil.BadRequest("sql-missing-query", + errutil.WithPublicMessage("missing SQL query")) + } + tables, err := sql.TablesList(rawSQL) + if err != nil { + logger.Warn("invalid sql query", "sql", rawSQL, "error", err) + return nil, errutil.BadRequest("sql-invalid-sql", + errutil.WithPublicMessage("error reading SQL command"), + ) + } + return &SQLCommand{ + query: rawSQL, + varsToQuery: tables, + timeRange: tr, + refID: refID, + }, nil +} + +// UnmarshalSQLCommand creates a SQLCommand from Grafana's frontend query. +func UnmarshalSQLCommand(rn *rawNode) (*SQLCommand, error) { + if rn.TimeRange == nil { + return nil, fmt.Errorf("time range must be specified for refID %s", rn.RefID) + } + + expressionRaw, ok := rn.Query["expression"] + if !ok { + return nil, errors.New("no expression in the query") + } + expression, ok := expressionRaw.(string) + if !ok { + return nil, fmt.Errorf("expected sql expression to be type string, but got type %T", expressionRaw) + } + + return NewSQLCommand(rn.RefID, expression, rn.TimeRange) +} + +// NeedsVars returns the variable names (refIds) that are dependencies +// to execute the command and allows the command to fulfill the Command interface. +func (gr *SQLCommand) NeedsVars() []string { + return gr.varsToQuery +} + +// Execute runs the command and returns the results or an error if the command +// failed to execute. +func (gr *SQLCommand) Execute(ctx context.Context, now time.Time, vars mathexp.Vars, tracer tracing.Tracer) (mathexp.Results, error) { + _, span := tracer.Start(ctx, "SSE.ExecuteSQL") + defer span.End() + + allFrames := []*data.Frame{} + for _, ref := range gr.varsToQuery { + results := vars[ref] + frames := results.Values.AsDataFrames(ref) + allFrames = append(allFrames, frames...) + } + + rsp := mathexp.Results{} + + duckDB := duck.NewInMemoryDB() + var frame = &data.Frame{} + err := duckDB.QueryFramesInto(gr.refID, gr.query, allFrames, frame) + if err != nil { + rsp.Error = err + return rsp, nil + } + + frame.RefID = gr.refID + + if frame.Rows() == 0 { + rsp.Values = mathexp.Values{ + mathexp.NoData{Frame: frame}, + } + } + + rsp.Values = mathexp.Values{ + mathexp.TableData{Frame: frame}, + } + + return rsp, nil +} diff --git a/pkg/expr/sql_command_test.go b/pkg/expr/sql_command_test.go new file mode 100644 index 00000000000..90ba470ae07 --- /dev/null +++ b/pkg/expr/sql_command_test.go @@ -0,0 +1,26 @@ +package expr + +import ( + "strings" + "testing" +) + +func TestNewCommand(t *testing.T) { + cmd, err := NewSQLCommand("a", "select a from foo, bar", nil) + if err != nil && strings.Contains(err.Error(), "feature is not enabled") { + return + } + + if err != nil { + t.Fail() + return + } + + for _, v := range cmd.varsToQuery { + if strings.Contains("foo bar", v) { + continue + } + t.Fail() + return + } +} diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 6d768fbfecb..b56ef5784ed 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1139,6 +1139,13 @@ var ( Stage: FeatureStageExperimental, Owner: grafanaObservabilityMetricsSquad, }, + { + Name: "sqlExpressions", + Description: "Enables using SQL and DuckDB functions as Expressions.", + Stage: FeatureStageExperimental, + FrontendOnly: false, + Owner: grafanaAppPlatformSquad, + }, { Name: "nodeGraphDotLayout", Description: "Changed the layout algorithm for the node graph", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index bbf88310eca..0a083ba800e 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -152,6 +152,7 @@ jitterAlertRulesWithinGroups,preview,@grafana/alerting-squad,false,true,false onPremToCloudMigrations,experimental,@grafana/grafana-operator-experience-squad,false,false,false alertingSaveStatePeriodic,privatePreview,@grafana/alerting-squad,false,false,false promQLScope,experimental,@grafana/observability-metrics,false,false,false +sqlExpressions,experimental,@grafana/grafana-app-platform-squad,false,false,false nodeGraphDotLayout,experimental,@grafana/observability-traces-and-profiling,false,false,true groupToNestedTableTransformation,preview,@grafana/dataviz-squad,false,false,true newPDFRendering,experimental,@grafana/sharing-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 17fa7f7610b..09e1a7657f3 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -619,6 +619,10 @@ const ( // In-development feature that will allow injection of labels into prometheus queries. FlagPromQLScope = "promQLScope" + // FlagSqlExpressions + // Enables using SQL and DuckDB functions as Expressions. + FlagSqlExpressions = "sqlExpressions" + // FlagNodeGraphDotLayout // Changed the layout algorithm for the node graph FlagNodeGraphDotLayout = "nodeGraphDotLayout" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 23a5c2204a7..e15db658b22 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -83,7 +83,7 @@ "name": "pluginsInstrumentationStatusSource", "resourceVersion": "1708108588074", "creationTimestamp": "2024-02-16T18:36:28Z", - "deletionTimestamp": "2024-02-23T13:23:30Z" + "deletionTimestamp": "2024-02-27T14:43:01Z" }, "spec": { "description": "Include a status source label for plugin request metrics and logs", @@ -511,7 +511,7 @@ "name": "displayAnonymousStats", "resourceVersion": "1708108588074", "creationTimestamp": "2024-02-16T18:36:28Z", - "deletionTimestamp": "2024-02-23T13:23:30Z" + "deletionTimestamp": "2024-02-27T14:43:01Z" }, "spec": { "description": "Enables anonymous stats to be shown in the UI for Grafana", @@ -1400,7 +1400,7 @@ "name": "traceToMetrics", "resourceVersion": "1708108588074", "creationTimestamp": "2024-02-16T18:36:28Z", - "deletionTimestamp": "2024-02-23T13:23:30Z" + "deletionTimestamp": "2024-02-27T14:43:01Z" }, "spec": { "description": "Enable trace to metrics links", @@ -1529,7 +1529,7 @@ "name": "splitScopes", "resourceVersion": "1708108588074", "creationTimestamp": "2024-02-16T18:36:28Z", - "deletionTimestamp": "2024-02-23T13:23:30Z" + "deletionTimestamp": "2024-02-27T14:43:01Z" }, "spec": { "description": "Support faster dashboard and folder search by splitting permission scopes into parts", @@ -1570,7 +1570,7 @@ "name": "externalServiceAuth", "resourceVersion": "1708108588074", "creationTimestamp": "2024-02-16T18:36:28Z", - "deletionTimestamp": "2024-02-21T10:10:41Z" + "deletionTimestamp": "2024-02-27T14:43:01Z" }, "spec": { "description": "Starts an OAuth2 authentication provider for external services", @@ -2131,6 +2131,21 @@ "codeowner": "@grafana/dataviz-squad", "frontend": true } + }, + { + "metadata": { + "name": "sqlExpressions", + "resourceVersion": "1709044973784", + "creationTimestamp": "2024-02-19T22:46:11Z", + "annotations": { + "grafana.app/updatedTimestamp": "2024-02-27 14:42:53.784398 +0000 UTC" + } + }, + "spec": { + "description": "Enables using SQL and DuckDB functions as Expressions.", + "stage": "experimental", + "codeowner": "@grafana/grafana-app-platform-squad" + } } ] } \ No newline at end of file diff --git a/public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx b/public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx index 52ba9bbb731..ff0a2c2fc9c 100644 --- a/public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx +++ b/public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx @@ -5,6 +5,7 @@ import React from 'react'; import { DataSourceInstanceSettings, GrafanaTheme2, PanelData, RelativeTimeRange } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { Preview } from '@grafana/sql/src/components/visual-query-builder/Preview'; import { Badge, Stack, useStyles2 } from '@grafana/ui'; import { mapRelativeTimeRangeToOption } from '@grafana/ui/src/components/DateTimePickers/RelativeTimeRangePicker/utils'; @@ -182,6 +183,9 @@ function ExpressionPreview({ refId, model, evalData, isAlertCondition }: Express case ExpressionQueryType.threshold: return ; + case ExpressionQueryType.sql: + return ; + default: return <>Expression not supported: {model.type}; } diff --git a/public/app/features/alerting/unified/components/expressions/Expression.tsx b/public/app/features/alerting/unified/components/expressions/Expression.tsx index f05b56596ba..73db0a2f5bf 100644 --- a/public/app/features/alerting/unified/components/expressions/Expression.tsx +++ b/public/app/features/alerting/unified/components/expressions/Expression.tsx @@ -9,6 +9,7 @@ import { ClassicConditions } from 'app/features/expressions/components/ClassicCo import { Math } from 'app/features/expressions/components/Math'; import { Reduce } from 'app/features/expressions/components/Reduce'; import { Resample } from 'app/features/expressions/components/Resample'; +import { SqlExpr } from 'app/features/expressions/components/SqlExpr'; import { Threshold } from 'app/features/expressions/components/Threshold'; import { ExpressionQuery, @@ -110,6 +111,9 @@ export const Expression: FC = ({ /> ); + case ExpressionQueryType.sql: + return ; + default: return <>Expression not supported: {query.type}; } diff --git a/public/app/features/alerting/unified/utils/timeRange.ts b/public/app/features/alerting/unified/utils/timeRange.ts index dd7b3370bd1..11edb6c9034 100644 --- a/public/app/features/alerting/unified/utils/timeRange.ts +++ b/public/app/features/alerting/unified/utils/timeRange.ts @@ -29,6 +29,7 @@ const getReferencedIds = (model: ExpressionQuery, queries: AlertQuery[]): string case ExpressionQueryType.classic: return getReferencedIdsForClassicCondition(model); case ExpressionQueryType.math: + case ExpressionQueryType.sql: return getReferencedIdsForMath(model, queries); case ExpressionQueryType.resample: case ExpressionQueryType.reduce: diff --git a/public/app/features/expressions/ExpressionQueryEditor.tsx b/public/app/features/expressions/ExpressionQueryEditor.tsx index 90c6ba95de0..419fbe067fd 100644 --- a/public/app/features/expressions/ExpressionQueryEditor.tsx +++ b/public/app/features/expressions/ExpressionQueryEditor.tsx @@ -7,6 +7,7 @@ import { ClassicConditions } from './components/ClassicConditions'; import { Math } from './components/Math'; import { Reduce } from './components/Reduce'; import { Resample } from './components/Resample'; +import { SqlExpr } from './components/SqlExpr'; import { Threshold } from './components/Threshold'; import { ExpressionQuery, ExpressionQueryType, expressionTypes } from './types'; import { getDefaults } from './utils/expressionTypes'; @@ -27,6 +28,7 @@ function useExpressionsCache() { case ExpressionQueryType.reduce: case ExpressionQueryType.resample: case ExpressionQueryType.threshold: + case ExpressionQueryType.sql: return expressionCache.current[queryType]; case ExpressionQueryType.classic: return undefined; @@ -47,6 +49,8 @@ function useExpressionsCache() { expressionCache.current.resample = value; expressionCache.current.threshold = value; break; + case ExpressionQueryType.sql: + expressionCache.current.sql = value; } }, []); @@ -89,6 +93,9 @@ export function ExpressionQueryEditor(props: Props) { case ExpressionQueryType.threshold: return ; + + case ExpressionQueryType.sql: + return ; } }; diff --git a/public/app/features/expressions/components/SqlExpr.tsx b/public/app/features/expressions/components/SqlExpr.tsx new file mode 100644 index 00000000000..f5857f88929 --- /dev/null +++ b/public/app/features/expressions/components/SqlExpr.tsx @@ -0,0 +1,27 @@ +import React, { useMemo } from 'react'; + +import { SelectableValue } from '@grafana/data'; +import { SQLEditor } from '@grafana/experimental'; + +import { ExpressionQuery } from '../types'; + +interface Props { + refIds: Array>; + query: ExpressionQuery; + onChange: (query: ExpressionQuery) => void; +} + +export const SqlExpr = ({ onChange, refIds, query }: Props) => { + const vars = useMemo(() => refIds.map((v) => v.value!), [refIds]); + + const initialQuery = `select * from ${vars[0]} limit 1`; + + const onEditorChange = (expression: string) => { + onChange({ + ...query, + expression, + }); + }; + + return ; +}; diff --git a/public/app/features/expressions/types.ts b/public/app/features/expressions/types.ts index fe4026903b2..ad4091d608d 100644 --- a/public/app/features/expressions/types.ts +++ b/public/app/features/expressions/types.ts @@ -1,4 +1,5 @@ import { DataQuery, ReducerID, SelectableValue } from '@grafana/data'; +import { config } from 'app/core/config'; import { EvalFunction } from '../alerting/state/alertDef'; @@ -13,6 +14,7 @@ export enum ExpressionQueryType { resample = 'resample', classic = 'classic_conditions', threshold = 'threshold', + sql = 'sql', } export const getExpressionLabel = (type: ExpressionQueryType) => { @@ -27,6 +29,8 @@ export const getExpressionLabel = (type: ExpressionQueryType) => { return 'Classic condition'; case ExpressionQueryType.threshold: return 'Threshold'; + case ExpressionQueryType.sql: + return 'SQL'; } }; @@ -59,7 +63,17 @@ export const expressionTypes: Array> = [ description: 'Takes one or more time series returned from a query or an expression and checks if any of the series match the threshold condition.', }, -]; + { + value: ExpressionQueryType.sql, + label: 'SQL', + description: 'Transform data using SQL. Supports Aggregate/Analytics functions from DuckDB', + }, +].filter((expr) => { + if (expr.value === ExpressionQueryType.sql) { + return config.featureToggles?.sqlExpressions; + } + return true; +}); export const reducerTypes: Array> = [ { value: ReducerID.min, label: 'Min', description: 'Get the minimum value' },