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:
Scott Lepper 2024-02-27 16:16:00 -05:00 committed by GitHub
parent d8b7992c0c
commit 70009201d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 555 additions and 20 deletions

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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=

View File

@ -171,6 +171,7 @@ export interface FeatureToggles {
onPremToCloudMigrations?: boolean;
alertingSaveStatePeriodic?: boolean;
promQLScope?: boolean;
sqlExpressions?: boolean;
nodeGraphDotLayout?: boolean;
groupToNestedTableTransformation?: boolean;
newPDFRendering?: boolean;

View File

@ -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)
}

View File

@ -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
// }

View File

@ -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"
}

View File

@ -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("")}
}

View File

@ -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
//-------------------------------

View File

@ -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

View File

@ -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)

View File

@ -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
View 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")
}

View 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
View 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
}

View 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
}
}

View File

@ -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",

View File

@ -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

1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
152 onPremToCloudMigrations experimental @grafana/grafana-operator-experience-squad false false false
153 alertingSaveStatePeriodic privatePreview @grafana/alerting-squad false false false
154 promQLScope experimental @grafana/observability-metrics false false false
155 sqlExpressions experimental @grafana/grafana-app-platform-squad false false false
156 nodeGraphDotLayout experimental @grafana/observability-traces-and-profiling false false true
157 groupToNestedTableTransformation preview @grafana/dataviz-squad false false true
158 newPDFRendering experimental @grafana/sharing-squad false false false

View File

@ -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"

View File

@ -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"
}
}
]
}

View File

@ -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}</>;
}

View File

@ -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}</>;
}

View File

@ -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:

View File

@ -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} />;
}
};

View 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>;
};

View File

@ -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' },