mirror of
https://github.com/grafana/grafana.git
synced 2024-11-21 16:38:03 -06:00
Expressions: Sql expressions with Duckdb (#81666)
duckdb temp storage of dataframes using parquet and querying from sql expressions --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
parent
d8b7992c0c
commit
70009201d4
@ -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 |
|
||||
|
11
go.mod
11
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
|
||||
|
21
go.sum
21
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=
|
||||
|
@ -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=
|
||||
|
@ -171,6 +171,7 @@ export interface FeatureToggles {
|
||||
onPremToCloudMigrations?: boolean;
|
||||
alertingSaveStatePeriodic?: boolean;
|
||||
promQLScope?: boolean;
|
||||
sqlExpressions?: boolean;
|
||||
nodeGraphDotLayout?: boolean;
|
||||
groupToNestedTableTransformation?: boolean;
|
||||
newPDFRendering?: boolean;
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
// }
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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("")}
|
||||
}
|
||||
|
@ -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
|
||||
//-------------------------------
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
99
pkg/expr/sql/parser.go
Normal file
99
pkg/expr/sql/parser.go
Normal file
@ -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")
|
||||
}
|
58
pkg/expr/sql/parser_test.go
Normal file
58
pkg/expr/sql/parser_test.go
Normal file
@ -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))
|
||||
}
|
107
pkg/expr/sql_command.go
Normal file
107
pkg/expr/sql_command.go
Normal file
@ -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
|
||||
}
|
26
pkg/expr/sql_command_test.go
Normal file
26
pkg/expr/sql_command_test.go
Normal file
@ -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
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -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 <ThresholdExpressionViewer model={model} />;
|
||||
|
||||
case ExpressionQueryType.sql:
|
||||
return <Preview rawSql={model.expression || ''} datasourceType={model.datasource?.type} />;
|
||||
|
||||
default:
|
||||
return <>Expression not supported: {model.type}</>;
|
||||
}
|
||||
|
@ -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<ExpressionProps> = ({
|
||||
/>
|
||||
);
|
||||
|
||||
case ExpressionQueryType.sql:
|
||||
return <SqlExpr onChange={onChangeQuery} query={query} refIds={availableRefIds} />;
|
||||
|
||||
default:
|
||||
return <>Expression not supported: {query.type}</>;
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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 <Threshold onChange={onChange} query={query} labelWidth={labelWidth} refIds={refIds} />;
|
||||
|
||||
case ExpressionQueryType.sql:
|
||||
return <SqlExpr onChange={onChange} query={query} refIds={refIds} />;
|
||||
}
|
||||
};
|
||||
|
||||
|
27
public/app/features/expressions/components/SqlExpr.tsx
Normal file
27
public/app/features/expressions/components/SqlExpr.tsx
Normal file
@ -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<SelectableValue<string>>;
|
||||
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 <SQLEditor query={query.expression || initialQuery} onChange={onEditorChange}></SQLEditor>;
|
||||
};
|
@ -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<SelectableValue<ExpressionQueryType>> = [
|
||||
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<SelectableValue<string>> = [
|
||||
{ value: ReducerID.min, label: 'Min', description: 'Get the minimum value' },
|
||||
|
Loading…
Reference in New Issue
Block a user