From 9798bef0a412eb428d89e632461f3fe8ff61bc32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Sj=C3=B8rslev?= Date: Tue, 25 Jun 2024 10:21:42 +0200 Subject: [PATCH 01/19] Improve install options UX and add install pre and post telemetry events (#5141) --- scripts/install.sh | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 2142a29b14a..d7cea2b6334 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -55,16 +55,22 @@ downloadBinary() { unzip -q rill_${PLATFORM}.zip } -# Ask for preferred install option -promtInstallChoice() { +# Print install options +printInstallOptions() { printf "\nWhere would you like to install rill? (Default [1])\n\n" printf "[1] /usr/local/bin/rill [recommended, but requires sudo privileges]\n" printf "[2] ~/.rill/rill [directory will be created & path configured]\n" printf "[3] ./rill [download to the current directory]\n\n" - printf "Install option: " +} +# Ask for preferred install option +promtInstallChoice() { + printf "Pick install option: (1/2/3)\n" read -r ans /dev/null 2>&1 + fi +} + # Add the Rill binary to the PATH via configuration of the shells we detect on the system addPathConfigEntries() { PATH_CONFIG_LINE="export PATH=\$HOME/.rill:\$PATH # Added by Rill install" @@ -179,12 +194,14 @@ removePathConfigEntries() { # Install Rill on the system installRill() { + publishSyftEvent install checkDependency curl checkDependency shasum checkDependency unzip initPlatform detectPreviousInstallation if [ -z "${INSTALL_DIR}" ]; then + printInstallOptions promtInstallChoice checkConflictingInstallation fi @@ -194,6 +211,7 @@ installRill() { testInstalledBinary addPathConfigEntries printStartHelp + publishSyftEvent installed } # Uninstall Rill from the system, this function is aware of both the privileged and unprivileged install methods From 1a8d8c9be907be71c0fad9fdb6e6bdcc4a66bf0d Mon Sep 17 00:00:00 2001 From: Alexander Thor Date: Tue, 25 Jun 2024 13:55:07 +0200 Subject: [PATCH 02/19] refactor: move markdown to template (#5133) * refactor: move markdown to template * fix: format * fix: parser tests * fix: spaces and qoutes * fix: variable rename --------- Co-authored-by: Alexander Thor --- proto/gen/rill/admin/v1/admin.swagger.yaml | 2 +- proto/gen/rill/runtime/v1/resources.pb.go | 242 +++++++++--------- .../rill/runtime/v1/resources.pb.validate.go | 2 - .../gen/rill/runtime/v1/runtime.swagger.yaml | 5 +- proto/rill/runtime/v1/resources.proto | 1 - .../rillv1/data/component-template-v1.json | 30 +++ runtime/compilers/rillv1/parse_component.go | 12 - runtime/compilers/rillv1/parse_dashboard.go | 2 - runtime/compilers/rillv1/parser_test.go | 5 +- web-admin/src/client/gen/index.schemas.ts | 2 +- .../custom-dashboards/Component.svelte | 4 - .../CustomDashboardEmbed.svelte | 1 - .../custom-dashboards/Markdown.svelte | 33 --- .../custom-dashboards/PreviewElement.svelte | 2 - .../src/features/file-explorer/new-files.ts | 6 +- .../templates/TemplateRenderer.svelte | 3 + .../templates/markdown/Markdown.svelte | 24 ++ web-common/src/features/templates/types.ts | 15 +- .../proto/gen/rill/runtime/v1/resources_pb.ts | 6 - .../src/runtime-client/gen/index.schemas.ts | 3 +- 20 files changed, 198 insertions(+), 202 deletions(-) delete mode 100644 web-common/src/features/custom-dashboards/Markdown.svelte create mode 100644 web-common/src/features/templates/markdown/Markdown.svelte diff --git a/proto/gen/rill/admin/v1/admin.swagger.yaml b/proto/gen/rill/admin/v1/admin.swagger.yaml index 76c474b36e8..5e8477175ea 100644 --- a/proto/gen/rill/admin/v1/admin.swagger.yaml +++ b/proto/gen/rill/admin/v1/admin.swagger.yaml @@ -2432,7 +2432,7 @@ definitions: `NullValue` is a singleton enumeration to represent the null value for the `Value` type union. - The JSON representation for `NullValue` is JSON `null`. + The JSON representation for `NullValue` is JSON `null`. - NULL_VALUE: Null value. rpcStatus: diff --git a/proto/gen/rill/runtime/v1/resources.pb.go b/proto/gen/rill/runtime/v1/resources.pb.go index 8eaf9364f61..0e039377cff 100644 --- a/proto/gen/rill/runtime/v1/resources.pb.go +++ b/proto/gen/rill/runtime/v1/resources.pb.go @@ -3896,7 +3896,6 @@ type DashboardItem struct { Y *uint32 `protobuf:"varint,3,opt,name=y,proto3,oneof" json:"y,omitempty"` Width *uint32 `protobuf:"varint,4,opt,name=width,proto3,oneof" json:"width,omitempty"` Height *uint32 `protobuf:"varint,5,opt,name=height,proto3,oneof" json:"height,omitempty"` - FontSize uint32 `protobuf:"varint,7,opt,name=font_size,json=fontSize,proto3" json:"font_size,omitempty"` } func (x *DashboardItem) Reset() { @@ -3973,13 +3972,6 @@ func (x *DashboardItem) GetHeight() uint32 { return 0 } -func (x *DashboardItem) GetFontSize() uint32 { - if x != nil { - return x.FontSize - } - return 0 -} - // API defines a custom operation for querying data stored in Rill. type API struct { state protoimpl.MessageState @@ -6106,7 +6098,7 @@ var file_rill_runtime_v1_resources_proto_rawDesc = []byte{ 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, - 0xfb, 0x01, 0x0a, 0x0d, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, + 0xde, 0x01, 0x0a, 0x0d, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x5f, 0x64, 0x61, @@ -6117,125 +6109,123 @@ var file_rill_runtime_v1_resources_proto_rawDesc = []byte{ 0x01, 0x52, 0x01, 0x79, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x02, 0x52, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x0d, 0x48, 0x03, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x88, 0x01, 0x01, 0x12, - 0x1b, 0x0a, 0x09, 0x66, 0x6f, 0x6e, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x08, 0x66, 0x6f, 0x6e, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x04, 0x0a, 0x02, - 0x5f, 0x78, 0x42, 0x04, 0x0a, 0x02, 0x5f, 0x79, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x77, 0x69, 0x64, - 0x74, 0x68, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x64, 0x0a, - 0x03, 0x41, 0x50, 0x49, 0x12, 0x2c, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, - 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x50, 0x49, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, - 0x65, 0x63, 0x12, 0x2f, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x50, 0x49, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x22, 0x6f, 0x0a, 0x07, 0x41, 0x50, 0x49, 0x53, 0x70, 0x65, 0x63, 0x12, 0x1a, - 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x12, 0x48, 0x0a, 0x13, 0x72, 0x65, - 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, - 0x52, 0x12, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, - 0x74, 0x69, 0x65, 0x73, 0x22, 0x0a, 0x0a, 0x08, 0x41, 0x50, 0x49, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x22, 0x9b, 0x01, 0x0a, 0x08, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x1d, 0x0a, - 0x0a, 0x72, 0x65, 0x66, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x09, 0x72, 0x65, 0x66, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, - 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, - 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x69, - 0x63, 0x6b, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, - 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5a, 0x6f, 0x6e, 0x65, 0x22, 0xa5, - 0x01, 0x0a, 0x0a, 0x50, 0x61, 0x72, 0x73, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, - 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, - 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, - 0x50, 0x61, 0x74, 0x68, 0x12, 0x44, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6c, 0x6f, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x72, - 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x68, 0x61, 0x72, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x22, 0x50, 0x0a, 0x0f, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x5f, - 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x70, - 0x65, 0x72, 0x74, 0x79, 0x50, 0x61, 0x74, 0x68, 0x22, 0x4b, 0x0a, 0x0f, 0x44, 0x65, 0x70, 0x65, - 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, - 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x65, 0x6e, - 0x64, 0x65, 0x6e, 0x63, 0x79, 0x22, 0x2a, 0x0a, 0x0e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, - 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x22, 0x22, 0x0a, 0x0c, 0x43, 0x68, 0x61, 0x72, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x22, 0xfb, 0x02, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x53, 0x70, 0x65, 0x63, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x72, 0x69, 0x76, 0x65, - 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, 0x12, - 0x4e, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, - 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, - 0x70, 0x65, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x12, - 0x77, 0x0a, 0x19, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x66, 0x72, - 0x6f, 0x6d, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, + 0x28, 0x0d, 0x48, 0x03, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x88, 0x01, 0x01, 0x42, + 0x04, 0x0a, 0x02, 0x5f, 0x78, 0x42, 0x04, 0x0a, 0x02, 0x5f, 0x79, 0x42, 0x08, 0x0a, 0x06, 0x5f, + 0x77, 0x69, 0x64, 0x74, 0x68, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x22, 0x64, 0x0a, 0x03, 0x41, 0x50, 0x49, 0x12, 0x2c, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, + 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x50, 0x49, 0x53, 0x70, 0x65, 0x63, 0x52, + 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x2f, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x50, 0x49, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, + 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x6f, 0x0a, 0x07, 0x41, 0x50, 0x49, 0x53, 0x70, 0x65, + 0x63, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x12, 0x48, 0x0a, + 0x13, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, + 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, + 0x75, 0x63, 0x74, 0x52, 0x12, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x50, 0x72, 0x6f, + 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x0a, 0x0a, 0x08, 0x41, 0x50, 0x49, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x22, 0x9b, 0x01, 0x0a, 0x08, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, + 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x66, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x07, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x72, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x12, 0x25, 0x0a, + 0x0e, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x53, 0x65, 0x63, + 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x7a, 0x6f, 0x6e, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5a, 0x6f, 0x6e, + 0x65, 0x22, 0xa5, 0x01, 0x0a, 0x0a, 0x50, 0x61, 0x72, 0x73, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, + 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, + 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x44, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1d, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, + 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x22, 0x50, 0x0a, 0x0f, 0x56, 0x61, 0x6c, + 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, + 0x74, 0x79, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x70, + 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x50, 0x61, 0x74, 0x68, 0x22, 0x4b, 0x0a, 0x0f, 0x44, + 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, + 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x65, + 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, + 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x22, 0x2a, 0x0a, 0x0e, 0x45, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x22, 0x22, 0x0a, 0x0c, 0x43, 0x68, 0x61, 0x72, 0x4c, 0x6f, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x22, 0xfb, 0x02, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x70, 0x65, 0x63, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x72, + 0x69, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x69, 0x76, + 0x65, 0x72, 0x12, 0x4e, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, + 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, + 0x65, 0x73, 0x12, 0x77, 0x0a, 0x19, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, + 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, + 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, + 0x46, 0x72, 0x6f, 0x6d, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x17, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x46, 0x72, + 0x6f, 0x6d, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x1a, 0x3d, 0x0a, 0x0f, 0x50, + 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4a, 0x0a, 0x1c, 0x50, 0x72, + 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x56, 0x61, 0x72, 0x69, + 0x61, 0x62, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x2d, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x70, 0x65, 0x63, + 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x70, 0x65, + 0x63, 0x48, 0x61, 0x73, 0x68, 0x22, 0x78, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x56, 0x32, 0x12, 0x32, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x70, - 0x65, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x46, 0x72, 0x6f, - 0x6d, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x17, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x56, - 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x1a, 0x3d, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x70, - 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4a, 0x0a, 0x1c, 0x50, 0x72, 0x6f, 0x70, 0x65, - 0x72, 0x74, 0x69, 0x65, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, - 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x22, 0x2d, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x70, 0x65, 0x63, 0x5f, 0x68, 0x61, - 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x70, 0x65, 0x63, 0x48, 0x61, - 0x73, 0x68, 0x22, 0x78, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x56, - 0x32, 0x12, 0x32, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x70, 0x65, 0x63, 0x52, - 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, - 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2a, 0x8a, 0x01, 0x0a, - 0x0f, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x43, 0x49, 0x4c, 0x45, 0x5f, 0x53, 0x54, + 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x35, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, + 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2a, + 0x8a, 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x43, 0x49, 0x4c, 0x45, + 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x43, 0x49, + 0x4c, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x49, 0x44, 0x4c, 0x45, 0x10, 0x01, + 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x43, 0x49, 0x4c, 0x45, 0x5f, 0x53, 0x54, + 0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x1c, + 0x0a, 0x18, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x43, 0x49, 0x4c, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, + 0x55, 0x53, 0x5f, 0x52, 0x55, 0x4e, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x03, 0x2a, 0x85, 0x01, 0x0a, + 0x0f, 0x41, 0x73, 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x20, 0x0a, 0x1c, 0x41, 0x53, 0x53, 0x45, 0x52, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, - 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x43, 0x49, 0x4c, 0x45, 0x5f, - 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x49, 0x44, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x1c, 0x0a, - 0x18, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x43, 0x49, 0x4c, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, - 0x53, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x1c, 0x0a, 0x18, 0x52, - 0x45, 0x43, 0x4f, 0x4e, 0x43, 0x49, 0x4c, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, - 0x52, 0x55, 0x4e, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x03, 0x2a, 0x85, 0x01, 0x0a, 0x0f, 0x41, 0x73, - 0x73, 0x65, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x20, 0x0a, - 0x1c, 0x41, 0x53, 0x53, 0x45, 0x52, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, - 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, - 0x19, 0x0a, 0x15, 0x41, 0x53, 0x53, 0x45, 0x52, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x55, 0x53, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x41, 0x53, - 0x53, 0x45, 0x52, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x46, - 0x41, 0x49, 0x4c, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x41, 0x53, 0x53, 0x45, 0x52, 0x54, 0x49, - 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, - 0x03, 0x42, 0xc1, 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, - 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x42, 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3c, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x69, 0x6c, 0x6c, 0x64, 0x61, 0x74, 0x61, - 0x2f, 0x72, 0x69, 0x6c, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, - 0x72, 0x69, 0x6c, 0x6c, 0x2f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x76, 0x31, 0x3b, - 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x52, 0x52, 0x58, 0xaa, - 0x02, 0x0f, 0x52, 0x69, 0x6c, 0x6c, 0x2e, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x56, - 0x31, 0xca, 0x02, 0x0f, 0x52, 0x69, 0x6c, 0x6c, 0x5c, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, - 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x1b, 0x52, 0x69, 0x6c, 0x6c, 0x5c, 0x52, 0x75, 0x6e, 0x74, 0x69, - 0x6d, 0x65, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0xea, 0x02, 0x11, 0x52, 0x69, 0x6c, 0x6c, 0x3a, 0x3a, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, - 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x41, 0x53, 0x53, 0x45, 0x52, 0x54, 0x49, 0x4f, 0x4e, 0x5f, + 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x10, 0x01, 0x12, 0x19, 0x0a, + 0x15, 0x41, 0x53, 0x53, 0x45, 0x52, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, + 0x53, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x41, 0x53, 0x53, 0x45, + 0x52, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x52, 0x52, + 0x4f, 0x52, 0x10, 0x03, 0x42, 0xc1, 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x69, 0x6c, + 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x42, 0x0e, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3c, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x69, 0x6c, 0x6c, 0x64, + 0x61, 0x74, 0x61, 0x2f, 0x72, 0x69, 0x6c, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, + 0x65, 0x6e, 0x2f, 0x72, 0x69, 0x6c, 0x6c, 0x2f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2f, + 0x76, 0x31, 0x3b, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x52, + 0x52, 0x58, 0xaa, 0x02, 0x0f, 0x52, 0x69, 0x6c, 0x6c, 0x2e, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, + 0x65, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0f, 0x52, 0x69, 0x6c, 0x6c, 0x5c, 0x52, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x1b, 0x52, 0x69, 0x6c, 0x6c, 0x5c, 0x52, 0x75, + 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x11, 0x52, 0x69, 0x6c, 0x6c, 0x3a, 0x3a, 0x52, 0x75, 0x6e, + 0x74, 0x69, 0x6d, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/gen/rill/runtime/v1/resources.pb.validate.go b/proto/gen/rill/runtime/v1/resources.pb.validate.go index ff1d0ac6cab..d8cafa1b1d3 100644 --- a/proto/gen/rill/runtime/v1/resources.pb.validate.go +++ b/proto/gen/rill/runtime/v1/resources.pb.validate.go @@ -8006,8 +8006,6 @@ func (m *DashboardItem) validate(all bool) error { // no validation rules for DefinedInDashboard - // no validation rules for FontSize - if m.X != nil { // no validation rules for X } diff --git a/proto/gen/rill/runtime/v1/runtime.swagger.yaml b/proto/gen/rill/runtime/v1/runtime.swagger.yaml index fe244f6e1dc..f5e2b48140b 100644 --- a/proto/gen/rill/runtime/v1/runtime.swagger.yaml +++ b/proto/gen/rill/runtime/v1/runtime.swagger.yaml @@ -2931,7 +2931,7 @@ definitions: `NullValue` is a singleton enumeration to represent the null value for the `Value` type union. - The JSON representation for `NullValue` is JSON `null`. + The JSON representation for `NullValue` is JSON `null`. - NULL_VALUE: Null value. rpcStatus: @@ -3719,9 +3719,6 @@ definitions: height: type: integer format: int64 - fontSize: - type: integer - format: int64 v1DashboardSpec: type: object properties: diff --git a/proto/rill/runtime/v1/resources.proto b/proto/rill/runtime/v1/resources.proto index ae86afa6d17..692eb03cfd0 100644 --- a/proto/rill/runtime/v1/resources.proto +++ b/proto/rill/runtime/v1/resources.proto @@ -483,7 +483,6 @@ message DashboardItem { optional uint32 y = 3; optional uint32 width = 4; optional uint32 height = 5; - uint32 font_size = 7; } // API defines a custom operation for querying data stored in Rill. diff --git a/runtime/compilers/rillv1/data/component-template-v1.json b/runtime/compilers/rillv1/data/component-template-v1.json index b594db9ddd6..5c65a8ba116 100644 --- a/runtime/compilers/rillv1/data/component-template-v1.json +++ b/runtime/compilers/rillv1/data/component-template-v1.json @@ -62,6 +62,33 @@ ], "type": "object" }, + "MarkdownProperties": { + "additionalProperties": false, + "properties": { + "content": { + "type": "string" + }, + "css": { + "type": "object" + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "MarkdownTemplateT": { + "additionalProperties": false, + "properties": { + "markdown": { + "$ref": "#/definitions/MarkdownProperties" + } + }, + "required": [ + "markdown" + ], + "type": "object" + }, "TableProperties": { "additionalProperties": false, "properties": { @@ -154,6 +181,9 @@ }, { "$ref": "#/definitions/TableTemplateT" + }, + { + "$ref": "#/definitions/MarkdownTemplateT" } ] } diff --git a/runtime/compilers/rillv1/parse_component.go b/runtime/compilers/rillv1/parse_component.go index b52d33a8cb1..41cc5920a57 100644 --- a/runtime/compilers/rillv1/parse_component.go +++ b/runtime/compilers/rillv1/parse_component.go @@ -28,8 +28,6 @@ type ComponentYAML struct { Subtitle string `yaml:"subtitle"` Data *DataYAML `yaml:"data"` VegaLite *string `yaml:"vega_lite"` - Markdown *string `yaml:"markdown"` - Image *string `yaml:"image"` Other map[string]map[string]any `yaml:",inline" mapstructure:",remain"` // Generic renderer: can only have one key } @@ -101,16 +99,6 @@ func (p *Parser) parseComponentYAML(tmp *ComponentYAML) (*runtimev1.ComponentSpe renderer = "vega_lite" rendererProps = must(structpb.NewStruct(map[string]any{"spec": *tmp.VegaLite})) } - if tmp.Markdown != nil { - n++ - renderer = "markdown" - rendererProps = must(structpb.NewStruct(map[string]any{"content": *tmp.Markdown})) - } - if tmp.Image != nil { - n++ - renderer = "image" - rendererProps = must(structpb.NewStruct(map[string]any{"url": *tmp.Image})) - } if len(tmp.Other) == 1 { n++ var props map[string]any diff --git a/runtime/compilers/rillv1/parse_dashboard.go b/runtime/compilers/rillv1/parse_dashboard.go index 1abcd06a811..18ad17ece92 100644 --- a/runtime/compilers/rillv1/parse_dashboard.go +++ b/runtime/compilers/rillv1/parse_dashboard.go @@ -20,7 +20,6 @@ type DashboardYAML struct { Y *uint32 `yaml:"y"` Width *uint32 `yaml:"width"` Height *uint32 `yaml:"height"` - FontSize uint32 `yaml:"font_size"` } `yaml:"items"` } @@ -66,7 +65,6 @@ func (p *Parser) parseDashboard(node *Node) error { Y: item.Y, Width: item.Width, Height: item.Height, - FontSize: item.FontSize, } node.Refs = append(node.Refs, ResourceName{Kind: ResourceKindComponent, Name: component}) diff --git a/runtime/compilers/rillv1/parser_test.go b/runtime/compilers/rillv1/parser_test.go index 3bacc9e8aa0..2f1029a958b 100644 --- a/runtime/compilers/rillv1/parser_test.go +++ b/runtime/compilers/rillv1/parser_test.go @@ -1470,7 +1470,8 @@ items: width: 1 height: 2 - component: - markdown: Hello world! + markdown: + content: "Hello world!" `, }) @@ -1512,7 +1513,7 @@ items: Paths: []string{"/dashboards/d1.yaml"}, ComponentSpec: &runtimev1.ComponentSpec{ Renderer: "markdown", - RendererProperties: must(structpb.NewStruct(map[string]any{"contents": "Hello world!"})), + RendererProperties: must(structpb.NewStruct(map[string]any{"content": "Hello world!"})), DefinedInDashboard: true, }, }, diff --git a/web-admin/src/client/gen/index.schemas.ts b/web-admin/src/client/gen/index.schemas.ts index 77da371a2d7..dd36341d542 100644 --- a/web-admin/src/client/gen/index.schemas.ts +++ b/web-admin/src/client/gen/index.schemas.ts @@ -1044,7 +1044,7 @@ export interface RpcStatus { * `NullValue` is a singleton enumeration to represent the null value for the `Value` type union. - The JSON representation for `NullValue` is JSON `null`. +The JSON representation for `NullValue` is JSON `null`. - NULL_VALUE: Null value. */ diff --git a/web-common/src/features/custom-dashboards/Component.svelte b/web-common/src/features/custom-dashboards/Component.svelte index 118b6eea1fd..df12e5a9aa9 100644 --- a/web-common/src/features/custom-dashboards/Component.svelte +++ b/web-common/src/features/custom-dashboards/Component.svelte @@ -8,7 +8,6 @@ useResource, } from "../entity-management/resource-selectors"; import Chart from "./Chart.svelte"; - import Markdown from "./Markdown.svelte"; import type ResizeHandle from "./ResizeHandle.svelte"; const options = [0, 0.5, 1]; @@ -34,7 +33,6 @@ export let chartView = false; export let componentName: string; export let instanceId: string; - export let fontSize: number = 20; $: resourceQuery = useResource( instanceId, @@ -110,8 +108,6 @@ chartName={componentName} {resolverProperties} /> - {:else if renderer === "markdown" && rendererProperties?.content} - {:else if renderer && rendererProperties} - import DOMPurify from "dompurify"; - import { marked } from "marked"; - - export let markdown: string; - export let fontSize: number; - export let editing = false; - export let onChange: - | undefined - | (( - e: Event & { - currentTarget: EventTarget & HTMLInputElement; - }, - ) => void) = undefined; - - -
- {#if editing} - - {:else} - {#await marked(markdown) then content} - {@html DOMPurify.sanitize(content)} - {/await} - {/if} -
diff --git a/web-common/src/features/custom-dashboards/PreviewElement.svelte b/web-common/src/features/custom-dashboards/PreviewElement.svelte index 603dab832dc..04013bfc26c 100644 --- a/web-common/src/features/custom-dashboards/PreviewElement.svelte +++ b/web-common/src/features/custom-dashboards/PreviewElement.svelte @@ -68,7 +68,6 @@ {radius} {scale} {selected} - fontSize={component.fontSize} builders={[builder]} height={finalHeight} left={finalLeft} @@ -106,7 +105,6 @@ {radius} {scale} {selected} - fontSize={component.fontSize} builders={[builder]} height={finalHeight} left={finalLeft} diff --git a/web-common/src/features/file-explorer/new-files.ts b/web-common/src/features/file-explorer/new-files.ts index faed593c992..4a7f0f77b62 100644 --- a/web-common/src/features/file-explorer/new-files.ts +++ b/web-common/src/features/file-explorer/new-files.ts @@ -152,8 +152,10 @@ gap: 2 items: - component: - type: markdown - markdown: First component + markdown: + content: "First Component" + css: + font-size: "40px" width: 4 height: 3 x: 2 diff --git a/web-common/src/features/templates/TemplateRenderer.svelte b/web-common/src/features/templates/TemplateRenderer.svelte index 948d080e51c..62167dea9af 100644 --- a/web-common/src/features/templates/TemplateRenderer.svelte +++ b/web-common/src/features/templates/TemplateRenderer.svelte @@ -1,6 +1,7 @@ + +
+ {#await marked(markdownProperties.content) then content} + {@html DOMPurify.sanitize(content)} + {/await} +
diff --git a/web-common/src/features/templates/types.ts b/web-common/src/features/templates/types.ts index 26e39a7f919..9511b3a29df 100644 --- a/web-common/src/features/templates/types.ts +++ b/web-common/src/features/templates/types.ts @@ -41,6 +41,19 @@ export interface TableTemplateT { table: TableProperties; } +export interface MarkdownProperties { + content: string; + css?: { [key: string]: any }; +} + +export interface MarkdownTemplateT { + markdown: MarkdownProperties; +} + type ChartTemplates = LineChart | BarChart | StackedBarChart; -export type TemplateSpec = ChartTemplates | KPITemplateT | TableTemplateT; +export type TemplateSpec = + | ChartTemplates + | KPITemplateT + | TableTemplateT + | MarkdownTemplateT; diff --git a/web-common/src/proto/gen/rill/runtime/v1/resources_pb.ts b/web-common/src/proto/gen/rill/runtime/v1/resources_pb.ts index 6dfadfc737a..c93d555396f 100644 --- a/web-common/src/proto/gen/rill/runtime/v1/resources_pb.ts +++ b/web-common/src/proto/gen/rill/runtime/v1/resources_pb.ts @@ -3448,11 +3448,6 @@ export class DashboardItem extends Message { */ height?: number; - /** - * @generated from field: uint32 font_size = 7; - */ - fontSize = 0; - constructor(data?: PartialMessage) { super(); proto3.util.initPartial(data, this); @@ -3467,7 +3462,6 @@ export class DashboardItem extends Message { { no: 3, name: "y", kind: "scalar", T: 13 /* ScalarType.UINT32 */, opt: true }, { no: 4, name: "width", kind: "scalar", T: 13 /* ScalarType.UINT32 */, opt: true }, { no: 5, name: "height", kind: "scalar", T: 13 /* ScalarType.UINT32 */, opt: true }, - { no: 7, name: "font_size", kind: "scalar", T: 13 /* ScalarType.UINT32 */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): DashboardItem { diff --git a/web-common/src/runtime-client/gen/index.schemas.ts b/web-common/src/runtime-client/gen/index.schemas.ts index c0f4d8f3d7e..31e71b9f590 100644 --- a/web-common/src/runtime-client/gen/index.schemas.ts +++ b/web-common/src/runtime-client/gen/index.schemas.ts @@ -1622,7 +1622,6 @@ export interface V1DashboardItem { y?: number; width?: number; height?: number; - fontSize?: number; } export interface V1DashboardSpec { @@ -2083,7 +2082,7 @@ export interface V1API { * `NullValue` is a singleton enumeration to represent the null value for the `Value` type union. - The JSON representation for `NullValue` is JSON `null`. +The JSON representation for `NullValue` is JSON `null`. - NULL_VALUE: Null value. */ From 6f2081750ff1a52e29a535fb7aca11705f8b1a40 Mon Sep 17 00:00:00 2001 From: Alexander Thor Date: Tue, 25 Jun 2024 15:27:25 +0200 Subject: [PATCH 03/19] fix: broken urls to refs in inspector (#5143) * fix: urls to files in inspector * fix: remove console * fix: type error --------- Co-authored-by: Alexander Thor --- .../src/features/models/workspace/inspector/References.svelte | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web-common/src/features/models/workspace/inspector/References.svelte b/web-common/src/features/models/workspace/inspector/References.svelte index a962a47991f..cd75513a3cd 100644 --- a/web-common/src/features/models/workspace/inspector/References.svelte +++ b/web-common/src/features/models/workspace/inspector/References.svelte @@ -69,9 +69,7 @@
{ From 35406b9494e6f30bf67cd7f80ec777e095d9a273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Egelund-M=C3=BCller?= Date: Tue, 25 Jun 2024 15:43:57 +0200 Subject: [PATCH 04/19] Runtime: Support spines in metrics queries (#5136) * Runtime: Support spines in metrics queries * Self review * Shim for MVA * Tests * Tests * Tests * Remove time offset comparison * Remove log * Remove old MVA code * Fix lint --- runtime/metricsview/ast.go | 158 +- runtime/metricsview/astsql.go | 7 + runtime/metricsview/query.go | 16 + .../metricsview/query_expression_sql.go.txt | 152 - runtime/queries/metricsview.go | 20 - runtime/queries/metricsview_aggregation.go | 2959 +---------------- .../queries/metricsview_aggregation_test.go | 1136 +++---- 7 files changed, 703 insertions(+), 3745 deletions(-) delete mode 100644 runtime/metricsview/query_expression_sql.go.txt diff --git a/runtime/metricsview/ast.go b/runtime/metricsview/ast.go index fb0589fb153..33a4ff81342 100644 --- a/runtime/metricsview/ast.go +++ b/runtime/metricsview/ast.go @@ -32,7 +32,7 @@ type AST struct { // SelectNode represents a query that computes measures by dimensions. // The from/join clauses are not all compatible. The allowed combinations are: // - FromTable -// - FromSelect and optionally LeftJoinSelects +// - FromSelect and optionally SpineSelect and/or LeftJoinSelects // - FromSelect and optionally JoinComparisonSelect type SelectNode struct { Alias string // Alias for the node used by outer SELECTs to reference it. @@ -40,6 +40,7 @@ type SelectNode struct { MeasureFields []FieldNode // Measure fields to select FromTable *string // Underlying table expression to select from (if set, FromSelect must not be set) FromSelect *SelectNode // Sub-select to select from (if set, FromTable must not be set) + SpineSelect *SelectNode // Sub-select that returns a spine of dimensions. Currently it will be right-joined onto FromSelect. LeftJoinSelects []*SelectNode // Sub-selects to left join onto FromSelect, to enable "per-dimension" measures JoinComparisonSelect *SelectNode // Sub-select to join onto FromSelect for comparison measures JoinComparisonType string // Type of join to use for JoinComparisonSelect @@ -69,6 +70,25 @@ type ExprNode struct { Args []any } +// and returns a new node that is the AND of the current node and the given expression. +func (n *ExprNode) and(expr string, args []any) *ExprNode { + if expr == "" { + return n + } + + if n == nil || n.Expr == "" { + return &ExprNode{ + Expr: expr, + Args: args, + } + } + + return &ExprNode{ + Expr: fmt.Sprintf("(%s) AND (%s)", n.Expr, expr), + Args: append(n.Args, args...), + } +} + // OrderFieldNode represents a field in an ORDER BY clause. type OrderFieldNode struct { Name string @@ -132,16 +152,11 @@ func NewAST(mv *runtimev1.MetricsViewSpec, sec *runtime.ResolvedMetricsViewSecur ast.underlyingWhere = where // Build initial root node (empty query against the base select) - ast.Root = &SelectNode{ - Alias: ast.generateIdentifier(), - DimFields: ast.dimFields, - Group: true, - FromTable: ast.underlyingTable, - Where: ast.underlyingWhere, + n, err := ast.buildBaseSelect(ast.generateIdentifier(), ast.query.TimeRange) + if err != nil { + return nil, err } - - // Add time range to the root node - ast.addTimeRange(ast.Root, ast.query.TimeRange) + ast.Root = n // Incrementally add each output measure. // As each measure is added, the AST is transformed to accommodate it based on its type. @@ -479,46 +494,93 @@ func (a *AST) checkRequiredDimensionsPresentInQuery(m *runtimev1.MetricsViewSpec // buildUnderlyingWhere constructs the base WHERE clause for the query. func (a *AST) buildUnderlyingWhere() (*ExprNode, error) { + var res *ExprNode + expr, args, err := a.sqlForExpression(a.query.Where, nil, false, true) if err != nil { return nil, fmt.Errorf("failed to compile 'where': %w", err) } + res = res.and(expr, args) - if a.security != nil { - var secExpr string - var secArgs []any + if a.security != nil && a.security.QueryFilter != nil { + e := NewExpressionFromProto(a.security.QueryFilter) + expr, args, err = a.sqlForExpression(e, nil, false, false) + if err != nil { + return nil, fmt.Errorf("failed to compile the security policy's query filter: %w", err) + } + res = res.and(expr, args) + } - if a.security.QueryFilter != nil { - e := NewExpressionFromProto(a.security.QueryFilter) - secExpr, secArgs, err = a.sqlForExpression(e, nil, false, false) - if err != nil { - return nil, fmt.Errorf("failed to compile the security policy's query filter: %w", err) - } + if a.security != nil && a.security.RowFilter != "" { + res = res.and(a.security.RowFilter, nil) + } + + return res, nil +} + +// buildBaseSelect constructs a base SELECT node against the underlying table. +func (a *AST) buildBaseSelect(alias string, tr *TimeRange) (*SelectNode, error) { + n := &SelectNode{ + Alias: alias, + DimFields: a.dimFields, + Group: true, + FromTable: a.underlyingTable, + Where: a.underlyingWhere, + } + a.addTimeRange(n, tr) + + // If there is a spine, we wrap the base SELECT in a new SELECT that we add the spine to. + // We do not join the spine directly to the FromTable because the join would be evaluated before the GROUP BY, + // which would impact the measure aggregations (e.g. counts per group would be wrong). + if a.query.Spine != nil { + sn, err := a.buildSpineSelect(a.generateIdentifier(), a.query.Spine, tr) + if err != nil { + return nil, err } - if a.security.RowFilter != "" { - if secExpr == "" { - secExpr = a.security.RowFilter - } else { - secExpr = fmt.Sprintf("(%s) AND (%s)", secExpr, a.security.RowFilter) - } + a.wrapSelect(n, a.generateIdentifier()) + n.SpineSelect = sn + + // Update the dimension fields to derive from the SpineSelect instead of the FromSelect + // (since by definition, some dimension values in the spine might not be present in FromSelect). + for i, f := range n.DimFields { + f.Expr = a.sqlForMember(sn.Alias, f.Name) + n.DimFields[i] = f } + } - if secExpr != "" { - if expr == "" { - expr = secExpr - args = secArgs - } else { - expr = fmt.Sprintf("(%s) AND (%s)", expr, secExpr) - args = append(args, secArgs...) - } + return n, nil +} + +// buildSpineSelect constructs a SELECT node for the given spine of dimension values. +func (a *AST) buildSpineSelect(alias string, spine *Spine, tr *TimeRange) (*SelectNode, error) { + if spine == nil { + return nil, nil + } + + if spine.Where != nil { + expr, args, err := a.sqlForExpression(spine.Where.Expression, nil, false, true) + if err != nil { + return nil, fmt.Errorf("failed to compile 'spine.where': %w", err) + } + + n := &SelectNode{ + Alias: alias, + DimFields: a.dimFields, + Group: true, + FromTable: a.underlyingTable, } + n.Where = n.Where.and(expr, args) + a.addTimeRange(n, tr) + + return n, nil } - return &ExprNode{ - Expr: expr, - Args: args, - }, nil + if spine.TimeRange != nil { + return nil, errors.New("time_range not yet supported in spine") + } + + return nil, errors.New("unhandled spine type") } // addTimeRange adds a time range to the given SelectNode's WHERE clause. @@ -642,9 +704,16 @@ func (a *AST) addDerivedMeasure(n *SelectNode, m *runtimev1.MetricsViewSpec_Meas return nil } - // Now we know it's NOT a node with a comparison join. + // If the current node has a spine join, we wrap it in a new SELECT that we add the derived measure to. + // Even though the spine join won't add any ambiguous measure names, it will make dimension names ambiguous. + // So in case the derived measure references a dimension by name (unlikely, but possible), we need to ensure the dimension is only in scope in one sub-query. + if n.SpineSelect != nil { + a.wrapSelect(n, a.generateIdentifier()) + } + // Now we know it's ALSO NOT a node with a spine join. + // If the referenced measures are not already in scope in the sub-selects, add them. // Since the node doesn't have a comparison join, addReferencedMeasuresToScope guarantees the referenced measures are ONLY in scope in ONE sub-query, which prevents ambiguous references in the measure expression. err := a.addReferencedMeasuresToScope(n, m.ReferencedMeasures) @@ -689,15 +758,11 @@ func (a *AST) addTimeComparisonMeasure(n *SelectNode, m *runtimev1.MetricsViewSp a.wrapSelect(n, "base") - n.JoinComparisonSelect = &SelectNode{ - Alias: "comparison", - DimFields: a.dimFields, - Group: true, - FromTable: a.underlyingTable, - Where: a.underlyingWhere, + csn, err := a.buildBaseSelect("comparison", a.query.ComparisonTimeRange) + if err != nil { + return err } - - a.addTimeRange(n.JoinComparisonSelect, a.query.ComparisonTimeRange) + n.JoinComparisonSelect = csn n.JoinComparisonType = "FULL OUTER" @@ -817,6 +882,7 @@ func (a *AST) wrapSelect(s *SelectNode, innerAlias string) { s.FromTable = nil s.FromSelect = &cpy + s.SpineSelect = nil s.LeftJoinSelects = nil s.JoinComparisonSelect = nil s.JoinComparisonType = "" diff --git a/runtime/metricsview/astsql.go b/runtime/metricsview/astsql.go index 629d1a96dcb..ee8662d45b0 100644 --- a/runtime/metricsview/astsql.go +++ b/runtime/metricsview/astsql.go @@ -142,6 +142,13 @@ func (b *sqlBuilder) writeSelect(n *SelectNode) error { } } + if n.SpineSelect != nil { + err := b.writeJoin("RIGHT", n.FromSelect, n.SpineSelect) + if err != nil { + return err + } + } + if n.JoinComparisonSelect != nil { err := b.writeJoin(n.JoinComparisonType, n.FromSelect, n.JoinComparisonSelect) if err != nil { diff --git a/runtime/metricsview/query.go b/runtime/metricsview/query.go index c0111287fea..fcce60329dc 100644 --- a/runtime/metricsview/query.go +++ b/runtime/metricsview/query.go @@ -13,6 +13,7 @@ type Query struct { Dimensions []Dimension `mapstructure:"dimensions"` Measures []Measure `mapstructure:"measures"` PivotOn []string `mapstructure:"pivot_on"` + Spine *Spine `mapstructure:"spine"` Sort []Sort `mapstructure:"sort"` TimeRange *TimeRange `mapstructure:"time_range"` ComparisonTimeRange *TimeRange `mapstructure:"comparison_time_range"` @@ -93,6 +94,21 @@ type MeasureComputeComparisonRatio struct { Measure string `mapstructure:"measure"` } +type Spine struct { + Where *WhereSpine `mapstructure:"where"` + TimeRange *TimeSpine `mapstructure:"time"` +} + +type WhereSpine struct { + Expression *Expression `mapstructure:"expr"` +} + +type TimeSpine struct { + Start time.Time `mapstructure:"start"` + End time.Time `mapstructure:"end"` + Grain TimeGrain `mapstructure:"grain"` +} + type Sort struct { Name string `mapstructure:"name"` Desc bool `mapstructure:"desc"` diff --git a/runtime/metricsview/query_expression_sql.go.txt b/runtime/metricsview/query_expression_sql.go.txt deleted file mode 100644 index 4ab82423432..00000000000 --- a/runtime/metricsview/query_expression_sql.go.txt +++ /dev/null @@ -1,152 +0,0 @@ -// nolint: govet -package metricsview - -import ( - "github.com/alecthomas/kong" - "github.com/alecthomas/participle/v2" - "github.com/alecthomas/participle/v2/lexer" - "github.com/alecthomas/repr" -) - -// func NewExpressionFromString(s string) (*Expression, error) { - -// } - -type Expression struct { - Or []*OrCondition `@@ ( "OR" @@ )*` -} - -type OrCondition struct { - And []*Condition `@@ ( "AND" @@ )*` -} - -type Condition struct { - Operand *ConditionOperand ` @@` - Not *Condition `| "NOT" @@` - Exists *Select `| "EXISTS" "(" @@ ")"` -} - -type ConditionOperand struct { - Operand *Operand `@@` - ConditionRHS *ConditionRHS `@@?` -} - -type ConditionRHS struct { - Compare *Compare ` @@` - Is *Is `| "IS" @@` - Between *Between `| "BETWEEN" @@` - In *In `| "IN" "(" @@ ")"` - Like *Like `| "LIKE" @@` -} - -type Compare struct { - Operator string `@( "<>" | "<=" | ">=" | "=" | "<" | ">" | "!=" )` - Operand *Operand `( @@` - Select *CompareSelect ` | @@ )` -} - -type CompareSelect struct { - All bool `( @"ALL"` - Any bool ` | @"ANY"` - Some bool ` | @"SOME" )` - Select *Select `"(" @@ ")"` -} - -type Like struct { - Not bool `[ @"NOT" ]` - Operand *Operand `@@` -} - -type Is struct { - Not bool `[ @"NOT" ]` - Null bool `( @"NULL"` - DistinctFrom *Operand ` | "DISTINCT" "FROM" @@ )` -} - -type Between struct { - Start *Operand `@@` - End *Operand `"AND" @@` -} - -type In struct { - Select *Select ` @@` - Expressions []*Expression `| @@ ( "," @@ )*` -} - -type Operand struct { - Summand []*Summand `@@ ( "|" "|" @@ )*` -} - -type Summand struct { - LHS *Factor `@@` - Op string `[ @("+" | "-")` - RHS *Factor ` @@ ]` -} - -type Factor struct { - LHS *Term `@@` - Op string `( @("*" | "/" | "%")` - RHS *Term ` @@ )?` -} - -type Term struct { - Select *Select ` @@` - Value *Value `| @@` - SymbolRef *SymbolRef `| @@` - SubExpression *Expression `| "(" @@ ")"` -} - -type SymbolRef struct { - Symbol string `@Ident @( "." Ident )*` - Parameters []*Expression `( "(" @@ ( "," @@ )* ")" )?` -} - -type Value struct { - Wildcard bool `( @"*"` - Number *float64 ` | @Number` - String *string ` | @String` - Boolean *Boolean ` | @("TRUE" | "FALSE")` - Null bool ` | @"NULL"` - Array *Array ` | @@ )` -} - -type Boolean bool - -func (b *Boolean) Capture(values []string) error { - *b = values[0] == "TRUE" - return nil -} - -type Array struct { - Expressions []*Expression `"(" @@ ( "," @@ )* ")"` -} - -var ( - cli struct { - SQL string `arg:"" required:"" help:"SQL to parse."` - } - - sqlLexer = lexer.MustSimple([]lexer.SimpleRule{ - {`Keyword`, `(?i)\b(SELECT|FROM|TOP|DISTINCT|ALL|WHERE|GROUP|BY|HAVING|UNION|MINUS|EXCEPT|INTERSECT|ORDER|LIMIT|OFFSET|TRUE|FALSE|NULL|IS|NOT|ANY|SOME|BETWEEN|AND|OR|LIKE|AS|IN)\b`}, - {`Ident`, `[a-zA-Z_][a-zA-Z0-9_]*`}, - {`Number`, `[-+]?\d*\.?\d+([eE][-+]?\d+)?`}, - {`String`, `'[^']*'|"[^"]*"`}, - {`Operators`, `<>|!=|<=|>=|[-+*/%,.()=<>]`}, - {"whitespace", `\s+`}, - }) - parser = participle.MustBuild[Select]( - participle.Lexer(sqlLexer), - participle.Unquote("String"), - participle.CaseInsensitive("Keyword"), - // participle.Elide("Comment"), - // Need to solve left recursion detection first, if possible. - // participle.UseLookahead(), - ) -) - -func main() { - ctx := kong.Parse(&cli) - sql, err := parser.ParseString("", cli.SQL) - repr.Println(sql, repr.Indent(" "), repr.OmitEmpty(true)) - ctx.FatalIfErrorf(err) -} diff --git a/runtime/queries/metricsview.go b/runtime/queries/metricsview.go index 1f475e22a3f..b0d5e448e44 100644 --- a/runtime/queries/metricsview.go +++ b/runtime/queries/metricsview.go @@ -171,26 +171,6 @@ func metricsQuery(ctx context.Context, olap drivers.OLAPStore, priority int, sql return structTypeToMetricsViewColumn(rows.Schema), data, nil } -func olapQuery(ctx context.Context, olap drivers.OLAPStore, priority int, sql string, args []any) (*runtimev1.StructType, []*structpb.Struct, error) { - rows, err := olap.Execute(ctx, &drivers.Statement{ - Query: sql, - Args: args, - Priority: priority, - ExecutionTimeout: defaultExecutionTimeout, - }) - if err != nil { - return nil, nil, err - } - defer rows.Close() - - data, err := rowsToData(rows) - if err != nil { - return nil, nil, err - } - - return rows.Schema, data, nil -} - func rowsToData(rows *drivers.Result) ([]*structpb.Struct, error) { var data []*structpb.Struct for rows.Next() { diff --git a/runtime/queries/metricsview_aggregation.go b/runtime/queries/metricsview_aggregation.go index 447fa346b2e..c9be2e4a453 100644 --- a/runtime/queries/metricsview_aggregation.go +++ b/runtime/queries/metricsview_aggregation.go @@ -2,27 +2,15 @@ package queries import ( "context" - databasesql "database/sql" - "database/sql/driver" "encoding/json" - "errors" "fmt" "io" "os" - "slices" "strings" - "time" - "github.com/jmoiron/sqlx" - "github.com/marcboeker/go-duckdb" runtimev1 "github.com/rilldata/rill/proto/gen/rill/runtime/v1" "github.com/rilldata/rill/runtime" - "github.com/rilldata/rill/runtime/drivers" - duckdbolap "github.com/rilldata/rill/runtime/drivers/duckdb" "github.com/rilldata/rill/runtime/metricsview" - "github.com/rilldata/rill/runtime/pkg/pbutil" - "google.golang.org/protobuf/types/known/structpb" - "google.golang.org/protobuf/types/known/timestamppb" ) type MetricsViewAggregation struct { @@ -44,9 +32,8 @@ type MetricsViewAggregation struct { Aliases []*runtimev1.MetricsViewComparisonMeasureAlias `json:"aliases,omitempty"` Exact bool `json:"exact,omitempty"` - Exporting bool `json:"-"` - Result *runtimev1.MetricsViewAggregationResponse `json:"-"` - measuresMeta map[string]metricsViewMeasureMeta `json:"-"` + Exporting bool `json:"-"` + Result *runtimev1.MetricsViewAggregationResponse `json:"-"` } var _ runtime.Query = &MetricsViewAggregation{} @@ -82,2899 +69,104 @@ func (q *MetricsViewAggregation) UnmarshalResult(v any) error { } func (q *MetricsViewAggregation) Resolve(ctx context.Context, rt *runtime.Runtime, instanceID string, priority int) error { - // Resolve metrics view - mv, security, err := resolveMVAndSecurityFromAttributes(ctx, rt, instanceID, q.MetricsViewName, q.SecurityAttributes, q.SecurityPolicy, q.Dimensions, q.Measures) - if err != nil { - return err - } - - // Attempt to route to metricsview executor - qry, ok, err := q.rewriteToMetricsViewQuery(mv) - if err != nil { - return fmt.Errorf("error rewriting to metrics query: %w", err) - } - if ok { - e, err := metricsview.NewExecutor(ctx, rt, instanceID, mv, security, priority) - if err != nil { - return err - } - defer e.Close() - - res, _, err := e.Query(ctx, qry, nil) - if err != nil { - return err - } - defer res.Close() - - data, err := rowsToData(res) - if err != nil { - return err - } - - q.Result = &runtimev1.MetricsViewAggregationResponse{ - Schema: res.Schema, - Data: data, - } - return nil - } - // Falling back to the old implementation - - cfg, err := rt.InstanceConfig(ctx, instanceID) - if err != nil { - return err - } - - olap, release, err := rt.OLAP(ctx, instanceID, mv.Connector) - if err != nil { - return err - } - defer release() - - if olap.Dialect() != drivers.DialectDuckDB && olap.Dialect() != drivers.DialectDruid && olap.Dialect() != drivers.DialectClickHouse && olap.Dialect() != drivers.DialectPinot { - return fmt.Errorf("not available for dialect '%s'", olap.Dialect()) - } - - if mv.TimeDimension == "" && !isTimeRangeNil(q.TimeRange) { - return fmt.Errorf("metrics view '%s' does not have a time dimension", mv) - } - - if !isTimeRangeNil(q.TimeRange) { - start, end, err := ResolveTimeRange(q.TimeRange, mv) - if err != nil { - return err - } - q.TimeRange = &runtimev1.TimeRange{ - Start: timestamppb.New(start.In(time.UTC)), - End: timestamppb.New(end.In(time.UTC)), - } - } - - // backwards compatibility - if q.Filter != nil { - if q.Where != nil { - return fmt.Errorf("both filter and where is provided") - } - q.Where = convertFilterToExpression(q.Filter) - } - - if q.ComparisonTimeRange != nil { - if isTimeRangeNil(q.ComparisonTimeRange) || isTimeRangeNil(q.TimeRange) { - return fmt.Errorf("Undefined time range boundaries") - } - - start, end, err := ResolveTimeRange(q.TimeRange, mv) - if err != nil { - return err - } - - q.TimeRange = &runtimev1.TimeRange{ - Start: timestamppb.New(start.In(time.UTC)), - End: timestamppb.New(end.In(time.UTC)), - } - - start, end, err = ResolveTimeRange(q.ComparisonTimeRange, mv) - if err != nil { - return err - } - q.ComparisonTimeRange = &runtimev1.TimeRange{ - Start: timestamppb.New(start.In(time.UTC)), - End: timestamppb.New(end.In(time.UTC)), - } - - filterCount := 0 - for _, f := range q.Measures { - if f.Filter != nil { - filterCount++ - } - } - - if filterCount > 1 { - return fmt.Errorf("multiple measures with filter") - } - - if filterCount == 1 { - return q.executeComparisonWithMeasureFilter(ctx, olap, priority, mv, olap.Dialect(), security) - } - - return q.executeComparisonAggregation(ctx, olap, priority, mv, olap.Dialect(), security, cfg) - } - - for _, m := range q.Measures { - switch m.GetCompute().(type) { - case *runtimev1.MetricsViewAggregationMeasure_ComparisonDelta, *runtimev1.MetricsViewAggregationMeasure_ComparisonValue, *runtimev1.MetricsViewAggregationMeasure_ComparisonRatio: - return fmt.Errorf("comaparison measures without comparison time range") - } - } - - if olap.Dialect() == drivers.DialectDuckDB { - sqlString, args, err := q.buildMetricsAggregationSQL(mv, olap.Dialect(), security, cfg.PivotCellLimit) - if err != nil { - return fmt.Errorf("error building query: %w", err) - } - - if len(q.PivotOn) == 0 { - schema, data, err := olapQuery(ctx, olap, priority, sqlString, args) - if err != nil { - return err - } - - q.Result = &runtimev1.MetricsViewAggregationResponse{ - Schema: schema, - Data: data, - } - return nil - } - return olap.WithConnection(ctx, priority, false, false, func(ctx context.Context, ensuredCtx context.Context, conn *databasesql.Conn) error { - temporaryTableName := tempName("_for_pivot_") - - err := olap.Exec(ctx, &drivers.Statement{ - Query: fmt.Sprintf("CREATE TEMPORARY TABLE %[1]s AS %[2]s", temporaryTableName, sqlString), - Args: args, - Priority: priority, - }) - if err != nil { - return err - } - - res, err := olap.Execute(ctx, &drivers.Statement{ // a separate query instead of the multi-statement query due to a DuckDB bug - Query: fmt.Sprintf("SELECT COUNT(*) FROM %[1]s", temporaryTableName), - Priority: priority, - }) - if err != nil { - return err - } - - count := 0 - if res.Next() { - err := res.Scan(&count) - if err != nil { - res.Close() - return err - } - - if count > int(cfg.PivotCellLimit)/q.cols() { - res.Close() - return fmt.Errorf("PIVOT cells count exceeded %d", cfg.PivotCellLimit) - } - } - res.Close() - - defer func() { - _ = olap.Exec(ensuredCtx, &drivers.Statement{ - Query: `DROP TABLE "` + temporaryTableName + `"`, - }) - }() - - schema, data, err := olapQuery(ctx, olap, int(q.Priority), q.createPivotSQL(temporaryTableName, mv), nil) - if err != nil { - return err - } - - if q.Limit != nil && *q.Limit > 0 && int64(len(data)) > *q.Limit { - return fmt.Errorf("Limit exceeded %d", *q.Limit) - } - - q.Result = &runtimev1.MetricsViewAggregationResponse{ - Schema: schema, - Data: data, - } - - return nil - }) - } - - sqlString, args, err := q.buildMetricsAggregationSQL(mv, olap.Dialect(), security, cfg.PivotCellLimit) - if err != nil { - return fmt.Errorf("error building query: %w", err) - } - - if len(q.PivotOn) == 0 { - schema, data, err := olapQuery(ctx, olap, priority, sqlString, args) - if err != nil { - return err - } - - q.Result = &runtimev1.MetricsViewAggregationResponse{ - Schema: schema, - Data: data, - } - return nil - } - - rows, err := olap.Execute(ctx, &drivers.Statement{ - Query: sqlString, - Args: args, - Priority: priority, - ExecutionTimeout: defaultExecutionTimeout, - }) - if err != nil { - return nil - } - defer rows.Close() - - return q.pivotDruid(ctx, rows, mv, cfg.PivotCellLimit, func(temporaryTableName string, mv *runtimev1.MetricsViewSpec) string { - return q.createPivotSQL(temporaryTableName, mv) - }) -} - -func (q *MetricsViewAggregation) executeComparisonWithMeasureFilter(ctx context.Context, olap drivers.OLAPStore, priority int, mv *runtimev1.MetricsViewSpec, dialect drivers.Dialect, security *runtime.ResolvedMetricsViewSecurity) error { - sqlString, args, err := q.buildMeasureFilterComparisonAggregationSQL(mv, dialect, security, false) - if err != nil { - return fmt.Errorf("error building query: %w", err) - } - - if len(q.PivotOn) == 0 { - schema, data, err := olapQuery(ctx, olap, priority, sqlString, args) - if err != nil { - return err - } - - q.Result = &runtimev1.MetricsViewAggregationResponse{ - Schema: schema, - Data: data, - } - return nil - } - - return fmt.Errorf("pivot unsupported for the measure filter") -} - -func (q *MetricsViewAggregation) executeComparisonAggregation(ctx context.Context, olap drivers.OLAPStore, priority int, mv *runtimev1.MetricsViewSpec, dialect drivers.Dialect, security *runtime.ResolvedMetricsViewSecurity, cfg drivers.InstanceConfig) error { - sqlString, args, err := q.buildMetricsComparisonAggregationSQL(ctx, olap, priority, mv, dialect, security, false) - if err != nil { - return fmt.Errorf("error building query: %w", err) - } - - if len(q.PivotOn) == 0 { - schema, data, err := olapQuery(ctx, olap, priority, sqlString, args) - if err != nil { - return err - } - - q.Result = &runtimev1.MetricsViewAggregationResponse{ - Schema: schema, - Data: data, - } - return nil - } - - if olap.Dialect() == drivers.DialectDuckDB { - return olap.WithConnection(ctx, priority, false, false, func(ctx context.Context, ensuredCtx context.Context, conn *databasesql.Conn) error { - temporaryTableName := tempName("_for_pivot_") - - err := olap.Exec(ctx, &drivers.Statement{ - Query: fmt.Sprintf("CREATE TEMPORARY TABLE %[1]s AS %[2]s", temporaryTableName, sqlString), - Args: args, - Priority: priority, - }) - if err != nil { - return err - } - - res, err := olap.Execute(ctx, &drivers.Statement{ // a separate query instead of the multi-statement query due to a DuckDB bug - Query: fmt.Sprintf("SELECT COUNT(*) FROM %[1]s", temporaryTableName), - Priority: priority, - }) - if err != nil { - return err - } - - count := 0 - if res.Next() { - err := res.Scan(&count) - if err != nil { - res.Close() - return err - } - - if count > int(cfg.PivotCellLimit)/q.cols() { - res.Close() - return fmt.Errorf("PIVOT cells count exceeded %d", cfg.PivotCellLimit) - } - } - res.Close() - - defer func() { - _ = olap.Exec(ensuredCtx, &drivers.Statement{ - Query: `DROP TABLE "` + temporaryTableName + `"`, - }) - }() - - schema, data, err := olapQuery(ctx, olap, int(q.Priority), q.createComparisonPivotSQL(temporaryTableName, mv), nil) - if err != nil { - return err - } - - if q.Limit != nil && *q.Limit > 0 && int64(len(data)) > *q.Limit { - return fmt.Errorf("Limit exceeded %d", *q.Limit) - } - - q.Result = &runtimev1.MetricsViewAggregationResponse{ - Schema: schema, - Data: data, - } - - return nil - }) - } - - rows, err := olap.Execute(ctx, &drivers.Statement{ - Query: sqlString, - Args: args, - Priority: priority, - ExecutionTimeout: defaultExecutionTimeout, - }) - if err != nil { - return nil - } - defer rows.Close() - - return q.pivotDruid(ctx, rows, mv, cfg.PivotCellLimit, func(temporaryTableName string, mv *runtimev1.MetricsViewSpec) string { - return q.createComparisonPivotSQL(temporaryTableName, mv) - }) -} - -func (q *MetricsViewAggregation) pivotDruid(ctx context.Context, rows *drivers.Result, mv *runtimev1.MetricsViewSpec, pivotCellLimit int64, pivotSQL func(temporaryTableName string, mv *runtimev1.MetricsViewSpec) string) error { - pivotDB, err := sqlx.Connect("duckdb", "") - if err != nil { - return err - } - defer pivotDB.Close() - - return func() error { - temporaryTableName := tempName("_for_pivot_") - createTableSQL, err := duckdbolap.CreateTableQuery(rows.Schema, temporaryTableName) - if err != nil { - return err - } - - _, err = pivotDB.ExecContext(ctx, createTableSQL) - if err != nil { - return err - } - defer func() { - _, _ = pivotDB.ExecContext(context.Background(), `DROP TABLE "`+temporaryTableName+`"`) - }() - - conn, err := pivotDB.Conn(ctx) - if err != nil { - return nil - } - defer conn.Close() - - err = conn.Raw(func(conn any) error { - driverCon, ok := conn.(driver.Conn) - if !ok { - return fmt.Errorf("cannot obtain driver.Conn") - } - appender, err := duckdb.NewAppenderFromConn(driverCon, "", temporaryTableName) - if err != nil { - return err - } - defer appender.Close() - - batchSize := 10000 - columns, err := rows.Columns() - if err != nil { - return err - } - - scanValues := make([]any, len(columns)) - appendValues := make([]driver.Value, len(columns)) - for i := range scanValues { - scanValues[i] = new(interface{}) - } - count := 0 - maxCount := int(pivotCellLimit) / q.cols() - - for rows.Next() { - err = rows.Scan(scanValues...) - if err != nil { - return err - } - for i := range columns { - appendValues[i] = driver.Value(*(scanValues[i].(*interface{}))) - } - err = appender.AppendRow(appendValues...) - if err != nil { - return fmt.Errorf("duckdb append failed: %w", err) - } - count++ - if count > maxCount { - return fmt.Errorf("PIVOT cells count limit exceeded %d", pivotCellLimit) - } - - if count >= batchSize { - appender.Flush() - count = 0 - } - } - appender.Flush() - - return nil - }) - if err != nil { - return err - } - if rows.Err() != nil { - return rows.Err() - } - - ctx, cancelFunc := context.WithTimeout(ctx, defaultExecutionTimeout) - defer cancelFunc() - pivotRows, err := pivotDB.QueryxContext(ctx, pivotSQL(temporaryTableName, mv)) - if err != nil { - return err - } - defer pivotRows.Close() - - schema, err := duckdbolap.RowsToSchema(pivotRows) - if err != nil { - return err - } - - data, err := toData(pivotRows, schema) - if err != nil { - return err - } - - if q.Limit != nil && *q.Limit > 0 && int64(len(data)) > *q.Limit { - return fmt.Errorf("Limit exceeded %d", *q.Limit) - } - - q.Result = &runtimev1.MetricsViewAggregationResponse{ - Schema: schema, - Data: data, - } - - return nil - }() -} - -func (q *MetricsViewAggregation) createPivotSQL(temporaryTableName string, mv *runtimev1.MetricsViewSpec) string { - selectCols := make([]string, 0, len(q.Dimensions)+len(q.Measures)) - aliasesMap := make(map[string]string) - pivotMap := make(map[string]bool) - for _, p := range q.PivotOn { - pivotMap[p] = true - } - if q.Exporting { - for _, e := range mv.Measures { - aliasesMap[e.Name] = e.Name - if e.Label != "" { - aliasesMap[e.Name] = e.Label - } - } - - for _, e := range mv.Dimensions { - aliasesMap[e.Name] = e.Name - if e.Label != "" { - aliasesMap[e.Name] = e.Label - } - } - for _, e := range q.Dimensions { - if e.TimeGrain != runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - aliasesMap[e.Name] = e.Name - if e.Alias != "" { - aliasesMap[e.Alias] = e.Alias - } - } - } - - for _, d := range q.Dimensions { - if d.TimeGrain == runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - expr := safeName(d.Name) - if pivotMap[d.Name] { - expr = fmt.Sprintf("lower(%s)", safeName(d.Name)) - } - selectCols = append(selectCols, fmt.Sprintf("%s AS %s", expr, safeName(aliasesMap[d.Name]))) - } else { - alias := d.Name - if d.Alias != "" { - alias = d.Alias - } - selectCols = append(selectCols, safeName(alias)) - } - } - for _, m := range q.Measures { - selectCols = append(selectCols, fmt.Sprintf("%s AS %s", safeName(m.Name), safeName(aliasesMap[m.Name]))) - } - } - measureCols := make([]string, 0, len(q.Measures)) - for _, m := range q.Measures { - alias := safeName(m.Name) - if q.Exporting { - alias = safeName(aliasesMap[m.Name]) - } - measureCols = append(measureCols, fmt.Sprintf("LAST(%s) as %s", alias, alias)) - } - - pivots := make([]string, len(q.PivotOn)) - for i, p := range q.PivotOn { - pivots[i] = p - if q.Exporting { - pivots[i] = safeName(aliasesMap[p]) - } - } - - sortingCriteria := make([]string, 0, len(q.Sort)) - for _, s := range q.Sort { - sortCriterion := safeName(s.Name) - if q.Exporting { - sortCriterion = safeName(aliasesMap[s.Name]) - } - - if s.Desc { - sortCriterion += " DESC" - } - sortCriterion += " NULLS LAST" - sortingCriteria = append(sortingCriteria, sortCriterion) - } - - orderClause := "" - if len(sortingCriteria) > 0 { - orderClause = "ORDER BY " + strings.Join(sortingCriteria, ", ") - } - - var limitClause string - if q.Limit != nil { - limit := *q.Limit - if limit == 0 { - limit = 100 - } - if q.Exporting && *q.Limit > 0 { - limit = *q.Limit + 1 - } - limitClause = fmt.Sprintf("LIMIT %d", limit) - } - - // PIVOT (SELECT m1 as M1, d1 as D1, d2 as D2) - // ON D1 USING LAST(M1) as M1 - // ORDER BY D2 LIMIT 10 OFFSET 0 - selectList := "*" - if q.Exporting { - selectList = strings.Join(selectCols, ",") - } - sql := fmt.Sprintf("PIVOT (SELECT %[7]s FROM %[1]s) ON %[2]s USING %[3]s %[4]s %[5]s OFFSET %[6]d", - temporaryTableName, // 1 - strings.Join(pivots, ", "), // 2 - strings.Join(measureCols, ", "), // 3 - orderClause, // 4 - limitClause, // 5 - q.Offset, // 6 - selectList, // 7 - ) - return sql -} - -func (q *MetricsViewAggregation) createComparisonPivotSQL(temporaryTableName string, mv *runtimev1.MetricsViewSpec) string { - selectCols := make([]string, 0, len(q.Dimensions)+len(q.Measures)) - aliasesMap := make(map[string]string) - pivotMap := make(map[string]bool) - for _, p := range q.PivotOn { - pivotMap[p] = true - } - if q.Exporting { - for _, e := range mv.Measures { - aliasesMap[e.Name] = e.Name - if e.Label != "" { - aliasesMap[e.Name] = e.Label - } - } - - for _, e := range mv.Dimensions { - aliasesMap[e.Name] = e.Name - if e.Label != "" { - aliasesMap[e.Name] = e.Label - } - } - for _, e := range q.Dimensions { - if e.TimeGrain != runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - aliasesMap[e.Name] = e.Name - if e.Alias != "" { - aliasesMap[e.Alias] = e.Alias - } - } - } - - for _, d := range q.Dimensions { - if d.TimeGrain == runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - expr := safeName(d.Name) - if pivotMap[d.Name] { - expr = fmt.Sprintf("lower(%s)", safeName(d.Name)) // workaround for DuckDB PIVOT issue - } - selectCols = append(selectCols, fmt.Sprintf("%s AS %s", expr, safeName(aliasesMap[d.Name]))) - } else { - alias := d.Name - if d.Alias != "" { - alias = d.Alias - } - selectCols = append(selectCols, safeName(alias)) - } - } - for _, m := range q.Measures { - selectCols = append(selectCols, fmt.Sprintf("%s AS %s", safeName(m.Name), safeName(aliasesMap[m.Name]))) - } - } - measureCols := make([]string, 0, len(q.Measures)) - for _, m := range q.Measures { - alias := m.Name - if q.Exporting && aliasesMap[m.Name] != "" { - alias = aliasesMap[m.Name] - } - qalias := safeName(alias) - measureCols = append(measureCols, fmt.Sprintf("LAST(%s) as %s", qalias, qalias)) - } - - pivots := make([]string, len(q.PivotOn)) - for i, p := range q.PivotOn { - pivots[i] = p - if q.Exporting { - pivots[i] = safeName(aliasesMap[p]) - } - } - - sortingCriteria := make([]string, 0, len(q.Sort)) - for _, s := range q.Sort { - sortCriterion := safeName(s.Name) - if q.Exporting { - sortCriterion = safeName(aliasesMap[s.Name]) - } - - if s.Desc { - sortCriterion += " DESC" - } - sortCriterion += " NULLS LAST" - sortingCriteria = append(sortingCriteria, sortCriterion) - } - - orderClause := "" - if len(sortingCriteria) > 0 { - orderClause = "ORDER BY " + strings.Join(sortingCriteria, ", ") - } - - var limitClause string - if q.Limit != nil { - limit := *q.Limit - if limit == 0 { - limit = 100 - } - if q.Exporting && *q.Limit > 0 { - limit = *q.Limit + 1 - } - limitClause = fmt.Sprintf("LIMIT %d", limit) - } - - selectList := "*" - if q.Exporting { - selectList = strings.Join(selectCols, ",") - } - sql := fmt.Sprintf("PIVOT (SELECT %[7]s FROM %[1]s) ON %[2]s USING %[3]s %[4]s %[5]s OFFSET %[6]d", - temporaryTableName, // 1 - strings.Join(pivots, ", "), // 2 - strings.Join(measureCols, ", "), // 3 - orderClause, // 4 - limitClause, // 5 - q.Offset, // 6 - selectList, // 7 - ) - return sql -} - -func toData(rows *sqlx.Rows, schema *runtimev1.StructType) ([]*structpb.Struct, error) { - var data []*structpb.Struct - for rows.Next() { - rowMap := make(map[string]any) - err := rows.MapScan(rowMap) - if err != nil { - return nil, err - } - - rowStruct, err := pbutil.ToStruct(rowMap, schema) - if err != nil { - return nil, err - } - - data = append(data, rowStruct) - } - - err := rows.Err() - if err != nil { - return nil, err - } - - return data, nil -} - -func (q *MetricsViewAggregation) Export(ctx context.Context, rt *runtime.Runtime, instanceID string, w io.Writer, opts *runtime.ExportOptions) error { - q.Exporting = true - - filename := strings.ReplaceAll(q.MetricsViewName, `"`, `_`) - if !isTimeRangeNil(q.TimeRange) || q.Where != nil || q.Having != nil { - filename += "_filtered" - } - - // Resolve metrics view - mv, security, err := resolveMVAndSecurityFromAttributes(ctx, rt, instanceID, q.MetricsViewName, q.SecurityAttributes, q.SecurityPolicy, q.Dimensions, q.Measures) - if err != nil { - return err - } - - // Attempt to route to metricsview executor - qry, ok, err := q.rewriteToMetricsViewQuery(mv) - if err != nil { - return fmt.Errorf("error rewriting to metrics query: %w", err) - } - if ok { - e, err := metricsview.NewExecutor(ctx, rt, instanceID, mv, security, opts.Priority) - if err != nil { - return err - } - defer e.Close() - - var format string - switch opts.Format { - case runtimev1.ExportFormat_EXPORT_FORMAT_CSV: - format = "csv" - case runtimev1.ExportFormat_EXPORT_FORMAT_XLSX: - format = "xlsx" - case runtimev1.ExportFormat_EXPORT_FORMAT_PARQUET: - format = "parquet" - default: - return fmt.Errorf("unsupported format: %s", opts.Format.String()) - } - - path, err := e.Export(ctx, qry, nil, format) - if err != nil { - return err - } - defer func() { _ = os.Remove(path) }() - - err = opts.PreWriteHook(filename) - if err != nil { - return err - } - - f, err := os.Open(path) - if err != nil { - return err - } - defer f.Close() - - _, err = io.Copy(w, f) - if err != nil { - return err - } - - return nil - } - // Falling back to the old implementation - - err = q.Resolve(ctx, rt, instanceID, opts.Priority) - if err != nil { - if errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("timeout exceeded") - } - return err - } - - meta := structTypeToMetricsViewColumn(q.Result.Schema) - - if opts.PreWriteHook != nil { - err = opts.PreWriteHook(filename) - if err != nil { - return err - } - } - - switch opts.Format { - case runtimev1.ExportFormat_EXPORT_FORMAT_UNSPECIFIED: - return fmt.Errorf("unspecified format") - case runtimev1.ExportFormat_EXPORT_FORMAT_CSV: - return WriteCSV(meta, q.Result.Data, w) - case runtimev1.ExportFormat_EXPORT_FORMAT_XLSX: - return WriteXLSX(meta, q.Result.Data, w) - case runtimev1.ExportFormat_EXPORT_FORMAT_PARQUET: - return WriteParquet(meta, q.Result.Data, w) - } - - return nil -} - -func (q *MetricsViewAggregation) cols() int { - return len(q.Dimensions) + len(q.Measures) -} - -func (q *MetricsViewAggregation) buildMetricsAggregationSQL(mv *runtimev1.MetricsViewSpec, dialect drivers.Dialect, policy *runtime.ResolvedMetricsViewSecurity, pivotCellLimit int64) (string, []any, error) { - if len(q.Dimensions) == 0 && len(q.Measures) == 0 { - return "", nil, errors.New("no dimensions or measures specified") - } - filterCount := 0 - for _, f := range q.Measures { - if f.Filter != nil { - filterCount++ - } - } - if filterCount != 0 && len(q.Measures) > 1 { - return "", nil, errors.New("multiple measures with filter") - } - if filterCount == 1 && len(q.PivotOn) > 0 { - return "", nil, errors.New("measure filter for pivot-on") - } - - cols := q.cols() - selectCols := make([]string, 0, cols) - - groupCols := make([]string, 0, len(q.Dimensions)) - unnestClauses := make([]string, 0) - var selectArgs []any - for _, d := range q.Dimensions { - // Handle regular dimensions - if d.TimeGrain == runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - dim, err := metricsViewDimension(mv, d.Name) - if err != nil { - return "", nil, err - } - dimSel, unnestClause := dialect.DimensionSelect(mv.Database, mv.DatabaseSchema, mv.Table, dim) - selectCols = append(selectCols, dimSel) - if unnestClause != "" { - unnestClauses = append(unnestClauses, unnestClause) - } - groupCols = append(groupCols, fmt.Sprintf("%d", len(selectCols))) - continue - } - - // Handle time dimension - expr, exprArgs, err := q.buildTimestampExpr(mv, d, dialect) - if err != nil { - return "", nil, err - } - alias := safeName(d.Name) - if d.Alias != "" { - alias = safeName(d.Alias) - } - selectCols = append(selectCols, fmt.Sprintf("%s as %s", expr, alias)) - // Using expr was causing issues with query arg expansion in duckdb. - // Using column name is not possible either since it will take the original column name instead of the aliased column name - // But using numbered group we can exactly target the correct selected column. - // Note that the non-timestamp columns also use the numbered group-by for constancy. - groupCols = append(groupCols, fmt.Sprintf("%d", len(selectCols))) - selectArgs = append(selectArgs, exprArgs...) - } - - for _, m := range q.Measures { - sn := safeName(m.Name) - switch m.BuiltinMeasure { - case runtimev1.BuiltinMeasure_BUILTIN_MEASURE_UNSPECIFIED: - expr, err := metricsViewMeasureExpression(mv, m.Name) - if err != nil { - return "", nil, err - } - - selectCols = append(selectCols, fmt.Sprintf("%s as %s", expr, sn)) - case runtimev1.BuiltinMeasure_BUILTIN_MEASURE_COUNT: - selectCols = append(selectCols, fmt.Sprintf("%s as %s", "COUNT(*)", sn)) - case runtimev1.BuiltinMeasure_BUILTIN_MEASURE_COUNT_DISTINCT: - if len(m.BuiltinMeasureArgs) != 1 { - return "", nil, fmt.Errorf("builtin measure '%s' expects 1 argument", m.BuiltinMeasure.String()) - } - arg := m.BuiltinMeasureArgs[0].GetStringValue() - if arg == "" { - return "", nil, fmt.Errorf("builtin measure '%s' expects non-empty string argument, got '%v'", m.BuiltinMeasure.String(), m.BuiltinMeasureArgs[0]) - } - selectCols = append(selectCols, fmt.Sprintf("%s as %s", fmt.Sprintf("COUNT(DISTINCT %s)", safeName(arg)), sn)) - default: - return "", nil, fmt.Errorf("unknown builtin measure '%d'", m.BuiltinMeasure) - } - } - - groupClause := "" - if len(groupCols) > 0 { - groupClause = "GROUP BY " + strings.Join(groupCols, ", ") - } - - whereClause := "" - var whereArgs []any - if mv.TimeDimension != "" { - timeCol := safeName(mv.TimeDimension) - if dialect == drivers.DialectDuckDB { - timeCol = fmt.Sprintf("%s::TIMESTAMP", timeCol) - } - clause, err := timeRangeClause(q.TimeRange, mv, timeCol, &whereArgs) - if err != nil { - return "", nil, err - } - whereClause += clause - } - - whereBuilder := &ExpressionBuilder{ - mv: mv, - dialect: dialect, - } - if q.Where != nil { - clause, clauseArgs, err := whereBuilder.buildExpression(q.Where) - if err != nil { - return "", nil, err - } - if strings.TrimSpace(clause) != "" { - whereClause += fmt.Sprintf(" AND (%s)", clause) - } - whereArgs = append(whereArgs, clauseArgs...) - } - - if policy != nil && policy.RowFilter != "" { - whereClause += fmt.Sprintf(" AND (%s)", policy.RowFilter) - } - - if whereClause != "" { - whereClause = "WHERE 1=1" + whereClause - } - - var havingClause, extraWhereClause string - var havingClauseArgs, extraWhereClauseArgs []any - if q.Having != nil { - var err error - // HAVING expression is converted to WHERE expression here - extraWhereClause, extraWhereClauseArgs, err = whereBuilder.buildExpression(q.Having) - if err != nil { - return "", nil, err - } - - havingBuilder := &ExpressionBuilder{ - mv: mv, - dialect: dialect, - having: true, - } - havingClause, havingClauseArgs, err = havingBuilder.buildExpression(q.Having) - if err != nil { - return "", nil, err - } - - if strings.TrimSpace(havingClause) != "" { - havingClause = "HAVING " + havingClause - } - } - - sortingCriteria := make([]string, 0, len(q.Sort)) - for _, s := range q.Sort { - sortCriterion := safeName(s.Name) - if s.Desc { - sortCriterion += " DESC" - } - if dialect == drivers.DialectDuckDB { - sortCriterion += " NULLS LAST" - } - sortingCriteria = append(sortingCriteria, sortCriterion) - } - orderClause := "" - if len(sortingCriteria) > 0 { - orderClause = "ORDER BY " + strings.Join(sortingCriteria, ", ") - } - - var limitClause string - if q.Limit != nil { - limit := *q.Limit - if limit == 0 { - limit = 100 - } - limitClause = fmt.Sprintf("LIMIT %d", limit) - } - - var args []any - args = append(args, selectArgs...) - args = append(args, whereArgs...) - args = append(args, havingClauseArgs...) - - var sql string - if len(q.PivotOn) > 0 { - l := int(pivotCellLimit) / q.cols() - limitClause = fmt.Sprintf("LIMIT %d", l+1) - - if q.Offset != 0 { - return "", nil, fmt.Errorf("offset not supported for pivot queries") - } - - // SELECT m1, m2, d1, d2 FROM t, LATERAL UNNEST(t.d1) tbl(unnested_d1_) WHERE d1 = 'a' GROUP BY d1, d2 - sql = fmt.Sprintf("SELECT %[1]s FROM %[2]s %[3]s %[4]s %[5]s %[6]s %[7]s %[8]s", - strings.Join(selectCols, ", "), // 1 - escapeMetricsViewTable(dialect, mv), // 2 - strings.Join(unnestClauses, ""), // 3 - whereClause, // 4 - groupClause, // 5 - havingClause, // 6 - orderClause, // 7 - limitClause, // 8 - ) - } else { - if filterCount == 1 { - return q.buildMeasureFilterSQL(mv, unnestClauses, selectCols, limitClause, orderClause, havingClause, whereClause, groupClause, args, selectArgs, whereArgs, havingClauseArgs, extraWhereClause, extraWhereClauseArgs, dialect) - } - sql = fmt.Sprintf("SELECT %[1]s FROM %[2]s %[3]s %[4]s %[5]s %[6]s %[7]s %[8]s OFFSET %[9]d", - strings.Join(selectCols, ", "), // 1 - escapeMetricsViewTable(dialect, mv), // 2 - strings.Join(unnestClauses, ""), // 3 - whereClause, // 3 - groupClause, // 4 - havingClause, // 5 - orderClause, // 6 - limitClause, // 7 - q.Offset, // 8 - ) - } - - return sql, args, nil -} - -func originalName(m *runtimev1.MetricsViewAggregationMeasure) string { - switch t := m.Compute.(type) { - case *runtimev1.MetricsViewAggregationMeasure_ComparisonRatio: - return t.ComparisonRatio.Measure - case *runtimev1.MetricsViewAggregationMeasure_ComparisonDelta: - return t.ComparisonDelta.Measure - case *runtimev1.MetricsViewAggregationMeasure_ComparisonValue: - return t.ComparisonValue.Measure - case *runtimev1.MetricsViewAggregationMeasure_Count: - return m.Name - case *runtimev1.MetricsViewAggregationMeasure_CountDistinct: - return m.Name - default: - return m.Name - } -} - -func (q *MetricsViewAggregation) buildMeasureFilterComparisonAggregationSQL(mv *runtimev1.MetricsViewSpec, dialect drivers.Dialect, policy *runtime.ResolvedMetricsViewSecurity, export bool) (string, []any, error) { - originals := make(map[string]bool, len(q.Measures)) - for _, m := range q.Measures { - if m.Compute != nil { - originals[originalName(m)] = true - } - } - if len(originals) > 1 { - return "", nil, errors.New("more than one original measures specified") - } - - if len(q.Dimensions) == 0 && len(q.Measures) == 0 { - return "", nil, errors.New("no dimensions or measures specified") - } - - if len(q.Measures) == 1 { - m := &runtimev1.MetricsViewAggregationMeasure{} - switch t := q.Measures[0].Compute.(type) { - case *runtimev1.MetricsViewAggregationMeasure_ComparisonValue: - m.Name = t.ComparisonValue.Measure - case *runtimev1.MetricsViewAggregationMeasure_ComparisonDelta: - m.Name = t.ComparisonDelta.Measure - case *runtimev1.MetricsViewAggregationMeasure_ComparisonRatio: - m.Name = t.ComparisonRatio.Measure - } - q.Measures = append(q.Measures, m) - } - - dimByName := make(map[string]*runtimev1.MetricsViewAggregationDimension, len(mv.Dimensions)) - measuresByFinalName := make(map[string]*runtimev1.MetricsViewAggregationMeasure, len(q.Measures)) - for _, d := range q.Dimensions { - if d.TimeGrain == runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED || d.Alias == "" { - dimByName[d.Name] = d - } else { - dimByName[d.Alias] = d - } - } - for _, m := range q.Measures { - measuresByFinalName[m.Name] = m - } - - cols := q.cols() - selectCols := make([]string, 0, cols+1) - var comparisonSelectCols []string - - finalDims := make([]string, 0, len(q.Dimensions)) - joinConditions := make([]string, 0, len(q.Dimensions)) - - unnestClauses := make([]string, 0) - var selectArgs []any - - err := q.calculateMeasuresMeta() - if err != nil { - return "", nil, err - } - - // Required for t_offset, ie - // SELECT t_offset, d1, d2, t1, t2, m1, m2 - minTimeGrain := runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED - for _, d := range q.Dimensions { - if d.TimeGrain != runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED && d.GetName() == mv.TimeDimension { - if minTimeGrain == runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED || d.TimeGrain < minTimeGrain { - minTimeGrain = d.TimeGrain - } - } - } - - // it's required for joining the base and comparison tables - timeOffsetExpression, err := q.buildOffsetExpression(mv.TimeDimension, minTimeGrain, dialect) - if err != nil { - return "", nil, err - } - - colMap := make(map[string]int, q.cols()) - - selectCols = append(selectCols, timeOffsetExpression) - comparisonSelectCols = append(comparisonSelectCols, timeOffsetExpression) - - joinConditions = append(joinConditions, "base.t_offset = comparison.t_offset") - var finalComparisonTimeDims []string - - mvDimsByName := make(map[string]*runtimev1.MetricsViewSpec_DimensionV2, len(mv.Dimensions)) - finalDims = append(finalDims, "COALESCE(base.t_offset, comparison.t_offset) AS t_offset") - var timeDims []string - var subSelectDimAliases []string - for _, d := range q.Dimensions { - // Handle regular dimensions - if d.TimeGrain == runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - dim, err := metricsViewDimension(mv, d.Name) - if err != nil { - return "", nil, err - } - mvDimsByName[d.Name] = dim - expr, alias, unnestClause := dialect.DimensionSelectPair(mv.Database, mv.DatabaseSchema, mv.Table, dim) - dimSel := fmt.Sprintf("%s AS %s", expr, alias) - selectCols = append(selectCols, dimSel) - subSelectDimAliases = append(subSelectDimAliases, alias) - comparisonSelectCols = append(comparisonSelectCols, dimSel) - finalDims = append(finalDims, fmt.Sprintf("COALESCE(base.%[1]s,comparison.%[1]s) as %[1]s", safeName(dim.Name))) - if unnestClause != "" { - unnestClauses = append(unnestClauses, unnestClause) - } - colMap[d.Name] = len(selectCols) - var joinCondition string - if dialect == drivers.DialectClickHouse { - joinCondition = fmt.Sprintf("isNotDistinctFrom(base.%[1]s, comparison.%[1]s)", safeName(dim.Name)) - } else { - joinCondition = fmt.Sprintf("base.%[1]s IS NOT DISTINCT FROM comparison.%[1]s", safeName(dim.Name)) - } - joinConditions = append(joinConditions, joinCondition) - continue - } - - // Handle time dimension - expr, exprArgs, err := q.buildTimestampExpr(mv, d, dialect) - if err != nil { - return "", nil, err - } - alias := d.Name - if d.Alias != "" { - alias = d.Alias - } - timeDimClause := fmt.Sprintf("%s as %s", expr, safeName(alias)) - - timeDims = append(timeDims, safeName(alias)) - selectCols = append(selectCols, timeDimClause) - subSelectDimAliases = append(subSelectDimAliases, safeName(alias)) - colMap[alias] = len(selectCols) - comparisonSelectCols = append(comparisonSelectCols, timeDimClause) - finalDims = append(finalDims, fmt.Sprintf("base.%[1]s as %[1]s", safeName(alias))) - if dialect == drivers.DialectDruid { - finalComparisonTimeDims = append(finalComparisonTimeDims, fmt.Sprintf("MILLIS_TO_TIMESTAMP(PARSE_LONG(ANY_VALUE(comparison.%[1]s))) as %[2]s", safeName(alias), safeName(alias+"__previous"))) - } else { - finalComparisonTimeDims = append(finalComparisonTimeDims, fmt.Sprintf("comparison.%[1]s as %[2]s", safeName(alias), safeName(alias+"__previous"))) - } - - selectArgs = append(selectArgs, exprArgs...) - } - - labelMap := make(map[string]string, len(mv.Measures)) - for _, m := range mv.Measures { - labelMap[m.Name] = m.Name - if m.Label != "" { - labelMap[m.Name] = m.Label - } - } - - var subselectMeasureAliases []string - var subselectComparisonAliases []string - - originalMeasure := OriginalColumnName(q.Measures[0]) - // collect subquery expressions - for _, m := range q.Measures { - switch m.Compute.(type) { - case *runtimev1.MetricsViewAggregationMeasure_ComparisonValue, *runtimev1.MetricsViewAggregationMeasure_ComparisonDelta, *runtimev1.MetricsViewAggregationMeasure_ComparisonRatio: - // nothing - case *runtimev1.MetricsViewAggregationMeasure_Count: - selectCols = append(selectCols, fmt.Sprintf("COUNT(*) as %s", safeName(m.Name))) - subselectMeasureAliases = append(subselectMeasureAliases, safeName(m.Name)) - if q.measuresMeta[m.Name].expand { - comparisonSelectCols = append(comparisonSelectCols, fmt.Sprintf("COUNT(*) as %s", safeName(m.Name))) - subselectComparisonAliases = append(subselectComparisonAliases, safeName(m.Name)) - } - case *runtimev1.MetricsViewAggregationMeasure_CountDistinct: - arg := m.GetCountDistinct().GetDimension() - if arg == "" { - return "", nil, fmt.Errorf("builtin measure '%s' expects non-empty string argument, got '%v'", m.BuiltinMeasure.String(), m.BuiltinMeasureArgs[0]) - } - selectCols = append(selectCols, fmt.Sprintf("COUNT(DISTINCT %s) as %s", safeName(arg), safeName(m.Name))) - subselectMeasureAliases = append(subselectMeasureAliases, safeName(m.Name)) - if q.measuresMeta[m.Name].expand { - comparisonSelectCols = append(comparisonSelectCols, fmt.Sprintf("COUNT(DISTINCT %s) as %s", safeName(arg), safeName(m.Name))) - subselectComparisonAliases = append(subselectComparisonAliases, safeName(m.Name)) - } - default: - expr, err := metricsViewMeasureExpression(mv, m.Name) - if err != nil { - return "", nil, err - } - selectCols = append(selectCols, fmt.Sprintf("%s as %s", expr, safeName(m.Name))) - subselectMeasureAliases = append(subselectMeasureAliases, safeName(m.Name)) - if q.measuresMeta[m.Name].expand { - comparisonSelectCols = append(comparisonSelectCols, fmt.Sprintf("%s as %s", expr, safeName(m.Name))) - subselectComparisonAliases = append(subselectComparisonAliases, safeName(m.Name)) - } - } - } - - var finalMeasures []*FinalMeasure - for _, m := range q.Measures { - var subqueryName, finalName string - prefix := "" - - var finalMeasure FinalMeasure - switch m.Compute.(type) { - case *runtimev1.MetricsViewAggregationMeasure_ComparisonRatio: - subqueryName = m.GetComparisonRatio().Measure - finalName = m.Name - if dialect == drivers.DialectDruid { - finalMeasure.expression = fmt.Sprintf("SAFE_DIVIDE(base.%[1]s - comparison.%[1]s, CAST(comparison.%[1]s AS DOUBLE))", safeName(subqueryName)) - finalMeasure.alias = safeName(finalName) - } else { - finalMeasure.expression = fmt.Sprintf( - "(base.%[1]s - comparison.%[1]s)/comparison.%[1]s::DOUBLE", - safeName(subqueryName)) - finalMeasure.alias = safeName(finalName) - } - case *runtimev1.MetricsViewAggregationMeasure_ComparisonDelta: - subqueryName = m.GetComparisonDelta().Measure - finalName = m.Name - - finalMeasure.alias = safeName(finalName) - finalMeasure.expression = fmt.Sprintf( // non-virtial columns have a label - "%[2]s(base.%[1]s - comparison.%[1]s)", - safeName(subqueryName), - prefix, - ) - case *runtimev1.MetricsViewAggregationMeasure_ComparisonValue: - subqueryName = m.GetComparisonValue().Measure - finalName = m.Name - - finalMeasure.alias = safeName(finalName) - finalMeasure.expression = fmt.Sprintf( // non-virtial columns have a label - "%[2]s(comparison.%[1]s)", - safeName(subqueryName), - prefix, - ) - case *runtimev1.MetricsViewAggregationMeasure_Count, *runtimev1.MetricsViewAggregationMeasure_CountDistinct: - subqueryName = m.Name - finalMeasure.alias = safeName(subqueryName) - finalMeasure.expression = fmt.Sprintf( // non-virtial columns have a label - "%[2]s(base.%[1]s)", - safeName(subqueryName), - prefix, - ) - default: // not a virtual (not a generated) column - subqueryName = m.Name - finalName = m.Name - - finalMeasure.expression = fmt.Sprintf( // non-virtial columns have a label - "%[2]s(base.%[1]s)", - safeName(subqueryName), - prefix, - ) - if export { - finalMeasure.alias = safeName(labelMap[subqueryName]) - } else { - finalMeasure.alias = safeName(finalName) - } - } - finalMeasures = append(finalMeasures, &finalMeasure) - } - - baseSelectClause := strings.Join(selectCols, ", ") - comparisonSelectClause := strings.Join(comparisonSelectCols, ", ") - - baseWhereClause := "1=1" - comparisonWhereClause := "1=1" - - if mv.TimeDimension == "" { - return "", nil, fmt.Errorf("metrics view '%s' doesn't have time dimension", q.MetricsViewName) - } - - td := safeName(mv.TimeDimension) - if dialect == drivers.DialectDuckDB { - td = fmt.Sprintf("%s::TIMESTAMP", td) - } - - whereBuilder := &ExpressionBuilder{ - mv: mv, - dialect: dialect, - measures: q.Measures, - } - whereClause, whereClauseArgs, err := whereBuilder.buildExpression(q.Where) - if err != nil { - return "", nil, err - } - - var baseTimeRangeArgs []any - trc, err := timeRangeClause(q.TimeRange, mv, td, &baseTimeRangeArgs) - if err != nil { - return "", nil, err - } - baseWhereClause += trc - - if whereClause != "" { - baseWhereClause += fmt.Sprintf(" AND (%s)", whereClause) - } - - var comparisonTimeRangeArgs []any - trc, err = timeRangeClause(q.ComparisonTimeRange, mv, td, &comparisonTimeRangeArgs) - if err != nil { - return "", nil, err - } - comparisonWhereClause += trc - - if whereClause != "" { - comparisonWhereClause += fmt.Sprintf(" AND (%s)", whereClause) - } - - if policy != nil && policy.RowFilter != "" { - baseWhereClause += fmt.Sprintf(" AND (%s)", policy.RowFilter) - comparisonWhereClause += fmt.Sprintf(" AND (%s)", policy.RowFilter) - } - - havingWhereClause := "1=1" - var havingClauseArgs []any - if q.Having != nil { - havingBuilder := &ExpressionBuilder{ - mv: mv, - dialect: dialect, - measures: q.Measures, - } - havingWhereClause, havingClauseArgs, err = havingBuilder.buildExpression(q.Having) - if err != nil { - return "", nil, err - } - } - - /* - Dimensions are referenced by index because time dimensions have an issue with aliases in DuckDB - and other dimensions cannot be referenced by alias in Druid. - */ - var sortConstructs []*SortConstruct - var outerSortConstructs []*SortConstruct - for _, s := range q.Sort { - dimClause := false - var outerClause, subQueryClause string - if dimByName[s.Name] != nil { // dimension - outerClause = fmt.Sprintf("%d", colMap[s.Name]-1) - subQueryClause = fmt.Sprintf("%d", colMap[s.Name]) - dimClause = true - } else if measuresByFinalName[s.Name] != nil { // measure - m := measuresByFinalName[s.Name] - outerClause = safeName(s.Name) - subQueryClause = OriginalColumnName(m) - } else { - return "", nil, fmt.Errorf("no selected dimension or measure '%s' found for sorting", s.Name) - } - - var ending string - if s.Desc { - ending += " DESC" - } - if dialect == drivers.DialectDuckDB { - ending += " NULLS LAST" - } - - if !dimClause { - sortConstructs = append(sortConstructs, &SortConstruct{ - expression: subQueryClause, - ending: ending, - }) - outerSortConstructs = append(outerSortConstructs, &SortConstruct{ - expression: outerClause, - ending: ending, - }) - } else { - sortConstructs = append(sortConstructs, &SortConstruct{ - expression: subQueryClause, - ending: ending, - dim: true, - }) - - outerSortConstructs = append(outerSortConstructs, &SortConstruct{ - expression: outerClause, - ending: ending, - dim: true, - }) - } - } - - outerWhereClause := "" - if q.Having != nil { - outerWhereClause += " WHERE " + havingWhereClause - } - - var args []any - var sql string - - cw := tempName("casewhere") - if dialect != drivers.DialectDruid { - // Using expr was causing issues with query arg expansion in duckdb. - // Using column name is not possible either since it will take the original column name instead of the aliased column name - // But using numbered group we can exactly target the correct selected column. - // Note that the non-timestamp columns also use the numbered group-by for constancy. - - // Inner grouping should include t_offset - // SELECT t_offset, d1, d2, d3 ... GROUP BY 1, 2, 3, 4 ... - innerGroupCols := make([]string, 0, len(q.Dimensions)+1) - innerGroupCols = append(innerGroupCols, "1") - for i := range q.Dimensions { - innerGroupCols = append(innerGroupCols, fmt.Sprintf("%d", i+2)) - } - - outerDims := make([]string, 0, len(q.Dimensions)) - for _, d := range q.Dimensions { - // Handle regular dimensions - if d.TimeGrain == runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - dim := mvDimsByName[d.Name] - outerDims = append(outerDims, safeName(dim.Name)) - } - } - - outerDims = append(outerDims, timeDims...) - - measureFilterClause := "" - var measureFilterArgs []any - for _, m := range q.Measures { - if m.Filter != nil { - whereBuilder := &ExpressionBuilder{ - mv: mv, - dialect: dialect, - measures: q.Measures, - } - measureFilterClause, measureFilterArgs, err = whereBuilder.buildExpression(m.Filter) - if err != nil { - return "", nil, err - } - } - } - - limitClause := "" - subqueryLimitClause := "" - limit := 0 - - if q.Limit != nil { - limit = int(*q.Limit) - subQueryLimit := limit * 2 // the amount can be halved due to duplicates - if q.Offset != 0 { - subQueryLimit = int(q.Offset) + limit*2 - } - subqueryLimitClause = fmt.Sprintf(" LIMIT %d", (subQueryLimit)) - limitClause = fmt.Sprintf(" LIMIT %d", limit) - } - - // base subquery - if minTimeGrain != runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - args = append(args, q.TimeRange.Start.AsTime()) - } - args = append(args, selectArgs...) - args = append(args, measureFilterArgs...) - args = append(args, baseTimeRangeArgs...) - args = append(args, whereClauseArgs...) - // comparison subquery - if minTimeGrain != runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - args = append(args, q.ComparisonTimeRange.Start.AsTime()) - } - args = append(args, selectArgs...) - args = append(args, measureFilterArgs...) - args = append(args, comparisonTimeRangeArgs...) - args = append(args, whereClauseArgs...) - // outer query - args = append(args, havingClauseArgs...) - - /* - CASE expression rationale: - Assume filter is `count(*)/10 FILTER publisher = 'Yahoo'`. FILTER doesn't support arithmetics on aggregation. - To mimic FILTER one can produce 2 tables, one with the filter condition and another without it and merge tables aftewards. - Full table: - Publisher count(*) - Microsoft 20 - Yahoo 40 - - Filtered table: - Publisher count(*) - Yahoo 40 - - Result: - Publisher count(*) - Microsoft nil - Yahoo 4 - - But those tables should be limited if possible to increase performance. If they're limited the tables should be ordered. - The full table should contain all the dimension values that are in the filtered table before being limited. That can be - accomplished by including a virtual column with `CASE AS "casewhere"` expression and ordering by it. - - For example, Google can have multiple domains (news.google.com, google.com, ...): - ┌───────────┬────────────────────┬───────────┬──────────────────┐ - │ pub │ m1 │ casewhere │ domain │ - │ varchar │ double │ int32 │ varchar │ - ├───────────┼────────────────────┼───────────┼──────────────────┤ - │ │ 2.8476409530549693 │ 1 │ news.google.com │ - │ Google │ 2.8545245256825496 │ 1 │ news.google.com │ - │ Google │ 3.0921039628421707 │ 0 │ google.com │ - - To get all the aggregations with specified condition grouping and ordering by 'casewhere' splits 'Google' group even when "domain" is unspecified: - ┌───────────┬────────────────────┬───────────┐ - │ pub │ m1 │ casewhere │ - │ varchar │ double │ int32 │ - ├───────────┼────────────────────┼───────────┤ - │ │ 2.8476409530549693 │ 1 │ - │ Google │ 2.8545245256825496 │ 1 │ - │ Yahoo │ 2.986993492174502 │ 0 │ - │ Facebook │ 2.9896851248642617 │ 0 │ - │ │ 3.0256741573033525 │ 0 │ - │ Google │ 3.0921039628421707 │ 0 │ - */ - sql = fmt.Sprintf(` - SELECT * EXCLUDE(`+cw+`) FROM ( - -- SELECT ... as t_offset, base.d1, ..., base.td1, ..., base.m1, ... , comparison.td1 as td1__previous, ... - SELECT `+strings.Join(slices.Concat(withPrefixCols("base", outerDims), withCaseAsClauses("coalesce(base."+cw+",comparison."+cw+") = 1", "null", finalMeasures), finalComparisonTimeDims), ",")+`, coalesce(base.`+cw+`,comparison.`+cw+`) as `+cw+` FROM - ( - -- removing duplicates - SELECT t_offset, `+strings.Join(subSelectDimAliases, ",")+","+strings.Join(inFuncCols("FIRST", subselectMeasureAliases), ",")+`, FIRST(`+cw+`) AS `+cw+` FROM ( - -- SELECT t_offset, dim1 as d1, ... timed1 as td1, ..., avg(price) as m1, ... AND d2 = 'a' ... - SELECT - %[1]s, - CASE WHEN `+measureFilterClause+` THEN 1 ELSE 0 END `+cw+` - FROM %[3]s %[7]s - WHERE (%[4]s) - GROUP BY %[2]s, %[9]d - ORDER BY `+strings.Join(convertToNullCaseClauses(cw+" = 1", sortConstructs), ",")+" "+subqueryLimitClause+` - ) - GROUP BY `+groupForDims(len(subSelectDimAliases)+1)+` - ) base - LEFT OUTER JOIN - ( - -- removing duplicates - SELECT t_offset, `+strings.Join(subSelectDimAliases, ",")+","+strings.Join(inFuncCols("FIRST", subselectComparisonAliases), ",")+`, FIRST(`+cw+`) AS `+cw+` FROM ( - -- SELECT t_offset, dim1 as d1, ..., timed1 as td1, ... avg(price) as m1, ... AND d2 = 'a' ... - SELECT - `+comparisonSelectClause+`,`+` CASE WHEN `+measureFilterClause+` THEN 1 ELSE 0 END `+cw+` - FROM %[3]s %[7]s - WHERE (%[5]s) - GROUP BY %[2]s, %[10]d - ORDER BY `+strings.Join(convertToNullCaseClauses(cw+" = 1", sortConstructs), ",")+" "+subqueryLimitClause+` - ) - GROUP BY `+groupForDims(len(subSelectDimAliases)+1)+` - ) comparison - ON - `+strings.Join(append(joinConditions, "base."+cw+"= comparison."+cw), " AND ")+` -- base.t_offset = comparison.t_offset AND ... - ) - `+outerWhereClause+` -- WHERE d1 = 'having_value' - ORDER BY `+strings.Join(convertToNullCaseClauses(cw+"= 1", outerSortConstructs), ",")+` -- ORDER BY d1, ... - `+limitClause+` - OFFSET %[8]d - `, - baseSelectClause, // 1 - strings.Join(innerGroupCols, ","), // 2 - escapeMetricsViewTable(dialect, mv), // 3 - baseWhereClause, // 4 - comparisonWhereClause, // 5 - measureFilterClause, // 6 - strings.Join(unnestClauses, ""), // 7 - q.Offset, // 8 - len(selectCols)+1, // 9 index of casewhere - len(comparisonSelectCols)+1, // 10 index of casewhere - ) - // ^^^ Outer SELECT rationale: - // Plain DuckDB query `SELECT publisher, avg(bid_price) AS bid_price ... HAVING bid_price > 0` is ambiguous because alias is the same. - } else { // Druid measure filter - limitClause := "" - subqueryLimitClause := "" - limit := 0 - - if q.Limit != nil { - limit = int(*q.Limit) - subQueryLimit := limit * 2 - if q.Offset != 0 { - subQueryLimit = int(q.Offset) + limit*2 - } - subqueryLimitClause = fmt.Sprintf(" LIMIT %d", (subQueryLimit)) - limitClause = fmt.Sprintf(" LIMIT %d", limit) - } - - measureFilterClause := "" - var measureFilterArgs []any - for _, m := range q.Measures { - if m.Filter != nil { - whereBuilder := &ExpressionBuilder{ - mv: mv, - dialect: dialect, - measures: q.Measures, - } - measureFilterClause, measureFilterArgs, err = whereBuilder.buildExpression(m.Filter) - if err != nil { - return "", nil, err - } - } - } - - innerGroupCols := make([]string, 0, len(q.Dimensions)+1) - outerGroupCols := make([]string, 0, len(q.Dimensions)) - innerGroupCols = append(innerGroupCols, "1") - for i := range q.Dimensions { - innerGroupCols = append(innerGroupCols, fmt.Sprintf("%d", i+2)) - outerGroupCols = append(outerGroupCols, fmt.Sprintf("%d", i+1)) - } - - args = args[:0] - // base subquery - if minTimeGrain != runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - args = append(args, q.TimeRange.Start.AsTime()) - } - args = append(args, selectArgs...) - args = append(args, measureFilterArgs...) - args = append(args, baseTimeRangeArgs...) - args = append(args, whereClauseArgs...) - - // comparison subquery - if minTimeGrain != runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - args = append(args, q.ComparisonTimeRange.Start.AsTime()) - } - args = append(args, selectArgs...) - args = append(args, measureFilterArgs...) - args = append(args, comparisonTimeRangeArgs...) - args = append(args, whereClauseArgs...) - - // outer query - args = append(args, havingClauseArgs...) - - if havingWhereClause != "" { - havingWhereClause = " HAVING " + havingWhereClause - } - sql = fmt.Sprintf(` - -- SELECT COALESCE(base.d1, comparison.d1), ..., base.m1, ..., base.m2 ... - SELECT `+strings.Join(slices.Concat(finalDims[1:], toClauses(inFunc("ANY_VALUE", withCase("coalesce(base."+cw+",comparison."+cw+") = 1", "null", finalMeasures))), finalComparisonTimeDims), ",")+` FROM - ( - -- 2 additional SELECTs because Druid doesn't support FIRST aggregation - -- SELECT t_offset, d1, d2 ..., CASE WHEN ARRAY_LENGTH(cw) = 1 OR ARRAY_ORDINAL(cw,1) = 1 THEN ARRAY_ORDINAL(m1,1) ELSE ARRAY_ORDINAL(m1, 2) end m1 FROM ( - SELECT t_offset, `+strings.Join(subSelectDimAliases, ",")+`,`+strings.Join(caseArraySelectCols(cw, []string{originalMeasure, cw}), ",")+` FROM ( - -- SELECT t_offset, d1, d2 ..., array_agg(m1), array_agg(casewhere) FROM ( - SELECT t_offset, `+strings.Join(subSelectDimAliases, ",")+","+strings.Join(inFuncCols("ARRAY_AGG", []string{originalMeasure, cw}), ",")+` FROM ( - -- SELECT t_offset, dim1 as d1, dim2 as d2, timed1 as td1, avg(price) as m1, ... - SELECT - %[1]s, - CASE WHEN `+measureFilterClause+` THEN 1 ELSE 0 END `+cw+` - FROM %[3]s %[6]s - WHERE %[4]s - GROUP BY %[2]s, %[7]d - ORDER BY `+strings.Join(slices.Concat(convertToNullCaseClauses(cw+"= 1", sortConstructs)), ",")+" "+subqueryLimitClause+` - ) GROUP BY t_offset, `+strings.Join(subSelectDimAliases, ",")+` - ) - ) base - LEFT JOIN - ( - SELECT t_offset, `+strings.Join(subSelectDimAliases, ",")+`,`+strings.Join(caseArraySelectCols(cw, []string{originalMeasure, cw}), ",")+` FROM ( - -- SELECT t_offset, d1, d2 ..., array_agg(m1), array_agg(casewhere) FROM ( - SELECT t_offset, `+strings.Join(subSelectDimAliases, ",")+","+strings.Join(inFuncCols("ARRAY_AGG", []string{originalMeasure, cw}), ",")+` FROM ( - SELECT - `+comparisonSelectClause+","+` CASE WHEN `+measureFilterClause+` THEN 1 ELSE 0 END `+cw+` - FROM %[3]s %[6]s - WHERE %[5]s - GROUP BY %[2]s, %[9]d - ORDER BY `+strings.Join(slices.Concat(convertToNullCaseClauses(cw+"= 1", sortConstructs)), ",")+" "+subqueryLimitClause+` - ) GROUP BY t_offset, `+strings.Join(subSelectDimAliases, ",")+` - ) - ) comparison - ON - -- base.d1 IS NOT DISTINCT FROM comparison.d1 AND base.d2 IS NOT DISTINCT FROM comparison.d2 AND ... - `+strings.Join(append(joinConditions, "base."+cw+" = comparison."+cw), " AND ")+` -- base.t_offset = comparison.t_offset AND ... - GROUP BY `+strings.Join(outerGroupCols, ",")+", coalesce(base."+cw+",comparison."+cw+") "+havingWhereClause+` - ORDER BY `+strings.Join(slices.Concat(convertToNullCaseClauses("coalesce(base."+cw+",comparison."+cw+") = 1", outerSortConstructs)), ",")+` -- ORDER BY d1, ... - `+limitClause+` - OFFSET %[8]d - `, - baseSelectClause, // 1 - strings.Join(innerGroupCols, ","), // 2 - escapeMetricsViewTable(dialect, mv), // 3 - baseWhereClause, // 4 - comparisonWhereClause, // 5 - strings.Join(unnestClauses, ""), // 6 - len(selectCols)+1, // 7 index of casewhere - q.Offset, // 8 - len(comparisonSelectCols)+1, // 9 - ) - } - - return sql, args, nil -} - -func groupForDims(n int) string { - s := "" - for i := range n { - s += fmt.Sprintf("%d", i+1) - if i != n-1 && n != 1 { - s += "," - } - } - return s -} - -type FinalMeasure struct { - expression string - alias string -} - -type SortConstruct struct { - expression string - ending string - dim bool -} - -func caseArraySelectCols(condCol string, cols []string) []string { - cs := make([]string, len(cols)) - for i, c := range cols { - cs[i] = "CASE WHEN ARRAY_LENGTH(" + condCol + ") = 1 OR ARRAY_ORDINAL(" + condCol + ",1) = 1 THEN ARRAY_ORDINAL(" + c + ",1) ELSE ARRAY_ORDINAL(" + c + ", 2) END " + c - } - return cs -} - -func convertToNullCaseClauses(cond string, sortConstructs []*SortConstruct) []string { - cs := make([]string, len(sortConstructs)) - for i, sc := range sortConstructs { - if sc.dim { - cs[i] = sc.expression + " " + sc.ending - } else { - cs[i] = "CASE WHEN " + cond + " THEN " + sc.expression + " ELSE null END " + sc.ending - } - } - return cs -} - -func withCaseAsClauses(cond, output2 string, finalMeasures []*FinalMeasure) []string { - cs := make([]string, len(finalMeasures)) - for i, fm := range finalMeasures { - cs[i] = "CASE WHEN " + cond + " THEN " + fm.expression + " ELSE " + output2 + " END AS " + fm.alias - } - return cs -} - -func withCase(cond, output2 string, finalMeasures []*FinalMeasure) []*FinalMeasure { - cs := make([]*FinalMeasure, len(finalMeasures)) - for i, fm := range finalMeasures { - cs[i] = &FinalMeasure{ - expression: "CASE WHEN " + cond + " THEN " + fm.expression + " ELSE " + output2 + " END", - alias: fm.alias, - } - } - return cs -} - -func inFunc(name string, cols []*FinalMeasure) []*FinalMeasure { - cs := make([]*FinalMeasure, len(cols)) - for i, c := range cols { - cs[i] = &FinalMeasure{ - expression: fmt.Sprintf("%s(%s)", name, c.expression), - alias: c.alias, - } - } - return cs -} - -func inFuncCols(name string, cols []string) []string { - cs := make([]string, len(cols)) - for i, c := range cols { - cs[i] = fmt.Sprintf("%s(%s) AS %s", name, c, c) - } - return cs -} - -func toClauses(cols []*FinalMeasure) []string { - cs := make([]string, len(cols)) - for i, c := range cols { - cs[i] = fmt.Sprintf("%s AS %s", c.expression, c.alias) - } - return cs -} - -func withPrefixCols(prefix string, cols []string) []string { - cs := make([]string, len(cols)) - for i, c := range cols { - cs[i] = prefix + "." + c - } - return cs -} - -func (q *MetricsViewAggregation) buildMetricsComparisonAggregationSQL(ctx context.Context, olap drivers.OLAPStore, priority int, mv *runtimev1.MetricsViewSpec, dialect drivers.Dialect, policy *runtime.ResolvedMetricsViewSecurity, export bool) (string, []any, error) { - if len(q.Dimensions) == 0 && len(q.Measures) == 0 { - return "", nil, errors.New("no dimensions or measures specified") - } - dimByName := make(map[string]*runtimev1.MetricsViewAggregationDimension, len(mv.Dimensions)) - measuresByFinalName := make(map[string]*runtimev1.MetricsViewAggregationMeasure, len(q.Measures)) - for _, d := range q.Dimensions { - if d.TimeGrain == runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED || d.Alias == "" { - dimByName[d.Name] = d - } else { - dimByName[d.Alias] = d - } - } - for _, m := range q.Measures { - measuresByFinalName[m.Name] = m - } - - cols := q.cols() - selectCols := make([]string, 0, cols+1) - var comparisonSelectCols []string - - finalDims := make([]string, 0, len(q.Dimensions)) - joinConditions := make([]string, 0, len(q.Dimensions)) - - unnestClauses := make([]string, 0) - var selectArgs []any - - err := q.calculateMeasuresMeta() + mv, security, err := resolveMVAndSecurityFromAttributes(ctx, rt, instanceID, q.MetricsViewName, q.SecurityAttributes, q.SecurityPolicy, q.Dimensions, q.Measures) if err != nil { - return "", nil, err - } - - // Required for t_offset, ie - // SELECT t_offset, d1, d2, t1, t2, m1, m2 - minTimeGrain := runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED - for _, d := range q.Dimensions { - if d.TimeGrain != runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED && d.GetName() == mv.TimeDimension { - if minTimeGrain == runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED || d.TimeGrain < minTimeGrain { - minTimeGrain = d.TimeGrain - } - } + return err } - // it's required for joining the base and comparison tables - timeOffsetExpression, err := q.buildOffsetExpression(mv.TimeDimension, minTimeGrain, dialect) + qry, err := q.rewriteToMetricsViewQuery() if err != nil { - return "", nil, err - } - - colMap := make(map[string]int, q.cols()) - - selectCols = append(selectCols, timeOffsetExpression) - comparisonSelectCols = append(comparisonSelectCols, timeOffsetExpression) - - joinConditions = append(joinConditions, "base.t_offset = comparison.t_offset") - - // these go last to decrease complexity of indexing columns - var finalComparisonTimeDims []string - mvDimsByName := make(map[string]*runtimev1.MetricsViewSpec_DimensionV2, len(mv.Dimensions)) - for _, d := range q.Dimensions { - // Handle regular dimensions - if d.TimeGrain == runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - dim, err := metricsViewDimension(mv, d.Name) - if err != nil { - return "", nil, err - } - mvDimsByName[d.Name] = dim - dimSel, unnestClause := dialect.DimensionSelect(mv.Database, mv.DatabaseSchema, mv.Table, dim) - selectCols = append(selectCols, dimSel) - comparisonSelectCols = append(comparisonSelectCols, dimSel) - finalDims = append(finalDims, fmt.Sprintf("COALESCE(base.%[1]s,comparison.%[1]s) as %[1]s", safeName(dim.Name))) - if unnestClause != "" { - unnestClauses = append(unnestClauses, unnestClause) - } - colMap[d.Name] = len(selectCols) - var joinCondition string - if dialect == drivers.DialectClickHouse { - joinCondition = fmt.Sprintf("isNotDistinctFrom(base.%[1]s, comparison.%[1]s)", safeName(dim.Name)) - } else { - joinCondition = fmt.Sprintf("base.%[1]s IS NOT DISTINCT FROM comparison.%[1]s", safeName(dim.Name)) - } - joinConditions = append(joinConditions, joinCondition) - continue - } - - // Handle time dimension - expr, exprArgs, err := q.buildTimestampExpr(mv, d, dialect) - if err != nil { - return "", nil, err - } - alias := d.Name - if d.Alias != "" { - alias = d.Alias - } - timeDimClause := fmt.Sprintf("%s as %s", expr, safeName(alias)) - - selectCols = append(selectCols, timeDimClause) - colMap[alias] = len(selectCols) - comparisonSelectCols = append(comparisonSelectCols, timeDimClause) - finalDims = append(finalDims, fmt.Sprintf("base.%[1]s as %[1]s", safeName(alias))) - // workaround for Druid time conversion with aggregates bug - if dialect == drivers.DialectDruid { - finalComparisonTimeDims = append(finalComparisonTimeDims, fmt.Sprintf("MILLIS_TO_TIMESTAMP(PARSE_LONG(ANY_VALUE(comparison.%[1]s))) as %[2]s", safeName(alias), safeName(alias+"__previous"))) - } else { - finalComparisonTimeDims = append(finalComparisonTimeDims, fmt.Sprintf("comparison.%[1]s as %[2]s", safeName(alias), safeName(alias+"__previous"))) - } - - selectArgs = append(selectArgs, exprArgs...) - } - - labelMap := make(map[string]string, len(mv.Measures)) - for _, m := range mv.Measures { - labelMap[m.Name] = m.Name - if m.Label != "" { - labelMap[m.Name] = m.Label - } - } - - // collect subquery expressions - for _, m := range q.Measures { - switch m.Compute.(type) { - case *runtimev1.MetricsViewAggregationMeasure_ComparisonValue, *runtimev1.MetricsViewAggregationMeasure_ComparisonDelta, *runtimev1.MetricsViewAggregationMeasure_ComparisonRatio: - // nothing - case *runtimev1.MetricsViewAggregationMeasure_Count: - selectCols = append(selectCols, fmt.Sprintf("COUNT(*) as %s", safeName(m.Name))) - if q.measuresMeta[m.Name].expand { - comparisonSelectCols = append(comparisonSelectCols, fmt.Sprintf("COUNT(*) as %s", safeName(m.Name))) - } - case *runtimev1.MetricsViewAggregationMeasure_CountDistinct: - arg := m.GetCountDistinct().GetDimension() - if arg == "" { - return "", nil, fmt.Errorf("builtin measure '%s' expects non-empty string argument, got '%v'", m.BuiltinMeasure.String(), m.BuiltinMeasureArgs[0]) - } - selectCols = append(selectCols, fmt.Sprintf("COUNT(DISTINCT %s) as %s", safeName(arg), safeName(m.Name))) - if q.measuresMeta[m.Name].expand { - comparisonSelectCols = append(comparisonSelectCols, fmt.Sprintf("COUNT(DISTINCT %s) as %s", safeName(arg), safeName(m.Name))) - } - default: - expr, err := metricsViewMeasureExpression(mv, m.Name) - if err != nil { - return "", nil, err - } - selectCols = append(selectCols, fmt.Sprintf("%s as %s", expr, safeName(m.Name))) - if q.measuresMeta[m.Name].expand { - comparisonSelectCols = append(comparisonSelectCols, fmt.Sprintf("%s as %s", expr, safeName(m.Name))) - } - } - } - - // collect final expressions - var finalSelectCols []string - var labelCols []string - for _, m := range q.Measures { - var columnsTuple string - var labelTuple string - var subqueryName, finalName string - prefix := "" - if dialect == drivers.DialectDruid { - prefix = "ANY_VALUE" - } - - switch m.Compute.(type) { - case *runtimev1.MetricsViewAggregationMeasure_ComparisonRatio: - subqueryName = m.GetComparisonRatio().Measure - finalName = m.Name - if dialect == drivers.DialectDruid { - columnsTuple = fmt.Sprintf( - "ANY_VALUE(SAFE_DIVIDE(base.%[1]s - comparison.%[1]s, CAST(comparison.%[1]s AS DOUBLE))) AS %[2]s", - safeName(subqueryName), - safeName(finalName), - ) - } else { - columnsTuple = fmt.Sprintf( - "(base.%[1]s - comparison.%[1]s)/comparison.%[1]s::DOUBLE AS %[2]s", - safeName(subqueryName), - safeName(finalName), - ) - } - labelTuple = columnsTuple - case *runtimev1.MetricsViewAggregationMeasure_ComparisonDelta: - subqueryName = m.GetComparisonDelta().Measure - finalName = m.Name - columnsTuple = fmt.Sprintf( - "%[3]s(base.%[1]s - comparison.%[1]s) AS %[2]s", - safeName(subqueryName), - safeName(finalName), - prefix, - ) - labelTuple = columnsTuple - case *runtimev1.MetricsViewAggregationMeasure_ComparisonValue: - subqueryName = m.GetComparisonValue().Measure - finalName = m.Name - columnsTuple = fmt.Sprintf( - "%[3]s(comparison.%[1]s) AS %[2]s", - safeName(subqueryName), - safeName(finalName), - prefix, - ) - labelTuple = columnsTuple - case *runtimev1.MetricsViewAggregationMeasure_Count, *runtimev1.MetricsViewAggregationMeasure_CountDistinct: - subqueryName = m.Name - finalName = m.Name - columnsTuple = fmt.Sprintf( - "%[3]s(base.%[1]s) AS %[1]s", - safeName(subqueryName), - safeName(finalName), - prefix, - ) - labelTuple = columnsTuple - default: // not a virtual (generated) column - subqueryName = m.Name - finalName = m.Name - columnsTuple = fmt.Sprintf( - "%[3]s(base.%[1]s) AS %[1]s", - safeName(subqueryName), - safeName(finalName), - prefix, - ) - labelTuple = fmt.Sprintf( // non-virtial columns have a label - "%[3]s(base.%[1]s) AS %[1]s", - safeName(subqueryName), - safeName(labelMap[subqueryName]), - prefix, - ) - } - finalSelectCols = append( - finalSelectCols, - columnsTuple, - ) - labelCols = append(labelCols, labelTuple) - } - - baseSelectClause := strings.Join(selectCols, ", ") - comparisonSelectClause := strings.Join(comparisonSelectCols, ", ") - finalSelectClause := strings.Join(finalSelectCols, ", ") - labelSelectClause := strings.Join(labelCols, ", ") - if export { - finalSelectClause = labelSelectClause - } - - baseWhereClause := "1=1" - comparisonWhereClause := "1=1" - - if mv.TimeDimension == "" { - return "", nil, fmt.Errorf("metrics view '%s' doesn't have time dimension", q.MetricsViewName) - } - - td := safeName(mv.TimeDimension) - if dialect == drivers.DialectDuckDB { - td = fmt.Sprintf("%s::TIMESTAMP", td) + return fmt.Errorf("error rewriting to metrics query: %w", err) } - whereBuilder := &ExpressionBuilder{ - mv: mv, - dialect: dialect, - measures: q.Measures, - } - whereClause, whereClauseArgs, err := whereBuilder.buildExpression(q.Where) + e, err := metricsview.NewExecutor(ctx, rt, instanceID, mv, security, priority) if err != nil { - return "", nil, err + return err } + defer e.Close() - var baseTimeRangeArgs []any - trc, err := timeRangeClause(q.TimeRange, mv, td, &baseTimeRangeArgs) + res, _, err := e.Query(ctx, qry, nil) if err != nil { - return "", nil, err - } - baseWhereClause += trc - - if whereClause != "" { - baseWhereClause += fmt.Sprintf(" AND (%s)", whereClause) + return err } + defer res.Close() - var comparisonTimeRangeArgs []any - trc, err = timeRangeClause(q.ComparisonTimeRange, mv, td, &comparisonTimeRangeArgs) + data, err := rowsToData(res) if err != nil { - return "", nil, err - } - comparisonWhereClause += trc - - if whereClause != "" { - comparisonWhereClause += fmt.Sprintf(" AND (%s)", whereClause) - } - - if policy != nil && policy.RowFilter != "" { - baseWhereClause += fmt.Sprintf(" AND (%s)", policy.RowFilter) - comparisonWhereClause += fmt.Sprintf(" AND (%s)", policy.RowFilter) - } - - havingClause := "1=1" - var havingClauseArgs []any - if q.Having != nil { - havingBuilder := &ExpressionBuilder{ - mv: mv, - dialect: dialect, - measures: q.Measures, - } - havingClause, havingClauseArgs, err = havingBuilder.buildExpression(q.Having) - if err != nil { - return "", nil, err - } - } - - var orderClauses []string - var baseOrderClauses []string - var comparisonOrderClauses []string - - for _, s := range q.Sort { - var outerClause, subQueryClause string - if dimByName[s.Name] != nil { // dimension - outerClause = fmt.Sprintf("%d", colMap[s.Name]-1) - subQueryClause = fmt.Sprintf("%d", colMap[s.Name]) - } else if measuresByFinalName[s.Name] != nil { // measure - m := measuresByFinalName[s.Name] - outerClause = s.Name - subQueryClause = OriginalColumnName(m) - } else { - return "", nil, fmt.Errorf("no selected dimension or measure '%s' found for sorting", s.Name) - } - - var ending string - if s.Desc { - ending += " DESC" - } - if dialect == drivers.DialectDuckDB { - ending += " NULLS LAST" - } - outerClause += ending - subQueryClause += ending - orderClauses = append(orderClauses, outerClause) - baseOrderClauses = append(baseOrderClauses, subQueryClause) - comparisonOrderClauses = append(comparisonOrderClauses, subQueryClause) - } - - orderByClause := "" - baseSubQueryOrderByClause := "" - comparisonSubQueryOrderByClause := "" - - if len(orderClauses) > 0 { - orderByClause = "ORDER BY " + strings.Join(orderClauses, ", ") - baseSubQueryOrderByClause = "ORDER BY " + strings.Join(baseOrderClauses, ", ") - comparisonSubQueryOrderByClause = "ORDER BY " + strings.Join(comparisonOrderClauses, ", ") - } - - limitClause := "" - if q.Limit != nil && *q.Limit > 0 { - limitClause = fmt.Sprintf(" LIMIT %d", *q.Limit) - } - - baseLimitClause := "" - comparisonLimitClause := "" - - joinType := "FULL" - comparisonSort := false - deltaComparison := false - for _, s := range q.Sort { - m := measuresByFinalName[s.Name] - if measuresByFinalName[s.Name] != nil { - switch m.Compute.(type) { - case *runtimev1.MetricsViewAggregationMeasure_ComparisonValue: - comparisonSort = true - case *runtimev1.MetricsViewAggregationMeasure_ComparisonDelta, *runtimev1.MetricsViewAggregationMeasure_ComparisonRatio: - deltaComparison = true - comparisonSort = true - } - } - } - if !q.Exact { - limit := 0 - if q.Limit != nil { - limit = int(*q.Limit) - } - approximationLimit := limit - if limit != 0 && limit < 100 && deltaComparison { - approximationLimit = 100 - } - approximationLimit = (approximationLimit + int(q.Offset)) * 2 - - if len(q.Sort) == 0 || !comparisonSort { - joinType = "LEFT OUTER" - baseLimitClause = baseSubQueryOrderByClause - if approximationLimit > 0 { - baseLimitClause += fmt.Sprintf(" LIMIT %d", approximationLimit) - } - } else { - joinType = "RIGHT OUTER" - comparisonLimitClause = comparisonSubQueryOrderByClause - if approximationLimit > 0 { - comparisonLimitClause += fmt.Sprintf(" LIMIT %d", approximationLimit) - } - } - } - - /* - Example of the SQL: - - SELECT * from ( - -- SELECT d1, d2, d3, td1, td2, m1, m2 ... , td1__previous, td2__previous - SELECT COALESCE(base."pub",comparison."pub") as "pub",COALESCE(base."dom",comparison."dom") as "dom",base."timestamp" as "timestamp",base."timestamp_year" as "timestamp_year", base."measure_0" AS "measure_0", comparison."measure_0" AS "measure_0__previous", base."measure_0" - comparison."measure_0" AS "measure_0__delta_abs", (base."measure_0" - comparison."measure_0")/comparison."measure_0"::DOUBLE AS "measure_0__delta_rel", base."measure_1" AS "measure_1", base."m1" AS "m1", comparison."m1" AS "m1__previous", base."m1" - comparison."m1" AS "m1__delta_abs", (base."m1" - comparison."m1")/comparison."m1"::DOUBLE AS "m1__delta_rel" , comparison."timestamp" as "timestamp__previous", comparison."timestamp_year" as "timestamp_year__previous" FROM - ( - -- SELECT t_offset, d1, d2, d3, td1, td2, m1, m2 ... - SELECT epoch_ms(date_trunc('DAY', "timestamp")::TIMESTAMP)-epoch_ms(date_trunc('DAY', ?)::TIMESTAMP) as t_offset, ("publisher") as "pub", ("domain") as "dom", date_trunc('DAY', "timestamp"::TIMESTAMP)::TIMESTAMP as "timestamp", date_trunc('YEAR', "timestamp"::TIMESTAMP)::TIMESTAMP as "timestamp_year", count(*) as "measure_0", avg(bid_price) as "measure_1", avg(bid_price) as "m1" FROM "ad_bids" WHERE 1=1 AND "timestamp"::TIMESTAMP >= ? AND "timestamp"::TIMESTAMP < ? AND ((("publisher") = (?)) OR (("publisher") = (?))) GROUP BY 1,2,3,4,5 - ) base - FULL JOIN - ( - SELECT epoch_ms(date_trunc('DAY', "timestamp")::TIMESTAMP)-epoch_ms(date_trunc('DAY', ?)::TIMESTAMP) as t_offset, ("publisher") as "pub", ("domain") as "dom", date_trunc('DAY', "timestamp"::TIMESTAMP)::TIMESTAMP as "timestamp", date_trunc('YEAR', "timestamp"::TIMESTAMP)::TIMESTAMP as "timestamp_year", count(*) as "measure_0", avg(bid_price) as "m1" FROM "ad_bids" WHERE 1=1 AND "timestamp"::TIMESTAMP >= ? AND "timestamp"::TIMESTAMP < ? AND ((("publisher") = (?)) OR (("publisher") = (?))) GROUP BY 1,2,3,4,5 - ) comparison - ON - base.t_offset = comparison.t_offset AND base."pub" IS NOT DISTINCT FROM comparison."pub" AND base."dom" IS NOT DISTINCT FROM comparison."dom" - ORDER BY 4 NULLS LAST, 2 NULLS LAST, 3 NULLS LAST, 5 NULLS LAST, 9 NULLS LAST - LIMIT 1374419126128 - OFFSET - 0 - ) WHERE 1=1 AND ("measure_1") > (?) - - Example of arguments: - [2022-01-01 00:00:00 +0000 UTC 2022-01-01 00:00:00 +0000 UTC 2022-01-03 00:00:00 +0000 UTC Yahoo Google 2022-01-03 00:00:00 +0000 UTC 2022-01-03 00:00:00 +0000 UTC 2022-01-05 00:00:00 +0000 UTC Yahoo Google 0] - */ - - var args []any - var sql string - if dialect != drivers.DialectDruid { - // base subquery - if minTimeGrain != runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - args = append(args, q.TimeRange.Start.AsTime()) - } - args = append(args, selectArgs...) - args = append(args, baseTimeRangeArgs...) - args = append(args, whereClauseArgs...) - // comparison subquery - if minTimeGrain != runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - args = append(args, q.ComparisonTimeRange.Start.AsTime()) - } - args = append(args, selectArgs...) - args = append(args, comparisonTimeRangeArgs...) - args = append(args, whereClauseArgs...) - // outer query - args = append(args, havingClauseArgs...) - - // Using expr was causing issues with query arg expansion in duckdb. - // Using column name is not possible either since it will take the original column name instead of the aliased column name - // But using numbered group we can exactly target the correct selected column. - // Note that the non-timestamp columns also use the numbered group-by for constancy. - - // Inner grouping should include t_offset - // SELECT t_offset, d1, d2, d3 ... GROUP BY 1, 2, 3, 4 ... - innerGroupCols := make([]string, 0, len(q.Dimensions)+1) - innerGroupCols = append(innerGroupCols, "1") - for i := range q.Dimensions { - innerGroupCols = append(innerGroupCols, fmt.Sprintf("%d", i+2)) - } - - // these go last to decrease complexity of indexing columns - finalTimeDimsClause := "" - if len(finalComparisonTimeDims) > 0 { - finalTimeDimsClause = fmt.Sprintf(", %s", strings.Join(finalComparisonTimeDims, ", ")) - } - - // measure filter could include the base measure name. - // this leads to ambiguity whether it applies to the base.measure ot comparison.measure. - // to keep the clause builder consistent we add an outer query here. - sql = fmt.Sprintf(` - SELECT * from ( - -- SELECT base.d1 d1, base.d2 d2, base.timed1 td1, base.m1 m1, comparison.m2 m2 ... , comparison.timed1 td1__previous, ... - SELECT %[2]s %[18]s FROM - ( - -- SELECT t_offset, d1, d2, d3, td1, td2, m1, m2 ... - SELECT %[1]s FROM %[3]s %[14]s WHERE %[4]s GROUP BY %[10]s %[12]s - ) base - %[11]s JOIN - ( - SELECT %[16]s FROM %[3]s %[14]s WHERE %[5]s GROUP BY %[10]s %[13]s - ) comparison - ON %[17]s - %[6]s - ) WHERE 1=1 AND %[15]s - %[7]s - OFFSET %[8]d - - `, - baseSelectClause, // 1 - strings.Join(slices.Concat(finalDims, []string{finalSelectClause}), ","), // 2 - escapeMetricsViewTable(dialect, mv), // 3 - baseWhereClause, // 4 - comparisonWhereClause, // 5 - orderByClause, // 6 - limitClause, // 7 - q.Offset, // 8 - finalSelectClause, // 9 - strings.Join(innerGroupCols, ","), // 10 - joinType, // 11 - baseLimitClause, // 12 - comparisonLimitClause, // 13 - strings.Join(unnestClauses, ""), // 14 - havingClause, // 15 - comparisonSelectClause, // 16 - strings.Join(joinConditions, " AND "), // 17 - finalTimeDimsClause, // 18 - ) - } else { - if !comparisonSort || len(q.Dimensions) == 0 { // no dimensions means a single row (totals) - no sorting is required - // SELECT d1, d2, d3 ... GROUP BY 1, 2, 3 ... - var innerGroupCols []string - var whereDimConditions []string - if len(q.Dimensions) > 0 { // an additional request for Druid to prevent full scan - innerGroupCols = make([]string, 0, len(q.Dimensions)) - for i := range q.Dimensions { - innerGroupCols = append(innerGroupCols, fmt.Sprintf("%d", i+2)) - } - - nonTimeCols := make([]string, 0, len(selectCols)) // avoid group by time cols - for i, s := range selectCols[1:] { // skip t_offset - if i >= len(q.Dimensions) { - nonTimeCols = append(nonTimeCols, s) - } else { - if q.Dimensions[i].TimeGrain != runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - nonTimeCols = append(nonTimeCols, "1") - } else { - nonTimeCols = append(nonTimeCols, s) - } - } - } - sql = fmt.Sprintf("SELECT %[1]s FROM %[2]s %[3]s WHERE %[4]s GROUP BY %[5]s %[6]s", - strings.Join(slices.Concat([]string{"1"}, nonTimeCols), ","), // 1 - escapeMetricsViewTable(dialect, mv), // 2 - strings.Join(unnestClauses, ""), // 3 - baseWhereClause, // 4 - strings.Join(innerGroupCols, ","), // 5 - baseLimitClause, // 6 - ) - - var druidArgs []any - druidArgs = append(druidArgs, selectArgs...) - druidArgs = append(druidArgs, baseTimeRangeArgs...) - druidArgs = append(druidArgs, whereClauseArgs...) - - _, result, err := olapQuery(ctx, olap, priority, sql, druidArgs) - if err != nil { - return "", nil, err - } - - // without this extra where condition, the join will be a full scan - for _, row := range result { - var dimConditions []string - for _, dim := range q.Dimensions { - if dim.TimeGrain == runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - field := row.Fields[dim.Name] - - // Druid doesn't resolve aliases in where clause - mvDim := mvDimsByName[dim.Name] - - _, ok := field.GetKind().(*structpb.Value_NullValue) - if ok { - dimConditions = append(dimConditions, fmt.Sprintf("%[1]s is null", safeName(mvDim.Column))) - } else { - dimConditions = append(dimConditions, fmt.Sprintf("%[1]s = '%[2]s'", safeName(mvDim.Column), field.AsInterface())) - } - } - } - whereDimConditions = append(whereDimConditions, strings.Join(dimConditions, " AND ")) - } - } - - innerGroupCols = make([]string, 0, len(q.Dimensions)+1) - outerGroupCols := make([]string, 0, len(q.Dimensions)) - innerGroupCols = append(innerGroupCols, "1") - for i := range q.Dimensions { - innerGroupCols = append(innerGroupCols, fmt.Sprintf("%d", i+2)) - outerGroupCols = append(outerGroupCols, fmt.Sprintf("%d", i+1)) - } - // base subquery - if minTimeGrain != runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - args = append(args, q.TimeRange.Start.AsTime()) - } - args = append(args, selectArgs...) - args = append(args, baseTimeRangeArgs...) - args = append(args, whereClauseArgs...) - // comparison subquery - if minTimeGrain != runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - args = append(args, q.ComparisonTimeRange.Start.AsTime()) - } - args = append(args, selectArgs...) - args = append(args, comparisonTimeRangeArgs...) - args = append(args, whereClauseArgs...) - // outer query - args = append(args, havingClauseArgs...) - - finalTimeDimsClause := "" - if len(finalComparisonTimeDims) > 0 { - finalTimeDimsClause = fmt.Sprintf(", %s", strings.Join(finalComparisonTimeDims, ", ")) - } - - whereDimClause := "" - outerGroupClause := "" - if len(whereDimConditions) > 0 { - whereDimClause = fmt.Sprintf(" AND (%s) ", strings.Join(whereDimConditions, " OR ")) - } - if len(q.Dimensions) > 0 { - outerGroupClause = " GROUP BY " + strings.Join(outerGroupCols, ",") - } - - sql = fmt.Sprintf(` - SELECT * from ( - -- SELECT base.d1 d1, base.d2 d2, base.timed1 td1, base.m1 m1, comparison.m2 m2 ... , comparison.timed1 td1__previous, ... - SELECT %[2]s %[9]s FROM - ( - -- SELECT t_offset, d1, d2, d3, td1, td2, m1, m2 ... - SELECT %[1]s FROM %[3]s %[14]s WHERE %[4]s GROUP BY %[10]s %[12]s - ) base - LEFT JOIN - ( - SELECT %[16]s FROM %[3]s %[14]s WHERE %[5]s %[18]s GROUP BY %[10]s %[13]s - ) comparison - ON - -- base.d1 IS NOT DISTINCT FROM comparison.d1 AND base.d2 IS NOT DISTINCT FROM comparison.d2 AND ... - %[17]s - %[11]s -- GROUP BY ... - %[6]s -- ORDER BY ... - ) WHERE 1=1 AND %[15]s - %[7]s -- LIMIT ... - OFFSET %[8]d - `, - baseSelectClause, // 1 - strings.Join(slices.Concat(finalDims, []string{finalSelectClause}), ","), // 2 - escapeMetricsViewTable(dialect, mv), // 3 - baseWhereClause, // 4 - comparisonWhereClause, // 5 - orderByClause, // 6 - limitClause, // 7 - q.Offset, // 8 - finalTimeDimsClause, // 9 - strings.Join(innerGroupCols, ","), // 10 - outerGroupClause, // 11 - baseLimitClause, // 12 - comparisonLimitClause, // 13 - strings.Join(unnestClauses, ""), // 14 - havingClause, // 15 - comparisonSelectClause, // 16 - strings.Join(joinConditions, " AND "), // 17 - whereDimClause, // 18 - outerGroupClause, // 19 - ) - } else { - limit := 0 - if q.Limit == nil { - limit = 0 - } - approximationLimit := limit - if limit != 0 && limit < 100 { - approximationLimit = 100 - } - - comparisonLimitClause = comparisonSubQueryOrderByClause - if approximationLimit > 0 { - comparisonLimitClause += fmt.Sprintf(" LIMIT %d OFFSET %d", approximationLimit, q.Offset) - } - innerGroupCols := make([]string, 0, len(q.Dimensions)) - for i := range q.Dimensions { - innerGroupCols = append(innerGroupCols, fmt.Sprintf("%d", i+2)) - } - nonTimeCols := make([]string, 0, len(comparisonSelectCols)) // avoid group by time cols - for i, s := range comparisonSelectCols[1:] { // skip t_offset - if i >= len(q.Dimensions) { - nonTimeCols = append(nonTimeCols, s) - } else { - if q.Dimensions[i].TimeGrain != runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - nonTimeCols = append(nonTimeCols, "1") - } else { - nonTimeCols = append(nonTimeCols, s) - } - } - } - sql = fmt.Sprintf("SELECT %[1]s FROM %[2]s %[3]s WHERE %[4]s GROUP BY %[5]s %[6]s", - strings.Join(slices.Concat([]string{"1"}, nonTimeCols), ","), // 1 - escapeMetricsViewTable(dialect, mv), // 2 - strings.Join(unnestClauses, ""), // 3 - comparisonWhereClause, // 4 - strings.Join(innerGroupCols, ","), // 5 - comparisonLimitClause, // 6 - ) - - var druidArgs []any - druidArgs = append(druidArgs, selectArgs...) - druidArgs = append(druidArgs, comparisonTimeRangeArgs...) - druidArgs = append(druidArgs, whereClauseArgs...) - - _, result, err := olapQuery(ctx, olap, priority, sql, druidArgs) - if err != nil { - return "", nil, err - } - - // without this extra where condition, the join will be a full scan - var whereDimConditions []string - for _, row := range result { - var dimConditions []string - for _, dim := range q.Dimensions { - if dim.TimeGrain == runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - field := row.Fields[dim.Name] - - // Druid doesn't resolve aliases in where clause - mvDim := mvDimsByName[dim.Name] - - _, ok := field.GetKind().(*structpb.Value_NullValue) - if ok { - dimConditions = append(dimConditions, fmt.Sprintf("%[1]s is null", safeName(mvDim.Column))) - } else { - dimConditions = append(dimConditions, fmt.Sprintf("%[1]s = '%[2]s'", safeName(mvDim.Column), field.AsInterface())) - } - } - } - whereDimConditions = append(whereDimConditions, strings.Join(dimConditions, " AND ")) - } - - innerGroupCols = make([]string, 0, len(q.Dimensions)+1) - outerGroupCols := make([]string, 0, len(q.Dimensions)) - innerGroupCols = append(innerGroupCols, "1") - for i := range q.Dimensions { - innerGroupCols = append(innerGroupCols, fmt.Sprintf("%d", i+2)) - outerGroupCols = append(outerGroupCols, fmt.Sprintf("%d", i+1)) - } - - // base subquery - if minTimeGrain != runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - args = append(args, q.TimeRange.Start.AsTime()) - } - args = append(args, selectArgs...) - args = append(args, baseTimeRangeArgs...) - args = append(args, whereClauseArgs...) - // comparison subquery - if minTimeGrain != runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - args = append(args, q.ComparisonTimeRange.Start.AsTime()) - } - args = append(args, selectArgs...) - args = append(args, comparisonTimeRangeArgs...) - args = append(args, whereClauseArgs...) - // outer query - args = append(args, havingClauseArgs...) - - finalTimeDimsClause := "" - if len(finalComparisonTimeDims) > 0 { - finalTimeDimsClause = fmt.Sprintf(", %s", strings.Join(finalComparisonTimeDims, ", ")) - } - - whereDimClause := "" - outerGroupClause := "" - if len(whereDimConditions) > 0 { - whereDimClause = fmt.Sprintf(" AND (%s) ", strings.Join(whereDimConditions, " OR ")) - } - if len(q.Dimensions) > 0 { - outerGroupClause = " GROUP BY " + strings.Join(outerGroupCols, ",") - } - - sql = fmt.Sprintf(` - SELECT * from ( - SELECT %[2]s %[9]s FROM - ( - SELECT %[1]s FROM %[3]s %[14]s WHERE %[4]s %[18]s GROUP BY %[10]s %[12]s - ) base - LEFT JOIN - ( - SELECT %[16]s FROM %[3]s %[14]s WHERE %[5]s GROUP BY %[10]s %[13]s - ) comparison - ON - %[17]s - %[11]s - %[6]s - %[7]s - OFFSET - %[8]d - ) WHERE 1=1 AND %[15]s - `, - baseSelectClause, // 1 - strings.Join(slices.Concat(finalDims, []string{finalSelectClause}), ","), // 2 - escapeMetricsViewTable(dialect, mv), // 3 - baseWhereClause, // 4 - comparisonWhereClause, // 5 - orderByClause, // 6 - limitClause, // 7 - q.Offset, // 8 - finalTimeDimsClause, // 9 - strings.Join(innerGroupCols, ","), // 10 - outerGroupClause, // 11 - baseLimitClause, // 12 - comparisonLimitClause, // 13 - strings.Join(unnestClauses, ""), // 14 - havingClause, // 15 - comparisonSelectClause, // 16 - strings.Join(joinConditions, " AND "), // 17 - whereDimClause, // 18 - ) - } - } - - return sql, args, nil -} - -func OriginalColumnName(m *runtimev1.MetricsViewAggregationMeasure) string { - switch v := m.Compute.(type) { - case *runtimev1.MetricsViewAggregationMeasure_ComparisonValue: - return v.ComparisonValue.Measure - case *runtimev1.MetricsViewAggregationMeasure_ComparisonDelta: - return v.ComparisonDelta.Measure - case *runtimev1.MetricsViewAggregationMeasure_ComparisonRatio: - return v.ComparisonRatio.Measure - default: - return m.Name - } -} - -func (q *MetricsViewAggregation) calculateMeasuresMeta() error { - q.measuresMeta = make(map[string]metricsViewMeasureMeta, len(q.Measures)) - - expands := make(map[string]bool, len(q.Measures)) - originalNames := make(map[string]bool, len(q.Measures)) - for _, m := range q.Measures { - name := OriginalColumnName(m) - if OriginalColumnName(m) != m.Name { - expands[name] = true - } else { - originalNames[name] = true - } - } - for n := range expands { - if !originalNames[n] { - return fmt.Errorf("original measure '%s' should be in the selection list", n) - } - } - - for _, m := range q.Measures { - expand := false - if expands[OriginalColumnName(m)] { - expand = true - } - q.measuresMeta[m.Name] = metricsViewMeasureMeta{ - expand: expand, - } + return err } - compare := !isTimeRangeNil(q.ComparisonTimeRange) - if compare && len(expands) == 0 { - return fmt.Errorf("no measures to compare") + q.Result = &runtimev1.MetricsViewAggregationResponse{ + Schema: res.Schema, + Data: data, } - return nil } -/* -Example: -SELECT d1, d2, d3, m1 FROM ( - - SELECT t.d1, t.d2, t.d3, t2.m1 ( - SELECT t.d1, t.d2, t.d3, t2.m1 FROM ( - SELECT d1, d2, d3, m1 FROM t WHERE ... GROUP BY d1, d2, d3 HAVING m1 > 10 ) t - ) t - LEFT JOIN ( - SELECT d1, d2, d3, m1 FROM t WHERE ... AND (d4 = 'Safari') GROUP BY d1, d2, d3 HAVING m1 > 10 - ) t2 ON (COALESCE(t.d1, 'val') = COALESCE(t2.d1, 'val') and COALESCE(t.d2, 'val') = COALESCE(t2.d2, 'val') and ... - ) - -) -WHERE m1 > 10 -- mimicing FILTER behavior for empty sets produced by HAVING -GROUP BY d1, d2, d3 -- GROUP BY is required for Apache Druid -ORDER BY ... -LIMIT 100 -OFFSET 0 - -This JOIN mirrors functionality of SELECT d1, d2, d3, m1 FILTER (WHERE d4 = 'Safari') FROM t WHERE... GROUP BY d1, d2, d3 -bacause FILTER cannot be applied for arbitrary measure, ie sum(a)/1000 -*/ -func (q *MetricsViewAggregation) buildMeasureFilterSQL(mv *runtimev1.MetricsViewSpec, unnestClauses, selectCols []string, limitClause, orderClause, havingClause, whereClause, groupClause string, args, selectArgs, whereArgs, havingClauseArgs []any, extraWhereClause string, extraWhereClauseArgs []any, dialect drivers.Dialect) (string, []any, error) { - joinConditions := make([]string, 0, len(q.Dimensions)) - selfJoinCols := make([]string, 0, len(q.Dimensions)+1) - finalProjection := make([]string, 0, len(q.Dimensions)+1) +func (q *MetricsViewAggregation) Export(ctx context.Context, rt *runtime.Runtime, instanceID string, w io.Writer, opts *runtime.ExportOptions) error { + q.Exporting = true - selfJoinTableAlias := tempName("self_join") - nonNullValue := tempName("non_null") - for _, d := range q.Dimensions { - name := d.Name - if d.TimeGrain != runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED && d.Alias != "" { - name = d.Alias - } - joinConditions = append(joinConditions, fmt.Sprintf("COALESCE(%[1]s.%[2]s, '%[4]s') = COALESCE(%[3]s.%[2]s, '%[4]s')", escapeMetricsViewTable(dialect, mv), safeName(name), selfJoinTableAlias, nonNullValue)) - selfJoinCols = append(selfJoinCols, fmt.Sprintf("%s.%s", escapeMetricsViewTable(dialect, mv), safeName(name))) - finalProjection = append(finalProjection, fmt.Sprintf("%[1]s", safeName(name))) - } - if dialect == drivers.DialectDruid { // Apache Druid cannot order without timestamp or GROUP BY - finalProjection = append(finalProjection, fmt.Sprintf("ANY_VALUE(%[1]s) as %[1]s", safeName(q.Measures[0].Name))) - } else { - finalProjection = append(finalProjection, fmt.Sprintf("%[1]s", safeName(q.Measures[0].Name))) - } - selfJoinCols = append(selfJoinCols, fmt.Sprintf("%[1]s.%[2]s as %[3]s", selfJoinTableAlias, safeName(q.Measures[0].Name), safeName(q.Measures[0].Name))) - builder := &ExpressionBuilder{ - mv: mv, - dialect: dialect, - } - measureExpression, measureWhereArgs, err := builder.buildExpression(q.Measures[0].Filter) - if err != nil { - return "", nil, err + filename := strings.ReplaceAll(q.MetricsViewName, `"`, `_`) + if !isTimeRangeNil(q.TimeRange) || q.Where != nil || q.Having != nil { + filename += "_filtered" } - if whereClause == "" { - whereClause = "WHERE 1=1" + // Resolve metrics view + mv, security, err := resolveMVAndSecurityFromAttributes(ctx, rt, instanceID, q.MetricsViewName, q.SecurityAttributes, q.SecurityPolicy, q.Dimensions, q.Measures) + if err != nil { + return err } - measureWhereClause := whereClause + fmt.Sprintf(" AND (%s)", measureExpression) - if extraWhereClause != "" { - extraWhereClause = "WHERE " + extraWhereClause - } - druidGroupBy := "" - if dialect == drivers.DialectDruid { - druidGroupBy = groupClause + // Route to metricsview executor + qry, err := q.rewriteToMetricsViewQuery() + if err != nil { + return fmt.Errorf("error rewriting to metrics query: %w", err) } - /* - SQL example: - SELECT "pub","measure_1" FROM ( - SELECT "ad_bids"."pub", self_join3ba680fe589e49ceabf404f6c6d920e7."measure_1" as "measure_1" FROM ( - SELECT ("publisher") as "pub", COUNT(*) as "measure_1" FROM "ad_bids" WHERE 1=1 GROUP BY 1 - ) "ad_bids" - LEFT JOIN ( - SELECT ("publisher") as "pub", COUNT(*) as "measure_1" FROM "ad_bids" WHERE 1=1 AND (("domain") = (?)) GROUP BY 1 - ) self_join3ba680fe589e49ceabf404f6c6d920e7 - ON (COALESCE("ad_bids"."pub", 'non_nulle9c9dae4c90746978d24a838c88b9879') = COALESCE(self_join3ba680fe589e49ceabf404f6c6d920e7."pub", 'non_nulle9c9dae4c90746978d24a838c88b9879')) - ) - ORDER BY "pub" NULLS LAST - OFFSET 0 - */ - - sql := fmt.Sprintf(` - SELECT %[16]s FROM ( - SELECT %[1]s FROM ( - SELECT %[10]s FROM %[2]s %[3]s %[4]s %[5]s %[6]s - ) %[2]s - LEFT JOIN ( - SELECT %[10]s FROM %[2]s %[3]s %[9]s %[5]s %[6]s - ) %[7]s - ON (%[8]s) - ) - %[14]s - %[15]s - %[13]s - %[11]s - OFFSET %[12]d - `, - strings.Join(selfJoinCols, ", "), // 1 - escapeMetricsViewTable(dialect, mv), // 2 - strings.Join(unnestClauses, ""), // 3 - whereClause, // 4 - groupClause, // 5 - havingClause, // 6 - selfJoinTableAlias, // 7 - strings.Join(joinConditions, " AND "), // 8 - measureWhereClause, // 9 - strings.Join(selectCols, ", "), // 10 - limitClause, // 11 - q.Offset, // 12 - orderClause, // 13 - extraWhereClause, // 14 - druidGroupBy, // 15 - strings.Join(finalProjection, ","), // 16 - ) - - args = args[:0] - args = append(args, selectArgs...) - args = append(args, whereArgs...) - args = append(args, havingClauseArgs...) - args = append(args, whereArgs...) - args = append(args, measureWhereArgs...) - args = append(args, havingClauseArgs...) - args = append(args, extraWhereClauseArgs...) - - return sql, args, nil -} - -func (q *MetricsViewAggregation) buildTimestampExpr(mv *runtimev1.MetricsViewSpec, dim *runtimev1.MetricsViewAggregationDimension, dialect drivers.Dialect) (string, []any, error) { - var col string - if dim.Name == mv.TimeDimension { - col = safeName(dim.Name) - if dialect == drivers.DialectDuckDB { - col = fmt.Sprintf("%s::TIMESTAMP", col) - } - } else { - d, err := metricsViewDimension(mv, dim.Name) - if err != nil { - return "", nil, err - } - if d.Expression != "" { - // TODO: we should add support for this in a future PR - return "", nil, fmt.Errorf("expression dimension not supported as time column") - } - col = dialect.MetricsViewDimensionExpression(d) + e, err := metricsview.NewExecutor(ctx, rt, instanceID, mv, security, opts.Priority) + if err != nil { + return err } + defer e.Close() - switch dialect { - case drivers.DialectDuckDB: - if dim.TimeZone == "" || dim.TimeZone == "UTC" || dim.TimeZone == "Etc/UTC" { - return fmt.Sprintf("date_trunc('%s', %s)::TIMESTAMP", dialect.ConvertToDateTruncSpecifier(dim.TimeGrain), col), nil, nil - } - return fmt.Sprintf("timezone(?, date_trunc('%s', timezone(?, %s::TIMESTAMPTZ)))::TIMESTAMP", dialect.ConvertToDateTruncSpecifier(dim.TimeGrain), col), []any{dim.TimeZone, dim.TimeZone}, nil - case drivers.DialectDruid: - if dim.TimeZone == "" || dim.TimeZone == "UTC" || dim.TimeZone == "Etc/UTC" { - return fmt.Sprintf("date_trunc('%s', %s)", dialect.ConvertToDateTruncSpecifier(dim.TimeGrain), col), nil, nil - } - return fmt.Sprintf("time_floor(%s, '%s', null, CAST(? AS VARCHAR))", col, convertToDruidTimeFloorSpecifier(dim.TimeGrain)), []any{dim.TimeZone}, nil - case drivers.DialectClickHouse: - if dim.TimeZone == "" || dim.TimeZone == "UTC" || dim.TimeZone == "Etc/UTC" { - return fmt.Sprintf("date_trunc('%s', %s)", dialect.ConvertToDateTruncSpecifier(dim.TimeGrain), col), nil, nil - } - // The return type of date_trunc('month', ...) is Date so need another TIMESTAMP cast - return fmt.Sprintf("toTimezone(date_trunc('%s', toTimezone(%s::TIMESTAMP, ?))::TIMESTAMP, ?)", dialect.ConvertToDateTruncSpecifier(dim.TimeGrain), col), []any{dim.TimeZone, dim.TimeZone}, nil - case drivers.DialectPinot: - // ToDateTime format truncates millis to secs because we don't support that, for example timeseries api does timestamppb.New(ts) which truncates to seconds - return fmt.Sprintf("ToDateTime(date_trunc('%s', %s, 'MILLISECONDS', ?), 'yyyy-MM-dd''T''HH:mm:ss''Z''')", dialect.ConvertToDateTruncSpecifier(dim.TimeGrain), col), []any{dim.TimeZone}, nil + var format string + switch opts.Format { + case runtimev1.ExportFormat_EXPORT_FORMAT_CSV: + format = "csv" + case runtimev1.ExportFormat_EXPORT_FORMAT_XLSX: + format = "xlsx" + case runtimev1.ExportFormat_EXPORT_FORMAT_PARQUET: + format = "parquet" default: - return "", nil, fmt.Errorf("unsupported dialect %q", dialect) + return fmt.Errorf("unsupported format: %s", opts.Format.String()) } -} -func (q *MetricsViewAggregation) buildOffsetExpression(col string, timeGrain runtimev1.TimeGrain, dialect drivers.Dialect) (string, error) { - if timeGrain == runtimev1.TimeGrain_TIME_GRAIN_UNSPECIFIED { - return "0 as t_offset", nil + path, err := e.Export(ctx, qry, nil, format) + if err != nil { + return err } + defer func() { _ = os.Remove(path) }() - timeCol, err := q.trancationExpression(safeName(col), timeGrain, dialect) + err = opts.PreWriteHook(filename) if err != nil { - return "", err + return err } - start, _ := q.trancationExpression("?", timeGrain, dialect) - var timeOffsetColumn string - if dialect == drivers.DialectDuckDB { - timeOffsetColumn = fmt.Sprintf("epoch_ms(%s)-epoch_ms(%s) as t_offset", timeCol, start) - } else if dialect == drivers.DialectDruid { - timeOffsetColumn = fmt.Sprintf("timestamp_to_millis(%s)-timestamp_to_millis(%s) as t_offset", timeCol, start) - } else if dialect == drivers.DialectClickHouse { - timeOffsetColumn = fmt.Sprintf("toUnixTimestamp(%s)-toUnixTimestamp(%s) as t_offset", timeCol, start) - } else { - return "", fmt.Errorf("unsupported dialect %q", dialect) + f, err := os.Open(path) + if err != nil { + return err } - return timeOffsetColumn, nil -} + defer f.Close() -func (q *MetricsViewAggregation) trancationExpression(s string, timeGrain runtimev1.TimeGrain, dialect drivers.Dialect) (string, error) { - switch dialect { - case drivers.DialectDuckDB: - return fmt.Sprintf("date_trunc('%s', %s)::TIMESTAMP", dialect.ConvertToDateTruncSpecifier(timeGrain), s), nil - case drivers.DialectDruid: - return fmt.Sprintf("date_trunc('%s', CAST(%s AS TIMESTAMP))", dialect.ConvertToDateTruncSpecifier(timeGrain), s), nil - case drivers.DialectClickHouse: - return fmt.Sprintf("date_trunc('%s', %s)", dialect.ConvertToDateTruncSpecifier(timeGrain), s), nil - default: - return "", fmt.Errorf("unsupported dialect %q", dialect) + _, err = io.Copy(w, f) + if err != nil { + return err } -} -func (q *MetricsViewAggregation) rewriteToMetricsViewQuery(mv *runtimev1.MetricsViewSpec) (*metricsview.Query, bool, error) { - // Time offset-based comparison joins not supported yet - if q.ComparisonTimeRange != nil && !isTimeRangeNil(q.ComparisonTimeRange) { - for _, d := range q.Dimensions { - if d.Name == mv.TimeDimension { - return nil, false, nil - } - } - } + return nil +} +func (q *MetricsViewAggregation) rewriteToMetricsViewQuery() (*metricsview.Query, error) { qry := &metricsview.Query{MetricsView: q.MetricsViewName} for _, d := range q.Dimensions { @@ -2996,6 +188,8 @@ func (q *MetricsViewAggregation) rewriteToMetricsViewQuery(mv *runtimev1.Metrics qry.Dimensions = append(qry.Dimensions, res) } + var measuresFilter *runtimev1.Expression + for _, m := range q.Measures { res := metricsview.Measure{Name: m.Name} switch m.BuiltinMeasure { @@ -3007,9 +201,11 @@ func (q *MetricsViewAggregation) rewriteToMetricsViewQuery(mv *runtimev1.Metrics }} } - // Measure filters not supported yet if m.Filter != nil { - return nil, false, nil + if len(q.Measures) > 1 { + return nil, fmt.Errorf("measure-level filter is not supported when multiple measures are present") + } + measuresFilter = m.Filter } if m.Compute != nil { @@ -3071,7 +267,7 @@ func (q *MetricsViewAggregation) rewriteToMetricsViewQuery(mv *runtimev1.Metrics if q.Filter != nil { // backwards backwards compatibility if q.Where != nil { - return nil, false, fmt.Errorf("both filter and where is provided") + return nil, fmt.Errorf("both filter and where is provided") } q.Where = convertFilterToExpression(q.Filter) } @@ -3080,6 +276,27 @@ func (q *MetricsViewAggregation) rewriteToMetricsViewQuery(mv *runtimev1.Metrics qry.Where = metricsview.NewExpressionFromProto(q.Where) } + // If a measure-level filter is present, we set qry.Where as the spine, and use (qry.Where AND measuresFilter) as the new where clause + if measuresFilter != nil { + measuresFilter := metricsview.NewExpressionFromProto(measuresFilter) + + qry.Spine = &metricsview.Spine{Where: &metricsview.WhereSpine{Expression: qry.Where}} + + if qry.Where == nil { + qry.Where = measuresFilter + } else { + qry.Where = &metricsview.Expression{ + Condition: &metricsview.Condition{ + Operator: metricsview.OperatorAnd, + Expressions: []*metricsview.Expression{ + qry.Where, + measuresFilter, + }, + }, + } + } + } + if q.Having != nil { qry.Having = metricsview.NewExpressionFromProto(q.Having) } @@ -3102,5 +319,5 @@ func (q *MetricsViewAggregation) rewriteToMetricsViewQuery(mv *runtimev1.Metrics qry.Label = q.Exporting - return qry, true, nil + return qry, nil } diff --git a/runtime/queries/metricsview_aggregation_test.go b/runtime/queries/metricsview_aggregation_test.go index 28b6a7373f9..d8e8d2cb26a 100644 --- a/runtime/queries/metricsview_aggregation_test.go +++ b/runtime/queries/metricsview_aggregation_test.go @@ -67,18 +67,15 @@ func Ignore_TestMetricViewAggregationAgainstClickHouse(t *testing.T) { t.Run("TestMetricsViewsAggregation_comparison_measure_filter_with_totals", func(t *testing.T) { TestMetricsViewsAggregation_comparison_measure_filter_with_totals(t) }) - t.Run("TestMetricsViewsAggregation_comparison_measure_filter_with_limit", func(t *testing.T) { - TestMetricsViewsAggregation_comparison_measure_filter_with_limit(t) - }) - t.Run("TestMetricsViewsAggregation_comparison_measure_filter", func(t *testing.T) { - TestMetricsViewsAggregation_comparison_measure_filter(t) - }) - t.Run("TestMetricsViewsAggregation_comparison_measure_filter_with_having", func(t *testing.T) { - TestMetricsViewsAggregation_comparison_measure_filter_with_having(t) - }) - t.Run("TestMetricsViewsAggregation_comparison", func(t *testing.T) { - TestMetricsViewsAggregation_comparison(t) - }) + // t.Run("TestMetricsViewsAggregation_comparison_measure_filter", func(t *testing.T) { + // TestMetricsViewsAggregation_comparison_measure_filter(t) + // }) + // t.Run("TestMetricsViewsAggregation_comparison_measure_filter_with_having", func(t *testing.T) { + // TestMetricsViewsAggregation_comparison_measure_filter_with_having(t) + // }) + // t.Run("TestMetricsViewsAggregation_comparison", func(t *testing.T) { + // TestMetricsViewsAggregation_comparison(t) + // }) t.Run("TestMetricsViewsAggregation_comparison_pivot", func(t *testing.T) { TestMetricsViewsAggregation_comparison_pivot(t) }) @@ -1841,77 +1838,6 @@ func TestMetricsViewsAggregation_where(t *testing.T) { require.Equal(t, "Microsoft,10406", fieldsToString(rows[i], "pub", "inline_1")) } -func TestMetricsViewsAggregation_measure_filter_same_name(t *testing.T) { - rt, instanceID := testruntime.NewInstanceForProject(t, "ad_bids") - - q := &queries.MetricsViewAggregation{ - MetricsViewName: "ad_bids_metrics", - Dimensions: []*runtimev1.MetricsViewAggregationDimension{ - { - Name: "pub", - }, - }, - Measures: []*runtimev1.MetricsViewAggregationMeasure{ - { - Name: "bid_price", - Filter: &runtimev1.Expression{ - Expression: &runtimev1.Expression_Cond{ - Cond: &runtimev1.Condition{ - Op: runtimev1.Operation_OPERATION_GT, - Exprs: []*runtimev1.Expression{ - { - Expression: &runtimev1.Expression_Ident{ - Ident: "bid_price", - }, - }, - { - Expression: &runtimev1.Expression_Val{ - Val: structpb.NewNumberValue(1), - }, - }, - }, - }, - }, - }, - }, - }, - Sort: []*runtimev1.MetricsViewAggregationSort{ - { - Name: "pub", - Desc: true, - }, - }, - Having: &runtimev1.Expression{ - Expression: &runtimev1.Expression_Cond{ - Cond: &runtimev1.Condition{ - Op: runtimev1.Operation_OPERATION_GT, - Exprs: []*runtimev1.Expression{ - { - Expression: &runtimev1.Expression_Ident{ - Ident: "bid_price", - }, - }, - { - Expression: &runtimev1.Expression_Val{ - Val: structpb.NewNumberValue(2), - }, - }, - }, - }, - }, - }, - } - err := q.Resolve(context.Background(), rt, instanceID, 0) - require.NoError(t, err) - require.NotEmpty(t, q.Result) - - rows := q.Result.Data - i := 0 - require.Equal(t, "Yahoo,3", fieldsToString(rows[i], "pub", "bid_price")) - i++ - require.Equal(t, "Microsoft,3", fieldsToString(rows[i], "pub", "bid_price")) -} - func TestMetricsViewsAggregation_filter_having_measure(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "ad_bids") @@ -2026,7 +1952,9 @@ func TestMetricsViewsAggregation_filter_having_measure(t *testing.T) { require.NotEmpty(t, q.Result) rows = q.Result.Data - require.Equal(t, 0, len(rows)) + require.Equal(t, 1, len(rows)) + i = 0 + require.Equal(t, "null,4296", fieldsToString(rows[i], "pub", "measure_0")) } func TestMetricsViewsAggregation_filter_with_where_and_having_measure(t *testing.T) { @@ -2226,111 +2154,112 @@ measures: require.Equal(t, "a,null,1", fieldsToString(q.Result.Data[0], "a", "b", "count")) } -func TestMetricsViewsAggregation_comparison_having_of_comparison(t *testing.T) { - rt, instanceID := testruntime.NewInstanceForProject(t, "ad_bids") - - limit := int64(10) - q := &queries.MetricsViewAggregation{ - MetricsViewName: "ad_bids_metrics", - Dimensions: []*runtimev1.MetricsViewAggregationDimension{ - { - Name: "pub", - }, - { - Name: "dom", - }, - - { - Name: "timestamp", - TimeGrain: runtimev1.TimeGrain_TIME_GRAIN_DAY, - }, - { - Name: "timestamp", - TimeGrain: runtimev1.TimeGrain_TIME_GRAIN_YEAR, - Alias: "timestamp_year", - }, - }, - Measures: []*runtimev1.MetricsViewAggregationMeasure{ - { - Name: "measure_0", - }, - { - Name: "measure_1", - }, - { - Name: "m1", - }, - { - Name: "measure_0__p", - Compute: &runtimev1.MetricsViewAggregationMeasure_ComparisonValue{ - ComparisonValue: &runtimev1.MetricsViewAggregationMeasureComputeComparisonValue{ - Measure: "measure_0", - }, - }, - }, - }, - Where: expressionpb.OrAll( - expressionpb.Eq("pub", "Yahoo"), - expressionpb.Eq("pub", "Google"), - ), - Having: expressionpb.Gt("measure_0__p", 0.0), - Sort: []*runtimev1.MetricsViewAggregationSort{ - { - Name: "pub", - }, - { - Name: "dom", - }, - { - Name: "timestamp", - }, - { - Name: "timestamp_year", - }, - { - Name: "measure_1", - }, - }, - - TimeRange: &runtimev1.TimeRange{ - Start: timestamppb.New(time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)), - End: timestamppb.New(time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC)), - }, - ComparisonTimeRange: &runtimev1.TimeRange{ - Start: timestamppb.New(time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC)), - End: timestamppb.New(time.Date(2022, 1, 5, 0, 0, 0, 0, time.UTC)), - }, - Limit: &limit, - } - err := q.Resolve(context.Background(), rt, instanceID, 0) - require.NoError(t, err) - require.NotEmpty(t, q.Result) - fields := q.Result.Schema.Fields - require.Equal(t, "pub,dom,timestamp,timestamp_year,measure_0,measure_1,m1,measure_0__p,timestamp__previous,timestamp_year__previous", columnNames(fields)) - i := 0 - - for _, sf := range q.Result.Schema.Fields { - fmt.Printf("%v ", sf.Name) - } - fmt.Printf("\n") - - for i, row := range q.Result.Data { - for _, sf := range q.Result.Schema.Fields { - fmt.Printf("%v ", row.Fields[sf.Name].AsInterface()) - } - fmt.Printf(" %d \n", i) - - } - rows := q.Result.Data - require.Equal(t, 8, len(rows)) - - i = 0 - require.Equal(t, "Google,google.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,44.00,50.00,1.53,1.53,2022-01-03T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "measure_0", "measure_0__p", "measure_1", "m1", "timestamp__previous", "timestamp_year__previous")) - i++ - require.Equal(t, "Google,google.com,2022-01-02T00:00:00Z,2022-01-01T00:00:00Z,62.00,51.00,1.45,1.45,2022-01-04T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "measure_0", "measure_0__p", "measure_1", "m1", "timestamp__previous", "timestamp_year__previous")) - i++ - require.Equal(t, "Google,news.google.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,187.00,183.00,3.55,3.55,2022-01-03T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "measure_0", "measure_0__p", "measure_1", "m1", "timestamp__previous", "timestamp_year__previous")) -} +// Uncommenting: We don't support comparison queries with time as a dimension +// func TestMetricsViewsAggregation_comparison_having_of_comparison(t *testing.T) { +// rt, instanceID := testruntime.NewInstanceForProject(t, "ad_bids") +// +// limit := int64(10) +// q := &queries.MetricsViewAggregation{ +// MetricsViewName: "ad_bids_metrics", +// Dimensions: []*runtimev1.MetricsViewAggregationDimension{ +// { +// Name: "pub", +// }, +// { +// Name: "dom", +// }, +// +// { +// Name: "timestamp", +// TimeGrain: runtimev1.TimeGrain_TIME_GRAIN_DAY, +// }, +// { +// Name: "timestamp", +// TimeGrain: runtimev1.TimeGrain_TIME_GRAIN_YEAR, +// Alias: "timestamp_year", +// }, +// }, +// Measures: []*runtimev1.MetricsViewAggregationMeasure{ +// { +// Name: "measure_0", +// }, +// { +// Name: "measure_1", +// }, +// { +// Name: "m1", +// }, +// { +// Name: "measure_0__p", +// Compute: &runtimev1.MetricsViewAggregationMeasure_ComparisonValue{ +// ComparisonValue: &runtimev1.MetricsViewAggregationMeasureComputeComparisonValue{ +// Measure: "measure_0", +// }, +// }, +// }, +// }, +// Where: expressionpb.OrAll( +// expressionpb.Eq("pub", "Yahoo"), +// expressionpb.Eq("pub", "Google"), +// ), +// Having: expressionpb.Gt("measure_0__p", 0.0), +// Sort: []*runtimev1.MetricsViewAggregationSort{ +// { +// Name: "pub", +// }, +// { +// Name: "dom", +// }, +// { +// Name: "timestamp", +// }, +// { +// Name: "timestamp_year", +// }, +// { +// Name: "measure_1", +// }, +// }, +// +// TimeRange: &runtimev1.TimeRange{ +// Start: timestamppb.New(time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)), +// End: timestamppb.New(time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC)), +// }, +// ComparisonTimeRange: &runtimev1.TimeRange{ +// Start: timestamppb.New(time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC)), +// End: timestamppb.New(time.Date(2022, 1, 5, 0, 0, 0, 0, time.UTC)), +// }, +// Limit: &limit, +// } +// err := q.Resolve(context.Background(), rt, instanceID, 0) +// require.NoError(t, err) +// require.NotEmpty(t, q.Result) +// fields := q.Result.Schema.Fields +// require.Equal(t, "pub,dom,timestamp,timestamp_year,measure_0,measure_1,m1,measure_0__p,timestamp__previous,timestamp_year__previous", columnNames(fields)) +// i := 0 +// +// for _, sf := range q.Result.Schema.Fields { +// fmt.Printf("%v ", sf.Name) +// } +// fmt.Printf("\n") +// +// for i, row := range q.Result.Data { +// for _, sf := range q.Result.Schema.Fields { +// fmt.Printf("%v ", row.Fields[sf.Name].AsInterface()) +// } +// fmt.Printf(" %d \n", i) +// +// } +// rows := q.Result.Data +// require.Equal(t, 8, len(rows)) +// +// i = 0 +// require.Equal(t, "Google,google.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,44.00,50.00,1.53,1.53,2022-01-03T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "measure_0", "measure_0__p", "measure_1", "m1", "timestamp__previous", "timestamp_year__previous")) +// i++ +// require.Equal(t, "Google,google.com,2022-01-02T00:00:00Z,2022-01-01T00:00:00Z,62.00,51.00,1.45,1.45,2022-01-04T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "measure_0", "measure_0__p", "measure_1", "m1", "timestamp__previous", "timestamp_year__previous")) +// i++ +// require.Equal(t, "Google,news.google.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,187.00,183.00,3.55,3.55,2022-01-03T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "measure_0", "measure_0__p", "measure_1", "m1", "timestamp__previous", "timestamp_year__previous")) +// } func TestMetricsViewsAggregation_comparison_no_time_dim(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "ad_bids") @@ -2740,7 +2669,7 @@ func TestMetricsViewsAggregation_comparison_measure_filter_with_a_single_derivat require.NoError(t, err) require.NotEmpty(t, q.Result) fields := q.Result.Schema.Fields - require.Equal(t, "pub,dom,m1_p,m1", columnNames(fields)) + require.Equal(t, "pub,dom,m1_p", columnNames(fields)) i := 0 for _, sf := range q.Result.Schema.Fields { @@ -2759,13 +2688,13 @@ func TestMetricsViewsAggregation_comparison_measure_filter_with_a_single_derivat require.Equal(t, 4, len(rows)) i = 0 - require.Equal(t, "Google,google.com,null,null", fieldsToString2digits(rows[i], "pub", "dom", "m1", "m1_p")) + require.Equal(t, "Google,google.com,null", fieldsToString2digits(rows[i], "pub", "dom", "m1_p")) i++ - require.Equal(t, "Google,news.google.com,3.55,3.74", fieldsToString2digits(rows[i], "pub", "dom", "m1", "m1_p")) + require.Equal(t, "Google,news.google.com,3.74", fieldsToString2digits(rows[i], "pub", "dom", "m1_p")) i++ - require.Equal(t, "Yahoo,news.yahoo.com,null,null", fieldsToString2digits(rows[i], "pub", "dom", "m1", "m1_p")) + require.Equal(t, "Yahoo,news.yahoo.com,null", fieldsToString2digits(rows[i], "pub", "dom", "m1_p")) i++ - require.Equal(t, "Yahoo,sports.yahoo.com,null,null", fieldsToString2digits(rows[i], "pub", "dom", "m1", "m1_p")) + require.Equal(t, "Yahoo,sports.yahoo.com,null", fieldsToString2digits(rows[i], "pub", "dom", "m1_p")) } func TestMetricsViewsAggregation_Druid_comparison_measure_filter_no_duplicates(t *testing.T) { @@ -2877,9 +2806,6 @@ func TestMetricsViewsAggregation_comparison_measure_filter_no_duplicates(t *test }, }, Measures: []*runtimev1.MetricsViewAggregationMeasure{ - { - Name: "m1", - }, { Name: "m1_p", Compute: &runtimev1.MetricsViewAggregationMeasure_ComparisonValue{ @@ -2935,7 +2861,7 @@ func TestMetricsViewsAggregation_comparison_measure_filter_no_duplicates(t *test require.NoError(t, err) require.NotEmpty(t, q.Result) fields := q.Result.Schema.Fields - require.Equal(t, "pub,m1,m1_p", columnNames(fields)) + require.Equal(t, "pub,m1_p", columnNames(fields)) i := 0 for _, sf := range q.Result.Schema.Fields { @@ -2954,9 +2880,9 @@ func TestMetricsViewsAggregation_comparison_measure_filter_no_duplicates(t *test require.Equal(t, 2, len(rows)) i = 0 - require.Equal(t, "Google,3.55,3.74", fieldsToString2digits(rows[i], "pub", "m1", "m1_p")) + require.Equal(t, "Google,3.74", fieldsToString2digits(rows[i], "pub", "m1_p")) i++ - require.Equal(t, "Yahoo,null,null", fieldsToString2digits(rows[i], "pub", "m1", "m1_p")) + require.Equal(t, "Yahoo,null", fieldsToString2digits(rows[i], "pub", "m1_p")) } func TestMetricsViewsAggregation_comparison_measure_filter_with_totals(t *testing.T) { @@ -2974,9 +2900,6 @@ func TestMetricsViewsAggregation_comparison_measure_filter_with_totals(t *testin }, }, Measures: []*runtimev1.MetricsViewAggregationMeasure{ - { - Name: "m1", - }, { Name: "m1_p", Compute: &runtimev1.MetricsViewAggregationMeasure_ComparisonValue{ @@ -3035,7 +2958,7 @@ func TestMetricsViewsAggregation_comparison_measure_filter_with_totals(t *testin require.NoError(t, err) require.NotEmpty(t, q.Result) fields := q.Result.Schema.Fields - require.Equal(t, "pub,dom,m1,m1_p", columnNames(fields)) + require.Equal(t, "pub,dom,m1_p", columnNames(fields)) i := 0 for _, sf := range q.Result.Schema.Fields { @@ -3054,13 +2977,13 @@ func TestMetricsViewsAggregation_comparison_measure_filter_with_totals(t *testin require.Equal(t, 4, len(rows)) i = 0 - require.Equal(t, "Google,google.com,null,null", fieldsToString2digits(rows[i], "pub", "dom", "m1", "m1_p")) + require.Equal(t, "Google,google.com,null", fieldsToString2digits(rows[i], "pub", "dom", "m1_p")) i++ - require.Equal(t, "Google,news.google.com,3.55,3.74", fieldsToString2digits(rows[i], "pub", "dom", "m1", "m1_p")) + require.Equal(t, "Google,news.google.com,3.74", fieldsToString2digits(rows[i], "pub", "dom", "m1_p")) i++ - require.Equal(t, "Yahoo,news.yahoo.com,null,null", fieldsToString2digits(rows[i], "pub", "dom", "m1", "m1_p")) + require.Equal(t, "Yahoo,news.yahoo.com,null", fieldsToString2digits(rows[i], "pub", "dom", "m1_p")) i++ - require.Equal(t, "Yahoo,sports.yahoo.com,null,null", fieldsToString2digits(rows[i], "pub", "dom", "m1", "m1_p")) + require.Equal(t, "Yahoo,sports.yahoo.com,null", fieldsToString2digits(rows[i], "pub", "dom", "m1_p")) } func TestMetricsViewsAggregation_Druid_comparison_measure_filter_with_limit(t *testing.T) { @@ -3166,13 +3089,357 @@ func TestMetricsViewsAggregation_Druid_comparison_measure_filter_with_limit(t *t require.Equal(t, "Yahoo,news.yahoo.com,null", fieldsToString2digits(rows[i], "pub", "dom", "m1")) i++ require.Equal(t, "Yahoo,sports.yahoo.com,null", fieldsToString2digits(rows[i], "pub", "dom", "m1")) - } -func TestMetricsViewsAggregation_comparison_measure_filter_with_limit(t *testing.T) { +// Uncommenting: We don't support comparison queries with time as a dimension +// func TestMetricsViewsAggregation_comparison_measure_filter(t *testing.T) { +// rt, instanceID := testruntime.NewInstanceForProject(t, "ad_bids") + +// limit := int64(10) +// q := &queries.MetricsViewAggregation{ +// MetricsViewName: "ad_bids_metrics", +// Dimensions: []*runtimev1.MetricsViewAggregationDimension{ +// { +// Name: "pub", +// }, +// { +// Name: "dom", +// }, + +// { +// Name: "timestamp", +// TimeGrain: runtimev1.TimeGrain_TIME_GRAIN_DAY, +// }, +// { +// Name: "timestamp", +// TimeGrain: runtimev1.TimeGrain_TIME_GRAIN_YEAR, +// Alias: "timestamp_year", +// }, +// }, +// Measures: []*runtimev1.MetricsViewAggregationMeasure{ +// { +// Name: "m1", +// }, +// { +// Name: "m1_p", +// Compute: &runtimev1.MetricsViewAggregationMeasure_ComparisonValue{ +// ComparisonValue: &runtimev1.MetricsViewAggregationMeasureComputeComparisonValue{ +// Measure: "m1", +// }, +// }, +// Filter: &runtimev1.Expression{ +// Expression: &runtimev1.Expression_Cond{ +// Cond: &runtimev1.Condition{ +// Op: runtimev1.Operation_OPERATION_EQ, +// Exprs: []*runtimev1.Expression{ +// { +// Expression: &runtimev1.Expression_Ident{ +// Ident: "dom", +// }, +// }, +// { +// Expression: &runtimev1.Expression_Val{ +// Val: structpb.NewStringValue("news.google.com"), +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// Where: expressionpb.OrAll( +// expressionpb.Eq("pub", "Yahoo"), +// expressionpb.Eq("pub", "Google"), +// ), +// // Having: expressionpb.Gt("m1", 0.0), +// Sort: []*runtimev1.MetricsViewAggregationSort{ +// { +// Name: "timestamp", +// }, +// { +// Name: "pub", +// }, +// { +// Name: "dom", +// }, +// { +// Name: "timestamp_year", +// }, +// { +// Name: "m1_p", +// }, +// }, + +// TimeRange: &runtimev1.TimeRange{ +// Start: timestamppb.New(time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)), +// End: timestamppb.New(time.Date(2022, 1, 2, 0, 0, 0, 0, time.UTC)), +// }, +// ComparisonTimeRange: &runtimev1.TimeRange{ +// Start: timestamppb.New(time.Date(2022, 1, 2, 0, 0, 0, 0, time.UTC)), +// End: timestamppb.New(time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC)), +// }, +// Limit: &limit, +// } +// err := q.Resolve(context.Background(), rt, instanceID, 0) +// require.NoError(t, err) +// require.NotEmpty(t, q.Result) +// fields := q.Result.Schema.Fields +// require.Equal(t, "pub,dom,timestamp,timestamp_year,m1,m1_p,timestamp__previous,timestamp_year__previous", columnNames(fields)) +// i := 0 + +// for _, sf := range q.Result.Schema.Fields { +// fmt.Printf("%v ", sf.Name) +// } +// fmt.Printf("\n") + +// for i, row := range q.Result.Data { +// for _, sf := range q.Result.Schema.Fields { +// fmt.Printf("%v ", row.Fields[sf.Name].AsInterface()) +// } +// fmt.Printf(" %d \n", i) + +// } +// rows := q.Result.Data +// require.Equal(t, 4, len(rows)) + +// i = 0 +// require.Equal(t, "Google,google.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,null,null,2022-01-02T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "m1", "m1_p", "timestamp__previous", "timestamp_year__previous")) +// i++ +// require.Equal(t, "Google,news.google.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,3.55,3.74,2022-01-02T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "m1", "m1_p", "timestamp__previous", "timestamp_year__previous")) +// i++ +// require.Equal(t, "Yahoo,news.yahoo.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,null,null,2022-01-02T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "m1", "m1_p", "timestamp__previous", "timestamp_year__previous")) +// i++ +// require.Equal(t, "Yahoo,sports.yahoo.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,null,null,2022-01-02T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "m1", "m1_p", "timestamp__previous", "timestamp_year__previous")) +// } + +// Uncommenting: We don't support comparison queries with time as a dimension +// func TestMetricsViewsAggregation_comparison_measure_filter_with_having(t *testing.T) { +// rt, instanceID := testruntime.NewInstanceForProject(t, "ad_bids") +// +// limit := int64(10) +// q := &queries.MetricsViewAggregation{ +// MetricsViewName: "ad_bids_metrics", +// Dimensions: []*runtimev1.MetricsViewAggregationDimension{ +// { +// Name: "pub", +// }, +// { +// Name: "dom", +// }, +// +// { +// Name: "timestamp", +// TimeGrain: runtimev1.TimeGrain_TIME_GRAIN_DAY, +// }, +// { +// Name: "timestamp", +// TimeGrain: runtimev1.TimeGrain_TIME_GRAIN_YEAR, +// Alias: "timestamp_year", +// }, +// }, +// Measures: []*runtimev1.MetricsViewAggregationMeasure{ +// { +// Name: "m1", +// }, +// { +// Name: "m1_p", +// Compute: &runtimev1.MetricsViewAggregationMeasure_ComparisonValue{ +// ComparisonValue: &runtimev1.MetricsViewAggregationMeasureComputeComparisonValue{ +// Measure: "m1", +// }, +// }, +// Filter: &runtimev1.Expression{ +// Expression: &runtimev1.Expression_Cond{ +// Cond: &runtimev1.Condition{ +// Op: runtimev1.Operation_OPERATION_EQ, +// Exprs: []*runtimev1.Expression{ +// { +// Expression: &runtimev1.Expression_Ident{ +// Ident: "dom", +// }, +// }, +// { +// Expression: &runtimev1.Expression_Val{ +// Val: structpb.NewStringValue("news.google.com"), +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// Where: expressionpb.OrAll( +// expressionpb.Eq("pub", "Yahoo"), +// expressionpb.Eq("pub", "Google"), +// ), +// Having: expressionpb.Gt("m1", 0.0), +// Sort: []*runtimev1.MetricsViewAggregationSort{ +// { +// Name: "timestamp", +// }, +// { +// Name: "pub", +// }, +// { +// Name: "dom", +// }, +// { +// Name: "timestamp_year", +// }, +// { +// Name: "m1_p", +// }, +// }, +// +// TimeRange: &runtimev1.TimeRange{ +// Start: timestamppb.New(time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)), +// End: timestamppb.New(time.Date(2022, 1, 2, 0, 0, 0, 0, time.UTC)), +// }, +// ComparisonTimeRange: &runtimev1.TimeRange{ +// Start: timestamppb.New(time.Date(2022, 1, 2, 0, 0, 0, 0, time.UTC)), +// End: timestamppb.New(time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC)), +// }, +// Limit: &limit, +// } +// err := q.Resolve(context.Background(), rt, instanceID, 0) +// require.NoError(t, err) +// require.NotEmpty(t, q.Result) +// fields := q.Result.Schema.Fields +// require.Equal(t, "pub,dom,timestamp,timestamp_year,m1,m1_p,timestamp__previous,timestamp_year__previous", columnNames(fields)) +// i := 0 +// +// for _, sf := range q.Result.Schema.Fields { +// fmt.Printf("%v ", sf.Name) +// } +// fmt.Printf("\n") +// +// for i, row := range q.Result.Data { +// for _, sf := range q.Result.Schema.Fields { +// fmt.Printf("%v ", row.Fields[sf.Name].AsInterface()) +// } +// fmt.Printf(" %d \n", i) +// +// } +// rows := q.Result.Data +// require.Equal(t, 1, len(rows)) +// +// i = 0 +// require.Equal(t, "Google,news.google.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,3.55,3.74,2022-01-02T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "m1", "m1_p", "timestamp__previous", "timestamp_year__previous")) +// } + +// Uncommenting: We don't support comparison queries with time as a dimension +// func TestMetricsViewsAggregation_comparison(t *testing.T) { +// rt, instanceID := testruntime.NewInstanceForProject(t, "ad_bids") + +// limit := int64(10) +// q := &queries.MetricsViewAggregation{ +// MetricsViewName: "ad_bids_metrics", +// Dimensions: []*runtimev1.MetricsViewAggregationDimension{ +// { +// Name: "pub", +// }, +// { +// Name: "dom", +// }, + +// { +// Name: "timestamp", +// TimeGrain: runtimev1.TimeGrain_TIME_GRAIN_DAY, +// }, +// { +// Name: "timestamp", +// TimeGrain: runtimev1.TimeGrain_TIME_GRAIN_YEAR, +// Alias: "timestamp_year", +// }, +// }, +// Measures: []*runtimev1.MetricsViewAggregationMeasure{ +// { +// Name: "measure_0", +// }, +// { +// Name: "measure_1", +// }, +// { +// Name: "m1", +// }, +// { +// Name: "measure_0__p", +// Compute: &runtimev1.MetricsViewAggregationMeasure_ComparisonValue{ +// ComparisonValue: &runtimev1.MetricsViewAggregationMeasureComputeComparisonValue{ +// Measure: "measure_0", +// }, +// }, +// }, +// }, +// Where: expressionpb.OrAll( +// expressionpb.Eq("pub", "Yahoo"), +// expressionpb.Eq("pub", "Google"), +// ), +// Having: expressionpb.Gt("measure_1", 0.0), +// Sort: []*runtimev1.MetricsViewAggregationSort{ +// { +// Name: "pub", +// }, +// { +// Name: "dom", +// }, +// { +// Name: "timestamp", +// }, +// { +// Name: "timestamp_year", +// }, +// { +// Name: "measure_1", +// }, +// }, + +// TimeRange: &runtimev1.TimeRange{ +// Start: timestamppb.New(time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)), +// End: timestamppb.New(time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC)), +// }, +// ComparisonTimeRange: &runtimev1.TimeRange{ +// Start: timestamppb.New(time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC)), +// End: timestamppb.New(time.Date(2022, 1, 5, 0, 0, 0, 0, time.UTC)), +// }, +// Limit: &limit, +// } +// err := q.Resolve(context.Background(), rt, instanceID, 0) +// require.NoError(t, err) +// require.NotEmpty(t, q.Result) +// fields := q.Result.Schema.Fields +// require.Equal(t, "pub,dom,timestamp,timestamp_year,measure_0,measure_1,m1,measure_0__p,timestamp__previous,timestamp_year__previous", columnNames(fields)) +// i := 0 + +// for _, sf := range q.Result.Schema.Fields { +// fmt.Printf("%v ", sf.Name) +// } +// fmt.Printf("\n") + +// for i, row := range q.Result.Data { +// for _, sf := range q.Result.Schema.Fields { +// fmt.Printf("%v ", row.Fields[sf.Name].AsInterface()) +// } +// fmt.Printf(" %d \n", i) + +// } +// rows := q.Result.Data +// require.Equal(t, 8, len(rows)) + +// i = 0 +// require.Equal(t, "Google,google.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,44.00,50.00,1.53,1.53,2022-01-03T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "measure_0", "measure_0__p", "measure_1", "m1", "timestamp__previous", "timestamp_year__previous")) +// i++ +// require.Equal(t, "Google,google.com,2022-01-02T00:00:00Z,2022-01-01T00:00:00Z,62.00,51.00,1.45,1.45,2022-01-04T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "measure_0", "measure_0__p", "measure_1", "m1", "timestamp__previous", "timestamp_year__previous")) +// i++ +// require.Equal(t, "Google,news.google.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,187.00,183.00,3.55,3.55,2022-01-03T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "measure_0", "measure_0__p", "measure_1", "m1", "timestamp__previous", "timestamp_year__previous")) +// } + +func TestMetricsViewsAggregation_comparison_pivot(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "ad_bids") - limit := int64(2) + limit := int64(10) q := &queries.MetricsViewAggregation{ MetricsViewName: "ad_bids_metrics", Dimensions: []*runtimev1.MetricsViewAggregationDimension{ @@ -3183,449 +3450,6 @@ func TestMetricsViewsAggregation_comparison_measure_filter_with_limit(t *testing Name: "dom", }, }, - Measures: []*runtimev1.MetricsViewAggregationMeasure{ - { - Name: "m1", - }, - { - Name: "m1_p", - Compute: &runtimev1.MetricsViewAggregationMeasure_ComparisonValue{ - ComparisonValue: &runtimev1.MetricsViewAggregationMeasureComputeComparisonValue{ - Measure: "m1", - }, - }, - Filter: &runtimev1.Expression{ - Expression: &runtimev1.Expression_Cond{ - Cond: &runtimev1.Condition{ - Op: runtimev1.Operation_OPERATION_EQ, - Exprs: []*runtimev1.Expression{ - { - Expression: &runtimev1.Expression_Ident{ - Ident: "dom", - }, - }, - { - Expression: &runtimev1.Expression_Val{ - Val: structpb.NewStringValue("news.google.com"), - }, - }, - }, - }, - }, - }, - }, - }, - // Having: expressionpb.Gt("m1", 0.0), - Sort: []*runtimev1.MetricsViewAggregationSort{ - { - Name: "m1", - }, - { - Name: "pub", - Desc: true, - }, - { - Name: "dom", - }, - }, - - TimeRange: &runtimev1.TimeRange{ - Start: timestamppb.New(time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)), - End: timestamppb.New(time.Date(2022, 1, 2, 0, 0, 0, 0, time.UTC)), - }, - ComparisonTimeRange: &runtimev1.TimeRange{ - Start: timestamppb.New(time.Date(2022, 1, 2, 0, 0, 0, 0, time.UTC)), - End: timestamppb.New(time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC)), - }, - Limit: &limit, - Offset: 1, - } - err := q.Resolve(context.Background(), rt, instanceID, 0) - require.NoError(t, err) - require.NotEmpty(t, q.Result) - fields := q.Result.Schema.Fields - require.Equal(t, "pub,dom,m1,m1_p", columnNames(fields)) - i := 0 - - for _, sf := range q.Result.Schema.Fields { - fmt.Printf("%v ", sf.Name) - } - fmt.Printf("\n") - - for i, row := range q.Result.Data { - for _, sf := range q.Result.Schema.Fields { - fmt.Printf("%v ", row.Fields[sf.Name].AsInterface()) - } - fmt.Printf(" %d \n", i) - - } - rows := q.Result.Data - require.Equal(t, 2, len(rows)) - - i = 0 - require.Equal(t, "null,news.google.com,3.70,3.58", fieldsToString2digits(rows[i], "pub", "dom", "m1", "m1_p")) - i++ - require.Equal(t, "Yahoo,news.yahoo.com,null,null", fieldsToString2digits(rows[i], "pub", "dom", "m1", "m1_p")) -} - -func TestMetricsViewsAggregation_comparison_measure_filter(t *testing.T) { - rt, instanceID := testruntime.NewInstanceForProject(t, "ad_bids") - - limit := int64(10) - q := &queries.MetricsViewAggregation{ - MetricsViewName: "ad_bids_metrics", - Dimensions: []*runtimev1.MetricsViewAggregationDimension{ - { - Name: "pub", - }, - { - Name: "dom", - }, - - { - Name: "timestamp", - TimeGrain: runtimev1.TimeGrain_TIME_GRAIN_DAY, - }, - { - Name: "timestamp", - TimeGrain: runtimev1.TimeGrain_TIME_GRAIN_YEAR, - Alias: "timestamp_year", - }, - }, - Measures: []*runtimev1.MetricsViewAggregationMeasure{ - { - Name: "m1", - }, - { - Name: "m1_p", - Compute: &runtimev1.MetricsViewAggregationMeasure_ComparisonValue{ - ComparisonValue: &runtimev1.MetricsViewAggregationMeasureComputeComparisonValue{ - Measure: "m1", - }, - }, - Filter: &runtimev1.Expression{ - Expression: &runtimev1.Expression_Cond{ - Cond: &runtimev1.Condition{ - Op: runtimev1.Operation_OPERATION_EQ, - Exprs: []*runtimev1.Expression{ - { - Expression: &runtimev1.Expression_Ident{ - Ident: "dom", - }, - }, - { - Expression: &runtimev1.Expression_Val{ - Val: structpb.NewStringValue("news.google.com"), - }, - }, - }, - }, - }, - }, - }, - }, - Where: expressionpb.OrAll( - expressionpb.Eq("pub", "Yahoo"), - expressionpb.Eq("pub", "Google"), - ), - // Having: expressionpb.Gt("m1", 0.0), - Sort: []*runtimev1.MetricsViewAggregationSort{ - { - Name: "timestamp", - }, - { - Name: "pub", - }, - { - Name: "dom", - }, - { - Name: "timestamp_year", - }, - { - Name: "m1_p", - }, - }, - - TimeRange: &runtimev1.TimeRange{ - Start: timestamppb.New(time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)), - End: timestamppb.New(time.Date(2022, 1, 2, 0, 0, 0, 0, time.UTC)), - }, - ComparisonTimeRange: &runtimev1.TimeRange{ - Start: timestamppb.New(time.Date(2022, 1, 2, 0, 0, 0, 0, time.UTC)), - End: timestamppb.New(time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC)), - }, - Limit: &limit, - } - err := q.Resolve(context.Background(), rt, instanceID, 0) - require.NoError(t, err) - require.NotEmpty(t, q.Result) - fields := q.Result.Schema.Fields - require.Equal(t, "pub,dom,timestamp,timestamp_year,m1,m1_p,timestamp__previous,timestamp_year__previous", columnNames(fields)) - i := 0 - - for _, sf := range q.Result.Schema.Fields { - fmt.Printf("%v ", sf.Name) - } - fmt.Printf("\n") - - for i, row := range q.Result.Data { - for _, sf := range q.Result.Schema.Fields { - fmt.Printf("%v ", row.Fields[sf.Name].AsInterface()) - } - fmt.Printf(" %d \n", i) - - } - rows := q.Result.Data - require.Equal(t, 4, len(rows)) - - i = 0 - require.Equal(t, "Google,google.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,null,null,2022-01-02T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "m1", "m1_p", "timestamp__previous", "timestamp_year__previous")) - i++ - require.Equal(t, "Google,news.google.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,3.55,3.74,2022-01-02T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "m1", "m1_p", "timestamp__previous", "timestamp_year__previous")) - i++ - require.Equal(t, "Yahoo,news.yahoo.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,null,null,2022-01-02T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "m1", "m1_p", "timestamp__previous", "timestamp_year__previous")) - i++ - require.Equal(t, "Yahoo,sports.yahoo.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,null,null,2022-01-02T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "m1", "m1_p", "timestamp__previous", "timestamp_year__previous")) -} - -func TestMetricsViewsAggregation_comparison_measure_filter_with_having(t *testing.T) { - rt, instanceID := testruntime.NewInstanceForProject(t, "ad_bids") - - limit := int64(10) - q := &queries.MetricsViewAggregation{ - MetricsViewName: "ad_bids_metrics", - Dimensions: []*runtimev1.MetricsViewAggregationDimension{ - { - Name: "pub", - }, - { - Name: "dom", - }, - - { - Name: "timestamp", - TimeGrain: runtimev1.TimeGrain_TIME_GRAIN_DAY, - }, - { - Name: "timestamp", - TimeGrain: runtimev1.TimeGrain_TIME_GRAIN_YEAR, - Alias: "timestamp_year", - }, - }, - Measures: []*runtimev1.MetricsViewAggregationMeasure{ - { - Name: "m1", - }, - { - Name: "m1_p", - Compute: &runtimev1.MetricsViewAggregationMeasure_ComparisonValue{ - ComparisonValue: &runtimev1.MetricsViewAggregationMeasureComputeComparisonValue{ - Measure: "m1", - }, - }, - Filter: &runtimev1.Expression{ - Expression: &runtimev1.Expression_Cond{ - Cond: &runtimev1.Condition{ - Op: runtimev1.Operation_OPERATION_EQ, - Exprs: []*runtimev1.Expression{ - { - Expression: &runtimev1.Expression_Ident{ - Ident: "dom", - }, - }, - { - Expression: &runtimev1.Expression_Val{ - Val: structpb.NewStringValue("news.google.com"), - }, - }, - }, - }, - }, - }, - }, - }, - Where: expressionpb.OrAll( - expressionpb.Eq("pub", "Yahoo"), - expressionpb.Eq("pub", "Google"), - ), - Having: expressionpb.Gt("m1", 0.0), - Sort: []*runtimev1.MetricsViewAggregationSort{ - { - Name: "timestamp", - }, - { - Name: "pub", - }, - { - Name: "dom", - }, - { - Name: "timestamp_year", - }, - { - Name: "m1_p", - }, - }, - - TimeRange: &runtimev1.TimeRange{ - Start: timestamppb.New(time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)), - End: timestamppb.New(time.Date(2022, 1, 2, 0, 0, 0, 0, time.UTC)), - }, - ComparisonTimeRange: &runtimev1.TimeRange{ - Start: timestamppb.New(time.Date(2022, 1, 2, 0, 0, 0, 0, time.UTC)), - End: timestamppb.New(time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC)), - }, - Limit: &limit, - } - err := q.Resolve(context.Background(), rt, instanceID, 0) - require.NoError(t, err) - require.NotEmpty(t, q.Result) - fields := q.Result.Schema.Fields - require.Equal(t, "pub,dom,timestamp,timestamp_year,m1,m1_p,timestamp__previous,timestamp_year__previous", columnNames(fields)) - i := 0 - - for _, sf := range q.Result.Schema.Fields { - fmt.Printf("%v ", sf.Name) - } - fmt.Printf("\n") - - for i, row := range q.Result.Data { - for _, sf := range q.Result.Schema.Fields { - fmt.Printf("%v ", row.Fields[sf.Name].AsInterface()) - } - fmt.Printf(" %d \n", i) - - } - rows := q.Result.Data - require.Equal(t, 1, len(rows)) - - i = 0 - require.Equal(t, "Google,news.google.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,3.55,3.74,2022-01-02T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "m1", "m1_p", "timestamp__previous", "timestamp_year__previous")) -} - -func TestMetricsViewsAggregation_comparison(t *testing.T) { - rt, instanceID := testruntime.NewInstanceForProject(t, "ad_bids") - - limit := int64(10) - q := &queries.MetricsViewAggregation{ - MetricsViewName: "ad_bids_metrics", - Dimensions: []*runtimev1.MetricsViewAggregationDimension{ - { - Name: "pub", - }, - { - Name: "dom", - }, - - { - Name: "timestamp", - TimeGrain: runtimev1.TimeGrain_TIME_GRAIN_DAY, - }, - { - Name: "timestamp", - TimeGrain: runtimev1.TimeGrain_TIME_GRAIN_YEAR, - Alias: "timestamp_year", - }, - }, - Measures: []*runtimev1.MetricsViewAggregationMeasure{ - { - Name: "measure_0", - }, - { - Name: "measure_1", - }, - { - Name: "m1", - }, - { - Name: "measure_0__p", - Compute: &runtimev1.MetricsViewAggregationMeasure_ComparisonValue{ - ComparisonValue: &runtimev1.MetricsViewAggregationMeasureComputeComparisonValue{ - Measure: "measure_0", - }, - }, - }, - }, - Where: expressionpb.OrAll( - expressionpb.Eq("pub", "Yahoo"), - expressionpb.Eq("pub", "Google"), - ), - Having: expressionpb.Gt("measure_1", 0.0), - Sort: []*runtimev1.MetricsViewAggregationSort{ - { - Name: "pub", - }, - { - Name: "dom", - }, - { - Name: "timestamp", - }, - { - Name: "timestamp_year", - }, - { - Name: "measure_1", - }, - }, - - TimeRange: &runtimev1.TimeRange{ - Start: timestamppb.New(time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)), - End: timestamppb.New(time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC)), - }, - ComparisonTimeRange: &runtimev1.TimeRange{ - Start: timestamppb.New(time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC)), - End: timestamppb.New(time.Date(2022, 1, 5, 0, 0, 0, 0, time.UTC)), - }, - Limit: &limit, - } - err := q.Resolve(context.Background(), rt, instanceID, 0) - require.NoError(t, err) - require.NotEmpty(t, q.Result) - fields := q.Result.Schema.Fields - require.Equal(t, "pub,dom,timestamp,timestamp_year,measure_0,measure_1,m1,measure_0__p,timestamp__previous,timestamp_year__previous", columnNames(fields)) - i := 0 - - for _, sf := range q.Result.Schema.Fields { - fmt.Printf("%v ", sf.Name) - } - fmt.Printf("\n") - - for i, row := range q.Result.Data { - for _, sf := range q.Result.Schema.Fields { - fmt.Printf("%v ", row.Fields[sf.Name].AsInterface()) - } - fmt.Printf(" %d \n", i) - - } - rows := q.Result.Data - require.Equal(t, 8, len(rows)) - - i = 0 - require.Equal(t, "Google,google.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,44.00,50.00,1.53,1.53,2022-01-03T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "measure_0", "measure_0__p", "measure_1", "m1", "timestamp__previous", "timestamp_year__previous")) - i++ - require.Equal(t, "Google,google.com,2022-01-02T00:00:00Z,2022-01-01T00:00:00Z,62.00,51.00,1.45,1.45,2022-01-04T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "measure_0", "measure_0__p", "measure_1", "m1", "timestamp__previous", "timestamp_year__previous")) - i++ - require.Equal(t, "Google,news.google.com,2022-01-01T00:00:00Z,2022-01-01T00:00:00Z,187.00,183.00,3.55,3.55,2022-01-03T00:00:00Z,2022-01-01T00:00:00Z", fieldsToString2digits(rows[i], "pub", "dom", "timestamp", "timestamp_year", "measure_0", "measure_0__p", "measure_1", "m1", "timestamp__previous", "timestamp_year__previous")) -} - -func TestMetricsViewsAggregation_comparison_pivot(t *testing.T) { - rt, instanceID := testruntime.NewInstanceForProject(t, "ad_bids") - - limit := int64(10) - q := &queries.MetricsViewAggregation{ - MetricsViewName: "ad_bids_metrics", - Dimensions: []*runtimev1.MetricsViewAggregationDimension{ - { - Name: "pub", - }, - { - Name: "timestamp", - TimeGrain: runtimev1.TimeGrain_TIME_GRAIN_YEAR, - Alias: "timestamp_year", - }, - }, Measures: []*runtimev1.MetricsViewAggregationMeasure{ { Name: "measure_0", @@ -3657,7 +3481,7 @@ func TestMetricsViewsAggregation_comparison_pivot(t *testing.T) { Start: timestamppb.New(time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC)), End: timestamppb.New(time.Date(2022, 1, 5, 0, 0, 0, 0, time.UTC)), }, - PivotOn: []string{"timestamp_year"}, + PivotOn: []string{"dom"}, Limit: &limit, } err := q.Resolve(context.Background(), rt, instanceID, 0) @@ -3668,7 +3492,7 @@ func TestMetricsViewsAggregation_comparison_pivot(t *testing.T) { } fmt.Printf("\n") fields := q.Result.Schema.Fields - require.Equal(t, "pub,timestamp_year__previous,2022-01-01 00:00:00_measure_0,2022-01-01 00:00:00_measure_0__p", columnNames(fields)) + require.Equal(t, "pub,google.com_measure_0,google.com_measure_0__p,news.google.com_measure_0,news.google.com_measure_0__p,news.yahoo.com_measure_0,news.yahoo.com_measure_0__p,sports.yahoo.com_measure_0,sports.yahoo.com_measure_0__p", columnNames(fields)) } // Can be used for local or metrics cluster. From 00763458f5719ed72ae4e16a7a84d0b0198ffbc8 Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Tue, 25 Jun 2024 20:56:07 +0530 Subject: [PATCH 05/19] Fix measure filter breaking sub table in pivot (#5135) --- .../src/features/dashboards/pivot/pivot-merge-filters.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web-common/src/features/dashboards/pivot/pivot-merge-filters.ts b/web-common/src/features/dashboards/pivot/pivot-merge-filters.ts index e31b95f4737..253f698c5a8 100644 --- a/web-common/src/features/dashboards/pivot/pivot-merge-filters.ts +++ b/web-common/src/features/dashboards/pivot/pivot-merge-filters.ts @@ -37,7 +37,7 @@ export function mergeFilters( if (likeExprMap.has(ident)) return; likeExprMap.set(ident, e); } else { - if (inExprMap.has(ident)) return; + if (inExprMap.has(ident) || !!e.cond?.exprs?.[1].subquery) return; inExprMap.set(ident, e); } }); @@ -51,7 +51,7 @@ export function mergeFilters( e.cond?.op === V1Operation.OPERATION_NLIKE ) return; - if (!inExprMap.has(ident)) return; + if (!inExprMap.has(ident) || !!e.cond?.exprs?.[1].subquery) return; /** * We take an intersection of the values in the IN expressions. From 57496360d09f6b60f54b8b1b0ff420e6eadaf7ff Mon Sep 17 00:00:00 2001 From: "Tony.J" Date: Tue, 25 Jun 2024 12:44:03 -0700 Subject: [PATCH 06/19] refactor: better logging for slots scaling (#5149) --- admin/worker/run_autoscaler.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/admin/worker/run_autoscaler.go b/admin/worker/run_autoscaler.go index 2cf5bd9a537..a478cec81a4 100644 --- a/admin/worker/run_autoscaler.go +++ b/admin/worker/run_autoscaler.go @@ -44,7 +44,7 @@ func (w *Worker) runAutoscaler(ctx context.Context) error { projectOrg, err := w.admin.DB.FindOrganization(ctx, targetProject.OrganizationID) if err != nil { - w.logger.Error("failed to find org for the project", zap.String("project_name", targetProject.Name), zap.String("org_id", targetProject.OrganizationID), zap.Error(err)) + w.logger.Error("failed to autoscale: unable to find org for the project", zap.String("project_name", targetProject.Name), zap.String("org_id", targetProject.OrganizationID), zap.Error(err)) continue } @@ -75,11 +75,18 @@ func (w *Worker) runAutoscaler(ctx context.Context) error { Annotations: targetProject.Annotations, }) if err != nil { - w.logger.Error("failed to autoscale", zap.String("project_name", targetProject.Name), zap.String("org_name", projectOrg.Name), zap.Error(err)) + w.logger.Error("failed to autoscale: error updating the project", zap.String("project_name", targetProject.Name), zap.String("org_name", projectOrg.Name), zap.Error(err)) continue } - w.logger.Info("succeeded in autoscaling", + scaleMsg := "succeeded in autoscaling " + if updatedProject.ProdSlots > targetProject.ProdSlots { + scaleMsg += "up" + } else { + scaleMsg += "down" + } + + w.logger.Info(scaleMsg, zap.String("project_name", updatedProject.Name), zap.Int("updated_slots", updatedProject.ProdSlots), zap.Int("prev_slots", targetProject.ProdSlots), From 574e235491b299168207d9f0da702c385c6a1055 Mon Sep 17 00:00:00 2001 From: Brian Holmes <120223836+briangregoryholmes@users.noreply.github.com> Date: Wed, 26 Jun 2024 04:24:35 -0400 Subject: [PATCH 07/19] feat: highlight SQL-in-YAML for API and MetricsView asset types (#5139) * init commit * parse sql in metrics yaml --- .../editor/presets/yamlWithJsonAndSql.ts | 39 +++++++++++++++++++ .../workspace/editor/MetricsEditor.svelte | 4 +- .../files/[...file]/+page.svelte | 8 +++- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/web-common/src/components/editor/presets/yamlWithJsonAndSql.ts b/web-common/src/components/editor/presets/yamlWithJsonAndSql.ts index 6ff2b0f4952..08961cc8d2d 100644 --- a/web-common/src/components/editor/presets/yamlWithJsonAndSql.ts +++ b/web-common/src/components/editor/presets/yamlWithJsonAndSql.ts @@ -29,10 +29,49 @@ const wrap = parseMixed((node) => { return null; }); +const names = new Set(["Literal", ":"]); +let foundExpression = false; +let foundColon = false; + +const metricsParsing = parseMixed(({ name, from, to }, input) => { + if (!names.has(name)) return null; + + if ( + !foundExpression && + name === "Literal" && + input.read(from, to) === "expression" + ) { + foundExpression = true; + return null; + } + + if (name === ":") { + foundColon = true; + return null; + } + + if (foundExpression && foundColon && name === "Literal") { + foundExpression = false; + foundColon = false; + return { + parser: DuckDBSQL.language.parser, + }; + } + return null; +}); + const customYAMLandSQLParser = yamlLanguage.parser.configure({ wrap, }); +const metricsPlusSQLParser = yamlLanguage.parser.configure({ + wrap: metricsParsing, +}); + +export const metricsPlusSQL = LRLanguage.define({ + parser: metricsPlusSQLParser, +}); + export const customYAMLwithJSONandSQL = LRLanguage.define({ parser: customYAMLandSQLParser, }); diff --git a/web-common/src/features/metrics-views/workspace/editor/MetricsEditor.svelte b/web-common/src/features/metrics-views/workspace/editor/MetricsEditor.svelte index 46bbeedaa20..de5d6bfb383 100644 --- a/web-common/src/features/metrics-views/workspace/editor/MetricsEditor.svelte +++ b/web-common/src/features/metrics-views/workspace/editor/MetricsEditor.svelte @@ -12,7 +12,7 @@ import { FileArtifact } from "@rilldata/web-common/features/entity-management/file-artifact"; import { metricsExplorerStore } from "@rilldata/web-common/features/dashboards/stores/dashboard-stores"; import { createPersistentDashboardStore } from "@rilldata/web-common/features/dashboards/stores/persistent-dashboard-state"; - import { FileExtensionToEditorExtension } from "@rilldata/web-common/features/editor/getExtensionsForFile"; + import { metricsPlusSQL } from "@rilldata/web-common/components/editor/presets/yamlWithJsonAndSql"; export let filePath: string; export let metricViewName: string; @@ -58,9 +58,9 @@ }} {fileArtifact} extensions={[ + metricsPlusSQL, placeholderElements.extension, yamlSchema(metricsJsonSchema), - FileExtensionToEditorExtension[".yaml"], ]} /> diff --git a/web-local/src/routes/(application)/files/[...file]/+page.svelte b/web-local/src/routes/(application)/files/[...file]/+page.svelte index 9c072f1a2b4..279fe607fd9 100644 --- a/web-local/src/routes/(application)/files/[...file]/+page.svelte +++ b/web-local/src/routes/(application)/files/[...file]/+page.svelte @@ -14,6 +14,7 @@ import MetricsWorkspace from "@rilldata/web-common/features/workspaces/MetricsWorkspace.svelte"; import ChartWorkspace from "@rilldata/web-common/features/workspaces/ChartWorkspace.svelte"; import CustomDashboardWorkspace from "@rilldata/web-common/features/workspaces/CustomDashboardWorkspace.svelte"; + import { customYAMLwithJSONandSQL } from "@rilldata/web-common/components/editor/presets/yamlWithJsonAndSql"; const workspaces = new Map([ [ResourceKind.Source, SourceWorkspace], @@ -35,6 +36,11 @@ $: workspace = workspaces.get(resourceKind); + $: extensions = + resourceKind === ResourceKind.API + ? [customYAMLwithJSONandSQL] + : getExtensionsForFile(filePath); + onMount(() => { expandDirectory(filePath); // TODO: Focus on the code editor @@ -69,7 +75,7 @@ From d614d39d31cecc39c76eb9cc65317c3634799ce9 Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Wed, 26 Jun 2024 14:59:41 +0530 Subject: [PATCH 08/19] feat: alerts measure filters (#5129) * Add export to time dimension detail * Add reporting in pivot * Update to make Aggregation Request * Update dashboard name for Aggregation Request * PR comments * Restore dashboard from stored state * Fix annotation being passed * URI encode dashboard state * Pass through measure filter into alerts * Cleanup --- .../alerts/metadata/AlertFilters.svelte | 38 +++++++++++-- .../getDashboardFromAggregationRequest.ts | 15 ++++- .../features/alerts/AlertPreviewTable.svelte | 2 - .../features/alerts/CreateAlertForm.svelte | 1 + .../alerts/criteria-tab/CriteriaForm.svelte | 2 +- .../alerts/data-tab/AlertDialogDataTab.svelte | 2 +- .../alerts/extract-alert-form-values.ts | 10 +++- web-common/src/features/alerts/form-utils.ts | 11 +++- .../measure-filters/measure-filter-utils.ts | 56 ++++++++++++++++--- 9 files changed, 115 insertions(+), 22 deletions(-) diff --git a/web-admin/src/features/alerts/metadata/AlertFilters.svelte b/web-admin/src/features/alerts/metadata/AlertFilters.svelte index b3a9db34260..40db2d1e967 100644 --- a/web-admin/src/features/alerts/metadata/AlertFilters.svelte +++ b/web-admin/src/features/alerts/metadata/AlertFilters.svelte @@ -1,8 +1,11 @@
@@ -41,7 +58,7 @@ {/if} {#each currentDimensionFilters as { name, label, selectedValues, isInclude } (name)} - {@const dimension = dimensions.find((d) => d.name === name)} + {@const dimension = dimensionIdMap.get(name)}
{#if dimension?.column} {/each} + {#if currentMeasureFilters.length > 0} + {#each currentMeasureFilters as { name, label, dimensionName, filter } (name)} +
+ +
+ {/each} + {/if} {:else}
1 || exprHasComparison(req.having)) { + if ( + req.having.cond.exprs.length > 1 || + exprHasComparison(req.having) || + dashboard.dimensionThresholdFilters.length > 0 + ) { const expr = await convertExprToToplist( queryClient, instanceId, diff --git a/web-common/src/features/alerts/AlertPreviewTable.svelte b/web-common/src/features/alerts/AlertPreviewTable.svelte index cf7d1043d4d..35205ce8d78 100644 --- a/web-common/src/features/alerts/AlertPreviewTable.svelte +++ b/web-common/src/features/alerts/AlertPreviewTable.svelte @@ -37,7 +37,6 @@ columns, rows, ); - console.log(columns, rows, largestColumnLength, config.columnHeaderHeight); if (largestColumnLength > CHARACTER_LIMIT_FOR_WRAPPING) { config.columnHeaderHeight = 46; } @@ -61,7 +60,6 @@ containerWidth, config, ); - console.log(estimateColumnSize); } $: columnVirtualizer = createVirtualizer({ diff --git a/web-common/src/features/alerts/CreateAlertForm.svelte b/web-common/src/features/alerts/CreateAlertForm.svelte index 56599df83d3..c8e20d2e7b1 100644 --- a/web-common/src/features/alerts/CreateAlertForm.svelte +++ b/web-common/src/features/alerts/CreateAlertForm.svelte @@ -99,6 +99,7 @@ // Also, in the future, they might even be editable. metricsViewName: $metricsViewName, whereFilter: $dashboardStore.whereFilter, + dimensionThresholdFilters: $dashboardStore.dimensionThresholdFilters, timeRange: timeRange ? { ...timeRange, diff --git a/web-common/src/features/alerts/criteria-tab/CriteriaForm.svelte b/web-common/src/features/alerts/criteria-tab/CriteriaForm.svelte index 50f36e2e693..0d834295839 100644 --- a/web-common/src/features/alerts/criteria-tab/CriteriaForm.svelte +++ b/web-common/src/features/alerts/criteria-tab/CriteriaForm.svelte @@ -59,7 +59,7 @@ // Debounce the update of value. This avoid constant refetches let value: string = $form["criteria"][index].value1; const valueUpdater = debounce(() => { - $form["criteria"][index].value1 = value; + if ($form["criteria"][index]) $form["criteria"][index].value1 = value; void validateField("criteria"); }, 500); diff --git a/web-common/src/features/alerts/data-tab/AlertDialogDataTab.svelte b/web-common/src/features/alerts/data-tab/AlertDialogDataTab.svelte index 5f96b0ffacf..7b57690d94b 100644 --- a/web-common/src/features/alerts/data-tab/AlertDialogDataTab.svelte +++ b/web-common/src/features/alerts/data-tab/AlertDialogDataTab.svelte @@ -45,7 +45,7 @@ title="Filters" > ; timeRange: V1TimeRange; comparisonTimeRange: V1TimeRange | undefined; }; @@ -59,7 +62,13 @@ export function getAlertQueryArgsFromFormValues( dimensions: formValues.splitByDimension ? [{ name: formValues.splitByDimension }] : [], - where: sanitiseExpression(formValues.whereFilter, undefined), + where: sanitiseExpression( + mergeDimensionAndMeasureFilter( + formValues.whereFilter, + formValues.dimensionThresholdFilters, + ), + undefined, + ), having: sanitiseExpression(undefined, { cond: { op: formValues.criteriaOperation, diff --git a/web-common/src/features/dashboards/filters/measure-filters/measure-filter-utils.ts b/web-common/src/features/dashboards/filters/measure-filters/measure-filter-utils.ts index 9b12270d812..aa0e97a7cb4 100644 --- a/web-common/src/features/dashboards/filters/measure-filters/measure-filter-utils.ts +++ b/web-common/src/features/dashboards/filters/measure-filters/measure-filter-utils.ts @@ -1,4 +1,8 @@ -import { mapMeasureFilterToExpr } from "@rilldata/web-common/features/dashboards/filters/measure-filters/measure-filter-entry"; +import { + mapExprToMeasureFilter, + mapMeasureFilterToExpr, + MeasureFilterEntry, +} from "@rilldata/web-common/features/dashboards/filters/measure-filters/measure-filter-entry"; import { createAndExpression, filterExpressions, @@ -9,21 +13,57 @@ import type { } from "@rilldata/web-common/features/dashboards/stores/metrics-explorer-entity"; import { V1Expression, V1Operation } from "@rilldata/web-common/runtime-client"; -export const mergeMeasureFilters = ( +export function mergeMeasureFilters( dashboard: MetricsExplorerEntity, whereFilter = dashboard.whereFilter, -) => { +) { + return mergeDimensionAndMeasureFilter( + whereFilter, + dashboard.dimensionThresholdFilters, + ); +} + +export function mergeDimensionAndMeasureFilter( + whereFilter: V1Expression, + dimensionThresholdFilters: DimensionThresholdFilter[], +) { const where = filterExpressions(whereFilter, () => true) ?? createAndExpression([]); where.cond?.exprs?.push( - ...dashboard.dimensionThresholdFilters.map(convertDimensionThresholdFilter), + ...dimensionThresholdFilters.map(convertDimensionThresholdFilter), ); return where; -}; +} + +/** + * Splits where filter into dimension and measure filters. + * Measure filters will be sub-queries + */ +export function splitWhereFilter(whereFilter: V1Expression | undefined) { + const dimensionFilters = createAndExpression([]); + const dimensionThresholdFilters: DimensionThresholdFilter[] = []; + whereFilter?.cond?.exprs?.filter((e) => { + const subqueryExpr = e.cond?.exprs?.[1]; + if (subqueryExpr?.subquery) { + dimensionThresholdFilters.push({ + name: subqueryExpr.subquery.dimension ?? "", + filters: + (subqueryExpr.subquery.having?.cond?.exprs + ?.map(mapExprToMeasureFilter) + .filter(Boolean) as MeasureFilterEntry[]) ?? [], + }); + return; + } + + dimensionFilters.cond?.exprs?.push(e); + }); + + return { dimensionFilters, dimensionThresholdFilters }; +} -const convertDimensionThresholdFilter = ( +function convertDimensionThresholdFilter( dtf: DimensionThresholdFilter, -): V1Expression => { +): V1Expression { return { cond: { op: V1Operation.OPERATION_IN, @@ -44,4 +84,4 @@ const convertDimensionThresholdFilter = ( ], }, }; -}; +} From 55ca91206318bdc2b6fa5d8442e0035c83421df4 Mon Sep 17 00:00:00 2001 From: Eric P Green Date: Wed, 26 Jun 2024 11:22:04 -0400 Subject: [PATCH 09/19] Cloud UI: Respect deployment status (#5126) * Fix project status indicators * Fix lint * Fix Typescript * Add a detail section to the Error page * Cleanup component * Add `detail` to the fallback error pages * Breadcrumbs: only query for alerts & reports when necessary * Retain breadcrumbs when there's an error * Move deployment checks to `[org]/[project]/+layout.svelte` * Show error icon when there are resource or parse errors --- web-admin/src/features/alerts/selectors.ts | 16 ++++-- .../navigation/TopNavigationBar.svelte | 9 +-- .../features/projects/ProjectBuilding.svelte | 25 +------- .../features/scheduled-reports/selectors.ts | 16 ++++-- .../[organization]/[project]/+layout.svelte | 57 +++++++++++-------- .../[organization]/[project]/+page.svelte | 21 ++----- .../[project]/[dashboard]/+page.svelte | 12 +--- 7 files changed, 73 insertions(+), 83 deletions(-) diff --git a/web-admin/src/features/alerts/selectors.ts b/web-admin/src/features/alerts/selectors.ts index 1edff9c39d0..92d0b460161 100644 --- a/web-admin/src/features/alerts/selectors.ts +++ b/web-admin/src/features/alerts/selectors.ts @@ -5,10 +5,18 @@ import { createRuntimeServiceListResources, } from "@rilldata/web-common/runtime-client"; -export function useAlerts(instanceId: string) { - return createRuntimeServiceListResources(instanceId, { - kind: ResourceKind.Alert, - }); +export function useAlerts(instanceId: string, enabled = true) { + return createRuntimeServiceListResources( + instanceId, + { + kind: ResourceKind.Alert, + }, + { + query: { + enabled, + }, + }, + ); } export function useAlert(instanceId: string, name: string) { diff --git a/web-admin/src/features/navigation/TopNavigationBar.svelte b/web-admin/src/features/navigation/TopNavigationBar.svelte index ac14cea3040..ead1e2f53f2 100644 --- a/web-admin/src/features/navigation/TopNavigationBar.svelte +++ b/web-admin/src/features/navigation/TopNavigationBar.svelte @@ -24,7 +24,6 @@ import SignIn from "../authentication/SignIn.svelte"; import LastRefreshedDate from "../dashboards/listing/LastRefreshedDate.svelte"; import ShareDashboardButton from "../dashboards/share/ShareDashboardButton.svelte"; - import { isErrorStoreEmpty } from "../errors/error-store"; import ShareProjectButton from "../projects/ShareProjectButton.svelte"; import { useReports } from "../scheduled-reports/selectors"; import { isMetricsExplorerPage, isProjectPage } from "./nav-utils"; @@ -37,6 +36,8 @@ $: ({ organization, project, dashboard, alert, report } = $page.params); $: onProjectPage = isProjectPage($page); + $: onAlertPage = !!alert; + $: onReportPage = !!report; $: onMetricsExplorerPage = isMetricsExplorerPage($page); $: organizationQuery = listOrgs( @@ -56,8 +57,8 @@ $: visualizationsQuery = useValidVisualizations(instanceId); - $: alertsQuery = useAlerts(instanceId); - $: reportsQuery = useReports(instanceId); + $: alertsQuery = useAlerts(instanceId, onAlertPage); + $: reportsQuery = useReports(instanceId, onReportPage); $: organizations = $organizationQuery.data?.organizations ?? @@ -129,7 +130,7 @@ Home - {#if $isErrorStoreEmpty && organization} + {#if organization} diff --git a/web-admin/src/features/projects/ProjectBuilding.svelte b/web-admin/src/features/projects/ProjectBuilding.svelte index ef8bf993f87..9b23f14102a 100644 --- a/web-admin/src/features/projects/ProjectBuilding.svelte +++ b/web-admin/src/features/projects/ProjectBuilding.svelte @@ -1,15 +1,10 @@ @@ -17,23 +12,9 @@
- Hang tight! We're building your dashboard... - - - View project status - - - - View project - - - + + Hang tight! We're deploying your project... +
diff --git a/web-admin/src/features/scheduled-reports/selectors.ts b/web-admin/src/features/scheduled-reports/selectors.ts index c1d8a304ddb..4294d659c7d 100644 --- a/web-admin/src/features/scheduled-reports/selectors.ts +++ b/web-admin/src/features/scheduled-reports/selectors.ts @@ -6,10 +6,18 @@ import { createRuntimeServiceListResources, } from "@rilldata/web-common/runtime-client"; -export function useReports(instanceId: string) { - return createRuntimeServiceListResources(instanceId, { - kind: ResourceKind.Report, - }); +export function useReports(instanceId: string, enabled = true) { + return createRuntimeServiceListResources( + instanceId, + { + kind: ResourceKind.Report, + }, + { + query: { + enabled, + }, + }, + ); } export function useReport(instanceId: string, name: string) { diff --git a/web-admin/src/routes/[organization]/[project]/+layout.svelte b/web-admin/src/routes/[organization]/[project]/+layout.svelte index 493da4be98e..f4660f14a0d 100644 --- a/web-admin/src/routes/[organization]/[project]/+layout.svelte +++ b/web-admin/src/routes/[organization]/[project]/+layout.svelte @@ -1,30 +1,30 @@ +{#if onProjectPage && deployment?.status === V1DeploymentStatus.DEPLOYMENT_STATUS_OK} + +{/if} + {#if $viewAsUserStore} - -{:else if isRuntimeHibernating} - +In these cases, the "View as" actions manually set the runtime. --> -{:else} +{:else if !deployment} + + +{:else if deployment?.status === V1DeploymentStatus.DEPLOYMENT_STATUS_PENDING} + +{:else if deployment?.status === V1DeploymentStatus.DEPLOYMENT_STATUS_ERROR} + +{:else if deployment?.status === V1DeploymentStatus.DEPLOYMENT_STATUS_OK && runtimeQueryIsSuccess} - - {#if onProjectPage} - - {/if} diff --git a/web-admin/src/routes/[organization]/[project]/+page.svelte b/web-admin/src/routes/[organization]/[project]/+page.svelte index 15c3818f214..7b39b548919 100644 --- a/web-admin/src/routes/[organization]/[project]/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/+page.svelte @@ -1,28 +1,17 @@ {project} overview - Rill -{#if isProjectHibernating} - -{:else if isProjectDeployed} - -
- -
-
-{/if} + +
+ +
+
diff --git a/web-admin/src/routes/[organization]/[project]/[dashboard]/+page.svelte b/web-admin/src/routes/[organization]/[project]/[dashboard]/+page.svelte index eb206de1890..f418a6d9e33 100644 --- a/web-admin/src/routes/[organization]/[project]/[dashboard]/+page.svelte +++ b/web-admin/src/routes/[organization]/[project]/[dashboard]/+page.svelte @@ -20,7 +20,6 @@ import { runtime } from "@rilldata/web-common/runtime-client/runtime-store"; import { useQueryClient } from "@tanstack/svelte-query"; import { errorStore } from "../../../../features/errors/error-store"; - import ProjectBuilding from "../../../../features/projects/ProjectBuilding.svelte"; const queryClient = useQueryClient(); @@ -34,11 +33,6 @@ $: projectDeployment = useProjectDeployment(orgName, projectName); // polls $: ({ data: deployment } = $projectDeployment); - $: isProjectPending = - deployment?.status === V1DeploymentStatus.DEPLOYMENT_STATUS_PENDING; - $: isProjectErrored = - deployment?.status === V1DeploymentStatus.DEPLOYMENT_STATUS_ERROR; - $: isProjectBuilt = isProjectOK || isProjectErrored; let isProjectOK: boolean; @@ -78,7 +72,7 @@ $: isDashboardErrored = !$dashboard.data?.metricsView?.state?.validSpec; // If no dashboard is found, show a 404 page - $: if (isProjectBuilt && isDashboardNotFound) { + $: if (isDashboardNotFound) { errorStore.set({ statusCode: 404, header: "Dashboard not found", @@ -94,9 +88,7 @@ -{#if isProjectPending && isDashboardNotFound} - -{:else if $dashboard.isSuccess} +{#if $dashboard.isSuccess} {#if isDashboardErrored} {:else} From e39e38df1fef1f08bdc584eb633f87a3078b3c7b Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Wed, 26 Jun 2024 20:54:30 +0530 Subject: [PATCH 10/19] fix: javascript error telemetry not firing anymore (#5150) * Fix javascript error telemetry * Fix cloud as well --- web-admin/src/routes/+layout.svelte | 12 ++++++++++-- .../entity-management/ResourceWatcher.svelte | 4 ---- web-local/src/routes/+layout.svelte | 11 +++++++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/web-admin/src/routes/+layout.svelte b/web-admin/src/routes/+layout.svelte index 1d53659abcc..0d5e16dcdb9 100644 --- a/web-admin/src/routes/+layout.svelte +++ b/web-admin/src/routes/+layout.svelte @@ -26,10 +26,18 @@ clearViewedAsUserAfterNavigate(queryClient); - initCloudMetrics(); + let removeJavascriptListeners: () => void; + initCloudMetrics() + .then(() => { + removeJavascriptListeners = + errorEventHandler.addJavascriptErrorListeners(); + }) + .catch(console.error); initPylonWidget(); - onMount(() => errorEventHandler?.addJavascriptErrorListeners()); + onMount(() => { + return () => removeJavascriptListeners(); + }); $: isEmbed = $page.url.pathname === "/-/embed"; diff --git a/web-common/src/features/entity-management/ResourceWatcher.svelte b/web-common/src/features/entity-management/ResourceWatcher.svelte index 7bf1272a004..04ed2c66d14 100644 --- a/web-common/src/features/entity-management/ResourceWatcher.svelte +++ b/web-common/src/features/entity-management/ResourceWatcher.svelte @@ -2,7 +2,6 @@ import { fileArtifacts } from "@rilldata/web-common/features/entity-management/file-artifacts"; import { WatchFilesClient } from "@rilldata/web-common/features/entity-management/WatchFilesClient"; import { WatchResourcesClient } from "@rilldata/web-common/features/entity-management/WatchResourcesClient"; - import { errorEventHandler } from "@rilldata/web-common/metrics/initMetrics"; import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryClient"; import { onMount } from "svelte"; import ErrorPage from "@rilldata/web-common/components/ErrorPage.svelte"; @@ -24,14 +23,11 @@ $: failed = $fileAttempts >= 2 || $resourceAttempts >= 2; onMount(() => { - const stopJavascriptErrorListeners = - errorEventHandler?.addJavascriptErrorListeners(); void fileArtifacts.init(queryClient, instanceId); return () => { fileWatcher.close(); resourceWatcher.close(); - stopJavascriptErrorListeners?.(); }; }); diff --git a/web-local/src/routes/+layout.svelte b/web-local/src/routes/+layout.svelte index 4c773c61bbf..9fed1d10a26 100644 --- a/web-local/src/routes/+layout.svelte +++ b/web-local/src/routes/+layout.svelte @@ -28,9 +28,12 @@ query: Query, ) => errorEventHandler?.requestErrorEventHandler(error, query); + let removeJavascriptListeners: () => void; + onMount(async () => { const config = await runtimeServiceGetConfig(); await initMetrics(config); + removeJavascriptListeners = errorEventHandler.addJavascriptErrorListeners(); featureFlags.set(false, "adminServer"); featureFlags.set(config.readonly, "readOnly"); @@ -41,6 +44,14 @@ }); }); + /** + * Async mount doesnt support an unsubscribe method. + * So we need this to make sure javascript listeners for error handler is removed. + */ + onMount(() => { + return () => removeJavascriptListeners?.(); + }); + $: ({ host, instanceId } = $runtime); From 5144913b2ca3e14b69c7754a5b5e9436e3d22d8e Mon Sep 17 00:00:00 2001 From: Eric P Green Date: Wed, 26 Jun 2024 11:31:32 -0400 Subject: [PATCH 11/19] Fix missing scrollbar (#5147) --- web-common/src/components/dialog/Dialog.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-common/src/components/dialog/Dialog.svelte b/web-common/src/components/dialog/Dialog.svelte index 6f7faf40a3d..eadfc6b0168 100644 --- a/web-common/src/components/dialog/Dialog.svelte +++ b/web-common/src/components/dialog/Dialog.svelte @@ -24,7 +24,7 @@ class="fixed inset-0 bg-gray-400 transition-opacity opacity-40" />
Date: Wed, 26 Jun 2024 11:32:56 -0400 Subject: [PATCH 12/19] Refactor: Account for multiple connectors (#4678) * Refactor source/model workspace to account for non-default connectors * Fix lint / cleanup file * Fix e2e test * Add `connector` param in a few more places * Supply `database` and `databaseSchema` to API calls * Provide missing props * Remove needless `enabled: !!connector` * Cleanups for consistency * Fix e2e test * Show all connectors, not just the default OLAP connector * Refactor `createModel` to specify non-default connector * Better formatting * Increment connector name in Add Connector form * Better spacing * For now, only show OLAP connectors --- .../column-profile/ColumnProfile.svelte | 22 ++++- .../column-types/NestedProfile.svelte | 14 +++- .../column-types/NumericProfile.svelte | 28 ++++++- .../column-types/TimestampProfile.svelte | 11 ++- .../column-types/VarcharProfile.svelte | 14 +++- .../src/components/column-profile/queries.ts | 83 ++++++++++++++++--- .../features/connectors/ConnectorEntry.svelte | 5 +- .../connectors/ConnectorExplorer.svelte | 13 ++- .../connectors/olap/DatabaseExplorer.svelte | 3 +- .../connectors/olap/TableMenuItems.svelte | 3 + .../features/connectors/olap/createModel.ts | 64 ++++++++++++-- .../inspector/MetricsInspector.svelte | 34 +++----- web-common/src/features/models/selectors.ts | 3 + .../inspector/WorkspaceInspector.svelte | 77 +++++++++++++---- .../sources/modal/submitRemoteSourceForm.ts | 18 ++-- web-common/src/features/sources/selectors.ts | 18 +++- .../features/workspaces/ModelWorkspace.svelte | 10 ++- .../workspaces/SourceWorkspace.svelte | 9 +- web-local/tests/utils/commonHelpers.ts | 2 +- 19 files changed, 347 insertions(+), 84 deletions(-) diff --git a/web-common/src/components/column-profile/ColumnProfile.svelte b/web-common/src/components/column-profile/ColumnProfile.svelte index b142c058d17..85d2a5d3e1e 100644 --- a/web-common/src/components/column-profile/ColumnProfile.svelte +++ b/web-common/src/components/column-profile/ColumnProfile.svelte @@ -10,8 +10,11 @@ import { getSummaries } from "./queries"; import { defaultSort, sortByName, sortByNullity } from "./utils"; - export let containerWidth = 0; + export let connector: string; + export let database: string; + export let databaseSchema: string; export let objectName: string; + export let containerWidth = 0; export let indentLevel = 0; let mode = "summaries"; @@ -29,7 +32,11 @@ $: profileColumns = createQueryServiceTableColumns( $runtime?.instanceId, objectName, - {}, + { + connector, + database, + databaseSchema, + }, { query: { keepPreviousData: true } }, ); @@ -38,13 +45,19 @@ $runtime?.instanceId, objectName, { + connector, + database, + databaseSchema, limit: 1, }, ); $: nestedColumnProfileQuery = getSummaries( - objectName, $runtime?.instanceId, + connector, + database, + databaseSchema, + objectName, $profileColumns, ); @@ -102,6 +115,9 @@ , ): Readable> { return derived( @@ -40,7 +43,12 @@ export function getSummaries( createQueryServiceColumnNullCount( instanceId, objectName, - { columnName: column.name }, + { + connector, + database, + databaseSchema, + columnName: column.name, + }, { query: { keepPreviousData: true, @@ -51,7 +59,12 @@ export function getSummaries( createQueryServiceColumnCardinality( instanceId, objectName, - { columnName: column.name }, + { + connector, + database, + databaseSchema, + columnName: column.name, + }, { query: { keepPreviousData: true, @@ -82,6 +95,9 @@ export function getSummaries( export function getNullPercentage( instanceId: string, + connector: string, + database: string, + databaseSchema: string, objectName: string, columnName: string, enabled = true, @@ -90,6 +106,9 @@ export function getNullPercentage( instanceId, objectName, { + connector, + database, + databaseSchema, columnName, }, { @@ -101,7 +120,11 @@ export function getNullPercentage( const totalRowsQuery = createQueryServiceTableCardinality( instanceId, objectName, - {}, + { + connector, + database, + databaseSchema, + }, { query: { enabled, @@ -123,6 +146,9 @@ export function getNullPercentage( export function getCountDistinct( instanceId: string, + connector: string, + database: string, + databaseSchema: string, objectName: string, columnName: string, enabled = true, @@ -130,18 +156,22 @@ export function getCountDistinct( const cardinalityQuery = createQueryServiceColumnCardinality( instanceId, objectName, - { columnName }, + { connector, database, databaseSchema, columnName }, { - query: { enabled }, + query: { + enabled, + }, }, ); const totalRowsQuery = createQueryServiceTableCardinality( instanceId, objectName, - {}, + { connector, database, databaseSchema }, { - query: { enabled }, + query: { + enabled, + }, }, ); @@ -163,6 +193,9 @@ export function getCountDistinct( export function getTopK( instanceId: string, + connector: string, + database: string, + databaseSchema: string, objectName: string, columnName: string, enabled = true, @@ -172,6 +205,9 @@ export function getTopK( instanceId, objectName, { + connector, + database, + databaseSchema, columnName: columnName, agg: "count(*)", k: 75, @@ -190,6 +226,9 @@ export function getTopK( export function getTimeSeriesAndSpark( instanceId: string, + connector: string, + database: string, + databaseSchema: string, objectName: string, columnName: string, enabled = true, @@ -200,6 +239,9 @@ export function getTimeSeriesAndSpark( objectName, // FIXME: convert pixel back to number once the API { + connector, + database, + databaseSchema, timestampColumnName: columnName, measures: [ { @@ -216,9 +258,17 @@ export function getTimeSeriesAndSpark( const estimatedInterval = createQueryServiceColumnRollupInterval( instanceId, objectName, - { columnName, priority: getPriorityForColumn("rollup-interval", active) }, { - query: { enabled }, + connector, + database, + databaseSchema, + columnName, + priority: getPriorityForColumn("rollup-interval", active), + }, + { + query: { + enabled, + }, }, ); @@ -226,11 +276,16 @@ export function getTimeSeriesAndSpark( instanceId, objectName, { + connector, + database, + databaseSchema, columnName, priority: getPriorityForColumn("smallest-time-grain", active), }, { - query: { enabled }, + query: { + enabled, + }, }, ); @@ -266,6 +321,9 @@ export function getTimeSeriesAndSpark( export function getNumericHistogram( instanceId: string, + connector: string, + database: string, + databaseSchema: string, objectName: string, columnName: string, histogramMethod: QueryServiceColumnNumericHistogramHistogramMethod, @@ -276,6 +334,9 @@ export function getNumericHistogram( instanceId, objectName, { + connector, + database, + databaseSchema, columnName, histogramMethod, priority: getPriorityForColumn("numeric-histogram", active), diff --git a/web-common/src/features/connectors/ConnectorEntry.svelte b/web-common/src/features/connectors/ConnectorEntry.svelte index 2f7a2ea819c..696b6acd3dc 100644 --- a/web-common/src/features/connectors/ConnectorEntry.svelte +++ b/web-common/src/features/connectors/ConnectorEntry.svelte @@ -19,10 +19,11 @@ }); $: olapConnector = $instance.data?.instance?.olapConnector; $: isOlapConnector = olapConnector === connector.name; + $: implementsOlap = connector.driver?.implementsOlap; - -{#if isOlapConnector} + +{#if implementsOlap} {#if connector.name}
  • - {#if tableName && resource} + {#if connector && tableName && resource} - {#if tableName && resource} + {#if connector && tableName && resource} page.waitForResponse( new RegExp( - `/queries/null-count/tables/${name}\\?columnName=${column}`, + `/queries/null-count/tables/${name}\\?connector=duckdb&database=&databaseSchema=&columnName=${column}`, ), ), ), From a81b038bda7670295a57f2727b8dccaafe789780 Mon Sep 17 00:00:00 2001 From: Alexander Thor Date: Wed, 26 Jun 2024 17:49:22 +0200 Subject: [PATCH 13/19] fix: math.random for eventbus (#5153) * fix: math.random for eventbus * chore: format * fix: i dont wanna talk about it * fix: i've got 99 problems and formats are all of them --------- Co-authored-by: Alexander Thor --- web-common/src/lib/event-bus/event-bus.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/web-common/src/lib/event-bus/event-bus.ts b/web-common/src/lib/event-bus/event-bus.ts index ec59b08b085..953d22d88d2 100644 --- a/web-common/src/lib/event-bus/event-bus.ts +++ b/web-common/src/lib/event-bus/event-bus.ts @@ -5,7 +5,7 @@ class EventBus { private listeners: EventMap = new Map(); on(event: Event, callback: Listener) { - const key = crypto.randomUUID(); + const key = generateUUID(); const eventMap = this.listeners.get(event); if (!eventMap) { @@ -31,6 +31,23 @@ class EventBus { } } +function generateUUID(): string { + // Generate random numbers for the UUID + const randomNumbers: number[] = new Array(16) + .fill(0) + .map(() => Math.floor(Math.random() * 256)); + + // Set the version and variant bits + randomNumbers[6] = (randomNumbers[6] & 0x0f) | 0x40; // Version 4 + randomNumbers[8] = (randomNumbers[8] & 0x3f) | 0x80; // Variant 10 + + // Convert to hexadecimal and format as a UUID + const hexDigits: string = randomNumbers + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + return `${hexDigits.slice(0, 8)}-${hexDigits.slice(8, 12)}-${hexDigits.slice(12, 16)}-${hexDigits.slice(16, 20)}-${hexDigits.slice(20, 32)}`; +} + export const eventBus = new EventBus(); export interface Events { From 208ede58c94abeb68946778aedd00c6b85b417df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Egelund-M=C3=BCller?= Date: Thu, 27 Jun 2024 10:03:42 +0200 Subject: [PATCH 14/19] Runtime: Retrofit metricsview package to MetricsView RPCs (#5148) * Retrofit MVC * Migrate totals, toplist, timeseries * Fix tests * Fix test * Review --- proto/gen/rill/admin/v1/admin.swagger.yaml | 2 +- proto/gen/rill/runtime/v1/queries.pb.go | 2584 ++++++++--------- .../rill/runtime/v1/queries.pb.validate.go | 102 - .../gen/rill/runtime/v1/runtime.swagger.yaml | 45 +- proto/rill/runtime/v1/queries.proto | 5 - .../duckdb/model_executor_self_file.go | 12 +- runtime/drivers/file/model_executor.go | 16 +- .../drivers/file/model_executor_olap_self.go | 10 +- runtime/drivers/models.go | 22 + runtime/metricsview/astexpr.go | 65 +- runtime/metricsview/executor.go | 2 +- runtime/metricsview/executor_export.go | 4 +- runtime/metricsview/executor_pivot.go | 2 +- .../column_timeseries_benchmark_test.go | 18 +- runtime/queries/metricsview.go | 55 - runtime/queries/metricsview_aggregation.go | 35 +- .../queries/metricsview_api_benchmark_test.go | 29 +- .../queries/metricsview_comparison_toplist.go | 1319 ++------- .../metricsview_comparison_toplist_test.go | 15 +- runtime/queries/metricsview_search.go | 2 +- runtime/queries/metricsview_timeseries.go | 392 +-- .../queries/metricsview_timeseries_test.go | 175 +- runtime/queries/metricsview_toplist.go | 285 +- runtime/queries/metricsview_toplist_test.go | 17 +- runtime/queries/metricsview_totals.go | 120 +- runtime/queries/protoutil.go | 12 + runtime/queries/sqlutil.go | 24 - runtime/server/downloads.go | 84 +- runtime/server/observability_utils.go | 12 - runtime/server/queries_metrics.go | 169 +- ...queries_metrics_comparison_toplist_test.go | 33 +- runtime/server/queries_metrics_test.go | 16 - .../server/queries_metrics_timeseries_test.go | 20 +- .../server/queries_metrics_toplist_test.go | 53 +- .../timeseries_dst_backwards_fdow6.yaml | 28 + web-admin/src/client/gen/index.schemas.ts | 2 +- .../proto/gen/rill/runtime/v1/queries_pb.ts | 22 - .../src/runtime-client/gen/index.schemas.ts | 109 +- 38 files changed, 2027 insertions(+), 3890 deletions(-) delete mode 100644 runtime/server/queries_metrics_test.go create mode 100644 runtime/testruntime/testdata/timeseries/dashboards/timeseries_dst_backwards_fdow6.yaml diff --git a/proto/gen/rill/admin/v1/admin.swagger.yaml b/proto/gen/rill/admin/v1/admin.swagger.yaml index 5e8477175ea..76c474b36e8 100644 --- a/proto/gen/rill/admin/v1/admin.swagger.yaml +++ b/proto/gen/rill/admin/v1/admin.swagger.yaml @@ -2432,7 +2432,7 @@ definitions: `NullValue` is a singleton enumeration to represent the null value for the `Value` type union. - The JSON representation for `NullValue` is JSON `null`. + The JSON representation for `NullValue` is JSON `null`. - NULL_VALUE: Null value. rpcStatus: diff --git a/proto/gen/rill/runtime/v1/queries.pb.go b/proto/gen/rill/runtime/v1/queries.pb.go index ceb5ec3d62e..352b1ba4e5e 100644 --- a/proto/gen/rill/runtime/v1/queries.pb.go +++ b/proto/gen/rill/runtime/v1/queries.pb.go @@ -2046,7 +2046,6 @@ type MetricsViewToplistRequest struct { MetricsViewName string `protobuf:"bytes,2,opt,name=metrics_view_name,json=metricsViewName,proto3" json:"metrics_view_name,omitempty"` DimensionName string `protobuf:"bytes,3,opt,name=dimension_name,json=dimensionName,proto3" json:"dimension_name,omitempty"` MeasureNames []string `protobuf:"bytes,4,rep,name=measure_names,json=measureNames,proto3" json:"measure_names,omitempty"` - InlineMeasures []*InlineMeasure `protobuf:"bytes,12,rep,name=inline_measures,json=inlineMeasures,proto3" json:"inline_measures,omitempty"` TimeStart *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=time_start,json=timeStart,proto3" json:"time_start,omitempty"` TimeEnd *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=time_end,json=timeEnd,proto3" json:"time_end,omitempty"` Limit int64 `protobuf:"varint,7,opt,name=limit,proto3" json:"limit,omitempty"` @@ -2118,13 +2117,6 @@ func (x *MetricsViewToplistRequest) GetMeasureNames() []string { return nil } -func (x *MetricsViewToplistRequest) GetInlineMeasures() []*InlineMeasure { - if x != nil { - return x.InlineMeasures - } - return nil -} - func (x *MetricsViewToplistRequest) GetTimeStart() *timestamppb.Timestamp { if x != nil { return x.TimeStart @@ -2852,11 +2844,9 @@ type MetricsViewTimeSeriesRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - InstanceId string `protobuf:"bytes,1,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"` - MetricsViewName string `protobuf:"bytes,2,opt,name=metrics_view_name,json=metricsViewName,proto3" json:"metrics_view_name,omitempty"` - // Required either measure_names or inline_measures - MeasureNames []string `protobuf:"bytes,3,rep,name=measure_names,json=measureNames,proto3" json:"measure_names,omitempty"` - InlineMeasures []*InlineMeasure `protobuf:"bytes,9,rep,name=inline_measures,json=inlineMeasures,proto3" json:"inline_measures,omitempty"` + InstanceId string `protobuf:"bytes,1,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"` + MetricsViewName string `protobuf:"bytes,2,opt,name=metrics_view_name,json=metricsViewName,proto3" json:"metrics_view_name,omitempty"` + MeasureNames []string `protobuf:"bytes,3,rep,name=measure_names,json=measureNames,proto3" json:"measure_names,omitempty"` // Optional. Defaults to min TimeStart *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=time_start,json=timeStart,proto3" json:"time_start,omitempty"` // Optional. Defaults to max @@ -2926,13 +2916,6 @@ func (x *MetricsViewTimeSeriesRequest) GetMeasureNames() []string { return nil } -func (x *MetricsViewTimeSeriesRequest) GetInlineMeasures() []*InlineMeasure { - if x != nil { - return x.InlineMeasures - } - return nil -} - func (x *MetricsViewTimeSeriesRequest) GetTimeStart() *timestamppb.Timestamp { if x != nil { return x.TimeStart @@ -3051,11 +3034,9 @@ type MetricsViewTotalsRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - InstanceId string `protobuf:"bytes,1,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"` - MetricsViewName string `protobuf:"bytes,2,opt,name=metrics_view_name,json=metricsViewName,proto3" json:"metrics_view_name,omitempty"` - // Required either measure_names or inline_measures - MeasureNames []string `protobuf:"bytes,3,rep,name=measure_names,json=measureNames,proto3" json:"measure_names,omitempty"` - InlineMeasures []*InlineMeasure `protobuf:"bytes,9,rep,name=inline_measures,json=inlineMeasures,proto3" json:"inline_measures,omitempty"` + InstanceId string `protobuf:"bytes,1,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"` + MetricsViewName string `protobuf:"bytes,2,opt,name=metrics_view_name,json=metricsViewName,proto3" json:"metrics_view_name,omitempty"` + MeasureNames []string `protobuf:"bytes,3,rep,name=measure_names,json=measureNames,proto3" json:"measure_names,omitempty"` // Optional. Defaults to min TimeStart *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=time_start,json=timeStart,proto3" json:"time_start,omitempty"` // Optional. Defaults to max @@ -3119,13 +3100,6 @@ func (x *MetricsViewTotalsRequest) GetMeasureNames() []string { return nil } -func (x *MetricsViewTotalsRequest) GetInlineMeasures() []*InlineMeasure { - if x != nil { - return x.InlineMeasures - } - return nil -} - func (x *MetricsViewTotalsRequest) GetTimeStart() *timestamppb.Timestamp { if x != nil { return x.TimeStart @@ -7686,7 +7660,7 @@ var file_rill_runtime_v1_queries_proto_rawDesc = []byte{ 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x6f, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x22, 0xb7, 0x05, 0x0a, 0x19, 0x4d, 0x65, 0x74, 0x72, 0x69, + 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x22, 0xee, 0x04, 0x0a, 0x19, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x6f, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, @@ -7699,630 +7673,488 @@ var file_rill_runtime_v1_queries_proto_rawDesc = []byte{ 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, - 0x47, 0x0a, 0x0f, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, - 0x65, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, - 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x6c, 0x69, 0x6e, - 0x65, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x52, 0x0e, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, - 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, - 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x39, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x09, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x74, 0x69, + 0x6d, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, - 0x61, 0x72, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x45, 0x6e, 0x64, 0x12, 0x1d, 0x0a, 0x05, 0x6c, 0x69, - 0x6d, 0x69, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x22, 0x02, - 0x28, 0x00, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1f, 0x0a, 0x06, 0x6f, 0x66, 0x66, - 0x73, 0x65, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x22, 0x02, - 0x28, 0x00, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x34, 0x0a, 0x04, 0x73, 0x6f, - 0x72, 0x74, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, - 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, - 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, 0x6f, 0x72, 0x74, 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, - 0x12, 0x31, 0x0a, 0x05, 0x77, 0x68, 0x65, 0x72, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x77, 0x68, - 0x65, 0x72, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x18, 0x0d, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, - 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x52, 0x06, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x0e, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, - 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, - 0x65, 0x77, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x22, 0x81, 0x01, 0x0a, 0x1a, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, - 0x54, 0x6f, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x36, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, - 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6c, 0x75, 0x6d, - 0x6e, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x2b, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x04, - 0x64, 0x61, 0x74, 0x61, 0x22, 0x84, 0x07, 0x0a, 0x1c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, - 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, - 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, - 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0f, 0x6d, 0x65, 0x74, 0x72, - 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x64, - 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, - 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x41, 0x67, 0x67, 0x72, - 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, - 0x52, 0x09, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x54, 0x0a, 0x08, 0x6d, - 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, - 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x41, 0x67, 0x67, 0x72, 0x65, - 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x42, 0x08, 0xfa, - 0x42, 0x05, 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, 0x08, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, - 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x5f, - 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, - 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, - 0x65, 0x73, 0x12, 0x48, 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x2a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, - 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x53, 0x6f, 0x72, 0x74, 0x42, 0x08, 0xfa, 0x42, - 0x05, 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x12, 0x39, 0x0a, 0x0a, - 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x09, 0x74, 0x69, - 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x4e, 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x70, 0x61, - 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, - 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, - 0x67, 0x65, 0x52, 0x13, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x54, 0x69, - 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x31, 0x0a, 0x05, 0x77, 0x68, 0x65, 0x72, 0x65, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x45, 0x6e, + 0x64, 0x12, 0x1d, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, + 0x42, 0x07, 0xfa, 0x42, 0x04, 0x22, 0x02, 0x28, 0x00, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x12, 0x1f, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, + 0x42, 0x07, 0xfa, 0x42, 0x04, 0x22, 0x02, 0x28, 0x00, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, + 0x74, 0x12, 0x34, 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x20, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, 0x6f, 0x72, + 0x74, 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x12, 0x31, 0x0a, 0x05, 0x77, 0x68, 0x65, 0x72, 0x65, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x77, 0x68, 0x65, 0x72, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x68, 0x61, - 0x76, 0x69, 0x6e, 0x67, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x69, 0x6c, + 0x76, 0x69, 0x6e, 0x67, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x12, - 0x4c, 0x0a, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x32, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, - 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x41, - 0x6c, 0x69, 0x61, 0x73, 0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1d, 0x0a, - 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xfa, 0x42, - 0x04, 0x22, 0x02, 0x28, 0x00, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1f, 0x0a, 0x06, - 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xfa, 0x42, - 0x04, 0x22, 0x02, 0x28, 0x00, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1a, 0x0a, - 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x78, 0x61, - 0x63, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x65, 0x78, 0x61, 0x63, 0x74, 0x12, - 0x3a, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x46, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x5e, 0x0a, 0x1d, 0x4d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, - 0x69, 0x73, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x04, - 0x72, 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x72, 0x69, 0x6c, - 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, - 0x6f, 0x6e, 0x52, 0x6f, 0x77, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x22, 0x8c, 0x02, 0x0a, 0x09, - 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, - 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x73, 0x6f, - 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x69, 0x73, 0x6f, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, - 0x69, 0x73, 0x6f, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x69, 0x73, 0x6f, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x40, 0x0a, 0x0e, 0x72, - 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x67, 0x72, 0x61, 0x69, 0x6e, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, - 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x69, 0x6e, 0x52, - 0x0c, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x6f, 0x47, 0x72, 0x61, 0x69, 0x6e, 0x12, 0x1b, 0x0a, - 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5a, 0x6f, 0x6e, 0x65, 0x22, 0xe0, 0x01, 0x0a, 0x19, 0x4d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, - 0x69, 0x73, 0x6f, 0x6e, 0x53, 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x12, 0x42, 0x0a, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, + 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x06, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x69, + 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, + 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x81, 0x01, 0x0a, 0x1a, 0x4d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x6f, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, + 0x65, 0x77, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x2b, + 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, + 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x84, 0x07, 0x0a, 0x1c, + 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, + 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, + 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x33, 0x0a, + 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, + 0x01, 0x52, 0x0f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, + 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, + 0x69, 0x65, 0x77, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, + 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x54, 0x0a, 0x08, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, + 0x65, 0x77, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x61, + 0x73, 0x75, 0x72, 0x65, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, 0x08, + 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x70, + 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x5f, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x18, + 0x10, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, + 0x6e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x12, 0x48, 0x0a, 0x04, 0x73, 0x6f, 0x72, + 0x74, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x53, - 0x6f, 0x72, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x4e, 0x0a, - 0x09, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x31, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, - 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x54, - 0x79, 0x70, 0x65, 0x52, 0x08, 0x73, 0x6f, 0x72, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xaf, 0x01, - 0x0a, 0x18, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, - 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x52, 0x6f, 0x77, 0x12, 0x3f, 0x0a, 0x0f, 0x64, 0x69, - 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x64, 0x69, 0x6d, - 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x52, 0x0a, 0x0e, 0x6d, - 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, - 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, - 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x52, 0x0d, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, - 0xa3, 0x02, 0x0a, 0x1a, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, - 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x21, - 0x0a, 0x0c, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x35, 0x0a, 0x0a, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x09, 0x62, - 0x61, 0x73, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x41, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x70, - 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x63, 0x6f, 0x6d, 0x70, - 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x33, 0x0a, 0x09, 0x64, - 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x61, 0x62, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x41, 0x62, 0x73, - 0x12, 0x33, 0x0a, 0x09, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x72, 0x65, 0x6c, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x64, 0x65, 0x6c, - 0x74, 0x61, 0x52, 0x65, 0x6c, 0x22, 0x94, 0x01, 0x0a, 0x21, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x6f, 0x72, 0x74, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, 0x04, 0x73, + 0x6f, 0x72, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, + 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, + 0x6e, 0x67, 0x65, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x4e, + 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x13, 0x63, 0x6f, 0x6d, 0x70, 0x61, + 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x31, + 0x0a, 0x05, 0x77, 0x68, 0x65, 0x72, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x77, 0x68, 0x65, 0x72, + 0x65, 0x12, 0x33, 0x0a, 0x06, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x06, + 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x12, 0x4c, 0x0a, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, + 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, + 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x4d, - 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x45, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, 0x2e, + 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x07, 0x61, 0x6c, 0x69, + 0x61, 0x73, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x03, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x22, 0x02, 0x28, 0x00, 0x52, 0x05, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x12, 0x1f, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x03, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x22, 0x02, 0x28, 0x00, 0x52, 0x06, 0x6f, 0x66, + 0x66, 0x73, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x65, 0x78, 0x61, 0x63, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x05, 0x65, 0x78, 0x61, 0x63, 0x74, 0x12, 0x3a, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, + 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, + 0x56, 0x69, 0x65, 0x77, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x22, 0x5e, 0x0a, 0x1d, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, + 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x29, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, + 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x52, 0x6f, 0x77, 0x52, 0x04, 0x72, 0x6f, + 0x77, 0x73, 0x22, 0x8c, 0x02, 0x0a, 0x09, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, + 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, + 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x73, 0x6f, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x73, 0x6f, 0x44, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x6f, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x73, 0x6f, 0x4f, 0x66, 0x66, 0x73, + 0x65, 0x74, 0x12, 0x40, 0x0a, 0x0e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x67, + 0x72, 0x61, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x72, 0x69, 0x6c, + 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x47, 0x72, 0x61, 0x69, 0x6e, 0x52, 0x0c, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x6f, 0x47, + 0x72, 0x61, 0x69, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x7a, 0x6f, 0x6e, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5a, 0x6f, 0x6e, + 0x65, 0x22, 0xe0, 0x01, 0x0a, 0x19, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, + 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x53, 0x6f, 0x72, 0x74, 0x12, + 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, + 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x64, 0x65, 0x73, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, + 0x12, 0x42, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2e, + 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, + 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x53, 0x6f, 0x72, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, + 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x4d, + 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x73, 0x6f, 0x72, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x22, 0xaf, 0x01, 0x0a, 0x18, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, + 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x52, 0x6f, + 0x77, 0x12, 0x3f, 0x0a, 0x0f, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x52, 0x0e, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x12, 0x52, 0x0a, 0x0e, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x72, 0x69, 0x6c, + 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, + 0x6f, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0d, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0xa3, 0x02, 0x0a, 0x1a, 0x4d, 0x65, 0x74, 0x72, 0x69, + 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x65, 0x61, + 0x73, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x0a, 0x62, 0x61, 0x73, 0x65, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x52, 0x09, 0x62, 0x61, 0x73, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x41, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x52, 0x0f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x12, 0x33, 0x0a, 0x09, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x61, 0x62, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x64, + 0x65, 0x6c, 0x74, 0x61, 0x41, 0x62, 0x73, 0x12, 0x33, 0x0a, 0x09, 0x64, 0x65, 0x6c, 0x74, 0x61, + 0x5f, 0x72, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x52, 0x08, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x52, 0x65, 0x6c, 0x22, 0x94, 0x01, 0x0a, + 0x21, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, + 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x41, 0x6c, 0x69, + 0x61, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x45, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, + 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x4d, 0x65, 0x61, 0x73, + 0x75, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, + 0x69, 0x61, 0x73, 0x22, 0xc3, 0x04, 0x0a, 0x1c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, + 0x69, 0x65, 0x77, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, + 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0f, 0x6d, 0x65, 0x74, 0x72, 0x69, + 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x0d, 0x6d, 0x65, + 0x61, 0x73, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, 0x0c, 0x6d, 0x65, 0x61, + 0x73, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x74, 0x69, 0x6d, + 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x53, + 0x74, 0x61, 0x72, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x65, 0x6e, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x45, 0x6e, 0x64, 0x12, 0x4f, 0x0a, 0x10, 0x74, + 0x69, 0x6d, 0x65, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, 0x79, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, + 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x69, + 0x6e, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x82, 0x01, 0x02, 0x20, 0x00, 0x52, 0x0f, 0x74, 0x69, 0x6d, + 0x65, 0x47, 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, 0x79, 0x12, 0x31, 0x0a, 0x05, + 0x77, 0x68, 0x65, 0x72, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x69, + 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, + 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x77, 0x68, 0x65, 0x72, 0x65, 0x12, + 0x33, 0x0a, 0x06, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x68, 0x61, + 0x76, 0x69, 0x6e, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x7a, 0x6f, 0x6e, + 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5a, 0x6f, 0x6e, + 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3a, 0x0a, + 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, - 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, - 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x8c, 0x05, 0x0a, - 0x1c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x69, 0x6d, 0x65, - 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, - 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x33, - 0x0a, 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, - 0x10, 0x01, 0x52, 0x0f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x0d, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x92, - 0x01, 0x02, 0x08, 0x01, 0x52, 0x0c, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, - 0x65, 0x73, 0x12, 0x47, 0x0a, 0x0f, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x6d, 0x65, 0x61, - 0x73, 0x75, 0x72, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x72, 0x69, - 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, - 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x52, 0x0e, 0x69, 0x6e, 0x6c, - 0x69, 0x6e, 0x65, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x74, + 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x46, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x8d, 0x01, 0x0a, 0x1d, 0x4d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, + 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, + 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x12, 0x34, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x20, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x9c, 0x03, 0x0a, 0x18, 0x4d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, + 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, + 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0f, 0x6d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x0d, + 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, 0x0c, 0x6d, + 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x45, 0x6e, 0x64, 0x12, 0x4f, 0x0a, - 0x10, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, - 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, - 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x47, 0x72, - 0x61, 0x69, 0x6e, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x82, 0x01, 0x02, 0x20, 0x00, 0x52, 0x0f, 0x74, - 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, 0x79, 0x12, 0x31, - 0x0a, 0x05, 0x77, 0x68, 0x65, 0x72, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, - 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x77, 0x68, 0x65, 0x72, - 0x65, 0x12, 0x33, 0x0a, 0x06, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x18, 0x0b, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x06, - 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x7a, - 0x6f, 0x6e, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5a, - 0x6f, 0x6e, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, - 0x3a, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x46, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x8d, 0x01, 0x0a, 0x1d, - 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x69, 0x6d, 0x65, 0x53, - 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x69, - 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, - 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x34, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, - 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xe5, 0x03, 0x0a, 0x18, - 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x6f, 0x74, 0x61, 0x6c, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, - 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x11, 0x6d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0f, 0x6d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2d, - 0x0a, 0x0d, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, - 0x0c, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x47, 0x0a, - 0x0f, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, - 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, - 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x4d, - 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x52, 0x0e, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x65, - 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x12, 0x35, 0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x07, 0x74, 0x69, 0x6d, 0x65, 0x45, 0x6e, 0x64, 0x12, 0x31, 0x0a, 0x05, 0x77, 0x68, 0x65, 0x72, - 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, - 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x77, 0x68, 0x65, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, - 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, - 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, - 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x45, 0x6e, 0x64, 0x12, 0x31, 0x0a, + 0x05, 0x77, 0x68, 0x65, 0x72, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, + 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, + 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x77, 0x68, 0x65, 0x72, 0x65, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x06, + 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, + 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, + 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x80, 0x01, 0x0a, 0x19, 0x4d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, + 0x65, 0x77, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x2b, + 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, + 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xc5, 0x04, 0x0a, 0x16, + 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x52, 0x6f, 0x77, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, + 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, + 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0f, 0x6d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0a, + 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, + 0x6d, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5f, + 0x65, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x45, 0x6e, 0x64, 0x12, 0x45, + 0x0a, 0x10, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, + 0x74, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, + 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x47, + 0x72, 0x61, 0x69, 0x6e, 0x52, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x75, 0x6c, + 0x61, 0x72, 0x69, 0x74, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x77, 0x68, 0x65, 0x72, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x52, 0x05, 0x77, 0x68, 0x65, 0x72, 0x65, 0x12, 0x34, 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, + 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, + 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, + 0x56, 0x69, 0x65, 0x77, 0x53, 0x6f, 0x72, 0x74, 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x12, 0x1d, + 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, + 0x42, 0x04, 0x1a, 0x02, 0x28, 0x00, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1f, 0x0a, + 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xfa, + 0x42, 0x04, 0x22, 0x02, 0x28, 0x00, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, + 0x6d, 0x65, 0x5f, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, + 0x69, 0x6d, 0x65, 0x5a, 0x6f, 0x6e, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x22, 0x80, 0x01, 0x0a, 0x19, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, - 0x69, 0x65, 0x77, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x36, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6c, - 0x75, 0x6d, 0x6e, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x2b, 0x0a, 0x04, 0x64, 0x61, 0x74, - 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, - 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xc5, 0x04, 0x0a, 0x16, 0x4d, 0x65, 0x74, 0x72, 0x69, - 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x52, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x49, 0x64, 0x12, 0x33, 0x0a, 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, - 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, - 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, - 0x69, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, - 0x72, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x45, 0x6e, 0x64, 0x12, 0x45, 0x0a, 0x10, 0x74, 0x69, 0x6d, - 0x65, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, 0x79, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, - 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x69, 0x6e, 0x52, - 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x6e, 0x75, 0x6c, 0x61, 0x72, 0x69, 0x74, 0x79, - 0x12, 0x31, 0x0a, 0x05, 0x77, 0x68, 0x65, 0x72, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x77, 0x68, - 0x65, 0x72, 0x65, 0x12, 0x34, 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x20, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, + 0x74, 0x65, 0x72, 0x22, 0x7e, 0x0a, 0x17, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, + 0x65, 0x77, 0x52, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, + 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, + 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, + 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, + 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x2b, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x04, 0x64, + 0x61, 0x74, 0x61, 0x22, 0x4c, 0x0a, 0x0f, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, + 0x65, 0x77, 0x53, 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x22, 0xf1, 0x01, 0x0a, 0x11, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, + 0x77, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x07, 0x69, 0x6e, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, + 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, + 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, + 0x64, 0x52, 0x07, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x12, 0x41, 0x0a, 0x07, 0x65, 0x78, + 0x63, 0x6c, 0x75, 0x64, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x72, 0x69, + 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x2e, + 0x43, 0x6f, 0x6e, 0x64, 0x52, 0x07, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x1a, 0x56, 0x0a, + 0x04, 0x43, 0x6f, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x02, 0x69, 0x6e, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x02, 0x69, + 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6b, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x04, 0x6c, 0x69, 0x6b, 0x65, 0x22, 0x57, 0x0a, 0x11, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, + 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x55, + 0x0a, 0x0d, 0x49, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x12, + 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, + 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0a, + 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x8f, 0x01, 0x0a, 0x1b, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0f, 0x6d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, + 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, + 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x6f, 0x0a, 0x1c, 0x4d, 0x65, 0x74, 0x72, 0x69, + 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x12, 0x74, 0x69, 0x6d, 0x65, 0x5f, + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, + 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x53, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x10, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, + 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x8c, 0x01, 0x0a, 0x18, 0x4d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0f, 0x6d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, + 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, + 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x50, 0x0a, 0x19, 0x4d, 0x65, 0x74, 0x72, 0x69, + 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x22, 0x86, 0x03, 0x0a, 0x18, 0x4d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, + 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, + 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0f, 0x6d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, + 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x0a, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, + 0x61, 0x72, 0x63, 0x68, 0x12, 0x39, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, + 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, + 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, + 0x61, 0x6e, 0x67, 0x65, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, + 0x31, 0x0a, 0x05, 0x77, 0x68, 0x65, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x77, 0x68, 0x65, + 0x72, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x06, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x12, 0x1d, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x00, 0x52, + 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x22, 0xca, 0x01, 0x0a, 0x19, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, + 0x65, 0x77, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x51, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x37, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, - 0x6f, 0x72, 0x74, 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x12, 0x1d, 0x0a, 0x05, 0x6c, 0x69, 0x6d, - 0x69, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, - 0x00, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1f, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, - 0x65, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x22, 0x02, 0x28, - 0x00, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x7a, 0x6f, - 0x6e, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5a, 0x6f, - 0x6e, 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x0c, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, - 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, - 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x7e, - 0x0a, 0x17, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x52, 0x6f, 0x77, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, - 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, - 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x12, 0x2b, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x4c, - 0x0a, 0x0f, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, 0x6f, 0x72, - 0x74, 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, - 0x0a, 0x09, 0x61, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x09, 0x61, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, 0xf1, 0x01, 0x0a, - 0x11, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x46, 0x69, 0x6c, 0x74, - 0x65, 0x72, 0x12, 0x41, 0x0a, 0x07, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, - 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, - 0x77, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x52, 0x07, 0x69, 0x6e, - 0x63, 0x6c, 0x75, 0x64, 0x65, 0x12, 0x41, 0x0a, 0x07, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, - 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, - 0x56, 0x69, 0x65, 0x77, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x52, - 0x07, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x1a, 0x56, 0x0a, 0x04, 0x43, 0x6f, 0x6e, 0x64, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x02, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, + 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x73, 0x1a, 0x5a, 0x0a, 0x0c, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x02, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, - 0x6c, 0x69, 0x6b, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6b, 0x65, - 0x22, 0x57, 0x0a, 0x11, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, - 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, - 0x08, 0x6e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x08, 0x6e, 0x75, 0x6c, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x55, 0x0a, 0x0d, 0x49, 0x6e, 0x6c, - 0x69, 0x6e, 0x65, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, - 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, - 0x72, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x22, 0x8f, 0x01, 0x0a, 0x1b, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, - 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, - 0x64, 0x12, 0x33, 0x0a, 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, - 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, - 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, - 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, - 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, - 0x74, 0x79, 0x22, 0x6f, 0x0a, 0x1c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, - 0x77, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x12, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, - 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, - 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, - 0x79, 0x52, 0x10, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x75, 0x6d, 0x6d, - 0x61, 0x72, 0x79, 0x22, 0x8c, 0x01, 0x0a, 0x18, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, - 0x69, 0x65, 0x77, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, + 0x8f, 0x02, 0x0a, 0x1b, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x6f, 0x6c, 0x6c, 0x75, 0x70, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, + 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, + 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x61, + 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x12, 0x26, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, + 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x0b, 0x63, + 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x63, 0x6f, 0x6c, 0x75, 0x6d, + 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, + 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, + 0x79, 0x22, 0xb6, 0x01, 0x0a, 0x1c, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x6f, 0x6c, 0x6c, + 0x75, 0x70, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, + 0x6e, 0x64, 0x12, 0x36, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x69, 0x6e, + 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x22, 0xa5, 0x02, 0x0a, 0x11, 0x43, + 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x6f, 0x70, 0x4b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, - 0x64, 0x12, 0x33, 0x0a, 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, - 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, - 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, - 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, - 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, - 0x74, 0x79, 0x22, 0x50, 0x0a, 0x19, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, - 0x77, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x33, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x73, 0x63, - 0x68, 0x65, 0x6d, 0x61, 0x22, 0x86, 0x03, 0x0a, 0x18, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, - 0x56, 0x69, 0x65, 0x77, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x49, 0x64, 0x12, 0x33, 0x0a, 0x11, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, - 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, - 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, - 0x69, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x69, 0x6d, 0x65, 0x6e, - 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x69, 0x6d, - 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x61, 0x72, 0x63, - 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, - 0x39, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, - 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, - 0x09, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x31, 0x0a, 0x05, 0x77, 0x68, - 0x65, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, - 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x72, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x77, 0x68, 0x65, 0x72, 0x65, 0x12, 0x33, 0x0a, - 0x06, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, - 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x45, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x68, 0x61, 0x76, 0x69, - 0x6e, 0x67, 0x12, 0x1d, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x00, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, - 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0xca, 0x01, - 0x0a, 0x19, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, 0x65, 0x61, - 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x07, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x72, - 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x1a, 0x5a, - 0x0a, 0x0c, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1c, - 0x0a, 0x09, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x8f, 0x02, 0x0a, 0x1b, 0x43, - 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x49, 0x6e, 0x74, 0x65, 0x72, - 0x76, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, - 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, - 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, - 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, - 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x26, - 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x09, 0x74, 0x61, 0x62, - 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x0b, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, - 0x72, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0xb6, 0x01, 0x0a, - 0x1c, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, - 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, - 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x36, 0x0a, - 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x1a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x69, 0x6e, 0x52, 0x08, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x76, 0x61, 0x6c, 0x22, 0xa5, 0x02, 0x0a, 0x11, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, - 0x54, 0x6f, 0x70, 0x4b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, - 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, - 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, - 0x73, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, - 0x26, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x09, 0x74, 0x61, - 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x0b, 0x63, 0x6f, 0x6c, 0x75, 0x6d, - 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, - 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x67, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x61, 0x67, 0x67, 0x12, 0x0c, 0x0a, 0x01, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x01, - 0x6b, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x6a, 0x0a, - 0x12, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x6f, 0x70, 0x4b, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x13, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x63, - 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x23, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x75, - 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x12, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x63, - 0x61, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x6e, 0x0a, 0x12, 0x43, 0x61, 0x74, - 0x65, 0x67, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, - 0x2c, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x5f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, - 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x54, 0x6f, 0x70, 0x4b, 0x48, 0x00, 0x52, 0x04, 0x74, 0x6f, 0x70, 0x4b, 0x12, 0x22, 0x0a, - 0x0b, 0x63, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, - 0x79, 0x42, 0x06, 0x0a, 0x04, 0x63, 0x61, 0x73, 0x65, 0x22, 0x8a, 0x01, 0x0a, 0x04, 0x54, 0x6f, - 0x70, 0x4b, 0x12, 0x35, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, - 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x6f, 0x70, 0x4b, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x1a, 0x4b, 0x0a, 0x05, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x8a, 0x02, 0x0a, 0x16, 0x43, 0x6f, 0x6c, 0x75, 0x6d, - 0x6e, 0x4e, 0x75, 0x6c, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, - 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, - 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x26, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, - 0x10, 0x01, 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, - 0x0b, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x63, 0x6f, 0x6c, - 0x75, 0x6d, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x22, 0x2f, 0x0a, 0x17, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x75, 0x6c, - 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x96, 0x02, 0x0a, 0x22, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x76, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, - 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, - 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, - 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, - 0x73, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, - 0x26, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x09, 0x74, 0x61, - 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x0b, 0x63, 0x6f, 0x6c, 0x75, 0x6d, - 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, - 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x6f, 0x0a, - 0x23, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x76, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x0f, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x5f, - 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, - 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x0e, - 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x9b, - 0x02, 0x0a, 0x0e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, - 0x79, 0x12, 0x5d, 0x0a, 0x16, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x68, 0x69, 0x73, - 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x62, 0x69, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x48, 0x69, 0x73, 0x74, 0x6f, - 0x67, 0x72, 0x61, 0x6d, 0x42, 0x69, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x14, 0x6e, 0x75, 0x6d, 0x65, - 0x72, 0x69, 0x63, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x42, 0x69, 0x6e, 0x73, - 0x12, 0x53, 0x0a, 0x12, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x73, 0x74, 0x61, 0x74, - 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x72, - 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, - 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, - 0x48, 0x00, 0x52, 0x11, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x53, 0x74, 0x61, 0x74, 0x69, - 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x4d, 0x0a, 0x10, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, - 0x5f, 0x6f, 0x75, 0x74, 0x6c, 0x69, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x20, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x4f, 0x75, 0x74, 0x6c, 0x69, 0x65, 0x72, - 0x73, 0x48, 0x00, 0x52, 0x0f, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x4f, 0x75, 0x74, 0x6c, - 0x69, 0x65, 0x72, 0x73, 0x42, 0x06, 0x0a, 0x04, 0x63, 0x61, 0x73, 0x65, 0x22, 0xcc, 0x01, 0x0a, - 0x14, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, - 0x6d, 0x42, 0x69, 0x6e, 0x73, 0x12, 0x3d, 0x0a, 0x04, 0x62, 0x69, 0x6e, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, - 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x48, 0x69, 0x73, - 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x42, 0x69, 0x6e, 0x73, 0x2e, 0x42, 0x69, 0x6e, 0x52, 0x04, - 0x62, 0x69, 0x6e, 0x73, 0x1a, 0x75, 0x0a, 0x03, 0x42, 0x69, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x62, - 0x75, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x62, 0x75, 0x63, - 0x6b, 0x65, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, - 0x52, 0x03, 0x6c, 0x6f, 0x77, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x69, 0x64, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x6d, 0x69, 0x64, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x67, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x04, 0x68, 0x69, 0x67, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x91, 0x01, 0x0a, 0x11, - 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, - 0x73, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, - 0x6d, 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, - 0x52, 0x03, 0x6d, 0x61, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x61, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x04, 0x6d, 0x65, 0x61, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x71, 0x32, 0x35, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x71, 0x32, 0x35, 0x12, 0x10, 0x0a, 0x03, 0x71, - 0x35, 0x30, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x71, 0x35, 0x30, 0x12, 0x10, 0x0a, - 0x03, 0x71, 0x37, 0x35, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x71, 0x37, 0x35, 0x12, - 0x0e, 0x0a, 0x02, 0x73, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x02, 0x73, 0x64, 0x22, - 0xd0, 0x01, 0x0a, 0x0f, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x4f, 0x75, 0x74, 0x6c, 0x69, - 0x65, 0x72, 0x73, 0x12, 0x44, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x6c, 0x69, 0x65, 0x72, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, - 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x4f, - 0x75, 0x74, 0x6c, 0x69, 0x65, 0x72, 0x73, 0x2e, 0x4f, 0x75, 0x74, 0x6c, 0x69, 0x65, 0x72, 0x52, - 0x08, 0x6f, 0x75, 0x74, 0x6c, 0x69, 0x65, 0x72, 0x73, 0x1a, 0x77, 0x0a, 0x07, 0x4f, 0x75, 0x74, - 0x6c, 0x69, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x10, 0x0a, 0x03, - 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x6c, 0x6f, 0x77, 0x12, 0x12, - 0x0a, 0x04, 0x68, 0x69, 0x67, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x04, 0x68, 0x69, - 0x67, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x22, 0x8a, 0x02, 0x0a, 0x16, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, - 0x65, 0x47, 0x72, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, - 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1c, - 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, - 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x61, - 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, - 0x61, 0x12, 0x26, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x09, - 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x0b, 0x63, 0x6f, 0x6c, - 0x75, 0x6d, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, - 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, - 0x54, 0x0a, 0x17, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, - 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x74, 0x69, - 0x6d, 0x65, 0x5f, 0x67, 0x72, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, - 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x69, 0x6e, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, - 0x47, 0x72, 0x61, 0x69, 0x6e, 0x22, 0xde, 0x02, 0x0a, 0x1d, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, - 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, - 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, - 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, - 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x61, 0x74, - 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x26, 0x0a, 0x0a, 0x74, - 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x0b, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, - 0x01, 0x52, 0x0a, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x4b, 0x0a, - 0x10, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, - 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0f, 0x68, 0x69, 0x73, 0x74, 0x6f, - 0x67, 0x72, 0x61, 0x6d, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, - 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, - 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x6a, 0x0a, 0x1e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, - 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x0f, 0x6e, 0x75, 0x6d, 0x65, - 0x72, 0x69, 0x63, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1f, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x53, 0x75, 0x6d, 0x6d, 0x61, - 0x72, 0x79, 0x52, 0x0e, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x53, 0x75, 0x6d, 0x6d, 0x61, - 0x72, 0x79, 0x22, 0x8d, 0x02, 0x0a, 0x19, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x75, 0x67, - 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, + 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, + 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x12, 0x26, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, + 0x01, 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x0b, + 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x63, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x67, 0x67, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x67, 0x67, 0x12, 0x0c, 0x0a, 0x01, 0x6b, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x01, 0x6b, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x22, 0x6a, 0x0a, 0x12, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x6f, 0x70, 0x4b, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x13, 0x63, 0x61, 0x74, 0x65, + 0x67, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, + 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, + 0x63, 0x61, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x12, 0x63, 0x61, 0x74, 0x65, + 0x67, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x6e, + 0x0a, 0x12, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x5f, 0x6b, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, + 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x6f, 0x70, 0x4b, 0x48, 0x00, 0x52, 0x04, 0x74, 0x6f, + 0x70, 0x4b, 0x12, 0x22, 0x0a, 0x0b, 0x63, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x61, 0x72, 0x64, 0x69, + 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x42, 0x06, 0x0a, 0x04, 0x63, 0x61, 0x73, 0x65, 0x22, 0x8a, + 0x01, 0x0a, 0x04, 0x54, 0x6f, 0x70, 0x4b, 0x12, 0x35, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, + 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x6f, 0x70, 0x4b, 0x2e, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x1a, 0x4b, + 0x0a, 0x05, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x8a, 0x02, 0x0a, 0x16, + 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x75, 0x6c, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, + 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, + 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x61, 0x74, 0x61, + 0x62, 0x61, 0x73, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x26, 0x0a, 0x0a, 0x74, 0x61, + 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, + 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x0b, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, + 0x52, 0x0a, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, + 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, + 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x2f, 0x0a, 0x17, 0x43, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x4e, 0x75, 0x6c, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x01, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x96, 0x02, 0x0a, 0x22, 0x43, 0x6f, + 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x76, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x05, @@ -8338,14 +8170,119 @@ var file_rill_runtime_v1_queries_proto_rawDesc = []byte{ 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, - 0x74, 0x79, 0x22, 0x66, 0x0a, 0x1a, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x75, 0x67, 0x48, - 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x48, 0x0a, 0x0f, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x73, 0x75, 0x6d, 0x6d, - 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x72, 0x69, 0x6c, 0x6c, - 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x75, 0x6d, 0x65, - 0x72, 0x69, 0x63, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x0e, 0x6e, 0x75, 0x6d, 0x65, - 0x72, 0x69, 0x63, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x8a, 0x02, 0x0a, 0x16, 0x43, - 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, + 0x74, 0x79, 0x22, 0x6f, 0x0a, 0x23, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x76, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x0f, 0x6e, 0x75, 0x6d, + 0x65, 0x72, 0x69, 0x63, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x53, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x79, 0x52, 0x0e, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x53, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x79, 0x22, 0x9b, 0x02, 0x0a, 0x0e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x53, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x5d, 0x0a, 0x16, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, + 0x63, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x62, 0x69, 0x6e, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, + 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x42, 0x69, 0x6e, 0x73, 0x48, 0x00, 0x52, + 0x14, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, + 0x6d, 0x42, 0x69, 0x6e, 0x73, 0x12, 0x53, 0x0a, 0x12, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x53, 0x74, 0x61, 0x74, 0x69, + 0x73, 0x74, 0x69, 0x63, 0x73, 0x48, 0x00, 0x52, 0x11, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x4d, 0x0a, 0x10, 0x6e, 0x75, + 0x6d, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x6f, 0x75, 0x74, 0x6c, 0x69, 0x65, 0x72, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x4f, 0x75, + 0x74, 0x6c, 0x69, 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x0f, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, + 0x63, 0x4f, 0x75, 0x74, 0x6c, 0x69, 0x65, 0x72, 0x73, 0x42, 0x06, 0x0a, 0x04, 0x63, 0x61, 0x73, + 0x65, 0x22, 0xcc, 0x01, 0x0a, 0x14, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x48, 0x69, 0x73, + 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x42, 0x69, 0x6e, 0x73, 0x12, 0x3d, 0x0a, 0x04, 0x62, 0x69, + 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, + 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x75, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x42, 0x69, 0x6e, 0x73, 0x2e, + 0x42, 0x69, 0x6e, 0x52, 0x04, 0x62, 0x69, 0x6e, 0x73, 0x1a, 0x75, 0x0a, 0x03, 0x42, 0x69, 0x6e, + 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x06, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6c, 0x6f, 0x77, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x6c, 0x6f, 0x77, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x69, + 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x6d, 0x69, + 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x67, 0x68, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x01, 0x52, 0x04, 0x68, 0x69, 0x67, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x22, 0x91, 0x01, 0x0a, 0x11, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x53, 0x74, 0x61, 0x74, + 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x01, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x6d, 0x61, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, + 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x04, 0x6d, 0x65, 0x61, 0x6e, 0x12, 0x10, + 0x0a, 0x03, 0x71, 0x32, 0x35, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x71, 0x32, 0x35, + 0x12, 0x10, 0x0a, 0x03, 0x71, 0x35, 0x30, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x71, + 0x35, 0x30, 0x12, 0x10, 0x0a, 0x03, 0x71, 0x37, 0x35, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, + 0x03, 0x71, 0x37, 0x35, 0x12, 0x0e, 0x0a, 0x02, 0x73, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, + 0x52, 0x02, 0x73, 0x64, 0x22, 0xd0, 0x01, 0x0a, 0x0f, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x4f, 0x75, 0x74, 0x6c, 0x69, 0x65, 0x72, 0x73, 0x12, 0x44, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x6c, + 0x69, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x72, 0x69, 0x6c, + 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x75, 0x6d, + 0x65, 0x72, 0x69, 0x63, 0x4f, 0x75, 0x74, 0x6c, 0x69, 0x65, 0x72, 0x73, 0x2e, 0x4f, 0x75, 0x74, + 0x6c, 0x69, 0x65, 0x72, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x6c, 0x69, 0x65, 0x72, 0x73, 0x1a, 0x77, + 0x0a, 0x07, 0x4f, 0x75, 0x74, 0x6c, 0x69, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x63, + 0x6b, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x62, 0x75, 0x63, 0x6b, 0x65, + 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, + 0x6c, 0x6f, 0x77, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x67, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x01, 0x52, 0x04, 0x68, 0x69, 0x67, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, + 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, + 0x74, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x8a, 0x02, 0x0a, 0x16, 0x43, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, + 0x0f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, + 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x26, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, + 0x02, 0x10, 0x01, 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, + 0x0a, 0x0b, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x63, 0x6f, + 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x22, 0x54, 0x0a, 0x17, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, + 0x6d, 0x65, 0x47, 0x72, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x39, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x67, 0x72, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, + 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x69, 0x6e, 0x52, + 0x09, 0x74, 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x69, 0x6e, 0x22, 0xde, 0x02, 0x0a, 0x1d, 0x43, + 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x48, 0x69, 0x73, 0x74, + 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, + 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, + 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x64, + 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, + 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x62, + 0x61, 0x73, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x12, 0x26, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x09, 0x74, + 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x0b, 0x63, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, + 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x10, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x72, + 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x48, + 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0f, + 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, + 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x6a, 0x0a, 0x1e, 0x43, + 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x48, 0x69, 0x73, 0x74, + 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, + 0x0f, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, + 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x0e, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x8d, 0x02, 0x0a, 0x19, 0x43, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x52, 0x75, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, @@ -8361,189 +8298,55 @@ var file_rill_runtime_v1_queries_proto_rawDesc = []byte{ 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, - 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x6a, 0x0a, 0x17, 0x43, 0x6f, 0x6c, 0x75, 0x6d, - 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x12, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, - 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, + 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x66, 0x0a, 0x1a, 0x43, 0x6f, 0x6c, 0x75, 0x6d, + 0x6e, 0x52, 0x75, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x0f, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, - 0x79, 0x52, 0x10, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x75, 0x6d, 0x6d, - 0x61, 0x72, 0x79, 0x22, 0x86, 0x02, 0x0a, 0x10, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, - 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x2c, 0x0a, 0x03, 0x6d, 0x69, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12, 0x2c, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x03, 0x6d, 0x61, 0x78, 0x12, 0x46, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, - 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, - 0x67, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, - 0x61, 0x6c, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x1a, 0x4e, 0x0a, 0x08, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x6f, 0x6e, 0x74, - 0x68, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x73, - 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x79, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, - 0x64, 0x61, 0x79, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x22, 0x8c, 0x02, 0x0a, - 0x18, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x43, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, - 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, - 0x62, 0x61, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, - 0x62, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, - 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, - 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x26, 0x0a, - 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, - 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x0b, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, - 0x02, 0x10, 0x01, 0x52, 0x0a, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, - 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x71, 0x0a, 0x19, 0x43, - 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x43, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x13, 0x63, 0x61, 0x74, 0x65, - 0x67, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, - 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, - 0x63, 0x61, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x12, 0x63, 0x61, 0x74, 0x65, - 0x67, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x8c, - 0x05, 0x0a, 0x17, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, - 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, + 0x2e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, + 0x0e, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, + 0x8a, 0x02, 0x0a, 0x16, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, + 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, - 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, + 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, - 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x26, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x09, 0x74, 0x61, 0x62, - 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x5b, 0x0a, 0x08, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, - 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, - 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, - 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x2e, 0x42, 0x61, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x42, - 0x08, 0xfa, 0x42, 0x05, 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, 0x08, 0x6d, 0x65, 0x61, 0x73, 0x75, - 0x72, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x15, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x13, 0x74, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x43, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, - 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, - 0x73, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, - 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x06, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x00, 0x52, 0x06, - 0x70, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x12, 0x28, 0x0a, 0x0b, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, - 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, - 0x1a, 0x02, 0x28, 0x00, 0x52, 0x0a, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1b, 0x0a, 0x09, - 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5a, 0x6f, 0x6e, 0x65, 0x1a, 0x62, 0x0a, 0x0c, 0x42, 0x61, 0x73, - 0x69, 0x63, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x27, 0x0a, 0x0a, 0x65, 0x78, 0x70, - 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, - 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x71, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x71, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x57, 0x0a, - 0x18, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x72, 0x6f, 0x6c, - 0x6c, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x72, 0x69, 0x6c, 0x6c, - 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x06, - 0x72, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x22, 0xb7, 0x01, 0x0a, 0x13, 0x54, 0x69, 0x6d, 0x65, 0x53, - 0x65, 0x72, 0x69, 0x65, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x30, - 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x40, - 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x1a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x69, 0x6e, 0x42, 0x08, 0xfa, 0x42, - 0x05, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, - 0x22, 0xa9, 0x01, 0x0a, 0x12, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, - 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x53, - 0x65, 0x72, 0x69, 0x65, 0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x73, 0x12, 0x36, 0x0a, 0x05, 0x73, 0x70, 0x61, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, - 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x73, 0x70, 0x61, 0x72, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x73, - 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0a, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x82, 0x01, 0x0a, - 0x0f, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x2a, 0x0a, 0x02, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x02, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, - 0x62, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, 0x62, 0x69, 0x6e, 0x12, 0x31, - 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, - 0x73, 0x22, 0xe1, 0x01, 0x0a, 0x17, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x72, 0x64, 0x69, - 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, - 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1c, - 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, - 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x61, - 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, - 0x61, 0x12, 0x26, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x09, - 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x3c, 0x0a, 0x18, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, - 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, - 0x69, 0x74, 0x79, 0x22, 0xdd, 0x01, 0x0a, 0x13, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, - 0x75, 0x6d, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, - 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, - 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, - 0x73, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, - 0x26, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x09, 0x74, 0x61, - 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, - 0x69, 0x74, 0x79, 0x22, 0x96, 0x02, 0x0a, 0x14, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, - 0x75, 0x6d, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0f, - 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, - 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, - 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x6f, - 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x12, 0x6e, 0x0a, 0x13, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, - 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, - 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x12, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x6f, - 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x1a, 0x45, 0x0a, 0x17, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x6b, 0x0a, 0x0d, - 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x6c, 0x61, 0x72, 0x67, 0x65, 0x73, 0x74, - 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x6c, 0x61, 0x72, 0x67, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, - 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0xf9, 0x01, 0x0a, 0x10, 0x54, 0x61, - 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, + 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x0b, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, + 0x72, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x6a, 0x0a, 0x17, + 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x12, 0x74, 0x69, 0x6d, 0x65, 0x5f, + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, + 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x53, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x10, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, + 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x86, 0x02, 0x0a, 0x10, 0x54, 0x69, 0x6d, + 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x2c, 0x0a, + 0x03, 0x6d, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x12, 0x2c, 0x0a, 0x03, 0x6d, + 0x61, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x6d, 0x61, 0x78, 0x12, 0x46, 0x0a, 0x08, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x72, 0x69, + 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x2e, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, + 0x6c, 0x1a, 0x4e, 0x0a, 0x08, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x16, 0x0a, + 0x06, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6d, + 0x6f, 0x6e, 0x74, 0x68, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x79, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x04, 0x64, 0x61, 0x79, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x69, 0x63, + 0x72, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6d, 0x69, 0x63, 0x72, 0x6f, + 0x73, 0x22, 0x8c, 0x02, 0x0a, 0x18, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x43, 0x61, 0x72, 0x64, + 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, @@ -8554,372 +8357,530 @@ var file_rill_runtime_v1_queries_proto_rawDesc = []byte{ 0x28, 0x09, 0x52, 0x0e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x26, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, - 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x05, 0x6c, 0x69, - 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, - 0x28, 0x00, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, - 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x40, 0x0a, 0x11, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, - 0x77, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x04, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, - 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x2a, 0x70, 0x0a, 0x0e, 0x42, 0x75, 0x69, 0x6c, 0x74, - 0x69, 0x6e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x12, 0x1f, 0x0a, 0x1b, 0x42, 0x55, 0x49, - 0x4c, 0x54, 0x49, 0x4e, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x5f, 0x55, 0x4e, 0x53, - 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x42, 0x55, + 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x0b, 0x63, 0x6f, + 0x6c, 0x75, 0x6d, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, + 0x22, 0x71, 0x0a, 0x19, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x43, 0x61, 0x72, 0x64, 0x69, 0x6e, + 0x61, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, + 0x13, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x72, 0x69, 0x6c, + 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x74, + 0x65, 0x67, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, + 0x12, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x79, 0x22, 0x8c, 0x05, 0x0a, 0x17, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, + 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, + 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, + 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x61, + 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x0d, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x12, 0x26, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, + 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x5b, 0x0a, 0x08, 0x6d, + 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, + 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x42, 0x61, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x61, + 0x73, 0x75, 0x72, 0x65, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, 0x08, + 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x15, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, + 0x52, 0x13, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x43, 0x6f, 0x6c, 0x75, 0x6d, + 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x43, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x72, 0x61, + 0x6e, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x72, 0x69, 0x6c, 0x6c, + 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, + 0x09, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x06, 0x70, 0x69, + 0x78, 0x65, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, + 0x02, 0x28, 0x00, 0x52, 0x06, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x12, 0x28, 0x0a, 0x0b, 0x73, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, + 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x00, 0x52, 0x0a, 0x73, 0x61, 0x6d, 0x70, 0x6c, + 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, + 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, + 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5a, 0x6f, 0x6e, 0x65, 0x1a, 0x62, + 0x0a, 0x0c, 0x42, 0x61, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x27, + 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x65, 0x78, 0x70, + 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x71, 0x6c, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x71, 0x6c, 0x4e, 0x61, + 0x6d, 0x65, 0x22, 0x57, 0x0a, 0x18, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, + 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, + 0x0a, 0x06, 0x72, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, + 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x52, 0x06, 0x72, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x22, 0xb7, 0x01, 0x0a, 0x13, + 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, + 0x6e, 0x67, 0x65, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, + 0x65, 0x6e, 0x64, 0x12, 0x40, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, + 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x69, + 0x6e, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x08, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x76, 0x61, 0x6c, 0x22, 0xa9, 0x01, 0x0a, 0x12, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, + 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x07, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, + 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, + 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x36, 0x0a, 0x05, 0x73, 0x70, 0x61, 0x72, + 0x6b, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, + 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, + 0x72, 0x69, 0x65, 0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x73, 0x70, 0x61, 0x72, 0x6b, + 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x53, 0x69, 0x7a, + 0x65, 0x22, 0x82, 0x01, 0x0a, 0x0f, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2a, 0x0a, 0x02, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x02, 0x74, + 0x73, 0x12, 0x10, 0x0a, 0x03, 0x62, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x03, + 0x62, 0x69, 0x6e, 0x12, 0x31, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x07, 0x72, + 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0xe1, 0x01, 0x0a, 0x17, 0x54, 0x61, 0x62, 0x6c, 0x65, + 0x43, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, + 0x0f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, + 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x26, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, + 0x02, 0x10, 0x01, 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x3c, 0x0a, 0x18, 0x54, 0x61, + 0x62, 0x6c, 0x65, 0x43, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x61, 0x72, 0x64, 0x69, 0x6e, + 0x61, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x61, 0x72, + 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x22, 0xdd, 0x01, 0x0a, 0x13, 0x54, 0x61, 0x62, + 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, + 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, + 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, + 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x12, 0x26, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, + 0x01, 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, + 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, + 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x96, 0x02, 0x0a, 0x14, 0x54, 0x61, 0x62, + 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x47, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6c, + 0x75, 0x6d, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x72, 0x69, 0x6c, + 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x12, 0x6e, 0x0a, 0x13, 0x75, 0x6e, + 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, + 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, + 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x55, + 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x12, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, + 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x1a, 0x45, 0x0a, 0x17, 0x55, 0x6e, + 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0x6b, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x6c, 0x61, + 0x72, 0x67, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x65, 0x6e, + 0x67, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x6c, 0x61, 0x72, 0x67, 0x65, + 0x73, 0x74, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0xf9, + 0x01, 0x0a, 0x10, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x27, + 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, + 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x26, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, + 0x72, 0x02, 0x10, 0x01, 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x1d, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, + 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x00, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x22, 0x40, 0x0a, 0x11, 0x54, 0x61, + 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2b, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x2a, 0x70, 0x0a, 0x0e, + 0x42, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x12, 0x1f, + 0x0a, 0x1b, 0x42, 0x55, 0x49, 0x4c, 0x54, 0x49, 0x4e, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, + 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x19, 0x0a, 0x15, 0x42, 0x55, 0x49, 0x4c, 0x54, 0x49, 0x4e, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, + 0x52, 0x45, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x22, 0x0a, 0x1e, 0x42, 0x55, 0x49, 0x4c, 0x54, 0x49, 0x4e, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x5f, 0x43, 0x4f, - 0x55, 0x4e, 0x54, 0x10, 0x01, 0x12, 0x22, 0x0a, 0x1e, 0x42, 0x55, 0x49, 0x4c, 0x54, 0x49, 0x4e, - 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x44, - 0x49, 0x53, 0x54, 0x49, 0x4e, 0x43, 0x54, 0x10, 0x02, 0x2a, 0x9e, 0x02, 0x0a, 0x1d, 0x4d, 0x65, - 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, - 0x73, 0x6f, 0x6e, 0x53, 0x6f, 0x72, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x31, 0x0a, 0x2d, 0x4d, - 0x45, 0x54, 0x52, 0x49, 0x43, 0x53, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x43, 0x4f, 0x4d, 0x50, - 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x30, - 0x0a, 0x2c, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x53, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x43, - 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x01, - 0x12, 0x36, 0x0a, 0x32, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x53, 0x5f, 0x56, 0x49, 0x45, 0x57, + 0x55, 0x4e, 0x54, 0x5f, 0x44, 0x49, 0x53, 0x54, 0x49, 0x4e, 0x43, 0x54, 0x10, 0x02, 0x2a, 0x9e, + 0x02, 0x0a, 0x1d, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, + 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x53, 0x6f, 0x72, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x31, 0x0a, 0x2d, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x53, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x4f, 0x52, 0x54, - 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, - 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12, 0x2f, 0x0a, 0x2b, 0x4d, 0x45, 0x54, 0x52, - 0x49, 0x43, 0x53, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, - 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x42, - 0x53, 0x5f, 0x44, 0x45, 0x4c, 0x54, 0x41, 0x10, 0x03, 0x12, 0x2f, 0x0a, 0x2b, 0x4d, 0x45, 0x54, - 0x52, 0x49, 0x43, 0x53, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, - 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, - 0x45, 0x4c, 0x5f, 0x44, 0x45, 0x4c, 0x54, 0x41, 0x10, 0x04, 0x2a, 0xb0, 0x02, 0x0a, 0x20, 0x4d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, - 0x69, 0x73, 0x6f, 0x6e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x34, 0x0a, 0x30, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x53, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, - 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, - 0x52, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, - 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x33, 0x0a, 0x2f, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x53, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x30, 0x0a, 0x2c, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x53, 0x5f, 0x56, + 0x49, 0x45, 0x57, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x53, + 0x4f, 0x52, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x56, 0x41, + 0x4c, 0x55, 0x45, 0x10, 0x01, 0x12, 0x36, 0x0a, 0x32, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x53, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, - 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x42, 0x41, - 0x53, 0x45, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x01, 0x12, 0x39, 0x0a, 0x35, 0x4d, 0x45, + 0x5f, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, + 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12, 0x2f, 0x0a, + 0x2b, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x53, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x43, 0x4f, + 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x41, 0x42, 0x53, 0x5f, 0x44, 0x45, 0x4c, 0x54, 0x41, 0x10, 0x03, 0x12, 0x2f, + 0x0a, 0x2b, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x53, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x43, + 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x4c, 0x5f, 0x44, 0x45, 0x4c, 0x54, 0x41, 0x10, 0x04, 0x2a, + 0xb0, 0x02, 0x0a, 0x20, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, + 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x34, 0x0a, 0x30, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x53, 0x5f, + 0x56, 0x49, 0x45, 0x57, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, + 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, + 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x33, 0x0a, 0x2f, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x53, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x5f, 0x54, 0x59, - 0x50, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x56, 0x41, - 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12, 0x32, 0x0a, 0x2e, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x53, - 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, - 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x42, - 0x53, 0x5f, 0x44, 0x45, 0x4c, 0x54, 0x41, 0x10, 0x03, 0x12, 0x32, 0x0a, 0x2e, 0x4d, 0x45, 0x54, - 0x52, 0x49, 0x43, 0x53, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, - 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x5f, 0x54, 0x59, 0x50, - 0x45, 0x5f, 0x52, 0x45, 0x4c, 0x5f, 0x44, 0x45, 0x4c, 0x54, 0x41, 0x10, 0x04, 0x2a, 0x6d, 0x0a, - 0x0f, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x12, 0x20, 0x0a, 0x1c, 0x48, 0x49, 0x53, 0x54, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x5f, 0x4d, 0x45, - 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, - 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x48, 0x49, 0x53, 0x54, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x5f, - 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x46, 0x44, 0x10, 0x01, 0x12, 0x1f, 0x0a, 0x1b, 0x48, - 0x49, 0x53, 0x54, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, - 0x44, 0x49, 0x41, 0x47, 0x4e, 0x4f, 0x53, 0x54, 0x49, 0x43, 0x10, 0x02, 0x32, 0xd4, 0x24, 0x0a, - 0x0c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x74, 0x0a, - 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x1d, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, - 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, - 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x3a, 0x01, 0x2a, - 0x22, 0x21, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, - 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, - 0x65, 0x72, 0x79, 0x12, 0x8b, 0x01, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x42, 0x61, 0x74, - 0x63, 0x68, 0x12, 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, - 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, - 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x42, 0x61, - 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x32, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x2c, 0x3a, 0x01, 0x2a, 0x22, 0x27, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, - 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, - 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2f, 0x62, 0x61, 0x74, 0x63, 0x68, 0x30, - 0x01, 0x12, 0x80, 0x01, 0x0a, 0x06, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1e, 0x2e, 0x72, - 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, - 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x72, - 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, - 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x2f, 0x3a, 0x01, 0x2a, 0x22, 0x2a, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x65, 0x78, - 0x70, 0x6f, 0x72, 0x74, 0x12, 0xd2, 0x01, 0x0a, 0x16, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, - 0x56, 0x69, 0x65, 0x77, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x2e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x41, 0x67, 0x67, - 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2f, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x41, 0x67, 0x67, - 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x57, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x51, 0x3a, 0x01, 0x2a, 0x22, 0x4c, 0x2f, 0x76, 0x31, + 0x50, 0x45, 0x5f, 0x42, 0x41, 0x53, 0x45, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x01, 0x12, + 0x39, 0x0a, 0x35, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x53, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, + 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, + 0x52, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, + 0x4f, 0x4e, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x02, 0x12, 0x32, 0x0a, 0x2e, 0x4d, 0x45, + 0x54, 0x52, 0x49, 0x43, 0x53, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, + 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, 0x45, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x41, 0x42, 0x53, 0x5f, 0x44, 0x45, 0x4c, 0x54, 0x41, 0x10, 0x03, 0x12, 0x32, + 0x0a, 0x2e, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x53, 0x5f, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x43, + 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x4d, 0x45, 0x41, 0x53, 0x55, 0x52, + 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x4c, 0x5f, 0x44, 0x45, 0x4c, 0x54, 0x41, + 0x10, 0x04, 0x2a, 0x6d, 0x0a, 0x0f, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x4d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x20, 0x0a, 0x1c, 0x48, 0x49, 0x53, 0x54, 0x4f, 0x47, 0x52, + 0x41, 0x4d, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, + 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x48, 0x49, 0x53, 0x54, 0x4f, + 0x47, 0x52, 0x41, 0x4d, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x46, 0x44, 0x10, 0x01, + 0x12, 0x1f, 0x0a, 0x1b, 0x48, 0x49, 0x53, 0x54, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x5f, 0x4d, 0x45, + 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x44, 0x49, 0x41, 0x47, 0x4e, 0x4f, 0x53, 0x54, 0x49, 0x43, 0x10, + 0x02, 0x32, 0xd4, 0x24, 0x0a, 0x0c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x12, 0x74, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x1d, 0x2e, 0x72, 0x69, + 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x72, 0x69, 0x6c, + 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2c, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x26, 0x3a, 0x01, 0x2a, 0x22, 0x21, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, + 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x8b, 0x01, 0x0a, 0x0a, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, + 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x42, + 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x72, 0x69, + 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x32, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2c, 0x3a, 0x01, 0x2a, 0x22, 0x27, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, - 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, - 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2d, 0x76, 0x69, 0x65, 0x77, 0x73, 0x2f, 0x7b, - 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x7d, 0x2f, 0x61, 0x67, - 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0xc7, 0x01, 0x0a, 0x12, 0x4d, 0x65, - 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x6f, 0x70, 0x6c, 0x69, 0x73, 0x74, - 0x12, 0x2a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x6f, - 0x70, 0x6c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x72, - 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x6f, 0x70, 0x6c, 0x69, 0x73, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x58, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x52, 0x3a, 0x01, 0x2a, 0x22, 0x4d, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, - 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, - 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, - 0x73, 0x2d, 0x76, 0x69, 0x65, 0x77, 0x73, 0x2f, 0x7b, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, - 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x74, 0x6f, 0x70, 0x6c, - 0x69, 0x73, 0x74, 0x12, 0xd8, 0x01, 0x0a, 0x15, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, - 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x12, 0x2d, 0x2e, + 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2f, 0x62, + 0x61, 0x74, 0x63, 0x68, 0x30, 0x01, 0x12, 0x80, 0x01, 0x0a, 0x06, 0x45, 0x78, 0x70, 0x6f, 0x72, + 0x74, 0x12, 0x1e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1f, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x35, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2f, 0x3a, 0x01, 0x2a, 0x22, 0x2a, 0x2f, + 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, + 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, + 0x65, 0x73, 0x2f, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x12, 0xd2, 0x01, 0x0a, 0x16, 0x4d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, + 0x65, 0x77, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, + 0x65, 0x77, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x57, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x51, 0x3a, 0x01, 0x2a, + 0x22, 0x4c, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, + 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, + 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2d, 0x76, 0x69, + 0x65, 0x77, 0x73, 0x2f, 0x7b, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, + 0x77, 0x7d, 0x2f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0xc7, + 0x01, 0x0a, 0x12, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x6f, + 0x70, 0x6c, 0x69, 0x73, 0x74, 0x12, 0x2a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, + 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, + 0x69, 0x65, 0x77, 0x54, 0x6f, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, + 0x6f, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x58, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x52, 0x3a, 0x01, 0x2a, 0x22, 0x4d, 0x2f, 0x76, 0x31, 0x2f, 0x69, + 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x6d, + 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2d, 0x76, 0x69, 0x65, 0x77, 0x73, 0x2f, 0x7b, 0x6d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, + 0x2f, 0x74, 0x6f, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x12, 0xd8, 0x01, 0x0a, 0x15, 0x4d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, + 0x6f, 0x6e, 0x12, 0x2d, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, + 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, + 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x60, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x5a, 0x3a, 0x01, 0x2a, 0x22, 0x55, 0x2f, 0x76, + 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, + 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, + 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2d, 0x76, 0x69, 0x65, 0x77, 0x73, 0x2f, + 0x7b, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x7d, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x65, 0x2d, 0x74, 0x6f, 0x70, 0x6c, + 0x69, 0x73, 0x74, 0x12, 0xd3, 0x01, 0x0a, 0x15, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, + 0x69, 0x65, 0x77, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x2d, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, - 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x72, + 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x69, 0x6d, 0x65, 0x53, + 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, - 0x69, 0x73, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x60, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x5a, 0x3a, 0x01, 0x2a, 0x22, 0x55, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, + 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, + 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5b, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x55, 0x3a, 0x01, 0x2a, 0x22, 0x50, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2d, 0x76, 0x69, 0x65, 0x77, 0x73, 0x2f, 0x7b, 0x6d, 0x65, 0x74, 0x72, - 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x63, - 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x65, 0x2d, 0x74, 0x6f, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x12, 0xd3, - 0x01, 0x0a, 0x15, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x69, - 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x2d, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, - 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, - 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, - 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, - 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x55, 0x3a, - 0x01, 0x2a, 0x22, 0x50, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, - 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2d, - 0x76, 0x69, 0x65, 0x77, 0x73, 0x2f, 0x7b, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, - 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x65, - 0x72, 0x69, 0x65, 0x73, 0x12, 0xc3, 0x01, 0x0a, 0x11, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, - 0x56, 0x69, 0x65, 0x77, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x12, 0x29, 0x2e, 0x72, 0x69, 0x6c, + 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0xc3, 0x01, 0x0a, 0x11, 0x4d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x12, + 0x29, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x6f, 0x74, + 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, - 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, - 0x69, 0x65, 0x77, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x57, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x51, 0x3a, 0x01, 0x2a, 0x22, 0x4c, 0x2f, 0x76, - 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, - 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2d, 0x76, 0x69, 0x65, 0x77, 0x73, 0x2f, - 0x7b, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x7d, 0x2f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x12, 0xbb, 0x01, 0x0a, 0x0f, 0x4d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x27, - 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x52, 0x6f, 0x77, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, - 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, - 0x73, 0x56, 0x69, 0x65, 0x77, 0x52, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x55, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4f, 0x3a, 0x01, 0x2a, 0x22, 0x4a, 0x2f, 0x76, - 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, - 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2d, 0x76, 0x69, 0x65, 0x77, 0x73, 0x2f, - 0x7b, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x7d, 0x2f, 0x72, 0x6f, 0x77, 0x73, 0x12, 0xd8, 0x01, 0x0a, 0x14, 0x4d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, - 0x65, 0x12, 0x2c, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, - 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2d, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x69, 0x6d, - 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x63, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x5d, 0x3a, 0x01, 0x2a, 0x22, 0x58, 0x2f, 0x76, 0x31, 0x2f, 0x69, - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, - 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x6d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2d, 0x76, 0x69, 0x65, 0x77, 0x73, 0x2f, 0x7b, 0x6d, 0x65, - 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, - 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x2d, 0x73, 0x75, 0x6d, 0x6d, - 0x61, 0x72, 0x79, 0x12, 0xc0, 0x01, 0x0a, 0x11, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, - 0x69, 0x65, 0x77, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x29, 0x2e, 0x72, 0x69, 0x6c, 0x6c, - 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, - 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, - 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, - 0x65, 0x77, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x54, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4e, 0x12, 0x4c, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x6d, 0x65, - 0x74, 0x72, 0x69, 0x63, 0x73, 0x2d, 0x76, 0x69, 0x65, 0x77, 0x73, 0x2f, 0x7b, 0x6d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, - 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0xc3, 0x01, 0x0a, 0x11, 0x4d, 0x65, 0x74, 0x72, 0x69, - 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x29, 0x2e, 0x72, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x57, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x51, 0x3a, 0x01, + 0x2a, 0x22, 0x4c, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, + 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, + 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2d, 0x76, + 0x69, 0x65, 0x77, 0x73, 0x2f, 0x7b, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, + 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x12, + 0xbb, 0x01, 0x0a, 0x0f, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x52, + 0x6f, 0x77, 0x73, 0x12, 0x27, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, + 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, + 0x77, 0x52, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, - 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, - 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x57, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x51, 0x3a, 0x01, 0x2a, 0x22, 0x4c, + 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x52, 0x6f, 0x77, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x55, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4f, 0x3a, 0x01, + 0x2a, 0x22, 0x4a, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, + 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, + 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2d, 0x76, + 0x69, 0x65, 0x77, 0x73, 0x2f, 0x7b, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, + 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x72, 0x6f, 0x77, 0x73, 0x12, 0xd8, 0x01, + 0x0a, 0x14, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x54, 0x69, 0x6d, + 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x2c, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, + 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, + 0x56, 0x69, 0x65, 0x77, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, + 0x65, 0x77, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x63, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x5d, 0x3a, 0x01, 0x2a, 0x22, 0x58, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2d, 0x76, 0x69, 0x65, 0x77, 0x73, 0x2f, 0x7b, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0xc7, 0x01, 0x0a, - 0x14, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2c, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, - 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x6f, - 0x6c, 0x6c, 0x75, 0x70, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, - 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x6f, 0x6c, 0x6c, - 0x75, 0x70, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x52, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4c, 0x3a, 0x01, 0x2a, 0x22, 0x47, 0x2f, + 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, + 0x2d, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0xc0, 0x01, 0x0a, 0x11, 0x4d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x29, + 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, + 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x54, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4e, 0x12, 0x4c, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, - 0x65, 0x73, 0x2f, 0x72, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, - 0x61, 0x6c, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x74, 0x61, 0x62, 0x6c, 0x65, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x9e, 0x01, 0x0a, 0x0a, 0x43, 0x6f, 0x6c, 0x75, 0x6d, - 0x6e, 0x54, 0x6f, 0x70, 0x4b, 0x12, 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, - 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x6f, - 0x70, 0x4b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x72, 0x69, 0x6c, 0x6c, - 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, - 0x6d, 0x6e, 0x54, 0x6f, 0x70, 0x4b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x47, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x41, 0x3a, 0x01, 0x2a, 0x22, 0x3c, 0x2f, 0x76, 0x31, 0x2f, 0x69, - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, - 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x74, - 0x6f, 0x70, 0x6b, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x74, 0x61, 0x62, 0x6c, - 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xb0, 0x01, 0x0a, 0x0f, 0x43, 0x6f, 0x6c, 0x75, - 0x6d, 0x6e, 0x4e, 0x75, 0x6c, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x2e, 0x72, 0x69, - 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, - 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x75, 0x6c, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, - 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x75, 0x6c, - 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4a, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x44, 0x12, 0x42, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, - 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, - 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x6e, 0x75, 0x6c, 0x6c, - 0x2d, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x74, - 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xe0, 0x01, 0x0a, 0x1b, 0x43, - 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x76, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x33, 0x2e, 0x72, 0x69, 0x6c, + 0x65, 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2d, 0x76, 0x69, 0x65, 0x77, 0x73, + 0x2f, 0x7b, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0xc3, 0x01, 0x0a, 0x11, + 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, 0x65, 0x61, 0x72, 0x63, + 0x68, 0x12, 0x29, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, + 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, + 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, + 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x56, 0x69, 0x65, 0x77, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x57, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x51, + 0x3a, 0x01, 0x2a, 0x22, 0x4c, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, + 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, + 0x2d, 0x76, 0x69, 0x65, 0x77, 0x73, 0x2f, 0x7b, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, + 0x76, 0x69, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, + 0x68, 0x12, 0xc7, 0x01, 0x0a, 0x14, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x6f, 0x6c, 0x6c, + 0x75, 0x70, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2c, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, - 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x76, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x34, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x76, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x56, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x50, 0x12, 0x4e, 0x2f, - 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, - 0x65, 0x73, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x76, 0x65, 0x2d, 0x73, - 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, - 0x2f, 0x7b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xb9, 0x01, - 0x0a, 0x0f, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x69, - 0x6e, 0x12, 0x27, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x47, 0x72, - 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x72, 0x69, 0x6c, + 0x75, 0x6d, 0x6e, 0x52, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, + 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, + 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, + 0x6e, 0x52, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x52, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4c, 0x3a, + 0x01, 0x2a, 0x22, 0x47, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, + 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x72, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x2d, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, 0x7b, + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x9e, 0x01, 0x0a, 0x0a, + 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x6f, 0x70, 0x4b, 0x12, 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, - 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x53, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4d, 0x12, 0x4b, 0x2f, 0x76, + 0x75, 0x6d, 0x6e, 0x54, 0x6f, 0x70, 0x4b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, + 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x6f, 0x70, 0x4b, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x47, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x41, 0x3a, 0x01, 0x2a, 0x22, 0x3c, + 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, + 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, + 0x69, 0x65, 0x73, 0x2f, 0x74, 0x6f, 0x70, 0x6b, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, + 0x7b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xb0, 0x01, 0x0a, + 0x0f, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x75, 0x6c, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x27, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x75, 0x6c, 0x6c, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x72, 0x69, 0x6c, 0x6c, + 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x4e, 0x75, 0x6c, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x4a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x44, 0x12, 0x42, 0x2f, 0x76, 0x31, + 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, + 0x2f, 0x6e, 0x75, 0x6c, 0x6c, 0x2d, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x74, 0x61, 0x62, 0x6c, + 0x65, 0x73, 0x2f, 0x7b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, + 0xe0, 0x01, 0x0a, 0x1b, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x76, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, + 0x33, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x76, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x76, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, + 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x56, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x50, 0x12, 0x4e, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, + 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x76, 0x65, 0x2d, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x2f, 0x74, + 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x7d, 0x12, 0xb9, 0x01, 0x0a, 0x0f, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, + 0x65, 0x47, 0x72, 0x61, 0x69, 0x6e, 0x12, 0x27, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, + 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, + 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x28, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x47, 0x72, 0x61, 0x69, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x53, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x4d, 0x12, 0x4b, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, + 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, + 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x65, 0x73, 0x74, 0x2d, + 0x74, 0x69, 0x6d, 0x65, 0x2d, 0x67, 0x72, 0x61, 0x69, 0x6e, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x73, 0x2f, 0x7b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xcc, + 0x01, 0x0a, 0x16, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, + 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, 0x2e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, + 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, + 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x72, 0x69, 0x6c, 0x6c, + 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, + 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x4b, 0x12, 0x49, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, + 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x2d, + 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, + 0x2f, 0x7b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xbc, 0x01, + 0x0a, 0x12, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x75, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, + 0x67, 0x72, 0x61, 0x6d, 0x12, 0x2a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x75, 0x67, + 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2b, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x75, 0x67, 0x48, 0x69, 0x73, 0x74, + 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4d, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x47, 0x12, 0x45, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, + 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x72, 0x75, 0x67, 0x2d, 0x68, + 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, + 0x7b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xb8, 0x01, 0x0a, + 0x0f, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, + 0x12, 0x27, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, + 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x72, 0x69, 0x6c, 0x6c, + 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x52, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4c, 0x12, 0x4a, 0x2f, 0x76, 0x31, + 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, + 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x2d, 0x73, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x79, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x74, 0x61, 0x62, 0x6c, + 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xbe, 0x01, 0x0a, 0x11, 0x43, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x43, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x29, 0x2e, + 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x43, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, + 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, + 0x6e, 0x43, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x52, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4c, 0x12, 0x4a, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, - 0x73, 0x2f, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x65, 0x73, 0x74, 0x2d, 0x74, 0x69, 0x6d, 0x65, 0x2d, - 0x67, 0x72, 0x61, 0x69, 0x6e, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x74, 0x61, - 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xcc, 0x01, 0x0a, 0x16, 0x43, 0x6f, - 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x48, 0x69, 0x73, 0x74, 0x6f, - 0x67, 0x72, 0x61, 0x6d, 0x12, 0x2e, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, - 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x75, 0x6d, - 0x65, 0x72, 0x69, 0x63, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, - 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x4e, 0x75, 0x6d, - 0x65, 0x72, 0x69, 0x63, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4b, 0x12, 0x49, 0x2f, + 0x73, 0x2f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x2d, 0x63, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, + 0x6c, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xb6, 0x01, 0x0a, 0x10, 0x43, 0x6f, 0x6c, + 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x28, 0x2e, + 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, + 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, + 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x4d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x47, 0x3a, 0x01, 0x2a, 0x22, 0x42, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, - 0x65, 0x73, 0x2f, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x2d, 0x68, 0x69, 0x73, 0x74, 0x6f, - 0x67, 0x72, 0x61, 0x6d, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x74, 0x61, 0x62, - 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xbc, 0x01, 0x0a, 0x12, 0x43, 0x6f, 0x6c, - 0x75, 0x6d, 0x6e, 0x52, 0x75, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, - 0x2a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x75, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, - 0x67, 0x72, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x72, 0x69, - 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, - 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x75, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x47, - 0x12, 0x45, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, - 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, - 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x72, 0x75, 0x67, 0x2d, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x74, 0x61, 0x62, 0x6c, - 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xb8, 0x01, 0x0a, 0x0f, 0x43, 0x6f, 0x6c, 0x75, - 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x27, 0x2e, 0x72, 0x69, - 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, - 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, - 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, - 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x52, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4c, 0x12, 0x4a, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, - 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, - 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x74, 0x69, 0x6d, 0x65, - 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x2d, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x2f, 0x74, - 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x7d, 0x12, 0xbe, 0x01, 0x0a, 0x11, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x43, 0x61, 0x72, - 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x29, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, - 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, - 0x6e, 0x43, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, - 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x43, 0x61, 0x72, 0x64, - 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x52, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4c, 0x12, 0x4a, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x63, 0x6f, 0x6c, - 0x75, 0x6d, 0x6e, 0x2d, 0x63, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x2f, - 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x7d, 0x12, 0xb6, 0x01, 0x0a, 0x10, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, - 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x28, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, - 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, - 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, - 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x53, - 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4d, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x47, 0x3a, 0x01, 0x2a, 0x22, 0x42, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x74, 0x69, - 0x6d, 0x65, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, - 0x7b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xba, 0x01, 0x0a, - 0x10, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, - 0x79, 0x12, 0x28, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, - 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, - 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x72, 0x69, + 0x65, 0x73, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x74, 0x61, + 0x62, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x7d, 0x12, 0xba, 0x01, 0x0a, 0x10, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x72, 0x64, 0x69, + 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x28, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, + 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, + 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x29, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, + 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x4b, 0x12, 0x49, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, + 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2d, 0x63, + 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x73, 0x2f, 0x7b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xac, + 0x01, 0x0a, 0x0c, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x12, + 0x24, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, + 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, + 0x75, 0x6d, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4f, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x49, 0x22, 0x47, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, + 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, + 0x73, 0x2d, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, + 0x2f, 0x7b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x98, 0x01, + 0x0a, 0x09, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x21, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, - 0x62, 0x6c, 0x65, 0x43, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x4b, 0x12, 0x49, - 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, - 0x69, 0x65, 0x73, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2d, 0x63, 0x61, 0x72, 0x64, 0x69, 0x6e, - 0x61, 0x6c, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x74, 0x61, - 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xac, 0x01, 0x0a, 0x0c, 0x54, 0x61, - 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x12, 0x24, 0x2e, 0x72, 0x69, 0x6c, - 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x62, - 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x25, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x49, 0x22, - 0x47, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, - 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, - 0x72, 0x69, 0x65, 0x73, 0x2f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x2d, 0x70, 0x72, 0x6f, - 0x66, 0x69, 0x6c, 0x65, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x74, 0x61, 0x62, - 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x98, 0x01, 0x0a, 0x09, 0x54, 0x61, 0x62, - 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x73, 0x12, 0x21, 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, - 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, - 0x77, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x72, 0x69, 0x6c, 0x6c, - 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x62, 0x6c, - 0x65, 0x52, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x44, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x3e, 0x12, 0x3c, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x73, 0x74, 0x61, - 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, - 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x72, 0x6f, 0x77, 0x73, 0x2f, - 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x7d, 0x42, 0xbf, 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x69, 0x6c, 0x6c, - 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x42, 0x0c, 0x51, 0x75, 0x65, - 0x72, 0x69, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3c, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x69, 0x6c, 0x6c, 0x64, 0x61, 0x74, 0x61, - 0x2f, 0x72, 0x69, 0x6c, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, - 0x72, 0x69, 0x6c, 0x6c, 0x2f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x76, 0x31, 0x3b, - 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x52, 0x52, 0x58, 0xaa, - 0x02, 0x0f, 0x52, 0x69, 0x6c, 0x6c, 0x2e, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x56, - 0x31, 0xca, 0x02, 0x0f, 0x52, 0x69, 0x6c, 0x6c, 0x5c, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, - 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x1b, 0x52, 0x69, 0x6c, 0x6c, 0x5c, 0x52, 0x75, 0x6e, 0x74, 0x69, - 0x6d, 0x65, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0xea, 0x02, 0x11, 0x52, 0x69, 0x6c, 0x6c, 0x3a, 0x3a, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, - 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, + 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x44, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3e, 0x12, 0x3c, 0x2f, 0x76, 0x31, 0x2f, + 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x2f, + 0x72, 0x6f, 0x77, 0x73, 0x2f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x2f, 0x7b, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x42, 0xbf, 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6d, + 0x2e, 0x72, 0x69, 0x6c, 0x6c, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, + 0x42, 0x0c, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x69, 0x6c, + 0x6c, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x72, 0x69, 0x6c, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x72, 0x69, 0x6c, 0x6c, 0x2f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, + 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x76, 0x31, 0xa2, 0x02, + 0x03, 0x52, 0x52, 0x58, 0xaa, 0x02, 0x0f, 0x52, 0x69, 0x6c, 0x6c, 0x2e, 0x52, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0f, 0x52, 0x69, 0x6c, 0x6c, 0x5c, 0x52, 0x75, + 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x1b, 0x52, 0x69, 0x6c, 0x6c, 0x5c, + 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x11, 0x52, 0x69, 0x6c, 0x6c, 0x3a, 0x3a, 0x52, + 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -9105,161 +9066,158 @@ var file_rill_runtime_v1_queries_proto_depIdxs = []int32{ 18, // 64: rill.runtime.v1.MetricsViewAggregationMeasure.comparison_value:type_name -> rill.runtime.v1.MetricsViewAggregationMeasureComputeComparisonValue 19, // 65: rill.runtime.v1.MetricsViewAggregationMeasure.comparison_delta:type_name -> rill.runtime.v1.MetricsViewAggregationMeasureComputeComparisonDelta 20, // 66: rill.runtime.v1.MetricsViewAggregationMeasure.comparison_ratio:type_name -> rill.runtime.v1.MetricsViewAggregationMeasureComputeComparisonRatio - 40, // 67: rill.runtime.v1.MetricsViewToplistRequest.inline_measures:type_name -> rill.runtime.v1.InlineMeasure - 96, // 68: rill.runtime.v1.MetricsViewToplistRequest.time_start:type_name -> google.protobuf.Timestamp - 96, // 69: rill.runtime.v1.MetricsViewToplistRequest.time_end:type_name -> google.protobuf.Timestamp - 37, // 70: rill.runtime.v1.MetricsViewToplistRequest.sort:type_name -> rill.runtime.v1.MetricsViewSort - 97, // 71: rill.runtime.v1.MetricsViewToplistRequest.where:type_name -> rill.runtime.v1.Expression - 97, // 72: rill.runtime.v1.MetricsViewToplistRequest.having:type_name -> rill.runtime.v1.Expression - 38, // 73: rill.runtime.v1.MetricsViewToplistRequest.filter:type_name -> rill.runtime.v1.MetricsViewFilter - 39, // 74: rill.runtime.v1.MetricsViewToplistResponse.meta:type_name -> rill.runtime.v1.MetricsViewColumn - 94, // 75: rill.runtime.v1.MetricsViewToplistResponse.data:type_name -> google.protobuf.Struct - 14, // 76: rill.runtime.v1.MetricsViewComparisonRequest.dimension:type_name -> rill.runtime.v1.MetricsViewAggregationDimension - 15, // 77: rill.runtime.v1.MetricsViewComparisonRequest.measures:type_name -> rill.runtime.v1.MetricsViewAggregationMeasure - 27, // 78: rill.runtime.v1.MetricsViewComparisonRequest.sort:type_name -> rill.runtime.v1.MetricsViewComparisonSort - 26, // 79: rill.runtime.v1.MetricsViewComparisonRequest.time_range:type_name -> rill.runtime.v1.TimeRange - 26, // 80: rill.runtime.v1.MetricsViewComparisonRequest.comparison_time_range:type_name -> rill.runtime.v1.TimeRange - 97, // 81: rill.runtime.v1.MetricsViewComparisonRequest.where:type_name -> rill.runtime.v1.Expression - 97, // 82: rill.runtime.v1.MetricsViewComparisonRequest.having:type_name -> rill.runtime.v1.Expression - 30, // 83: rill.runtime.v1.MetricsViewComparisonRequest.aliases:type_name -> rill.runtime.v1.MetricsViewComparisonMeasureAlias - 38, // 84: rill.runtime.v1.MetricsViewComparisonRequest.filter:type_name -> rill.runtime.v1.MetricsViewFilter - 28, // 85: rill.runtime.v1.MetricsViewComparisonResponse.rows:type_name -> rill.runtime.v1.MetricsViewComparisonRow - 96, // 86: rill.runtime.v1.TimeRange.start:type_name -> google.protobuf.Timestamp - 96, // 87: rill.runtime.v1.TimeRange.end:type_name -> google.protobuf.Timestamp - 98, // 88: rill.runtime.v1.TimeRange.round_to_grain:type_name -> rill.runtime.v1.TimeGrain - 1, // 89: rill.runtime.v1.MetricsViewComparisonSort.type:type_name -> rill.runtime.v1.MetricsViewComparisonSortType - 2, // 90: rill.runtime.v1.MetricsViewComparisonSort.sort_type:type_name -> rill.runtime.v1.MetricsViewComparisonMeasureType - 92, // 91: rill.runtime.v1.MetricsViewComparisonRow.dimension_value:type_name -> google.protobuf.Value - 29, // 92: rill.runtime.v1.MetricsViewComparisonRow.measure_values:type_name -> rill.runtime.v1.MetricsViewComparisonValue - 92, // 93: rill.runtime.v1.MetricsViewComparisonValue.base_value:type_name -> google.protobuf.Value - 92, // 94: rill.runtime.v1.MetricsViewComparisonValue.comparison_value:type_name -> google.protobuf.Value - 92, // 95: rill.runtime.v1.MetricsViewComparisonValue.delta_abs:type_name -> google.protobuf.Value - 92, // 96: rill.runtime.v1.MetricsViewComparisonValue.delta_rel:type_name -> google.protobuf.Value - 2, // 97: rill.runtime.v1.MetricsViewComparisonMeasureAlias.type:type_name -> rill.runtime.v1.MetricsViewComparisonMeasureType - 40, // 98: rill.runtime.v1.MetricsViewTimeSeriesRequest.inline_measures:type_name -> rill.runtime.v1.InlineMeasure - 96, // 99: rill.runtime.v1.MetricsViewTimeSeriesRequest.time_start:type_name -> google.protobuf.Timestamp - 96, // 100: rill.runtime.v1.MetricsViewTimeSeriesRequest.time_end:type_name -> google.protobuf.Timestamp - 98, // 101: rill.runtime.v1.MetricsViewTimeSeriesRequest.time_granularity:type_name -> rill.runtime.v1.TimeGrain - 97, // 102: rill.runtime.v1.MetricsViewTimeSeriesRequest.where:type_name -> rill.runtime.v1.Expression - 97, // 103: rill.runtime.v1.MetricsViewTimeSeriesRequest.having:type_name -> rill.runtime.v1.Expression - 38, // 104: rill.runtime.v1.MetricsViewTimeSeriesRequest.filter:type_name -> rill.runtime.v1.MetricsViewFilter - 39, // 105: rill.runtime.v1.MetricsViewTimeSeriesResponse.meta:type_name -> rill.runtime.v1.MetricsViewColumn - 76, // 106: rill.runtime.v1.MetricsViewTimeSeriesResponse.data:type_name -> rill.runtime.v1.TimeSeriesValue - 40, // 107: rill.runtime.v1.MetricsViewTotalsRequest.inline_measures:type_name -> rill.runtime.v1.InlineMeasure - 96, // 108: rill.runtime.v1.MetricsViewTotalsRequest.time_start:type_name -> google.protobuf.Timestamp - 96, // 109: rill.runtime.v1.MetricsViewTotalsRequest.time_end:type_name -> google.protobuf.Timestamp - 97, // 110: rill.runtime.v1.MetricsViewTotalsRequest.where:type_name -> rill.runtime.v1.Expression - 38, // 111: rill.runtime.v1.MetricsViewTotalsRequest.filter:type_name -> rill.runtime.v1.MetricsViewFilter - 39, // 112: rill.runtime.v1.MetricsViewTotalsResponse.meta:type_name -> rill.runtime.v1.MetricsViewColumn - 94, // 113: rill.runtime.v1.MetricsViewTotalsResponse.data:type_name -> google.protobuf.Struct - 96, // 114: rill.runtime.v1.MetricsViewRowsRequest.time_start:type_name -> google.protobuf.Timestamp - 96, // 115: rill.runtime.v1.MetricsViewRowsRequest.time_end:type_name -> google.protobuf.Timestamp - 98, // 116: rill.runtime.v1.MetricsViewRowsRequest.time_granularity:type_name -> rill.runtime.v1.TimeGrain - 97, // 117: rill.runtime.v1.MetricsViewRowsRequest.where:type_name -> rill.runtime.v1.Expression - 37, // 118: rill.runtime.v1.MetricsViewRowsRequest.sort:type_name -> rill.runtime.v1.MetricsViewSort - 38, // 119: rill.runtime.v1.MetricsViewRowsRequest.filter:type_name -> rill.runtime.v1.MetricsViewFilter - 39, // 120: rill.runtime.v1.MetricsViewRowsResponse.meta:type_name -> rill.runtime.v1.MetricsViewColumn - 94, // 121: rill.runtime.v1.MetricsViewRowsResponse.data:type_name -> google.protobuf.Struct - 84, // 122: rill.runtime.v1.MetricsViewFilter.include:type_name -> rill.runtime.v1.MetricsViewFilter.Cond - 84, // 123: rill.runtime.v1.MetricsViewFilter.exclude:type_name -> rill.runtime.v1.MetricsViewFilter.Cond - 69, // 124: rill.runtime.v1.MetricsViewTimeRangeResponse.time_range_summary:type_name -> rill.runtime.v1.TimeRangeSummary - 93, // 125: rill.runtime.v1.MetricsViewSchemaResponse.schema:type_name -> rill.runtime.v1.StructType - 26, // 126: rill.runtime.v1.MetricsViewSearchRequest.time_range:type_name -> rill.runtime.v1.TimeRange - 97, // 127: rill.runtime.v1.MetricsViewSearchRequest.where:type_name -> rill.runtime.v1.Expression - 97, // 128: rill.runtime.v1.MetricsViewSearchRequest.having:type_name -> rill.runtime.v1.Expression - 85, // 129: rill.runtime.v1.MetricsViewSearchResponse.results:type_name -> rill.runtime.v1.MetricsViewSearchResponse.SearchResult - 96, // 130: rill.runtime.v1.ColumnRollupIntervalResponse.start:type_name -> google.protobuf.Timestamp - 96, // 131: rill.runtime.v1.ColumnRollupIntervalResponse.end:type_name -> google.protobuf.Timestamp - 98, // 132: rill.runtime.v1.ColumnRollupIntervalResponse.interval:type_name -> rill.runtime.v1.TimeGrain - 51, // 133: rill.runtime.v1.ColumnTopKResponse.categorical_summary:type_name -> rill.runtime.v1.CategoricalSummary - 52, // 134: rill.runtime.v1.CategoricalSummary.top_k:type_name -> rill.runtime.v1.TopK - 86, // 135: rill.runtime.v1.TopK.entries:type_name -> rill.runtime.v1.TopK.Entry - 57, // 136: rill.runtime.v1.ColumnDescriptiveStatisticsResponse.numeric_summary:type_name -> rill.runtime.v1.NumericSummary - 58, // 137: rill.runtime.v1.NumericSummary.numeric_histogram_bins:type_name -> rill.runtime.v1.NumericHistogramBins - 59, // 138: rill.runtime.v1.NumericSummary.numeric_statistics:type_name -> rill.runtime.v1.NumericStatistics - 60, // 139: rill.runtime.v1.NumericSummary.numeric_outliers:type_name -> rill.runtime.v1.NumericOutliers - 87, // 140: rill.runtime.v1.NumericHistogramBins.bins:type_name -> rill.runtime.v1.NumericHistogramBins.Bin - 88, // 141: rill.runtime.v1.NumericOutliers.outliers:type_name -> rill.runtime.v1.NumericOutliers.Outlier - 98, // 142: rill.runtime.v1.ColumnTimeGrainResponse.time_grain:type_name -> rill.runtime.v1.TimeGrain - 3, // 143: rill.runtime.v1.ColumnNumericHistogramRequest.histogram_method:type_name -> rill.runtime.v1.HistogramMethod - 57, // 144: rill.runtime.v1.ColumnNumericHistogramResponse.numeric_summary:type_name -> rill.runtime.v1.NumericSummary - 57, // 145: rill.runtime.v1.ColumnRugHistogramResponse.numeric_summary:type_name -> rill.runtime.v1.NumericSummary - 69, // 146: rill.runtime.v1.ColumnTimeRangeResponse.time_range_summary:type_name -> rill.runtime.v1.TimeRangeSummary - 96, // 147: rill.runtime.v1.TimeRangeSummary.min:type_name -> google.protobuf.Timestamp - 96, // 148: rill.runtime.v1.TimeRangeSummary.max:type_name -> google.protobuf.Timestamp - 89, // 149: rill.runtime.v1.TimeRangeSummary.interval:type_name -> rill.runtime.v1.TimeRangeSummary.Interval - 51, // 150: rill.runtime.v1.ColumnCardinalityResponse.categorical_summary:type_name -> rill.runtime.v1.CategoricalSummary - 90, // 151: rill.runtime.v1.ColumnTimeSeriesRequest.measures:type_name -> rill.runtime.v1.ColumnTimeSeriesRequest.BasicMeasure - 74, // 152: rill.runtime.v1.ColumnTimeSeriesRequest.time_range:type_name -> rill.runtime.v1.TimeSeriesTimeRange - 75, // 153: rill.runtime.v1.ColumnTimeSeriesResponse.rollup:type_name -> rill.runtime.v1.TimeSeriesResponse - 96, // 154: rill.runtime.v1.TimeSeriesTimeRange.start:type_name -> google.protobuf.Timestamp - 96, // 155: rill.runtime.v1.TimeSeriesTimeRange.end:type_name -> google.protobuf.Timestamp - 98, // 156: rill.runtime.v1.TimeSeriesTimeRange.interval:type_name -> rill.runtime.v1.TimeGrain - 76, // 157: rill.runtime.v1.TimeSeriesResponse.results:type_name -> rill.runtime.v1.TimeSeriesValue - 76, // 158: rill.runtime.v1.TimeSeriesResponse.spark:type_name -> rill.runtime.v1.TimeSeriesValue - 96, // 159: rill.runtime.v1.TimeSeriesValue.ts:type_name -> google.protobuf.Timestamp - 94, // 160: rill.runtime.v1.TimeSeriesValue.records:type_name -> google.protobuf.Struct - 81, // 161: rill.runtime.v1.TableColumnsResponse.profile_columns:type_name -> rill.runtime.v1.ProfileColumn - 91, // 162: rill.runtime.v1.TableColumnsResponse.unsupported_columns:type_name -> rill.runtime.v1.TableColumnsResponse.UnsupportedColumnsEntry - 94, // 163: rill.runtime.v1.TableRowsResponse.data:type_name -> google.protobuf.Struct - 92, // 164: rill.runtime.v1.MetricsViewFilter.Cond.in:type_name -> google.protobuf.Value - 92, // 165: rill.runtime.v1.MetricsViewSearchResponse.SearchResult.value:type_name -> google.protobuf.Value - 92, // 166: rill.runtime.v1.TopK.Entry.value:type_name -> google.protobuf.Value - 4, // 167: rill.runtime.v1.QueryService.Query:input_type -> rill.runtime.v1.QueryRequest - 6, // 168: rill.runtime.v1.QueryService.QueryBatch:input_type -> rill.runtime.v1.QueryBatchRequest - 8, // 169: rill.runtime.v1.QueryService.Export:input_type -> rill.runtime.v1.ExportRequest - 12, // 170: rill.runtime.v1.QueryService.MetricsViewAggregation:input_type -> rill.runtime.v1.MetricsViewAggregationRequest - 22, // 171: rill.runtime.v1.QueryService.MetricsViewToplist:input_type -> rill.runtime.v1.MetricsViewToplistRequest - 24, // 172: rill.runtime.v1.QueryService.MetricsViewComparison:input_type -> rill.runtime.v1.MetricsViewComparisonRequest - 31, // 173: rill.runtime.v1.QueryService.MetricsViewTimeSeries:input_type -> rill.runtime.v1.MetricsViewTimeSeriesRequest - 33, // 174: rill.runtime.v1.QueryService.MetricsViewTotals:input_type -> rill.runtime.v1.MetricsViewTotalsRequest - 35, // 175: rill.runtime.v1.QueryService.MetricsViewRows:input_type -> rill.runtime.v1.MetricsViewRowsRequest - 41, // 176: rill.runtime.v1.QueryService.MetricsViewTimeRange:input_type -> rill.runtime.v1.MetricsViewTimeRangeRequest - 43, // 177: rill.runtime.v1.QueryService.MetricsViewSchema:input_type -> rill.runtime.v1.MetricsViewSchemaRequest - 45, // 178: rill.runtime.v1.QueryService.MetricsViewSearch:input_type -> rill.runtime.v1.MetricsViewSearchRequest - 47, // 179: rill.runtime.v1.QueryService.ColumnRollupInterval:input_type -> rill.runtime.v1.ColumnRollupIntervalRequest - 49, // 180: rill.runtime.v1.QueryService.ColumnTopK:input_type -> rill.runtime.v1.ColumnTopKRequest - 53, // 181: rill.runtime.v1.QueryService.ColumnNullCount:input_type -> rill.runtime.v1.ColumnNullCountRequest - 55, // 182: rill.runtime.v1.QueryService.ColumnDescriptiveStatistics:input_type -> rill.runtime.v1.ColumnDescriptiveStatisticsRequest - 61, // 183: rill.runtime.v1.QueryService.ColumnTimeGrain:input_type -> rill.runtime.v1.ColumnTimeGrainRequest - 63, // 184: rill.runtime.v1.QueryService.ColumnNumericHistogram:input_type -> rill.runtime.v1.ColumnNumericHistogramRequest - 65, // 185: rill.runtime.v1.QueryService.ColumnRugHistogram:input_type -> rill.runtime.v1.ColumnRugHistogramRequest - 67, // 186: rill.runtime.v1.QueryService.ColumnTimeRange:input_type -> rill.runtime.v1.ColumnTimeRangeRequest - 70, // 187: rill.runtime.v1.QueryService.ColumnCardinality:input_type -> rill.runtime.v1.ColumnCardinalityRequest - 72, // 188: rill.runtime.v1.QueryService.ColumnTimeSeries:input_type -> rill.runtime.v1.ColumnTimeSeriesRequest - 77, // 189: rill.runtime.v1.QueryService.TableCardinality:input_type -> rill.runtime.v1.TableCardinalityRequest - 79, // 190: rill.runtime.v1.QueryService.TableColumns:input_type -> rill.runtime.v1.TableColumnsRequest - 82, // 191: rill.runtime.v1.QueryService.TableRows:input_type -> rill.runtime.v1.TableRowsRequest - 5, // 192: rill.runtime.v1.QueryService.Query:output_type -> rill.runtime.v1.QueryResponse - 7, // 193: rill.runtime.v1.QueryService.QueryBatch:output_type -> rill.runtime.v1.QueryBatchResponse - 9, // 194: rill.runtime.v1.QueryService.Export:output_type -> rill.runtime.v1.ExportResponse - 13, // 195: rill.runtime.v1.QueryService.MetricsViewAggregation:output_type -> rill.runtime.v1.MetricsViewAggregationResponse - 23, // 196: rill.runtime.v1.QueryService.MetricsViewToplist:output_type -> rill.runtime.v1.MetricsViewToplistResponse - 25, // 197: rill.runtime.v1.QueryService.MetricsViewComparison:output_type -> rill.runtime.v1.MetricsViewComparisonResponse - 32, // 198: rill.runtime.v1.QueryService.MetricsViewTimeSeries:output_type -> rill.runtime.v1.MetricsViewTimeSeriesResponse - 34, // 199: rill.runtime.v1.QueryService.MetricsViewTotals:output_type -> rill.runtime.v1.MetricsViewTotalsResponse - 36, // 200: rill.runtime.v1.QueryService.MetricsViewRows:output_type -> rill.runtime.v1.MetricsViewRowsResponse - 42, // 201: rill.runtime.v1.QueryService.MetricsViewTimeRange:output_type -> rill.runtime.v1.MetricsViewTimeRangeResponse - 44, // 202: rill.runtime.v1.QueryService.MetricsViewSchema:output_type -> rill.runtime.v1.MetricsViewSchemaResponse - 46, // 203: rill.runtime.v1.QueryService.MetricsViewSearch:output_type -> rill.runtime.v1.MetricsViewSearchResponse - 48, // 204: rill.runtime.v1.QueryService.ColumnRollupInterval:output_type -> rill.runtime.v1.ColumnRollupIntervalResponse - 50, // 205: rill.runtime.v1.QueryService.ColumnTopK:output_type -> rill.runtime.v1.ColumnTopKResponse - 54, // 206: rill.runtime.v1.QueryService.ColumnNullCount:output_type -> rill.runtime.v1.ColumnNullCountResponse - 56, // 207: rill.runtime.v1.QueryService.ColumnDescriptiveStatistics:output_type -> rill.runtime.v1.ColumnDescriptiveStatisticsResponse - 62, // 208: rill.runtime.v1.QueryService.ColumnTimeGrain:output_type -> rill.runtime.v1.ColumnTimeGrainResponse - 64, // 209: rill.runtime.v1.QueryService.ColumnNumericHistogram:output_type -> rill.runtime.v1.ColumnNumericHistogramResponse - 66, // 210: rill.runtime.v1.QueryService.ColumnRugHistogram:output_type -> rill.runtime.v1.ColumnRugHistogramResponse - 68, // 211: rill.runtime.v1.QueryService.ColumnTimeRange:output_type -> rill.runtime.v1.ColumnTimeRangeResponse - 71, // 212: rill.runtime.v1.QueryService.ColumnCardinality:output_type -> rill.runtime.v1.ColumnCardinalityResponse - 73, // 213: rill.runtime.v1.QueryService.ColumnTimeSeries:output_type -> rill.runtime.v1.ColumnTimeSeriesResponse - 78, // 214: rill.runtime.v1.QueryService.TableCardinality:output_type -> rill.runtime.v1.TableCardinalityResponse - 80, // 215: rill.runtime.v1.QueryService.TableColumns:output_type -> rill.runtime.v1.TableColumnsResponse - 83, // 216: rill.runtime.v1.QueryService.TableRows:output_type -> rill.runtime.v1.TableRowsResponse - 192, // [192:217] is the sub-list for method output_type - 167, // [167:192] is the sub-list for method input_type - 167, // [167:167] is the sub-list for extension type_name - 167, // [167:167] is the sub-list for extension extendee - 0, // [0:167] is the sub-list for field type_name + 96, // 67: rill.runtime.v1.MetricsViewToplistRequest.time_start:type_name -> google.protobuf.Timestamp + 96, // 68: rill.runtime.v1.MetricsViewToplistRequest.time_end:type_name -> google.protobuf.Timestamp + 37, // 69: rill.runtime.v1.MetricsViewToplistRequest.sort:type_name -> rill.runtime.v1.MetricsViewSort + 97, // 70: rill.runtime.v1.MetricsViewToplistRequest.where:type_name -> rill.runtime.v1.Expression + 97, // 71: rill.runtime.v1.MetricsViewToplistRequest.having:type_name -> rill.runtime.v1.Expression + 38, // 72: rill.runtime.v1.MetricsViewToplistRequest.filter:type_name -> rill.runtime.v1.MetricsViewFilter + 39, // 73: rill.runtime.v1.MetricsViewToplistResponse.meta:type_name -> rill.runtime.v1.MetricsViewColumn + 94, // 74: rill.runtime.v1.MetricsViewToplistResponse.data:type_name -> google.protobuf.Struct + 14, // 75: rill.runtime.v1.MetricsViewComparisonRequest.dimension:type_name -> rill.runtime.v1.MetricsViewAggregationDimension + 15, // 76: rill.runtime.v1.MetricsViewComparisonRequest.measures:type_name -> rill.runtime.v1.MetricsViewAggregationMeasure + 27, // 77: rill.runtime.v1.MetricsViewComparisonRequest.sort:type_name -> rill.runtime.v1.MetricsViewComparisonSort + 26, // 78: rill.runtime.v1.MetricsViewComparisonRequest.time_range:type_name -> rill.runtime.v1.TimeRange + 26, // 79: rill.runtime.v1.MetricsViewComparisonRequest.comparison_time_range:type_name -> rill.runtime.v1.TimeRange + 97, // 80: rill.runtime.v1.MetricsViewComparisonRequest.where:type_name -> rill.runtime.v1.Expression + 97, // 81: rill.runtime.v1.MetricsViewComparisonRequest.having:type_name -> rill.runtime.v1.Expression + 30, // 82: rill.runtime.v1.MetricsViewComparisonRequest.aliases:type_name -> rill.runtime.v1.MetricsViewComparisonMeasureAlias + 38, // 83: rill.runtime.v1.MetricsViewComparisonRequest.filter:type_name -> rill.runtime.v1.MetricsViewFilter + 28, // 84: rill.runtime.v1.MetricsViewComparisonResponse.rows:type_name -> rill.runtime.v1.MetricsViewComparisonRow + 96, // 85: rill.runtime.v1.TimeRange.start:type_name -> google.protobuf.Timestamp + 96, // 86: rill.runtime.v1.TimeRange.end:type_name -> google.protobuf.Timestamp + 98, // 87: rill.runtime.v1.TimeRange.round_to_grain:type_name -> rill.runtime.v1.TimeGrain + 1, // 88: rill.runtime.v1.MetricsViewComparisonSort.type:type_name -> rill.runtime.v1.MetricsViewComparisonSortType + 2, // 89: rill.runtime.v1.MetricsViewComparisonSort.sort_type:type_name -> rill.runtime.v1.MetricsViewComparisonMeasureType + 92, // 90: rill.runtime.v1.MetricsViewComparisonRow.dimension_value:type_name -> google.protobuf.Value + 29, // 91: rill.runtime.v1.MetricsViewComparisonRow.measure_values:type_name -> rill.runtime.v1.MetricsViewComparisonValue + 92, // 92: rill.runtime.v1.MetricsViewComparisonValue.base_value:type_name -> google.protobuf.Value + 92, // 93: rill.runtime.v1.MetricsViewComparisonValue.comparison_value:type_name -> google.protobuf.Value + 92, // 94: rill.runtime.v1.MetricsViewComparisonValue.delta_abs:type_name -> google.protobuf.Value + 92, // 95: rill.runtime.v1.MetricsViewComparisonValue.delta_rel:type_name -> google.protobuf.Value + 2, // 96: rill.runtime.v1.MetricsViewComparisonMeasureAlias.type:type_name -> rill.runtime.v1.MetricsViewComparisonMeasureType + 96, // 97: rill.runtime.v1.MetricsViewTimeSeriesRequest.time_start:type_name -> google.protobuf.Timestamp + 96, // 98: rill.runtime.v1.MetricsViewTimeSeriesRequest.time_end:type_name -> google.protobuf.Timestamp + 98, // 99: rill.runtime.v1.MetricsViewTimeSeriesRequest.time_granularity:type_name -> rill.runtime.v1.TimeGrain + 97, // 100: rill.runtime.v1.MetricsViewTimeSeriesRequest.where:type_name -> rill.runtime.v1.Expression + 97, // 101: rill.runtime.v1.MetricsViewTimeSeriesRequest.having:type_name -> rill.runtime.v1.Expression + 38, // 102: rill.runtime.v1.MetricsViewTimeSeriesRequest.filter:type_name -> rill.runtime.v1.MetricsViewFilter + 39, // 103: rill.runtime.v1.MetricsViewTimeSeriesResponse.meta:type_name -> rill.runtime.v1.MetricsViewColumn + 76, // 104: rill.runtime.v1.MetricsViewTimeSeriesResponse.data:type_name -> rill.runtime.v1.TimeSeriesValue + 96, // 105: rill.runtime.v1.MetricsViewTotalsRequest.time_start:type_name -> google.protobuf.Timestamp + 96, // 106: rill.runtime.v1.MetricsViewTotalsRequest.time_end:type_name -> google.protobuf.Timestamp + 97, // 107: rill.runtime.v1.MetricsViewTotalsRequest.where:type_name -> rill.runtime.v1.Expression + 38, // 108: rill.runtime.v1.MetricsViewTotalsRequest.filter:type_name -> rill.runtime.v1.MetricsViewFilter + 39, // 109: rill.runtime.v1.MetricsViewTotalsResponse.meta:type_name -> rill.runtime.v1.MetricsViewColumn + 94, // 110: rill.runtime.v1.MetricsViewTotalsResponse.data:type_name -> google.protobuf.Struct + 96, // 111: rill.runtime.v1.MetricsViewRowsRequest.time_start:type_name -> google.protobuf.Timestamp + 96, // 112: rill.runtime.v1.MetricsViewRowsRequest.time_end:type_name -> google.protobuf.Timestamp + 98, // 113: rill.runtime.v1.MetricsViewRowsRequest.time_granularity:type_name -> rill.runtime.v1.TimeGrain + 97, // 114: rill.runtime.v1.MetricsViewRowsRequest.where:type_name -> rill.runtime.v1.Expression + 37, // 115: rill.runtime.v1.MetricsViewRowsRequest.sort:type_name -> rill.runtime.v1.MetricsViewSort + 38, // 116: rill.runtime.v1.MetricsViewRowsRequest.filter:type_name -> rill.runtime.v1.MetricsViewFilter + 39, // 117: rill.runtime.v1.MetricsViewRowsResponse.meta:type_name -> rill.runtime.v1.MetricsViewColumn + 94, // 118: rill.runtime.v1.MetricsViewRowsResponse.data:type_name -> google.protobuf.Struct + 84, // 119: rill.runtime.v1.MetricsViewFilter.include:type_name -> rill.runtime.v1.MetricsViewFilter.Cond + 84, // 120: rill.runtime.v1.MetricsViewFilter.exclude:type_name -> rill.runtime.v1.MetricsViewFilter.Cond + 69, // 121: rill.runtime.v1.MetricsViewTimeRangeResponse.time_range_summary:type_name -> rill.runtime.v1.TimeRangeSummary + 93, // 122: rill.runtime.v1.MetricsViewSchemaResponse.schema:type_name -> rill.runtime.v1.StructType + 26, // 123: rill.runtime.v1.MetricsViewSearchRequest.time_range:type_name -> rill.runtime.v1.TimeRange + 97, // 124: rill.runtime.v1.MetricsViewSearchRequest.where:type_name -> rill.runtime.v1.Expression + 97, // 125: rill.runtime.v1.MetricsViewSearchRequest.having:type_name -> rill.runtime.v1.Expression + 85, // 126: rill.runtime.v1.MetricsViewSearchResponse.results:type_name -> rill.runtime.v1.MetricsViewSearchResponse.SearchResult + 96, // 127: rill.runtime.v1.ColumnRollupIntervalResponse.start:type_name -> google.protobuf.Timestamp + 96, // 128: rill.runtime.v1.ColumnRollupIntervalResponse.end:type_name -> google.protobuf.Timestamp + 98, // 129: rill.runtime.v1.ColumnRollupIntervalResponse.interval:type_name -> rill.runtime.v1.TimeGrain + 51, // 130: rill.runtime.v1.ColumnTopKResponse.categorical_summary:type_name -> rill.runtime.v1.CategoricalSummary + 52, // 131: rill.runtime.v1.CategoricalSummary.top_k:type_name -> rill.runtime.v1.TopK + 86, // 132: rill.runtime.v1.TopK.entries:type_name -> rill.runtime.v1.TopK.Entry + 57, // 133: rill.runtime.v1.ColumnDescriptiveStatisticsResponse.numeric_summary:type_name -> rill.runtime.v1.NumericSummary + 58, // 134: rill.runtime.v1.NumericSummary.numeric_histogram_bins:type_name -> rill.runtime.v1.NumericHistogramBins + 59, // 135: rill.runtime.v1.NumericSummary.numeric_statistics:type_name -> rill.runtime.v1.NumericStatistics + 60, // 136: rill.runtime.v1.NumericSummary.numeric_outliers:type_name -> rill.runtime.v1.NumericOutliers + 87, // 137: rill.runtime.v1.NumericHistogramBins.bins:type_name -> rill.runtime.v1.NumericHistogramBins.Bin + 88, // 138: rill.runtime.v1.NumericOutliers.outliers:type_name -> rill.runtime.v1.NumericOutliers.Outlier + 98, // 139: rill.runtime.v1.ColumnTimeGrainResponse.time_grain:type_name -> rill.runtime.v1.TimeGrain + 3, // 140: rill.runtime.v1.ColumnNumericHistogramRequest.histogram_method:type_name -> rill.runtime.v1.HistogramMethod + 57, // 141: rill.runtime.v1.ColumnNumericHistogramResponse.numeric_summary:type_name -> rill.runtime.v1.NumericSummary + 57, // 142: rill.runtime.v1.ColumnRugHistogramResponse.numeric_summary:type_name -> rill.runtime.v1.NumericSummary + 69, // 143: rill.runtime.v1.ColumnTimeRangeResponse.time_range_summary:type_name -> rill.runtime.v1.TimeRangeSummary + 96, // 144: rill.runtime.v1.TimeRangeSummary.min:type_name -> google.protobuf.Timestamp + 96, // 145: rill.runtime.v1.TimeRangeSummary.max:type_name -> google.protobuf.Timestamp + 89, // 146: rill.runtime.v1.TimeRangeSummary.interval:type_name -> rill.runtime.v1.TimeRangeSummary.Interval + 51, // 147: rill.runtime.v1.ColumnCardinalityResponse.categorical_summary:type_name -> rill.runtime.v1.CategoricalSummary + 90, // 148: rill.runtime.v1.ColumnTimeSeriesRequest.measures:type_name -> rill.runtime.v1.ColumnTimeSeriesRequest.BasicMeasure + 74, // 149: rill.runtime.v1.ColumnTimeSeriesRequest.time_range:type_name -> rill.runtime.v1.TimeSeriesTimeRange + 75, // 150: rill.runtime.v1.ColumnTimeSeriesResponse.rollup:type_name -> rill.runtime.v1.TimeSeriesResponse + 96, // 151: rill.runtime.v1.TimeSeriesTimeRange.start:type_name -> google.protobuf.Timestamp + 96, // 152: rill.runtime.v1.TimeSeriesTimeRange.end:type_name -> google.protobuf.Timestamp + 98, // 153: rill.runtime.v1.TimeSeriesTimeRange.interval:type_name -> rill.runtime.v1.TimeGrain + 76, // 154: rill.runtime.v1.TimeSeriesResponse.results:type_name -> rill.runtime.v1.TimeSeriesValue + 76, // 155: rill.runtime.v1.TimeSeriesResponse.spark:type_name -> rill.runtime.v1.TimeSeriesValue + 96, // 156: rill.runtime.v1.TimeSeriesValue.ts:type_name -> google.protobuf.Timestamp + 94, // 157: rill.runtime.v1.TimeSeriesValue.records:type_name -> google.protobuf.Struct + 81, // 158: rill.runtime.v1.TableColumnsResponse.profile_columns:type_name -> rill.runtime.v1.ProfileColumn + 91, // 159: rill.runtime.v1.TableColumnsResponse.unsupported_columns:type_name -> rill.runtime.v1.TableColumnsResponse.UnsupportedColumnsEntry + 94, // 160: rill.runtime.v1.TableRowsResponse.data:type_name -> google.protobuf.Struct + 92, // 161: rill.runtime.v1.MetricsViewFilter.Cond.in:type_name -> google.protobuf.Value + 92, // 162: rill.runtime.v1.MetricsViewSearchResponse.SearchResult.value:type_name -> google.protobuf.Value + 92, // 163: rill.runtime.v1.TopK.Entry.value:type_name -> google.protobuf.Value + 4, // 164: rill.runtime.v1.QueryService.Query:input_type -> rill.runtime.v1.QueryRequest + 6, // 165: rill.runtime.v1.QueryService.QueryBatch:input_type -> rill.runtime.v1.QueryBatchRequest + 8, // 166: rill.runtime.v1.QueryService.Export:input_type -> rill.runtime.v1.ExportRequest + 12, // 167: rill.runtime.v1.QueryService.MetricsViewAggregation:input_type -> rill.runtime.v1.MetricsViewAggregationRequest + 22, // 168: rill.runtime.v1.QueryService.MetricsViewToplist:input_type -> rill.runtime.v1.MetricsViewToplistRequest + 24, // 169: rill.runtime.v1.QueryService.MetricsViewComparison:input_type -> rill.runtime.v1.MetricsViewComparisonRequest + 31, // 170: rill.runtime.v1.QueryService.MetricsViewTimeSeries:input_type -> rill.runtime.v1.MetricsViewTimeSeriesRequest + 33, // 171: rill.runtime.v1.QueryService.MetricsViewTotals:input_type -> rill.runtime.v1.MetricsViewTotalsRequest + 35, // 172: rill.runtime.v1.QueryService.MetricsViewRows:input_type -> rill.runtime.v1.MetricsViewRowsRequest + 41, // 173: rill.runtime.v1.QueryService.MetricsViewTimeRange:input_type -> rill.runtime.v1.MetricsViewTimeRangeRequest + 43, // 174: rill.runtime.v1.QueryService.MetricsViewSchema:input_type -> rill.runtime.v1.MetricsViewSchemaRequest + 45, // 175: rill.runtime.v1.QueryService.MetricsViewSearch:input_type -> rill.runtime.v1.MetricsViewSearchRequest + 47, // 176: rill.runtime.v1.QueryService.ColumnRollupInterval:input_type -> rill.runtime.v1.ColumnRollupIntervalRequest + 49, // 177: rill.runtime.v1.QueryService.ColumnTopK:input_type -> rill.runtime.v1.ColumnTopKRequest + 53, // 178: rill.runtime.v1.QueryService.ColumnNullCount:input_type -> rill.runtime.v1.ColumnNullCountRequest + 55, // 179: rill.runtime.v1.QueryService.ColumnDescriptiveStatistics:input_type -> rill.runtime.v1.ColumnDescriptiveStatisticsRequest + 61, // 180: rill.runtime.v1.QueryService.ColumnTimeGrain:input_type -> rill.runtime.v1.ColumnTimeGrainRequest + 63, // 181: rill.runtime.v1.QueryService.ColumnNumericHistogram:input_type -> rill.runtime.v1.ColumnNumericHistogramRequest + 65, // 182: rill.runtime.v1.QueryService.ColumnRugHistogram:input_type -> rill.runtime.v1.ColumnRugHistogramRequest + 67, // 183: rill.runtime.v1.QueryService.ColumnTimeRange:input_type -> rill.runtime.v1.ColumnTimeRangeRequest + 70, // 184: rill.runtime.v1.QueryService.ColumnCardinality:input_type -> rill.runtime.v1.ColumnCardinalityRequest + 72, // 185: rill.runtime.v1.QueryService.ColumnTimeSeries:input_type -> rill.runtime.v1.ColumnTimeSeriesRequest + 77, // 186: rill.runtime.v1.QueryService.TableCardinality:input_type -> rill.runtime.v1.TableCardinalityRequest + 79, // 187: rill.runtime.v1.QueryService.TableColumns:input_type -> rill.runtime.v1.TableColumnsRequest + 82, // 188: rill.runtime.v1.QueryService.TableRows:input_type -> rill.runtime.v1.TableRowsRequest + 5, // 189: rill.runtime.v1.QueryService.Query:output_type -> rill.runtime.v1.QueryResponse + 7, // 190: rill.runtime.v1.QueryService.QueryBatch:output_type -> rill.runtime.v1.QueryBatchResponse + 9, // 191: rill.runtime.v1.QueryService.Export:output_type -> rill.runtime.v1.ExportResponse + 13, // 192: rill.runtime.v1.QueryService.MetricsViewAggregation:output_type -> rill.runtime.v1.MetricsViewAggregationResponse + 23, // 193: rill.runtime.v1.QueryService.MetricsViewToplist:output_type -> rill.runtime.v1.MetricsViewToplistResponse + 25, // 194: rill.runtime.v1.QueryService.MetricsViewComparison:output_type -> rill.runtime.v1.MetricsViewComparisonResponse + 32, // 195: rill.runtime.v1.QueryService.MetricsViewTimeSeries:output_type -> rill.runtime.v1.MetricsViewTimeSeriesResponse + 34, // 196: rill.runtime.v1.QueryService.MetricsViewTotals:output_type -> rill.runtime.v1.MetricsViewTotalsResponse + 36, // 197: rill.runtime.v1.QueryService.MetricsViewRows:output_type -> rill.runtime.v1.MetricsViewRowsResponse + 42, // 198: rill.runtime.v1.QueryService.MetricsViewTimeRange:output_type -> rill.runtime.v1.MetricsViewTimeRangeResponse + 44, // 199: rill.runtime.v1.QueryService.MetricsViewSchema:output_type -> rill.runtime.v1.MetricsViewSchemaResponse + 46, // 200: rill.runtime.v1.QueryService.MetricsViewSearch:output_type -> rill.runtime.v1.MetricsViewSearchResponse + 48, // 201: rill.runtime.v1.QueryService.ColumnRollupInterval:output_type -> rill.runtime.v1.ColumnRollupIntervalResponse + 50, // 202: rill.runtime.v1.QueryService.ColumnTopK:output_type -> rill.runtime.v1.ColumnTopKResponse + 54, // 203: rill.runtime.v1.QueryService.ColumnNullCount:output_type -> rill.runtime.v1.ColumnNullCountResponse + 56, // 204: rill.runtime.v1.QueryService.ColumnDescriptiveStatistics:output_type -> rill.runtime.v1.ColumnDescriptiveStatisticsResponse + 62, // 205: rill.runtime.v1.QueryService.ColumnTimeGrain:output_type -> rill.runtime.v1.ColumnTimeGrainResponse + 64, // 206: rill.runtime.v1.QueryService.ColumnNumericHistogram:output_type -> rill.runtime.v1.ColumnNumericHistogramResponse + 66, // 207: rill.runtime.v1.QueryService.ColumnRugHistogram:output_type -> rill.runtime.v1.ColumnRugHistogramResponse + 68, // 208: rill.runtime.v1.QueryService.ColumnTimeRange:output_type -> rill.runtime.v1.ColumnTimeRangeResponse + 71, // 209: rill.runtime.v1.QueryService.ColumnCardinality:output_type -> rill.runtime.v1.ColumnCardinalityResponse + 73, // 210: rill.runtime.v1.QueryService.ColumnTimeSeries:output_type -> rill.runtime.v1.ColumnTimeSeriesResponse + 78, // 211: rill.runtime.v1.QueryService.TableCardinality:output_type -> rill.runtime.v1.TableCardinalityResponse + 80, // 212: rill.runtime.v1.QueryService.TableColumns:output_type -> rill.runtime.v1.TableColumnsResponse + 83, // 213: rill.runtime.v1.QueryService.TableRows:output_type -> rill.runtime.v1.TableRowsResponse + 189, // [189:214] is the sub-list for method output_type + 164, // [164:189] is the sub-list for method input_type + 164, // [164:164] is the sub-list for extension type_name + 164, // [164:164] is the sub-list for extension extendee + 0, // [0:164] is the sub-list for field type_name } func init() { file_rill_runtime_v1_queries_proto_init() } diff --git a/proto/gen/rill/runtime/v1/queries.pb.validate.go b/proto/gen/rill/runtime/v1/queries.pb.validate.go index 015c1afa2a1..ea7e7bc58f1 100644 --- a/proto/gen/rill/runtime/v1/queries.pb.validate.go +++ b/proto/gen/rill/runtime/v1/queries.pb.validate.go @@ -4499,40 +4499,6 @@ func (m *MetricsViewToplistRequest) validate(all bool) error { errors = append(errors, err) } - for idx, item := range m.GetInlineMeasures() { - _, _ = idx, item - - if all { - switch v := interface{}(item).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, MetricsViewToplistRequestValidationError{ - field: fmt.Sprintf("InlineMeasures[%v]", idx), - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, MetricsViewToplistRequestValidationError{ - field: fmt.Sprintf("InlineMeasures[%v]", idx), - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return MetricsViewToplistRequestValidationError{ - field: fmt.Sprintf("InlineMeasures[%v]", idx), - reason: "embedded message failed validation", - cause: err, - } - } - } - - } - if all { switch v := interface{}(m.GetTimeStart()).(type) { case interface{ ValidateAll() error }: @@ -6389,40 +6355,6 @@ func (m *MetricsViewTimeSeriesRequest) validate(all bool) error { errors = append(errors, err) } - for idx, item := range m.GetInlineMeasures() { - _, _ = idx, item - - if all { - switch v := interface{}(item).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, MetricsViewTimeSeriesRequestValidationError{ - field: fmt.Sprintf("InlineMeasures[%v]", idx), - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, MetricsViewTimeSeriesRequestValidationError{ - field: fmt.Sprintf("InlineMeasures[%v]", idx), - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return MetricsViewTimeSeriesRequestValidationError{ - field: fmt.Sprintf("InlineMeasures[%v]", idx), - reason: "embedded message failed validation", - cause: err, - } - } - } - - } - if all { switch v := interface{}(m.GetTimeStart()).(type) { case interface{ ValidateAll() error }: @@ -6885,40 +6817,6 @@ func (m *MetricsViewTotalsRequest) validate(all bool) error { errors = append(errors, err) } - for idx, item := range m.GetInlineMeasures() { - _, _ = idx, item - - if all { - switch v := interface{}(item).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, MetricsViewTotalsRequestValidationError{ - field: fmt.Sprintf("InlineMeasures[%v]", idx), - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, MetricsViewTotalsRequestValidationError{ - field: fmt.Sprintf("InlineMeasures[%v]", idx), - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return MetricsViewTotalsRequestValidationError{ - field: fmt.Sprintf("InlineMeasures[%v]", idx), - reason: "embedded message failed validation", - cause: err, - } - } - } - - } - if all { switch v := interface{}(m.GetTimeStart()).(type) { case interface{ ValidateAll() error }: diff --git a/proto/gen/rill/runtime/v1/runtime.swagger.yaml b/proto/gen/rill/runtime/v1/runtime.swagger.yaml index f5e2b48140b..7c20d6e83f5 100644 --- a/proto/gen/rill/runtime/v1/runtime.swagger.yaml +++ b/proto/gen/rill/runtime/v1/runtime.swagger.yaml @@ -1553,12 +1553,6 @@ paths: type: array items: type: string - title: Required either measure_names or inline_measures - inlineMeasures: - type: array - items: - type: object - $ref: '#/definitions/v1InlineMeasure' timeStart: type: string format: date-time @@ -1624,11 +1618,6 @@ paths: type: array items: type: string - inlineMeasures: - type: array - items: - type: object - $ref: '#/definitions/v1InlineMeasure' timeStart: type: string format: date-time @@ -1693,12 +1682,6 @@ paths: type: array items: type: string - title: Required either measure_names or inline_measures - inlineMeasures: - type: array - items: - type: object - $ref: '#/definitions/v1InlineMeasure' timeStart: type: string format: date-time @@ -2931,7 +2914,7 @@ definitions: `NullValue` is a singleton enumeration to represent the null value for the `Value` type union. - The JSON representation for `NullValue` is JSON `null`. + The JSON representation for `NullValue` is JSON `null`. - NULL_VALUE: Null value. rpcStatus: @@ -3891,15 +3874,6 @@ definitions: - HISTOGRAM_METHOD_FD - HISTOGRAM_METHOD_DIAGNOSTIC default: HISTOGRAM_METHOD_UNSPECIFIED - v1InlineMeasure: - type: object - properties: - name: - type: string - title: Required - expression: - type: string - title: Required, ie 'count(*)' v1Instance: type: object properties: @@ -4569,12 +4543,6 @@ definitions: type: array items: type: string - title: Required either measure_names or inline_measures - inlineMeasures: - type: array - items: - type: object - $ref: '#/definitions/v1InlineMeasure' timeStart: type: string format: date-time @@ -4629,11 +4597,6 @@ definitions: type: array items: type: string - inlineMeasures: - type: array - items: - type: object - $ref: '#/definitions/v1InlineMeasure' timeStart: type: string format: date-time @@ -4687,12 +4650,6 @@ definitions: type: array items: type: string - title: Required either measure_names or inline_measures - inlineMeasures: - type: array - items: - type: object - $ref: '#/definitions/v1InlineMeasure' timeStart: type: string format: date-time diff --git a/proto/rill/runtime/v1/queries.proto b/proto/rill/runtime/v1/queries.proto index 11396156f1d..55f235102c3 100644 --- a/proto/rill/runtime/v1/queries.proto +++ b/proto/rill/runtime/v1/queries.proto @@ -429,7 +429,6 @@ message MetricsViewToplistRequest { string metrics_view_name = 2 [(validate.rules).string.min_len = 1]; string dimension_name = 3 [(validate.rules).string.min_len = 1]; repeated string measure_names = 4; - repeated InlineMeasure inline_measures = 12; google.protobuf.Timestamp time_start = 5; google.protobuf.Timestamp time_end = 6; int64 limit = 7 [(validate.rules).int64.gte = 0]; @@ -559,9 +558,7 @@ message MetricsViewComparisonMeasureAlias { message MetricsViewTimeSeriesRequest { string instance_id = 1; string metrics_view_name = 2 [(validate.rules).string.min_len = 1]; - // Required either measure_names or inline_measures repeated string measure_names = 3 [(validate.rules).repeated.min_items = 1]; - repeated InlineMeasure inline_measures = 9; // Optional. Defaults to min google.protobuf.Timestamp time_start = 4; // Optional. Defaults to max @@ -590,9 +587,7 @@ message MetricsViewTimeSeriesResponse { message MetricsViewTotalsRequest { string instance_id = 1; string metrics_view_name = 2 [(validate.rules).string.min_len = 1]; - // Required either measure_names or inline_measures repeated string measure_names = 3 [(validate.rules).repeated.min_items = 1]; - repeated InlineMeasure inline_measures = 9; // Optional. Defaults to min google.protobuf.Timestamp time_start = 4; // Optional. Defaults to max diff --git a/runtime/drivers/duckdb/model_executor_self_file.go b/runtime/drivers/duckdb/model_executor_self_file.go index 69ea3982fa1..78fd2886459 100644 --- a/runtime/drivers/duckdb/model_executor_self_file.go +++ b/runtime/drivers/duckdb/model_executor_self_file.go @@ -72,22 +72,22 @@ func (e *selfToFileExecutor) Execute(ctx context.Context) (*drivers.ModelResult, }, nil } -func exportSQL(qry, path, format string) (string, error) { +func exportSQL(qry, path string, format drivers.FileFormat) (string, error) { switch format { - case "parquet": + case drivers.FileFormatParquet: return fmt.Sprintf("COPY (%s\n) TO '%s' (FORMAT PARQUET)", qry, path), nil - case "csv": + case drivers.FileFormatCSV: return fmt.Sprintf("COPY (%s\n) TO '%s' (FORMAT CSV, HEADER true)", qry, path), nil - case "json": + case drivers.FileFormatJSON: return fmt.Sprintf("COPY (%s\n) TO '%s' (FORMAT JSON)", qry, path), nil default: return "", fmt.Errorf("duckdb: unsupported export format %q", format) } } -func supportsExportFormat(format string) bool { +func supportsExportFormat(format drivers.FileFormat) bool { switch format { - case "parquet", "csv", "json": + case drivers.FileFormatParquet, drivers.FileFormatCSV, drivers.FileFormatJSON: return true default: return false diff --git a/runtime/drivers/file/model_executor.go b/runtime/drivers/file/model_executor.go index f607b3167e6..63fff050d58 100644 --- a/runtime/drivers/file/model_executor.go +++ b/runtime/drivers/file/model_executor.go @@ -1,10 +1,14 @@ package file -import "fmt" +import ( + "fmt" + + "github.com/rilldata/rill/runtime/drivers" +) type ModelOutputProperties struct { - Path string `mapstructure:"path"` - Format string `mapstructure:"format"` + Path string `mapstructure:"path"` + Format drivers.FileFormat `mapstructure:"format"` } func (p *ModelOutputProperties) Validate() error { @@ -13,11 +17,13 @@ func (p *ModelOutputProperties) Validate() error { } if p.Format == "" { return fmt.Errorf("missing property 'format'") + } else if !p.Format.Valid() { + return fmt.Errorf("invalid property 'format': %q", p.Format) } return nil } type ModelResultProperties struct { - Path string `mapstructure:"path"` - Format string `mapstructure:"format"` + Path string `mapstructure:"path"` + Format drivers.FileFormat `mapstructure:"format"` } diff --git a/runtime/drivers/file/model_executor_olap_self.go b/runtime/drivers/file/model_executor_olap_self.go index f9c551827c3..fe3aeceb0ee 100644 --- a/runtime/drivers/file/model_executor_olap_self.go +++ b/runtime/drivers/file/model_executor_olap_self.go @@ -62,12 +62,14 @@ func (e *olapToSelfExecutor) Execute(ctx context.Context) (*drivers.ModelResult, defer res.Close() switch outputProps.Format { - case "csv": + case drivers.FileFormatParquet: + err = writeParquet(res, outputProps.Path) + case drivers.FileFormatCSV: err = writeCSV(res, outputProps.Path) - case "xlsx": + case drivers.FileFormatJSON: + return nil, errors.New("json file output not currently supported") + case drivers.FileFormatXLSX: err = writeXLSX(res, outputProps.Path) - case "parquet": - err = writeParquet(res, outputProps.Path) default: return nil, fmt.Errorf("unsupported output format %q", outputProps.Format) } diff --git a/runtime/drivers/models.go b/runtime/drivers/models.go index 04432f6fc3e..c76384b2ee3 100644 --- a/runtime/drivers/models.go +++ b/runtime/drivers/models.go @@ -40,3 +40,25 @@ type ModelExecutorOptions struct { IncrementalRun bool PreviousResult *ModelResult } + +type FileFormat string + +const ( + FileFormatUnspecified FileFormat = "" + FileFormatParquet FileFormat = "parquet" + FileFormatCSV FileFormat = "csv" + FileFormatJSON FileFormat = "json" + FileFormatXLSX FileFormat = "xlsx" +) + +func (f FileFormat) Filename(stem string) string { + return stem + "." + string(f) +} + +func (f FileFormat) Valid() bool { + switch f { + case FileFormatParquet, FileFormatCSV, FileFormatJSON, FileFormatXLSX: + return true + } + return false +} diff --git a/runtime/metricsview/astexpr.go b/runtime/metricsview/astexpr.go index 9e03271fe00..bc92443bf78 100644 --- a/runtime/metricsview/astexpr.go +++ b/runtime/metricsview/astexpr.go @@ -408,10 +408,14 @@ func (b *sqlExprBuilder) writeInCondition(left, right *Expression, leftOverride } func (b *sqlExprBuilder) writeInConditionForValues(left *Expression, leftOverride string, vals []any, not bool) error { - var hasNull bool + var hasNull, hasNonNull bool for _, v := range vals { if v == nil { hasNull = true + } else { + hasNonNull = true + } + if hasNull && hasNonNull { break } } @@ -425,42 +429,51 @@ func (b *sqlExprBuilder) writeInConditionForValues(left *Expression, leftOverrid return nil } - wrapParens := not || hasNull + wrapParens := not || (hasNull && hasNonNull) if wrapParens { b.writeByte('(') } - if leftOverride != "" { - b.writeParenthesizedString(leftOverride) - } else { - err := b.writeExpression(left) - if err != nil { - return err + if hasNonNull { + if leftOverride != "" { + b.writeParenthesizedString(leftOverride) + } else { + err := b.writeExpression(left) + if err != nil { + return err + } } - } - - if not { - b.writeString(" NOT IN ") - } else { - b.writeString(" IN ") - } - b.writeByte('(') - for i := 0; i < len(vals); i++ { - if i == 0 { - b.writeString("?") + if not { + b.writeString(" NOT IN ") } else { - b.writeString(",?") + b.writeString(" IN ") } + + b.writeByte('(') + var comma bool + for _, val := range vals { + if val == nil { + continue + } + if comma { + b.writeString(",?") + } else { + comma = true + b.writeString("?") + } + b.args = append(b.args, val) + } + b.writeByte(')') } - b.writeByte(')') - b.args = append(b.args, vals...) if hasNull { - if not { - b.writeString(" AND ") - } else { - b.writeString(" OR ") + if hasNonNull { + if not { + b.writeString(" AND ") + } else { + b.writeString(" OR ") + } } if leftOverride != "" { diff --git a/runtime/metricsview/executor.go b/runtime/metricsview/executor.go index 0e682b49f8b..fd8d741fe14 100644 --- a/runtime/metricsview/executor.go +++ b/runtime/metricsview/executor.go @@ -261,7 +261,7 @@ func (e *Executor) Query(ctx context.Context, qry *Query, executionTime *time.Ti // Export executes and exports the provided query against the metrics view. // It returns a path to a temporary file containing the export. The caller is responsible for cleaning up the file. -func (e *Executor) Export(ctx context.Context, qry *Query, executionTime *time.Time, format string) (string, error) { +func (e *Executor) Export(ctx context.Context, qry *Query, executionTime *time.Time, format drivers.FileFormat) (string, error) { if e.security != nil && !e.security.Access { return "", runtime.ErrForbidden } diff --git a/runtime/metricsview/executor_export.go b/runtime/metricsview/executor_export.go index 8a9d6711259..5448ba16e68 100644 --- a/runtime/metricsview/executor_export.go +++ b/runtime/metricsview/executor_export.go @@ -15,7 +15,7 @@ import ( // executeExport works by simulating a model that outputs to a file. // This means it creates a ModelExecutor with the provided input connector and props as input, // and with the "file" driver as the output connector targeting a temporary output path. -func (e *Executor) executeExport(ctx context.Context, format, inputConnector string, inputProps map[string]any) (string, error) { +func (e *Executor) executeExport(ctx context.Context, format drivers.FileFormat, inputConnector string, inputProps map[string]any) (string, error) { ctx, cancel := context.WithTimeout(ctx, defaultExportTimeout) defer cancel() @@ -29,7 +29,7 @@ func (e *Executor) executeExport(ctx context.Context, format, inputConnector str if err != nil { return "", err } - name = fmt.Sprintf("%s.%s", name, format) + name = format.Filename(name) path = filepath.Join(path, name) ic, ir, err := e.rt.AcquireHandle(ctx, e.instanceID, inputConnector) diff --git a/runtime/metricsview/executor_pivot.go b/runtime/metricsview/executor_pivot.go index 8bd281dd14c..054c43160e3 100644 --- a/runtime/metricsview/executor_pivot.go +++ b/runtime/metricsview/executor_pivot.go @@ -115,7 +115,7 @@ func (e *Executor) rewriteQueryForPivot(qry *Query) (*pivotAST, bool, error) { } // executePivotExport executes a PIVOT query prepared using rewriteQueryForPivot, and exports the result to a file in the given format. -func (e *Executor) executePivotExport(ctx context.Context, ast *AST, pivot *pivotAST, format string) (string, error) { +func (e *Executor) executePivotExport(ctx context.Context, ast *AST, pivot *pivotAST, format drivers.FileFormat) (string, error) { ctx, cancel := context.WithTimeout(ctx, defaultPivotExportTimeout) defer cancel() diff --git a/runtime/queries/column_timeseries_benchmark_test.go b/runtime/queries/column_timeseries_benchmark_test.go index 0bec1527f56..dccc6367e88 100644 --- a/runtime/queries/column_timeseries_benchmark_test.go +++ b/runtime/queries/column_timeseries_benchmark_test.go @@ -7,11 +7,12 @@ import ( runtimev1 "github.com/rilldata/rill/proto/gen/rill/runtime/v1" _ "github.com/rilldata/rill/runtime/drivers/duckdb" "github.com/rilldata/rill/runtime/queries" + "github.com/rilldata/rill/runtime/testruntime" "github.com/stretchr/testify/require" ) func BenchmarkTimeSeries_hourly(b *testing.B) { - rt, instanceID, mv := prepareEnvironment(b) + rt, instanceID := testruntime.NewInstanceForProject(b, "ad_bids") q := &queries.ColumnTimeseries{ TableName: "ad_bids", TimestampColumnName: "timestamp", @@ -25,7 +26,6 @@ func BenchmarkTimeSeries_hourly(b *testing.B) { Expression: "avg(bid_price)", }, }, - MetricsView: mv, } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -35,7 +35,7 @@ func BenchmarkTimeSeries_hourly(b *testing.B) { } } func BenchmarkTimeSeries_daily(b *testing.B) { - rt, instanceID, mv := prepareEnvironment(b) + rt, instanceID := testruntime.NewInstanceForProject(b, "ad_bids") q := &queries.ColumnTimeseries{ TableName: "ad_bids", TimestampColumnName: "timestamp", @@ -49,7 +49,6 @@ func BenchmarkTimeSeries_daily(b *testing.B) { Expression: "avg(bid_price)", }, }, - MetricsView: mv, } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -60,7 +59,7 @@ func BenchmarkTimeSeries_daily(b *testing.B) { } func BenchmarkTimeSeries_weekly(b *testing.B) { - rt, instanceID, mv := prepareEnvironment(b) + rt, instanceID := testruntime.NewInstanceForProject(b, "ad_bids") q := &queries.ColumnTimeseries{ TableName: "ad_bids", TimestampColumnName: "timestamp", @@ -74,7 +73,6 @@ func BenchmarkTimeSeries_weekly(b *testing.B) { Expression: "avg(bid_price)", }, }, - MetricsView: mv, } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -84,7 +82,7 @@ func BenchmarkTimeSeries_weekly(b *testing.B) { } } func BenchmarkTimeSeries_weekly_first_day_of_week_Monday(b *testing.B) { - rt, instanceID, mv := prepareEnvironment(b) + rt, instanceID := testruntime.NewInstanceForProject(b, "ad_bids") q := &queries.ColumnTimeseries{ TableName: "ad_bids", @@ -98,8 +96,7 @@ func BenchmarkTimeSeries_weekly_first_day_of_week_Monday(b *testing.B) { Expression: "avg(bid_price)", }, }, - MetricsView: mv, - TimeZone: "Asia/Kathmandu", + TimeZone: "Asia/Kathmandu", } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -110,7 +107,7 @@ func BenchmarkTimeSeries_weekly_first_day_of_week_Monday(b *testing.B) { } func BenchmarkTimeSeries_weekly_first_day_of_week_Sunday(b *testing.B) { - rt, instanceID, mv := prepareEnvironment(b) + rt, instanceID := testruntime.NewInstanceForProject(b, "ad_bids") q := &queries.ColumnTimeseries{ TableName: "ad_bids", @@ -125,7 +122,6 @@ func BenchmarkTimeSeries_weekly_first_day_of_week_Sunday(b *testing.B) { }, }, FirstDayOfWeek: 7, - MetricsView: mv, TimeZone: "Asia/Kathmandu", } diff --git a/runtime/queries/metricsview.go b/runtime/queries/metricsview.go index b0d5e448e44..ded84e8bb3a 100644 --- a/runtime/queries/metricsview.go +++ b/runtime/queries/metricsview.go @@ -114,43 +114,6 @@ func checkFieldAccess(field string, policy *runtime.ResolvedMetricsViewSecurity) return true } -// resolveMeasures returns the selected measures -func resolveMeasures(mv *runtimev1.MetricsViewSpec, inlines []*runtimev1.InlineMeasure, selectedNames []string) ([]*runtimev1.MetricsViewSpec_MeasureV2, error) { - // Build combined measures - ms := make([]*runtimev1.MetricsViewSpec_MeasureV2, len(selectedNames)) - for i, n := range selectedNames { - found := false - // Search in the inlines (take precedence) - for _, m := range inlines { - if m.Name == n { - ms[i] = &runtimev1.MetricsViewSpec_MeasureV2{ - Name: m.Name, - Expression: m.Expression, - Type: runtimev1.MetricsViewSpec_MEASURE_TYPE_SIMPLE, - } - found = true - break - } - } - if found { - continue - } - // Search in the metrics view - for _, m := range mv.Measures { - if m.Name == n { - ms[i] = m - found = true - break - } - } - if !found { - return nil, fmt.Errorf("measure does not exist: '%s'", n) - } - } - - return ms, nil -} - func metricsQuery(ctx context.Context, olap drivers.OLAPStore, priority int, sql string, args []any) ([]*runtimev1.MetricsViewColumn, []*structpb.Struct, error) { rows, err := olap.Execute(ctx, &drivers.Statement{ Query: sql, @@ -603,24 +566,6 @@ func convertToXLSXValue(pbvalue *structpb.Value) (interface{}, error) { } } -func metricsViewDimension(mv *runtimev1.MetricsViewSpec, dimName string) (*runtimev1.MetricsViewSpec_DimensionV2, error) { - for _, dimension := range mv.Dimensions { - if strings.EqualFold(dimension.Name, dimName) { - return dimension, nil - } - } - return nil, fmt.Errorf("dimension %s not found", dimName) -} - -func metricsViewMeasureExpression(mv *runtimev1.MetricsViewSpec, measureName string) (string, error) { - for _, measure := range mv.Measures { - if strings.EqualFold(measure.Name, measureName) { - return measure.Expression, nil - } - } - return "", fmt.Errorf("measure %s not found", measureName) -} - func WriteCSV(meta []*runtimev1.MetricsViewColumn, data []*structpb.Struct, writer io.Writer) error { w := csv.NewWriter(writer) diff --git a/runtime/queries/metricsview_aggregation.go b/runtime/queries/metricsview_aggregation.go index c9be2e4a453..a6937d703e3 100644 --- a/runtime/queries/metricsview_aggregation.go +++ b/runtime/queries/metricsview_aggregation.go @@ -10,6 +10,7 @@ import ( runtimev1 "github.com/rilldata/rill/proto/gen/rill/runtime/v1" "github.com/rilldata/rill/runtime" + "github.com/rilldata/rill/runtime/drivers" "github.com/rilldata/rill/runtime/metricsview" ) @@ -32,8 +33,8 @@ type MetricsViewAggregation struct { Aliases []*runtimev1.MetricsViewComparisonMeasureAlias `json:"aliases,omitempty"` Exact bool `json:"exact,omitempty"` - Exporting bool `json:"-"` Result *runtimev1.MetricsViewAggregationResponse `json:"-"` + Exporting bool `json:"-"` // Deprecated: Remove when tests call Export directly } var _ runtime.Query = &MetricsViewAggregation{} @@ -74,7 +75,7 @@ func (q *MetricsViewAggregation) Resolve(ctx context.Context, rt *runtime.Runtim return err } - qry, err := q.rewriteToMetricsViewQuery() + qry, err := q.rewriteToMetricsViewQuery(q.Exporting) if err != nil { return fmt.Errorf("error rewriting to metrics query: %w", err) } @@ -104,8 +105,6 @@ func (q *MetricsViewAggregation) Resolve(ctx context.Context, rt *runtime.Runtim } func (q *MetricsViewAggregation) Export(ctx context.Context, rt *runtime.Runtime, instanceID string, w io.Writer, opts *runtime.ExportOptions) error { - q.Exporting = true - filename := strings.ReplaceAll(q.MetricsViewName, `"`, `_`) if !isTimeRangeNil(q.TimeRange) || q.Where != nil || q.Having != nil { filename += "_filtered" @@ -118,7 +117,7 @@ func (q *MetricsViewAggregation) Export(ctx context.Context, rt *runtime.Runtime } // Route to metricsview executor - qry, err := q.rewriteToMetricsViewQuery() + qry, err := q.rewriteToMetricsViewQuery(true) if err != nil { return fmt.Errorf("error rewriting to metrics query: %w", err) } @@ -129,14 +128,14 @@ func (q *MetricsViewAggregation) Export(ctx context.Context, rt *runtime.Runtime } defer e.Close() - var format string + var format drivers.FileFormat switch opts.Format { case runtimev1.ExportFormat_EXPORT_FORMAT_CSV: - format = "csv" + format = drivers.FileFormatCSV case runtimev1.ExportFormat_EXPORT_FORMAT_XLSX: - format = "xlsx" + format = drivers.FileFormatXLSX case runtimev1.ExportFormat_EXPORT_FORMAT_PARQUET: - format = "parquet" + format = drivers.FileFormatParquet default: return fmt.Errorf("unsupported format: %s", opts.Format.String()) } @@ -166,7 +165,7 @@ func (q *MetricsViewAggregation) Export(ctx context.Context, rt *runtime.Runtime return nil } -func (q *MetricsViewAggregation) rewriteToMetricsViewQuery() (*metricsview.Query, error) { +func (q *MetricsViewAggregation) rewriteToMetricsViewQuery(export bool) (*metricsview.Query, error) { qry := &metricsview.Query{MetricsView: q.MetricsViewName} for _, d := range q.Dimensions { @@ -188,7 +187,7 @@ func (q *MetricsViewAggregation) rewriteToMetricsViewQuery() (*metricsview.Query qry.Dimensions = append(qry.Dimensions, res) } - var measuresFilter *runtimev1.Expression + var measureFilter *runtimev1.Expression for _, m := range q.Measures { res := metricsview.Measure{Name: m.Name} @@ -205,7 +204,7 @@ func (q *MetricsViewAggregation) rewriteToMetricsViewQuery() (*metricsview.Query if len(q.Measures) > 1 { return nil, fmt.Errorf("measure-level filter is not supported when multiple measures are present") } - measuresFilter = m.Filter + measureFilter = m.Filter } if m.Compute != nil { @@ -265,7 +264,7 @@ func (q *MetricsViewAggregation) rewriteToMetricsViewQuery() (*metricsview.Query qry.ComparisonTimeRange = res } - if q.Filter != nil { // backwards backwards compatibility + if q.Filter != nil { // Backwards compatibility if q.Where != nil { return nil, fmt.Errorf("both filter and where is provided") } @@ -277,20 +276,18 @@ func (q *MetricsViewAggregation) rewriteToMetricsViewQuery() (*metricsview.Query } // If a measure-level filter is present, we set qry.Where as the spine, and use (qry.Where AND measuresFilter) as the new where clause - if measuresFilter != nil { - measuresFilter := metricsview.NewExpressionFromProto(measuresFilter) - + if measureFilter != nil { qry.Spine = &metricsview.Spine{Where: &metricsview.WhereSpine{Expression: qry.Where}} if qry.Where == nil { - qry.Where = measuresFilter + qry.Where = metricsview.NewExpressionFromProto(measureFilter) } else { qry.Where = &metricsview.Expression{ Condition: &metricsview.Condition{ Operator: metricsview.OperatorAnd, Expressions: []*metricsview.Expression{ qry.Where, - measuresFilter, + metricsview.NewExpressionFromProto(measureFilter), }, }, } @@ -317,7 +314,7 @@ func (q *MetricsViewAggregation) rewriteToMetricsViewQuery() (*metricsview.Query qry.PivotOn = q.PivotOn } - qry.Label = q.Exporting + qry.Label = export return qry, nil } diff --git a/runtime/queries/metricsview_api_benchmark_test.go b/runtime/queries/metricsview_api_benchmark_test.go index 2858b6702f3..e120fb5f781 100644 --- a/runtime/queries/metricsview_api_benchmark_test.go +++ b/runtime/queries/metricsview_api_benchmark_test.go @@ -5,7 +5,6 @@ import ( "testing" runtimev1 "github.com/rilldata/rill/proto/gen/rill/runtime/v1" - "github.com/rilldata/rill/runtime" _ "github.com/rilldata/rill/runtime/drivers/duckdb" "github.com/rilldata/rill/runtime/queries" "github.com/rilldata/rill/runtime/testruntime" @@ -13,14 +12,13 @@ import ( ) func BenchmarkMetricsViewsTotals(b *testing.B) { - rt, instanceID, mv := prepareEnvironment(b) + rt, instanceID := testruntime.NewInstanceForProject(b, "ad_bids") b.ResetTimer() for i := 0; i < b.N; i++ { q := &queries.MetricsViewTotals{ MetricsViewName: "ad_bids_metrics", MeasureNames: []string{"measure_1"}, - MetricsView: mv, } err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(b, err) @@ -29,7 +27,7 @@ func BenchmarkMetricsViewsTotals(b *testing.B) { } func BenchmarkMetricsViewsToplist(b *testing.B) { - rt, instanceID, mv := prepareEnvironment(b) + rt, instanceID := testruntime.NewInstanceForProject(b, "ad_bids") b.ResetTimer() for i := 0; i < b.N; i++ { @@ -42,7 +40,6 @@ func BenchmarkMetricsViewsToplist(b *testing.B) { Name: "measure_1", }, }, - MetricsView: mv, } err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(b, err) @@ -51,7 +48,7 @@ func BenchmarkMetricsViewsToplist(b *testing.B) { } func BenchmarkMetricsViewsTimeSeries(b *testing.B) { - rt, instanceID, mv := prepareEnvironment(b) + rt, instanceID := testruntime.NewInstanceForProject(b, "ad_bids") b.ResetTimer() for i := 0; i < b.N; i++ { @@ -59,7 +56,6 @@ func BenchmarkMetricsViewsTimeSeries(b *testing.B) { MetricsViewName: "ad_bids_metrics", TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_DAY, MeasureNames: []string{"measure_1"}, - MetricsView: mv, } err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(b, err) @@ -68,7 +64,7 @@ func BenchmarkMetricsViewsTimeSeries(b *testing.B) { } func BenchmarkMetricsViewsTimeSeries_TimeZone(b *testing.B) { - rt, instanceID, mv := prepareEnvironment(b) + rt, instanceID := testruntime.NewInstanceForProject(b, "ad_bids") b.ResetTimer() for i := 0; i < b.N; i++ { @@ -77,7 +73,6 @@ func BenchmarkMetricsViewsTimeSeries_TimeZone(b *testing.B) { TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_DAY, MeasureNames: []string{"measure_1"}, TimeZone: "Asia/Kathmandu", - MetricsView: mv, } err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(b, err) @@ -86,7 +81,7 @@ func BenchmarkMetricsViewsTimeSeries_TimeZone(b *testing.B) { } func BenchmarkMetricsViewsTimeSeries_TimeZone_Hour(b *testing.B) { - rt, instanceID, mv := prepareEnvironment(b) + rt, instanceID := testruntime.NewInstanceForProject(b, "ad_bids") b.ResetTimer() for i := 0; i < b.N; i++ { q := &queries.MetricsViewTimeSeries{ @@ -94,23 +89,9 @@ func BenchmarkMetricsViewsTimeSeries_TimeZone_Hour(b *testing.B) { TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_HOUR, MeasureNames: []string{"measure_1"}, TimeZone: "Asia/Kathmandu", - MetricsView: mv, } err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(b, err) require.NotEmpty(b, q.Result) } } - -func prepareEnvironment(b *testing.B) (*runtime.Runtime, string, *runtimev1.MetricsViewSpec) { - rt, instanceID := testruntime.NewInstanceForProject(b, "ad_bids") - - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(b, err) - - obj, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "ad_bids_metrics"}, false) - require.NoError(b, err) - - mv := obj.GetMetricsView().Spec - return rt, instanceID, mv -} diff --git a/runtime/queries/metricsview_comparison_toplist.go b/runtime/queries/metricsview_comparison_toplist.go index 4858e6a650d..3dbe8aaf47c 100644 --- a/runtime/queries/metricsview_comparison_toplist.go +++ b/runtime/queries/metricsview_comparison_toplist.go @@ -5,14 +5,14 @@ import ( "encoding/json" "fmt" "io" + "os" "strings" runtimev1 "github.com/rilldata/rill/proto/gen/rill/runtime/v1" "github.com/rilldata/rill/runtime" "github.com/rilldata/rill/runtime/drivers" - "github.com/rilldata/rill/runtime/pkg/expressionpb" + "github.com/rilldata/rill/runtime/metricsview" "github.com/rilldata/rill/runtime/pkg/pbutil" - "google.golang.org/protobuf/types/known/structpb" // Load IANA time zone data _ "time/tzdata" @@ -36,10 +36,8 @@ type MetricsViewComparison struct { SecurityAttributes map[string]any `json:"security_attributes,omitempty"` SecurityPolicy *runtimev1.MetricsViewSpec_SecurityV2 `json:"security_policy,omitempty"` - Result *runtimev1.MetricsViewComparisonResponse `json:"-"` - - // Internal - measuresMeta map[string]metricsViewMeasureMeta `json:"-"` + Result *runtimev1.MetricsViewComparisonResponse `json:"-"` + measuresMeta map[string]metricsViewMeasureMeta `json:"-"` } type metricsViewMeasureMeta struct { @@ -87,1207 +85,393 @@ func (q *MetricsViewComparison) Resolve(ctx context.Context, rt *runtime.Runtime return err } - olap, release, err := rt.OLAP(ctx, instanceID, mv.Connector) - if err != nil { - return err - } - defer release() - - if olap.Dialect() != drivers.DialectDuckDB && olap.Dialect() != drivers.DialectDruid && olap.Dialect() != drivers.DialectClickHouse && olap.Dialect() != drivers.DialectPinot { - return fmt.Errorf("not available for dialect '%s'", olap.Dialect()) - } - - if mv.TimeDimension == "" && (!isTimeRangeNil(q.TimeRange) || !isTimeRangeNil(q.ComparisonTimeRange)) { - return fmt.Errorf("metrics view '%s' does not have a time dimension", q.MetricsViewName) - } - - // backwards compatibility - if q.Filter != nil { - if q.Where != nil { - return fmt.Errorf("both filter and where is provided") - } - q.Where = convertFilterToExpression(q.Filter) - } - err = q.calculateMeasuresMeta() if err != nil { return err } - // comparison toplist - if !isTimeRangeNil(q.ComparisonTimeRange) { - // execute toplist for base and get dim list - // create and add filter and execute comprison toplist - // remove strict limits in comp toplist sql - if drivers.DialectDruid != olap.Dialect() || q.Exact { - return q.executeComparisonToplist(ctx, olap, mv, priority, security) - } - - return q.executeDruidApproximateComparison(ctx, olap, mv, priority, security) - } - - // general toplist - if drivers.DialectDruid != olap.Dialect() || q.Exact { - return q.executeToplist(ctx, olap, mv, priority, security) - } - - return q.executeDruidApproximateToplist(ctx, olap, mv, priority, security) -} - -// Druid-based `exactify` approach: -// 1. The first query fetch topN dimensions. -// 2. The second query fetches topN filtered by the collected dimensions. -// The dimension filter contrains topN table avoiding approximation in measures (due to mearging multiple topN Druid results from different nodes). -func (q *MetricsViewComparison) executeDruidApproximateComparison(ctx context.Context, olap drivers.OLAPStore, mv *runtimev1.MetricsViewSpec, priority int, security *runtime.ResolvedMetricsViewSecurity) error { - originalMeasures := q.removeNoSortMeasures() - - if q.isBase() || q.isDeltaComparison() { - err := q.executeToplist(ctx, olap, mv, priority, security) - if err != nil { - return err - } - } else { - ttr := q.TimeRange - q.TimeRange = q.ComparisonTimeRange - err := q.executeToplist(ctx, olap, mv, priority, security) - if err != nil { - return err - } - - q.TimeRange = ttr - } - - q.addDimsAsFilter() - q.Measures = originalMeasures - return q.executeComparisonToplist(ctx, olap, mv, priority, security) -} - -// Druid-based `exactify` approach (see comments above) -// Optimizations: -// * the first query fetches only sorted dimensions -// * the second query isn't run if the topN already less than the limit -func (q *MetricsViewComparison) executeDruidApproximateToplist(ctx context.Context, olap drivers.OLAPStore, mv *runtimev1.MetricsViewSpec, priority int, security *runtime.ResolvedMetricsViewSecurity) error { - originalMeasures := q.Measures - if len(q.Measures) >= 5 { - originalMeasures = q.removeNoSortMeasures() - } - - err := q.executeToplist(ctx, olap, mv, priority, security) + qry, err := q.rewriteToMetricsViewQuery(false) if err != nil { - return err - } - - if len(q.Result.Rows) < int(q.Limit) && len(q.Measures) == len(originalMeasures) { - return nil - } - - q.addDimsAsFilter() - - q.Measures = originalMeasures - - // remove limit since we have already added filter with only toplist values and order clause will be present - q.Limit = 0 - - return q.executeToplist(ctx, olap, mv, priority, security) -} - -func (q *MetricsViewComparison) removeNoSortMeasures() []*runtimev1.MetricsViewAggregationMeasure { - measures := q.Measures - sortMeasures := make([]*runtimev1.MetricsViewAggregationMeasure, 0, len(q.Sort)) - for _, m := range q.Measures { - for _, s := range q.Sort { - if s.Name == m.Name { - sortMeasures = append(sortMeasures, m) - } - } - } - q.Measures = sortMeasures - return measures -} - -func (q *MetricsViewComparison) addDimsAsFilter() { - if len(q.Result.Rows) > 0 { - inExpressions := make([]*runtimev1.Expression, 0, len(q.Result.Rows)) - for _, r := range q.Result.Rows { - inExpressions = append(inExpressions, expressionpb.Value(r.DimensionValue)) - } - if q.Where != nil { - q.Where = expressionpb.And([]*runtimev1.Expression{q.Where, expressionpb.In(expressionpb.Identifier(q.DimensionName), inExpressions)}) - } else { - q.Where = expressionpb.In(expressionpb.Identifier(q.DimensionName), inExpressions) - } - } -} - -func (q *MetricsViewComparison) calculateMeasuresMeta() error { - compare := !isTimeRangeNil(q.ComparisonTimeRange) - - if !compare && len(q.ComparisonMeasures) > 0 { - return fmt.Errorf("comparison measures are provided but comparison time range is not") - } - - if len(q.ComparisonMeasures) == 0 && compare { - // backwards compatibility - q.ComparisonMeasures = make([]string, len(q.Measures)) - for i, m := range q.Measures { - q.ComparisonMeasures[i] = m.Name - } - } - - q.measuresMeta = make(map[string]metricsViewMeasureMeta, len(q.Measures)) - - inner := 1 - outer := 1 - for _, m := range q.Measures { - expand := false - for _, cm := range q.ComparisonMeasures { - if m.Name == cm { - expand = true - break - } - } - q.measuresMeta[m.Name] = metricsViewMeasureMeta{ - baseSubqueryIndex: inner, - outerIndex: outer, - expand: expand, - } - if expand { - outer += 4 - } else { - outer++ - } - inner++ + return fmt.Errorf("error rewriting to metrics query: %w", err) } - // check all comparison measures are present in the measures list - for _, cm := range q.ComparisonMeasures { - if _, ok := q.measuresMeta[cm]; !ok { - return fmt.Errorf("comparison measure '%s' is not present in the measures list", cm) - } - } - - err := validateSort(q.Sort, q.measuresMeta, compare) + e, err := metricsview.NewExecutor(ctx, rt, instanceID, mv, security, priority) if err != nil { return err } + defer e.Close() - err = validateMeasureAliases(q.Aliases, q.measuresMeta, compare) + res, _, err := e.Query(ctx, qry, nil) if err != nil { return err } + defer res.Close() - return nil -} - -func (q *MetricsViewComparison) executeToplist(ctx context.Context, olap drivers.OLAPStore, mv *runtimev1.MetricsViewSpec, priority int, policy *runtime.ResolvedMetricsViewSecurity) error { - sql, args, err := q.buildMetricsTopListSQL(mv, olap.Dialect(), policy, false) - if err != nil { - return fmt.Errorf("error building query: %w", err) - } - - rows, err := olap.Execute(ctx, &drivers.Statement{ - Query: sql, - Args: args, - Priority: priority, - }) + data, err := rowsToData(res) if err != nil { return err } - defer rows.Close() - - var data []*runtimev1.MetricsViewComparisonRow - for rows.Next() { - values, err := rows.SliceScan() - if err != nil { - return err - } - measureValues := make([]*runtimev1.MetricsViewComparisonValue, 0, len(q.Measures)) - - for i, m := range q.Measures { - v, err := pbutil.ToValue(values[1+i], safeFieldType(rows.Schema, 1+i)) - if err != nil { - return err - } - measureValues = append(measureValues, &runtimev1.MetricsViewComparisonValue{ - MeasureName: m.Name, - BaseValue: v, - }) - } + var rows []*runtimev1.MetricsViewComparisonRow + for _, val := range data { + val := val.AsMap() - dv, err := pbutil.ToValue(values[0], safeFieldType(rows.Schema, 0)) + dv, err := pbutil.ToValue(val[q.DimensionName], safeFieldTypeName(res.Schema, q.DimensionName)) if err != nil { return err } - data = append(data, &runtimev1.MetricsViewComparisonRow{ - DimensionValue: dv, - MeasureValues: measureValues, - }) - } - - q.Result = &runtimev1.MetricsViewComparisonResponse{ - Rows: data, - } - - return nil -} - -func (q *MetricsViewComparison) executeComparisonToplist(ctx context.Context, olap drivers.OLAPStore, mv *runtimev1.MetricsViewSpec, priority int, policy *runtime.ResolvedMetricsViewSecurity) error { - sql, args, err := q.buildMetricsComparisonTopListSQL(mv, olap.Dialect(), policy, false) - if err != nil { - return fmt.Errorf("error building query: %w", err) - } - rows, err := olap.Execute(ctx, &drivers.Statement{ - Query: sql, - Args: args, - Priority: priority, - }) - if err != nil { - return err - } - defer rows.Close() - - var data []*runtimev1.MetricsViewComparisonRow - for rows.Next() { - values, err := rows.SliceScan() - if err != nil { - return err - } - var measureValues []*runtimev1.MetricsViewComparisonValue + out := &runtimev1.MetricsViewComparisonRow{DimensionValue: dv} for _, m := range q.Measures { - measureMeta := q.measuresMeta[m.Name] - index := measureMeta.outerIndex - if measureMeta.expand { - bv, err := pbutil.ToValue(values[index], safeFieldType(rows.Schema, index)) - if err != nil { - return err - } + mv := &runtimev1.MetricsViewComparisonValue{MeasureName: m.Name} - cv, err := pbutil.ToValue(values[1+index], safeFieldType(rows.Schema, 1+index)) - if err != nil { - return err - } + bv := val[m.Name] + mv.BaseValue, err = pbutil.ToValue(bv, safeFieldTypeName(res.Schema, m.Name)) + if err != nil { + return err + } - da, err := pbutil.ToValue(values[2+index], safeFieldType(rows.Schema, 2+index)) + cv, ok := val[m.Name+"__previous"] + if ok { + mv.ComparisonValue, err = pbutil.ToValue(cv, safeFieldTypeName(res.Schema, m.Name+"__previous")) if err != nil { return err } + } - dr, err := pbutil.ToValue(values[3+index], safeFieldType(rows.Schema, 3+index)) + da, ok := val[m.Name+"__delta_abs"] + if ok { + mv.DeltaAbs, err = pbutil.ToValue(da, safeFieldTypeName(res.Schema, m.Name+"__delta_abs")) if err != nil { return err } + } - measureValues = append(measureValues, &runtimev1.MetricsViewComparisonValue{ - MeasureName: m.Name, - BaseValue: bv, - ComparisonValue: cv, - DeltaAbs: da, - DeltaRel: dr, - }) - } else { - v, err := pbutil.ToValue(values[index], safeFieldType(rows.Schema, index)) + dr, ok := val[m.Name+"__delta_rel"] + if ok { + mv.DeltaRel, err = pbutil.ToValue(dr, safeFieldTypeName(res.Schema, m.Name+"__delta_rel")) if err != nil { return err } - - measureValues = append(measureValues, &runtimev1.MetricsViewComparisonValue{ - MeasureName: m.Name, - BaseValue: v, - }) } - } - dv, err := pbutil.ToValue(values[0], safeFieldType(rows.Schema, 0)) - if err != nil { - return err + out.MeasureValues = append(out.MeasureValues, mv) } - data = append(data, &runtimev1.MetricsViewComparisonRow{ - DimensionValue: dv, - MeasureValues: measureValues, - }) + rows = append(rows, out) } q.Result = &runtimev1.MetricsViewComparisonResponse{ - Rows: data, + Rows: rows, } return nil } -func (q *MetricsViewComparison) buildMetricsTopListSQL(mv *runtimev1.MetricsViewSpec, dialect drivers.Dialect, policy *runtime.ResolvedMetricsViewSecurity, export bool) (string, []any, error) { - dim, err := metricsViewDimension(mv, q.DimensionName) +func (q *MetricsViewComparison) Export(ctx context.Context, rt *runtime.Runtime, instanceID string, w io.Writer, opts *runtime.ExportOptions) error { + // Resolve metrics view + mv, security, err := resolveMVAndSecurityFromAttributes(ctx, rt, instanceID, q.MetricsViewName, q.SecurityAttributes, q.SecurityPolicy, []*runtimev1.MetricsViewAggregationDimension{{Name: q.DimensionName}}, q.Measures) if err != nil { - return "", nil, err - } - colName := safeName(dim.Name) - - labelMap := make(map[string]string, len(mv.Measures)) - for _, m := range mv.Measures { - labelMap[m.Name] = m.Name - if m.Label != "" { - labelMap[m.Name] = m.Label - } - } - - var labelCols []string - var selectCols []string - dimLabel := colName - if dim.Label != "" { - dimLabel = safeName(dim.Label) - } - dimSel, unnestClause := dialect.DimensionSelect(mv.Database, mv.DatabaseSchema, mv.Table, dim) - selectCols = append(selectCols, dimSel) - labelCols = []string{fmt.Sprintf("%s as %s", safeName(dim.Name), dimLabel)} - - for _, m := range q.Measures { - switch m.BuiltinMeasure { - case runtimev1.BuiltinMeasure_BUILTIN_MEASURE_UNSPECIFIED: - expr, err := metricsViewMeasureExpression(mv, m.Name) - if err != nil { - return "", nil, err - } - selectCols = append(selectCols, fmt.Sprintf("%s as %s", expr, safeName(m.Name))) - labelCols = append(labelCols, fmt.Sprintf("%s as %s", safeName(m.Name), safeName(labelMap[m.Name]))) - case runtimev1.BuiltinMeasure_BUILTIN_MEASURE_COUNT: - selectCols = append(selectCols, fmt.Sprintf("COUNT(*) as %s", safeName(m.Name))) - labelCols = append(labelCols, fmt.Sprintf("%s as %s", safeName(m.Name), safeName(labelMap[m.Name]))) - case runtimev1.BuiltinMeasure_BUILTIN_MEASURE_COUNT_DISTINCT: - if len(m.BuiltinMeasureArgs) != 1 { - return "", nil, fmt.Errorf("builtin measure '%s' expects 1 argument", m.BuiltinMeasure.String()) - } - arg := m.BuiltinMeasureArgs[0].GetStringValue() - if arg == "" { - return "", nil, fmt.Errorf("builtin measure '%s' expects non-empty string argument, got '%v'", m.BuiltinMeasure.String(), m.BuiltinMeasureArgs[0]) - } - selectCols = append(selectCols, fmt.Sprintf("COUNT(DISTINCT %s) as %s", safeName(arg), safeName(m.Name))) - labelCols = append(labelCols, fmt.Sprintf("%s as %s", safeName(m.Name), safeName(labelMap[m.Name]))) - default: - return "", nil, fmt.Errorf("unknown builtin measure '%d'", m.BuiltinMeasure) - } - } - - selectClause := strings.Join(selectCols, ", ") - baseWhereClause := "1=1" - - args := []any{} - td := safeName(mv.TimeDimension) - if dialect == drivers.DialectDuckDB { - td = fmt.Sprintf("%s::TIMESTAMP", td) + return err } - trc, err := timeRangeClause(q.TimeRange, mv, td, &args) + err = q.calculateMeasuresMeta() if err != nil { - return "", nil, err + return err } - baseWhereClause += trc - - if q.Where != nil { - builder := &ExpressionBuilder{ - mv: mv, - dialect: dialect, - } - clause, clauseArgs, err := builder.buildExpression(q.Where) - if err != nil { - return "", nil, err - } - if strings.TrimSpace(clause) != "" { - baseWhereClause += fmt.Sprintf(" AND (%s)", clause) - } - args = append(args, clauseArgs...) + qry, err := q.rewriteToMetricsViewQuery(true) + if err != nil { + return fmt.Errorf("error rewriting to metrics query: %w", err) } - if policy != nil && policy.RowFilter != "" { - baseWhereClause += fmt.Sprintf(" AND (%s)", policy.RowFilter) + e, err := metricsview.NewExecutor(ctx, rt, instanceID, mv, security, opts.Priority) + if err != nil { + return err } + defer e.Close() - havingClause := "" - if q.Having != nil { - var havingClauseArgs []any - builder := &ExpressionBuilder{ - mv: mv, - dialect: dialect, - having: true, - aliases: q.Aliases, - } - havingClause, havingClauseArgs, err = builder.buildExpression(q.Having) - if err != nil { - return "", nil, err - } - if strings.TrimSpace(havingClause) != "" { - havingClause = "HAVING " + havingClause - } - args = append(args, havingClauseArgs...) + var format drivers.FileFormat + switch opts.Format { + case runtimev1.ExportFormat_EXPORT_FORMAT_CSV: + format = drivers.FileFormatCSV + case runtimev1.ExportFormat_EXPORT_FORMAT_XLSX: + format = drivers.FileFormatXLSX + case runtimev1.ExportFormat_EXPORT_FORMAT_PARQUET: + format = drivers.FileFormatParquet + default: + return fmt.Errorf("unsupported format: %s", opts.Format.String()) } - var orderClauses []string - for _, s := range q.Sort { - if s.Name == q.DimensionName { - clause := "1" - if s.Desc { - clause += " DESC" - } - if dialect == drivers.DialectDuckDB { - clause += " NULLS LAST" - } - orderClauses = append(orderClauses, clause) - break - } - clause := safeName(s.Name) - if s.Desc { - clause += " DESC" - } - if dialect == drivers.DialectDuckDB { - clause += " NULLS LAST" - } - orderClauses = append(orderClauses, clause) + path, err := e.Export(ctx, qry, nil, format) + if err != nil { + return err } + defer func() { _ = os.Remove(path) }() - orderByClause := "" - if len(orderClauses) > 0 { - orderByClause = "ORDER BY " + strings.Join(orderClauses, ", ") + filename := q.generateFilename() + err = opts.PreWriteHook(filename) + if err != nil { + return err } - limitClause := "" - if q.Limit > 0 { - limitClause = fmt.Sprintf(" LIMIT %d", q.Limit) + f, err := os.Open(path) + if err != nil { + return err } + defer f.Close() - var sql string - if export { - labelSelectClause := strings.Join(labelCols, ", ") - sql = fmt.Sprintf( - `SELECT %[8]s FROM (SELECT %[1]s FROM %[2]s %[7]s WHERE %[3]s GROUP BY 1 %[9]s %[4]s %[5]s OFFSET %[6]d)`, - selectClause, // 1 - escapeMetricsViewTable(dialect, mv), // 2 - baseWhereClause, // 3 - orderByClause, // 4 - limitClause, // 5 - q.Offset, // 6 - unnestClause, // 7 - labelSelectClause, // 8 - havingClause, // 9 - ) - } else { - sql = fmt.Sprintf( - `SELECT %[1]s FROM %[2]s %[7]s WHERE %[3]s GROUP BY 1 %[8]s %[4]s %[5]s OFFSET %[6]d`, - selectClause, // 1 - escapeMetricsViewTable(dialect, mv), // 2 - baseWhereClause, // 3 - orderByClause, // 4 - limitClause, // 5 - q.Offset, // 6 - unnestClause, // 7 - havingClause, // 8 - ) + _, err = io.Copy(w, f) + if err != nil { + return err } - return sql, args, nil + return nil } -func (q *MetricsViewComparison) buildMetricsComparisonTopListSQL(mv *runtimev1.MetricsViewSpec, dialect drivers.Dialect, policy *runtime.ResolvedMetricsViewSecurity, export bool) (string, []any, error) { - dim, err := metricsViewDimension(mv, q.DimensionName) - if err != nil { - return "", nil, err +func (q *MetricsViewComparison) generateFilename() string { + filename := strings.ReplaceAll(q.MetricsViewName, `"`, `_`) + filename += "_" + q.DimensionName + if q.Where != nil || q.Having != nil { + filename += "_filtered" } + return filename +} - colName := safeName(dim.Name) +func (q *MetricsViewComparison) rewriteToMetricsViewQuery(export bool) (*metricsview.Query, error) { + qry := &metricsview.Query{MetricsView: q.MetricsViewName} - labelMap := make(map[string]string, len(mv.Measures)) - for _, m := range mv.Measures { - labelMap[m.Name] = m.Name - if m.Label != "" { - labelMap[m.Name] = m.Label - } - } + qry.Dimensions = append(qry.Dimensions, metricsview.Dimension{Name: q.DimensionName}) - var selectCols []string - var comparisonSelectCols []string - dimSel, unnestClause := dialect.DimensionSelect(mv.Database, mv.DatabaseSchema, mv.Table, dim) - selectCols = append(selectCols, dimSel) - comparisonSelectCols = append(comparisonSelectCols, dimSel) + var measureFilter *runtimev1.Expression for _, m := range q.Measures { + res := metricsview.Measure{Name: m.Name} switch m.BuiltinMeasure { - case runtimev1.BuiltinMeasure_BUILTIN_MEASURE_UNSPECIFIED: - expr, err := metricsViewMeasureExpression(mv, m.Name) - if err != nil { - return "", nil, err - } - selectCols = append(selectCols, fmt.Sprintf("%s as %s", expr, safeName(m.Name))) - if q.measuresMeta[m.Name].expand { - comparisonSelectCols = append(comparisonSelectCols, fmt.Sprintf("%s as %s", expr, safeName(m.Name))) - } case runtimev1.BuiltinMeasure_BUILTIN_MEASURE_COUNT: - selectCols = append(selectCols, fmt.Sprintf("COUNT(*) as %s", safeName(m.Name))) - if q.measuresMeta[m.Name].expand { - comparisonSelectCols = append(comparisonSelectCols, fmt.Sprintf("COUNT(*) as %s", safeName(m.Name))) - } + res.Compute = &metricsview.MeasureCompute{Count: true} case runtimev1.BuiltinMeasure_BUILTIN_MEASURE_COUNT_DISTINCT: - if len(m.BuiltinMeasureArgs) != 1 { - return "", nil, fmt.Errorf("builtin measure '%s' expects 1 argument", m.BuiltinMeasure.String()) - } - arg := m.BuiltinMeasureArgs[0].GetStringValue() - if arg == "" { - return "", nil, fmt.Errorf("builtin measure '%s' expects non-empty string argument, got '%v'", m.BuiltinMeasure.String(), m.BuiltinMeasureArgs[0]) - } - selectCols = append(selectCols, fmt.Sprintf("COUNT(DISTINCT %s) as %s", safeName(arg), safeName(m.Name))) - if q.measuresMeta[m.Name].expand { - comparisonSelectCols = append(comparisonSelectCols, fmt.Sprintf("COUNT(DISTINCT %s) as %s", safeName(arg), safeName(m.Name))) - } - default: - return "", nil, fmt.Errorf("unknown builtin measure '%d'", m.BuiltinMeasure) + res.Compute = &metricsview.MeasureCompute{CountDistinct: &metricsview.MeasureComputeCountDistinct{ + Dimension: m.BuiltinMeasureArgs[0].GetStringValue(), + }} } - } - var finalSelectCols []string - var labelCols []string - for _, m := range q.Measures { - var columnsTuple string - var labelTuple string - if dialect != drivers.DialectDruid { - if q.measuresMeta[m.Name].expand { - columnsTuple = fmt.Sprintf( - "base.%[1]s AS %[1]s, comparison.%[1]s AS %[2]s, base.%[1]s - comparison.%[1]s AS %[3]s, CAST((base.%[1]s - comparison.%[1]s) AS DOUBLE)/comparison.%[1]s AS %[4]s", - safeName(m.Name), - safeName(m.Name+"__previous"), - safeName(m.Name+"__delta_abs"), - safeName(m.Name+"__delta_rel"), - ) - labelTuple = fmt.Sprintf( - "base.%[1]s AS %[5]s, comparison.%[1]s AS %[2]s, base.%[1]s - comparison.%[1]s AS %[3]s, CAST((base.%[1]s - comparison.%[1]s) AS DOUBLE)/comparison.%[1]s AS %[4]s", - safeName(m.Name), - safeName(labelMap[m.Name]+" (prev)"), - safeName(labelMap[m.Name]+" (Δ)"), - safeName(labelMap[m.Name]+" (Δ%)"), - safeName(labelMap[m.Name]), - ) - } else { - columnsTuple = fmt.Sprintf("base.%[1]s AS %[1]s", safeName(m.Name)) - labelTuple = fmt.Sprintf("base.%[1]s AS %[2]s", safeName(m.Name), safeName(labelMap[m.Name])) - } - } else { - if q.measuresMeta[m.Name].expand { - columnsTuple = fmt.Sprintf( - "ANY_VALUE(base.%[1]s) AS %[1]s, ANY_VALUE(comparison.%[1]s) AS %[2]s, ANY_VALUE(base.%[1]s - comparison.%[1]s) AS %[3]s, ANY_VALUE(SAFE_DIVIDE(base.%[1]s - comparison.%[1]s, CAST(comparison.%[1]s AS DOUBLE))) AS %[4]s", - safeName(m.Name), - safeName(m.Name+"__previous"), - safeName(m.Name+"__delta_abs"), - safeName(m.Name+"__delta_rel"), - ) - labelTuple = fmt.Sprintf( - "ANY_VALUE(base.%[1]s) AS %[2]s, ANY_VALUE(comparison.%[1]s) AS %[3]s, ANY_VALUE(base.%[1]s - comparison.%[1]s) AS %[4]s, ANY_VALUE(SAFE_DIVIDE(base.%[1]s - comparison.%[1]s, CAST(comparison.%[1]s AS DOUBLE))) AS %[5]s", - safeName(m.Name), - safeName(labelMap[m.Name]), - safeName(labelMap[m.Name]+" (prev)"), - safeName(labelMap[m.Name]+" (Δ)"), - safeName(labelMap[m.Name]+" (Δ%)"), - ) - } else { - columnsTuple = fmt.Sprintf("ANY_VALUE(base.%[1]s) AS %[1]s", safeName(m.Name)) - labelTuple = fmt.Sprintf("ANY_VALUE(base.%[1]s) AS %[2]s", safeName(m.Name), safeName(labelMap[m.Name])) + if m.Filter != nil { + if len(q.Measures) > 1 { + return nil, fmt.Errorf("measure-level filter is not supported when multiple measures are present") } + measureFilter = m.Filter } - finalSelectCols = append( - finalSelectCols, - columnsTuple, - ) - labelCols = append(labelCols, labelTuple) - } - - subSelectClause := strings.Join(selectCols, ", ") - subComparisonSelectClause := strings.Join(comparisonSelectCols, ", ") - finalSelectClause := strings.Join(finalSelectCols, ", ") - labelSelectClause := strings.Join(labelCols, ", ") - if export { - finalSelectClause = labelSelectClause - } - - baseWhereClause := "1=1" - comparisonWhereClause := "1=1" - - var args []any - if mv.TimeDimension == "" { - return "", nil, fmt.Errorf("metrics view '%s' doesn't have time dimension", q.MetricsViewName) - } - - td := safeName(mv.TimeDimension) - if dialect == drivers.DialectDuckDB { - td = fmt.Sprintf("%s::TIMESTAMP", td) - } - - builder := &ExpressionBuilder{ - mv: mv, - dialect: dialect, - } - whereClause, whereClauseArgs, err := builder.buildExpression(q.Where) - if err != nil { - return "", nil, err - } - - trc, err := timeRangeClause(q.TimeRange, mv, td, &args) - if err != nil { - return "", nil, err - } - baseWhereClause += trc - if whereClause != "" { - baseWhereClause += fmt.Sprintf(" AND (%s)", whereClause) - args = append(args, whereClauseArgs...) - } - - trc, err = timeRangeClause(q.ComparisonTimeRange, mv, td, &args) - if err != nil { - return "", nil, err - } - comparisonWhereClause += trc + if m.Compute != nil { + switch c := m.Compute.(type) { + case *runtimev1.MetricsViewAggregationMeasure_ComparisonValue: + res.Compute = &metricsview.MeasureCompute{ComparisonValue: &metricsview.MeasureComputeComparisonValue{ + Measure: c.ComparisonValue.Measure, + }} + case *runtimev1.MetricsViewAggregationMeasure_ComparisonDelta: + res.Compute = &metricsview.MeasureCompute{ComparisonDelta: &metricsview.MeasureComputeComparisonDelta{ + Measure: c.ComparisonDelta.Measure, + }} + case *runtimev1.MetricsViewAggregationMeasure_ComparisonRatio: + res.Compute = &metricsview.MeasureCompute{ComparisonRatio: &metricsview.MeasureComputeComparisonRatio{ + Measure: c.ComparisonRatio.Measure, + }} + } + } - if whereClause != "" { - comparisonWhereClause += fmt.Sprintf(" AND (%s)", whereClause) - args = append(args, whereClauseArgs...) + qry.Measures = append(qry.Measures, res) } - if policy != nil && policy.RowFilter != "" { - baseWhereClause += fmt.Sprintf(" AND (%s)", policy.RowFilter) - comparisonWhereClause += fmt.Sprintf(" AND (%s)", policy.RowFilter) + for _, m := range q.ComparisonMeasures { + qry.Measures = append(qry.Measures, metricsview.Measure{ + Name: q.aliasForMeasure(m, runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_COMPARISON_VALUE), + Compute: &metricsview.MeasureCompute{ + ComparisonValue: &metricsview.MeasureComputeComparisonValue{ + Measure: m, + }, + }, + }, metricsview.Measure{ + Name: q.aliasForMeasure(m, runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_ABS_DELTA), + Compute: &metricsview.MeasureCompute{ + ComparisonDelta: &metricsview.MeasureComputeComparisonDelta{ + Measure: m, + }, + }, + }, metricsview.Measure{ + Name: q.aliasForMeasure(m, runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_REL_DELTA), + Compute: &metricsview.MeasureCompute{ + ComparisonRatio: &metricsview.MeasureComputeComparisonRatio{ + Measure: m, + }, + }, + }) } - var orderClauses []string - var subQueryOrderClauses []string - for _, s := range q.Sort { - if s.Name == q.DimensionName { - clause := "1" - subQueryClause := "1" - var ending string - if s.Desc { - ending += " DESC" - } - if dialect == drivers.DialectDuckDB { - ending += " NULLS LAST" - } - clause += ending - subQueryClause += ending - orderClauses = append(orderClauses, clause) - subQueryOrderClauses = append(subQueryOrderClauses, subQueryClause) - break + if q.TimeRange != nil { + res := &metricsview.TimeRange{} + if q.TimeRange.Start != nil { + res.Start = q.TimeRange.Start.AsTime() } - measureMeta, ok := q.measuresMeta[s.Name] - if !ok { - return "", nil, fmt.Errorf("metrics view '%s' doesn't contain '%s' sort column", q.MetricsViewName, s.Name) + if q.TimeRange.End != nil { + res.End = q.TimeRange.End.AsTime() } + res.IsoDuration = q.TimeRange.IsoDuration + res.IsoOffset = q.TimeRange.IsoOffset + res.RoundToGrain = metricsview.TimeGrainFromProto(q.TimeRange.RoundToGrain) + qry.TimeRange = res + qry.TimeZone = q.TimeRange.TimeZone + } - var pos int - switch s.SortType { - case runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_BASE_VALUE: - pos = 1 + measureMeta.outerIndex - case runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_COMPARISON_VALUE: - pos = 2 + measureMeta.outerIndex - case runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_ABS_DELTA: - pos = 3 + measureMeta.outerIndex - case runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_REL_DELTA: - pos = 4 + measureMeta.outerIndex - default: - return "", nil, fmt.Errorf("undefined sort type for measure %s", s.Name) + if q.ComparisonTimeRange != nil { + res := &metricsview.TimeRange{} + if q.ComparisonTimeRange.Start != nil { + res.Start = q.ComparisonTimeRange.Start.AsTime() } - orderClause := fmt.Sprint(pos) - subQueryOrderClause := fmt.Sprint(measureMeta.baseSubqueryIndex + 1) // 1-based + skip the first dim column - ending := "" - if s.Desc { - ending += " DESC" + if q.ComparisonTimeRange.End != nil { + res.End = q.ComparisonTimeRange.End.AsTime() } - if dialect == drivers.DialectDuckDB { - ending += " NULLS LAST" - } - orderClause += ending - subQueryOrderClause += ending - orderClauses = append(orderClauses, orderClause) - subQueryOrderClauses = append(subQueryOrderClauses, subQueryOrderClause) + res.IsoDuration = q.ComparisonTimeRange.IsoDuration + res.IsoOffset = q.ComparisonTimeRange.IsoOffset + res.RoundToGrain = metricsview.TimeGrainFromProto(q.ComparisonTimeRange.RoundToGrain) + qry.ComparisonTimeRange = res } - orderByClause := "" - subQueryOrderByClause := "" - if len(orderClauses) > 0 { - orderByClause = "ORDER BY " + strings.Join(orderClauses, ", ") - subQueryOrderByClause = "ORDER BY " + strings.Join(subQueryOrderClauses, ", ") + if q.Limit != 0 { + qry.Limit = &q.Limit } - limitClause := "" - if q.Limit > 0 { - limitClause = fmt.Sprintf(" LIMIT %d", q.Limit) + if q.Offset != 0 { + qry.Offset = &q.Offset } - baseLimitClause := "" - comparisonLimitClause := "" - - joinType := "FULL" - if !q.Exact { - deltaComparison := q.Sort[0].SortType == runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_ABS_DELTA || - q.Sort[0].SortType == runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_REL_DELTA - - approximationLimit := int(q.Limit) - if q.Limit != 0 && q.Limit < 100 && deltaComparison { - approximationLimit = 100 - } + for _, s := range q.Sort { + qry.Sort = append(qry.Sort, metricsview.Sort{ + Name: q.aliasForMeasure(s.Name, s.SortType), + Desc: s.Desc, + }) + } - if q.Sort[0].SortType == runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_BASE_VALUE || deltaComparison { - joinType = "LEFT OUTER" - baseLimitClause = subQueryOrderByClause - if approximationLimit > 0 { - baseLimitClause += fmt.Sprintf(" LIMIT %d", approximationLimit) - } - } else if q.Sort[0].SortType == runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_COMPARISON_VALUE { - joinType = "RIGHT OUTER" - comparisonLimitClause = subQueryOrderByClause - if approximationLimit > 0 { - comparisonLimitClause += fmt.Sprintf(" LIMIT %d", approximationLimit) - } + if q.Filter != nil { // Backwards compatibility + if q.Where != nil { + return nil, fmt.Errorf("both filter and where is provided") } + q.Where = convertFilterToExpression(q.Filter) } - /* - Example of the SQL: - - SELECT COALESCE(base."domain", comparison."domain") AS "dom", base."measure_1", comparison."measure_1" AS "measure_1__previous", base."measure_1" - comparison."measure_1" AS "measure_1__delta_abs", (base."measure_1" - comparison."measure_1")/comparison."measure_1"::DOUBLE AS "measure_1__delta_rel" FROM - ( - SELECT "domain", avg(bid_price) as "measure_1" FROM "ad_bids" WHERE 1=1 AND "timestamp" >= ? AND "timestamp" < ? GROUP BY "domain" ORDER BY true, 1 NULLS LAST LIMIT 100 - ) base - LEFT OUTER JOIN - ( - SELECT "domain", avg(bid_price) as "measure_1" FROM "ad_bids" WHERE 1=1 AND "timestamp" >= ? AND "timestamp" < ? GROUP BY "domain" - ) comparison - ON - base."domain" = comparison."domain" OR (base."domain" is null and comparison."domain" is null) - ORDER BY - true, 1 NULLS LAST - LIMIT 10 - OFFSET 0 - */ - - finalDimName := safeName(q.DimensionName) - if export && dim.Label != "" { - finalDimName = safeName(dim.Label) + if q.Where != nil { + qry.Where = metricsview.NewExpressionFromProto(q.Where) } - var sql string - if dialect != drivers.DialectDruid { - var joinOnClause string - if dialect == drivers.DialectClickHouse { - joinOnClause = fmt.Sprintf("isNotDistinctFrom(base.%[1]s, comparison.%[1]s)", colName) - } else { - joinOnClause = fmt.Sprintf("base.%[1]s = comparison.%[1]s OR (base.%[1]s is null and comparison.%[1]s is null)", colName) - } - if q.Having != nil { - // measure filter could include the base measure name. - // this leads to ambiguity whether it applies to the base.measure ot comparison.measure. - // to keep the clause builder consistent we add an outer query here. - - var whereClauseArgs []any - builder := &ExpressionBuilder{ - mv: mv, - dialect: dialect, - aliases: q.Aliases, - } - whereClause, whereClauseArgs, err := builder.buildExpression(q.Having) - if err != nil { - return "", nil, err - } - args = append(args, whereClauseArgs...) - - sql = fmt.Sprintf(` - SELECT * from ( - SELECT COALESCE(base.%[2]s, comparison.%[2]s) AS %[10]s, %[9]s FROM - ( - SELECT %[1]s FROM %[3]s %[14]s WHERE %[4]s GROUP BY 1 %[12]s - ) base - %[11]s JOIN - ( - SELECT %[16]s FROM %[3]s %[14]s WHERE %[5]s GROUP BY 1 %[13]s - ) comparison - ON - %[17]s - %[6]s - %[7]s - OFFSET - %[8]d - ) WHERE %[15]s - `, - subSelectClause, // 1 - colName, // 2 - escapeMetricsViewTable(dialect, mv), // 3 - baseWhereClause, // 4 - comparisonWhereClause, // 5 - orderByClause, // 6 - limitClause, // 7 - q.Offset, // 8 - finalSelectClause, // 9 - finalDimName, // 10 - joinType, // 11 - baseLimitClause, // 12 - comparisonLimitClause, // 13 - unnestClause, // 14 - whereClause, // 15 - subComparisonSelectClause, // 16 - joinOnClause, // 17 - ) - } else { - sql = fmt.Sprintf(` - SELECT COALESCE(base.%[2]s, comparison.%[2]s) AS %[10]s, %[9]s FROM - ( - SELECT %[1]s FROM %[3]s %[14]s WHERE %[4]s GROUP BY 1 %[12]s - ) base - %[11]s JOIN - ( - SELECT %[15]s FROM %[3]s %[14]s WHERE %[5]s GROUP BY 1 %[13]s - ) comparison - ON - %[16]s - %[6]s - %[7]s - OFFSET - %[8]d - `, - subSelectClause, // 1 - colName, // 2 - escapeMetricsViewTable(dialect, mv), // 3 - baseWhereClause, // 4 - comparisonWhereClause, // 5 - orderByClause, // 6 - limitClause, // 7 - q.Offset, // 8 - finalSelectClause, // 9 - finalDimName, // 10 - joinType, // 11 - baseLimitClause, // 12 - comparisonLimitClause, // 13 - unnestClause, // 14 - subComparisonSelectClause, // 15 - joinOnClause, // 16 - ) - } - } else { - /* - Example of the SQL query with expression based dimension: - - WITH base AS ( - SELECT (replace("channel", 'a', 'b')) as "b", - count(*) as "total_records", sum("added") as "sum" - FROM "wikipedia" - WHERE 1=1 AND "__time" >= '2016-06-27T02:00:00.000Z' AND "__time" < '2016-06-27T03:00:00.000Z' - GROUP BY 1 -- Druid does not support group by aliases - ORDER BY 2 DESC - LIMIT 500 OFFSET 0 - ), comparison AS ( - SELECT (replace("channel", 'a', 'b')) as "c", - count(*) as "total_records" - FROM "wikipedia" - WHERE 1=1 AND "__time" >= '2016-06-27T01:00:00.000Z' AND "__time" < '2016-06-27T02:00:00.000Z' - AND replace("channel", 'a', 'b') IN (SELECT "b" FROM base) - GROUP BY 1 -- Druid does not support group by aliases - LIMIT 500 - ) - SELECT base."b" AS "channel", - ANY_VALUE(base."total_records") AS "total_records", - ANY_VALUE(comparison."total_records") AS "total_records__previous", - ANY_VALUE(base."total_records" - comparison."total_records") AS "total_records__delta_abs", - ANY_VALUE(SAFE_DIVIDE(base."total_records" - comparison."total_records", CAST(comparison."total_records" AS DOUBLE))) AS "total_records__delta_rel", - ANY_VALUE(base."sum") AS "sum", - FROM base LEFT JOIN comparison ON base."b" = comparison."c" - GROUP BY 1 -- Druid does not support group by aliases - HAVING 1=1 - ORDER BY 2 DESC -- order by without group by is not supported by Druid - LIMIT 250 - OFFSET 0 - - Apache Druid requires that one part of the JOIN fits in memory, that can be achieved by pushing down the limit clause to a subquery (works only if the sorting is based entirely on a single subquery result) - */ - leftSubQueryAlias := "base" - rightSubQueryAlias := "comparison" - leftWhereClause := baseWhereClause - rightWhereClause := comparisonWhereClause - - if q.Sort[0].SortType == runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_COMPARISON_VALUE { - leftSubQueryAlias = "comparison" - rightSubQueryAlias = "base" - leftWhereClause = comparisonWhereClause - rightWhereClause = baseWhereClause - } - twiceTheLimitClause := "" - if q.Exact { - if q.Limit > 0 { - twiceTheLimitClause = fmt.Sprintf(" LIMIT %d", q.Limit*2) - } else if q.Limit == 0 { - twiceTheLimitClause = fmt.Sprintf(" LIMIT %d", 100_000) // use Druid limit - } - } + // If a measure-level filter is present, we set qry.Where as the spine, and use (qry.Where AND measuresFilter) as the new where clause + if measureFilter != nil { + qry.Spine = &metricsview.Spine{Where: &metricsview.WhereSpine{Expression: qry.Where}} - havingClause := "" - if q.Having != nil { - var havingClauseArgs []any - builder := &ExpressionBuilder{ - mv: mv, - dialect: dialect, - aliases: q.Aliases, - having: true, - } - havingClause, havingClauseArgs, err = builder.buildExpression(q.Having) - if err != nil { - return "", nil, err + if qry.Where == nil { + qry.Where = metricsview.NewExpressionFromProto(measureFilter) + } else { + qry.Where = &metricsview.Expression{ + Condition: &metricsview.Condition{ + Operator: metricsview.OperatorAnd, + Expressions: []*metricsview.Expression{ + qry.Where, + metricsview.NewExpressionFromProto(measureFilter), + }, + }, } - havingClause = "HAVING " + havingClause - args = append(args, havingClauseArgs...) } - - sql = fmt.Sprintf(` - WITH %[11]s AS ( - SELECT %[1]s FROM %[3]s WHERE %[4]s GROUP BY 1 %[13]s %[10]s OFFSET %[8]d - ), %[12]s AS ( - SELECT %[17]s FROM %[3]s WHERE %[5]s AND %[16]s IN (SELECT %[2]s FROM %[11]s) GROUP BY 1 %[10]s - ) - SELECT %[11]s.%[2]s AS %[14]s, %[9]s FROM %[11]s LEFT JOIN %[12]s ON base.%[2]s = comparison.%[2]s - GROUP BY 1 - %[15]s - %[6]s - %[7]s - OFFSET %[8]d - `, - subSelectClause, // 1 - colName, // 2 - escapeMetricsViewTable(dialect, mv), // 3 - leftWhereClause, // 4 - rightWhereClause, // 5 - orderByClause, // 6 - limitClause, // 7 - q.Offset, // 8 - finalSelectClause, // 9 - twiceTheLimitClause, // 10 - leftSubQueryAlias, // 11 - rightSubQueryAlias, // 12 - subQueryOrderByClause, // 13 - finalDimName, // 14 - havingClause, // 15 - dialect.MetricsViewDimensionExpression(dim), // 16 - subComparisonSelectClause, // 17 - ) } - return sql, args, nil -} - -func (q *MetricsViewComparison) Export(ctx context.Context, rt *runtime.Runtime, instanceID string, w io.Writer, opts *runtime.ExportOptions) error { - // Resolve metrics view - mv, security, err := resolveMVAndSecurityFromAttributes(ctx, rt, instanceID, q.MetricsViewName, q.SecurityAttributes, q.SecurityPolicy, []*runtimev1.MetricsViewAggregationDimension{{Name: q.DimensionName}}, q.Measures) - if err != nil { - return err + if q.Having != nil { + qry.Having = metricsview.NewExpressionFromProto(q.Having) } - olap, release, err := rt.OLAP(ctx, instanceID, mv.Connector) - if err != nil { - return err - } - defer release() - - switch olap.Dialect() { - case drivers.DialectDuckDB: - if opts.Format == runtimev1.ExportFormat_EXPORT_FORMAT_CSV || opts.Format == runtimev1.ExportFormat_EXPORT_FORMAT_PARQUET { - // temporary backwards compatibility - if q.Filter != nil { - if q.Where != nil { - return fmt.Errorf("both filter and where is provided") - } - q.Where = convertFilterToExpression(q.Filter) - } + qry.Label = export - err := q.calculateMeasuresMeta() - if err != nil { - return err - } - var sql string - var args []any - if !isTimeRangeNil(q.ComparisonTimeRange) { - sql, args, err = q.buildMetricsComparisonTopListSQL(mv, olap.Dialect(), security, true) - if err != nil { - return fmt.Errorf("error building query: %w", err) - } - } else { - sql, args, err = q.buildMetricsTopListSQL(mv, olap.Dialect(), security, true) - if err != nil { - return fmt.Errorf("error building query: %w", err) - } - } + return qry, nil +} - filename := q.generateFilename() - if err := DuckDBCopyExport(ctx, w, opts, sql, args, filename, olap, opts.Format); err != nil { - return err - } - } else { - if err := q.generalExport(ctx, rt, instanceID, w, opts, mv); err != nil { - return err - } +func (q *MetricsViewComparison) aliasForMeasure(name string, t runtimev1.MetricsViewComparisonMeasureType) string { + for _, a := range q.Aliases { + if a.Name == name && a.Type == t { + return a.Alias } - case drivers.DialectDruid, drivers.DialectClickHouse, drivers.DialectPinot: - if err := q.generalExport(ctx, rt, instanceID, w, opts, mv); err != nil { - return err - } - default: - return fmt.Errorf("not available for dialect '%s'", olap.Dialect()) } - return nil + switch t { + case runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_COMPARISON_VALUE: + return name + "__previous" + case runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_ABS_DELTA: + return name + "__delta_abs" + case runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_REL_DELTA: + return name + "__delta_rel" + } + + return name } -func (q *MetricsViewComparison) generalExport(ctx context.Context, rt *runtime.Runtime, instanceID string, w io.Writer, opts *runtime.ExportOptions, mv *runtimev1.MetricsViewSpec) error { - err := q.Resolve(ctx, rt, instanceID, opts.Priority) - if err != nil { - return err - } +func (q *MetricsViewComparison) calculateMeasuresMeta() error { + compare := !isTimeRangeNil(q.ComparisonTimeRange) - if opts.PreWriteHook != nil { - err = opts.PreWriteHook(q.generateFilename()) - if err != nil { - return err - } + if !compare && len(q.ComparisonMeasures) > 0 { + return fmt.Errorf("comparison measures are provided but comparison time range is not") } - labelMap := make(map[string]string, len(mv.Measures)) - for _, m := range mv.Measures { - labelMap[m.Name] = m.Name - if m.Label != "" { - labelMap[m.Name] = m.Label - } - } - var metaLen int - for _, m := range q.Result.Rows[0].MeasureValues { - if q.measuresMeta[m.MeasureName].expand { - metaLen += 4 - } else { - metaLen++ + if len(q.ComparisonMeasures) == 0 && compare { + // backwards compatibility + q.ComparisonMeasures = make([]string, 0, len(q.Measures)) + for _, m := range q.Measures { + if m.BuiltinMeasure != runtimev1.BuiltinMeasure_BUILTIN_MEASURE_UNSPECIFIED { + continue + } + q.ComparisonMeasures = append(q.ComparisonMeasures, m.Name) } } - meta := make([]*runtimev1.MetricsViewColumn, metaLen+1) - dimName := q.DimensionName - for _, d := range mv.Dimensions { - if d.Name == q.DimensionName && d.Label != "" { - dimName = d.Label - } - } - meta[0] = &runtimev1.MetricsViewColumn{ - Name: dimName, - Type: runtimev1.Type_CODE_STRING.String(), - } + q.measuresMeta = make(map[string]metricsViewMeasureMeta, len(q.Measures)) - i := 1 - for _, m := range q.Result.Rows[0].MeasureValues { - meta[i] = &runtimev1.MetricsViewColumn{ - Name: labelMap[m.MeasureName], - Type: runtimev1.Type_CODE_FLOAT64.String(), - } - i++ - if q.measuresMeta[m.MeasureName].expand { - meta[i] = &runtimev1.MetricsViewColumn{ - Name: fmt.Sprintf("%s (prev)", labelMap[m.MeasureName]), - Type: runtimev1.Type_CODE_FLOAT64.String(), - } - meta[i+1] = &runtimev1.MetricsViewColumn{ - Name: fmt.Sprintf("%s (Δ)", labelMap[m.MeasureName]), - Type: runtimev1.Type_CODE_FLOAT64.String(), - } - meta[i+2] = &runtimev1.MetricsViewColumn{ - Name: fmt.Sprintf("%s (Δ%%)", labelMap[m.MeasureName]), - Type: runtimev1.Type_CODE_FLOAT64.String(), + inner := 1 + outer := 1 + for _, m := range q.Measures { + expand := false + for _, cm := range q.ComparisonMeasures { + if m.Name == cm { + expand = true + break } - i += 3 } - } - - data := make([]*structpb.Struct, len(q.Result.Rows)) - for i, row := range q.Result.Rows { - data[i] = &structpb.Struct{ - Fields: map[string]*structpb.Value{ - dimName: { - Kind: &structpb.Value_StringValue{ - StringValue: row.DimensionValue.GetStringValue(), - }, - }, - }, + q.measuresMeta[m.Name] = metricsViewMeasureMeta{ + baseSubqueryIndex: inner, + outerIndex: outer, + expand: expand, } - for _, m := range row.MeasureValues { - data[i].Fields[labelMap[m.MeasureName]] = &structpb.Value{ - Kind: &structpb.Value_NumberValue{ - NumberValue: m.BaseValue.GetNumberValue(), - }, - } - if q.measuresMeta[m.MeasureName].expand { - data[i].Fields[fmt.Sprintf("%s (prev)", labelMap[m.MeasureName])] = &structpb.Value{ - Kind: &structpb.Value_NumberValue{ - NumberValue: m.ComparisonValue.GetNumberValue(), - }, - } - data[i].Fields[fmt.Sprintf("%s (Δ)", labelMap[m.MeasureName])] = &structpb.Value{ - Kind: &structpb.Value_NumberValue{ - NumberValue: m.DeltaAbs.GetNumberValue(), - }, - } - data[i].Fields[fmt.Sprintf("%s (Δ%%)", labelMap[m.MeasureName])] = &structpb.Value{ - Kind: &structpb.Value_NumberValue{ - NumberValue: m.DeltaRel.GetNumberValue(), - }, - } - } + if expand { + outer += 4 + } else { + outer++ } + inner++ } - switch opts.Format { - case runtimev1.ExportFormat_EXPORT_FORMAT_UNSPECIFIED: - return fmt.Errorf("unspecified format") - case runtimev1.ExportFormat_EXPORT_FORMAT_CSV: - return WriteCSV(meta, data, w) - case runtimev1.ExportFormat_EXPORT_FORMAT_XLSX: - return WriteXLSX(meta, data, w) - case runtimev1.ExportFormat_EXPORT_FORMAT_PARQUET: - return WriteParquet(meta, data, w) - } - - return nil -} - -func (q *MetricsViewComparison) generateFilename() string { - filename := strings.ReplaceAll(q.MetricsViewName, `"`, `_`) - filename += "_" + q.DimensionName - if q.Where != nil || q.Having != nil { - filename += "_filtered" - } - return filename -} - -// TODO: a) Ensure correct time zone handling, b) Implement support for tr.RoundToGrain -// (Maybe consider pushing all this logic into the SQL instead?) -func timeRangeClause(tr *runtimev1.TimeRange, mv *runtimev1.MetricsViewSpec, timeCol string, args *[]any) (string, error) { - var clause string - if isTimeRangeNil(tr) { - return clause, nil + // check all comparison measures are present in the measures list + for _, cm := range q.ComparisonMeasures { + if _, ok := q.measuresMeta[cm]; !ok { + return fmt.Errorf("comparison measure '%s' is not present in the measures list", cm) + } } - start, end, err := ResolveTimeRange(tr, mv) + err := validateSort(q.Sort, q.measuresMeta, compare) if err != nil { - return "", err - } - - if !start.IsZero() { - clause += fmt.Sprintf(" AND %s >= ?", timeCol) - *args = append(*args, start) + return err } - if !end.IsZero() { - clause += fmt.Sprintf(" AND %s < ?", timeCol) - *args = append(*args, end) + err = validateMeasureAliases(q.Aliases, q.measuresMeta, compare) + if err != nil { + return err } - return clause, nil + return nil } func validateSort(sorts []*runtimev1.MetricsViewComparisonSort, measuresMeta map[string]metricsViewMeasureMeta, hasComparison bool) error { @@ -1347,12 +531,3 @@ func validateMeasureAliases(aliases []*runtimev1.MetricsViewComparisonMeasureAli func isTimeRangeNil(tr *runtimev1.TimeRange) bool { return tr == nil || (tr.Start == nil && tr.End == nil) } - -func (q *MetricsViewComparison) isDeltaComparison() bool { - return q.Sort[0].SortType == runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_ABS_DELTA || - q.Sort[0].SortType == runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_REL_DELTA -} - -func (q *MetricsViewComparison) isBase() bool { - return q.Sort[0].SortType == runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_BASE_VALUE -} diff --git a/runtime/queries/metricsview_comparison_toplist_test.go b/runtime/queries/metricsview_comparison_toplist_test.go index e56835328da..7e1318955b9 100644 --- a/runtime/queries/metricsview_comparison_toplist_test.go +++ b/runtime/queries/metricsview_comparison_toplist_test.go @@ -379,6 +379,13 @@ func TestMetricsViewsComparison_measure_filters_with_compare_no_alias(t *testing }, }, Limit: 250, + Aliases: []*runtimev1.MetricsViewComparisonMeasureAlias{ + { + Name: "measure_1", + Type: runtimev1.MetricsViewComparisonMeasureType_METRICS_VIEW_COMPARISON_MEASURE_TYPE_REL_DELTA, + Alias: "measure_1_something_else", + }, + }, Having: &runtimev1.Expression{ Expression: &runtimev1.Expression_Cond{ Cond: &runtimev1.Condition{ @@ -401,7 +408,7 @@ func TestMetricsViewsComparison_measure_filters_with_compare_no_alias(t *testing } err = q.Resolve(context.Background(), rt, instanceID, 0) - require.ErrorContains(t, err, "unknown column filter: measure_1__delta_rel") + require.ErrorContains(t, err, `name "measure_1__delta_rel" in expression is not a dimension or measure available in the current context`) } func TestMetricsViewsComparison_measure_filters_with_compare_base_measure(t *testing.T) { @@ -581,7 +588,8 @@ func TestMetricsViewsComparison_export_xlsx(t *testing.T) { var buf bytes.Buffer err = q.Export(context.Background(), rt, instanceId, &buf, &runtime.ExportOptions{ - Format: runtimev1.ExportFormat_EXPORT_FORMAT_XLSX, + Format: runtimev1.ExportFormat_EXPORT_FORMAT_XLSX, + PreWriteHook: func(filename string) error { return nil }, }) require.NoError(t, err) @@ -633,7 +641,8 @@ func TestServer_MetricsViewTimeseries_export_csv(t *testing.T) { var buf bytes.Buffer err = q.Export(context.Background(), rt, instanceId, &buf, &runtime.ExportOptions{ - Format: runtimev1.ExportFormat_EXPORT_FORMAT_CSV, + Format: runtimev1.ExportFormat_EXPORT_FORMAT_CSV, + PreWriteHook: func(filename string) error { return nil }, }) require.NoError(t, err) diff --git a/runtime/queries/metricsview_search.go b/runtime/queries/metricsview_search.go index 6fd933d9164..2e1f82592b3 100644 --- a/runtime/queries/metricsview_search.go +++ b/runtime/queries/metricsview_search.go @@ -220,7 +220,7 @@ func (q *MetricsViewSearch) executeSearchInDruid(ctx context.Context, rt *runtim } nq := druid.NewNativeQuery(druidSQLDSN.ReplaceAllString(dsn, "/v2/")) - req := druid.NewNativeSearchQueryRequest(table, q.Search, q.Dimensions, q.TimeRange.Start.AsTime(), q.TimeRange.End.AsTime(), query) + req := druid.NewNativeSearchQueryRequest(table, q.Search, q.Dimensions, q.TimeRange.Start.AsTime(), q.TimeRange.End.AsTime(), query) // TODO: timestamps may be nil! var res druid.NativeSearchQueryResponse err = nq.Do(ctx, req, &res, req.Context.QueryID) if err != nil { diff --git a/runtime/queries/metricsview_timeseries.go b/runtime/queries/metricsview_timeseries.go index 86896071a49..ee1cef6e9be 100644 --- a/runtime/queries/metricsview_timeseries.go +++ b/runtime/queries/metricsview_timeseries.go @@ -21,23 +21,20 @@ import ( ) type MetricsViewTimeSeries struct { - MetricsViewName string `json:"metrics_view_name,omitempty"` - MeasureNames []string `json:"measure_names,omitempty"` - InlineMeasures []*runtimev1.InlineMeasure `json:"inline_measures,omitempty"` - TimeStart *timestamppb.Timestamp `json:"time_start,omitempty"` - TimeEnd *timestamppb.Timestamp `json:"time_end,omitempty"` - Limit int64 `json:"limit,omitempty"` - Offset int64 `json:"offset,omitempty"` - Sort []*runtimev1.MetricsViewSort `json:"sort,omitempty"` - Where *runtimev1.Expression `json:"where,omitempty"` - Having *runtimev1.Expression `json:"having,omitempty"` - TimeGranularity runtimev1.TimeGrain `json:"time_granularity,omitempty"` - TimeZone string `json:"time_zone,omitempty"` - MetricsView *runtimev1.MetricsViewSpec `json:"-"` - ResolvedMVSecurity *runtime.ResolvedMetricsViewSecurity `json:"security"` - - // backwards compatibility - Filter *runtimev1.MetricsViewFilter `json:"filter,omitempty"` + MetricsViewName string `json:"metrics_view_name,omitempty"` + MeasureNames []string `json:"measure_names,omitempty"` + TimeStart *timestamppb.Timestamp `json:"time_start,omitempty"` + TimeEnd *timestamppb.Timestamp `json:"time_end,omitempty"` + Limit int64 `json:"limit,omitempty"` + Offset int64 `json:"offset,omitempty"` + Sort []*runtimev1.MetricsViewSort `json:"sort,omitempty"` + Where *runtimev1.Expression `json:"where,omitempty"` + Filter *runtimev1.MetricsViewFilter `json:"filter,omitempty"` // backwards compatibility + Having *runtimev1.Expression `json:"having,omitempty"` + TimeGranularity runtimev1.TimeGrain `json:"time_granularity,omitempty"` + TimeZone string `json:"time_zone,omitempty"` + SecurityAttributes map[string]any `json:"security_attributes,omitempty"` + SecurityPolicy *runtimev1.MetricsViewSpec_SecurityV2 `json:"security_policy,omitempty"` Result *runtimev1.MetricsViewTimeSeriesResponse `json:"-"` } @@ -75,74 +72,38 @@ func (q *MetricsViewTimeSeries) UnmarshalResult(v any) error { } func (q *MetricsViewTimeSeries) Resolve(ctx context.Context, rt *runtime.Runtime, instanceID string, priority int) error { - if q.MetricsView.TimeDimension == "" { - return fmt.Errorf("metrics view '%s' does not have a time dimension", q.MetricsViewName) + var ms []*runtimev1.MetricsViewAggregationMeasure + for _, m := range q.MeasureNames { + ms = append(ms, &runtimev1.MetricsViewAggregationMeasure{Name: m}) } - ctrl, err := rt.Controller(ctx, instanceID) + mv, security, err := resolveMVAndSecurityFromAttributes(ctx, rt, instanceID, q.MetricsViewName, q.SecurityAttributes, q.SecurityPolicy, nil, ms) if err != nil { return err } - r, err := ctrl.Get(ctx, &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: q.MetricsViewName}, false) - if err != nil { - return err + if mv.TimeDimension == "" { + return fmt.Errorf("metrics view '%s' does not have a time dimension", q.MetricsViewName) } - mv := r.GetMetricsView().Spec - // Attempt to route to metricsview executor - qry, ok, err := q.rewriteToMetricsViewQuery() + qry, err := q.rewriteToMetricsViewQuery(mv.TimeDimension) if err != nil { return fmt.Errorf("error rewriting to metrics query: %w", err) } - if ok { - e, err := metricsview.NewExecutor(ctx, rt, instanceID, mv, q.ResolvedMVSecurity, priority) - if err != nil { - return err - } - defer e.Close() - - res, _, err := e.Query(ctx, qry, nil) - if err != nil { - return err - } - defer res.Close() - return q.populateResult(res, q.MetricsView.TimeDimension, mv) - } - // Falling back to the old implementation - - olap, release, err := rt.OLAP(ctx, instanceID, q.MetricsView.Connector) + e, err := metricsview.NewExecutor(ctx, rt, instanceID, mv, security, priority) if err != nil { return err } - defer release() + defer e.Close() - // backwards compatibility - if q.Filter != nil { - if q.Where != nil { - return fmt.Errorf("both filter and where is provided") - } - q.Where = convertFilterToExpression(q.Filter) - } - - sql, tsAlias, args, err := q.buildMetricsTimeseriesSQL(olap, mv, q.ResolvedMVSecurity) - if err != nil { - return fmt.Errorf("error building query: %w", err) - } - - rows, err := olap.Execute(ctx, &drivers.Statement{ - Query: sql, - Args: args, - Priority: priority, - ExecutionTimeout: defaultExecutionTimeout, - }) + res, _, err := e.Query(ctx, qry, nil) if err != nil { return err } - defer rows.Close() + defer res.Close() - return q.populateResult(rows, tsAlias, mv) + return q.populateResult(res, mv.TimeDimension, mv) } func (q *MetricsViewTimeSeries) Export(ctx context.Context, rt *runtime.Runtime, instanceID string, w io.Writer, opts *runtime.ExportOptions) error { @@ -300,281 +261,6 @@ func (q *MetricsViewTimeSeries) generateFilename() string { return filename } -func (q *MetricsViewTimeSeries) buildMetricsTimeseriesSQL(olap drivers.OLAPStore, mv *runtimev1.MetricsViewSpec, policy *runtime.ResolvedMetricsViewSecurity) (string, string, []any, error) { - ms, err := resolveMeasures(mv, q.InlineMeasures, q.MeasureNames) - if err != nil { - return "", "", nil, err - } - - selectCols := []string{} - for _, m := range ms { - expr := fmt.Sprintf(`%s as "%s"`, m.Expression, m.Name) - selectCols = append(selectCols, expr) - } - - td := safeName(mv.TimeDimension) - if olap.Dialect() == drivers.DialectDuckDB { - td = fmt.Sprintf("%s::TIMESTAMP", td) - } - - whereClause := "1=1" - args := []any{} - if q.TimeStart != nil { - whereClause += fmt.Sprintf(" AND %s >= ?", td) - args = append(args, q.TimeStart.AsTime()) - } - if q.TimeEnd != nil { - whereClause += fmt.Sprintf(" AND %s < ?", td) - args = append(args, q.TimeEnd.AsTime()) - } - - if q.Where != nil { - builder := &ExpressionBuilder{ - mv: mv, - dialect: olap.Dialect(), - } - clause, clauseArgs, err := builder.buildExpression(q.Where) - if err != nil { - return "", "", nil, err - } - if strings.TrimSpace(clause) != "" { - whereClause += fmt.Sprintf(" AND (%s)", clause) - } - args = append(args, clauseArgs...) - } - - if policy != nil && policy.RowFilter != "" { - whereClause += fmt.Sprintf(" AND (%s)", policy.RowFilter) - } - - havingClause := "" - if q.Having != nil { - builder := &ExpressionBuilder{ - mv: mv, - dialect: olap.Dialect(), - having: true, - } - clause, clauseArgs, err := builder.buildExpression(q.Having) - if err != nil { - return "", "", nil, err - } - if strings.TrimSpace(clause) != "" { - havingClause = " HAVING " + clause - } - args = append(args, clauseArgs...) - } - - tsAlias := tempName("_ts_") - timezone := "UTC" - if q.TimeZone != "" { - timezone = q.TimeZone - } - - var sql string - switch olap.Dialect() { - case drivers.DialectDuckDB: - sql = q.buildDuckDBSQL(mv, tsAlias, selectCols, whereClause, havingClause, timezone) - case drivers.DialectDruid: - args = append([]any{timezone}, args...) - sql = q.buildDruidSQL(mv, tsAlias, selectCols, whereClause, havingClause) - case drivers.DialectPinot: - args = append([]any{timezone}, args...) - sql = q.buildPinotSQL(mv, tsAlias, selectCols, whereClause, havingClause) - case drivers.DialectClickHouse: - sql = q.buildClickHouseSQL(mv, tsAlias, selectCols, whereClause, havingClause, timezone) - default: - return "", "", nil, fmt.Errorf("not available for dialect '%s'", olap.Dialect()) - } - - return sql, tsAlias, args, nil -} - -func (q *MetricsViewTimeSeries) buildPinotSQL(mv *runtimev1.MetricsViewSpec, tsAlias string, selectCols []string, whereClause, havingClause string) string { - dateTruncSpecifier := drivers.DialectPinot.ConvertToDateTruncSpecifier(q.TimeGranularity) - - // TODO: handle shift, currently we add validation error for this, see runtime/validate.go - - timeClause := fmt.Sprintf("DATETRUNC('%s', %s,'MILLISECONDS', ?)", dateTruncSpecifier, safeName(mv.TimeDimension)) - sql := fmt.Sprintf( - `SELECT %s AS %s, %s FROM %s WHERE %s GROUP BY 1 %s ORDER BY 1`, - timeClause, - tsAlias, - strings.Join(selectCols, ", "), - safeName(mv.Table), - whereClause, - havingClause, - ) - - return sql -} - -func (q *MetricsViewTimeSeries) buildDruidSQL(mv *runtimev1.MetricsViewSpec, tsAlias string, selectCols []string, whereClause, havingClause string) string { - tsSpecifier := convertToDruidTimeFloorSpecifier(q.TimeGranularity) - - timeClause := fmt.Sprintf("time_floor(%s, '%s', null, CAST(? AS VARCHAR))", safeName(mv.TimeDimension), tsSpecifier) - if q.TimeGranularity == runtimev1.TimeGrain_TIME_GRAIN_WEEK && mv.FirstDayOfWeek > 1 { - dayOffset := 8 - mv.FirstDayOfWeek - timeClause = fmt.Sprintf("time_shift(time_floor(time_shift(%[1]s, 'P1D', %[3]d), '%[2]s', null, CAST(? AS VARCHAR)), 'P1D', -%[3]d)", safeName(mv.TimeDimension), tsSpecifier, dayOffset) - } else if q.TimeGranularity == runtimev1.TimeGrain_TIME_GRAIN_YEAR && mv.FirstMonthOfYear > 1 { - monthOffset := 13 - mv.FirstMonthOfYear - timeClause = fmt.Sprintf("time_shift(time_floor(time_shift(%[1]s, 'P1M', %[3]d), '%[2]s', null, CAST(? AS VARCHAR)), 'P1M', -%[3]d)", safeName(mv.TimeDimension), tsSpecifier, monthOffset) - } - - sql := fmt.Sprintf( - `SELECT %s AS %s, %s FROM %s WHERE %s GROUP BY 1 %s ORDER BY 1`, - timeClause, - tsAlias, - strings.Join(selectCols, ", "), - escapeMetricsViewTable(drivers.DialectDruid, mv), - whereClause, - havingClause, - ) - - return sql -} - -func (q *MetricsViewTimeSeries) buildClickHouseSQL(mv *runtimev1.MetricsViewSpec, tsAlias string, selectCols []string, whereClause, havingClause, timezone string) string { - dateTruncSpecifier := drivers.DialectClickHouse.ConvertToDateTruncSpecifier(q.TimeGranularity) - - shift := "" // shift to accommodate FirstDayOfWeek or FirstMonthOfYear - if q.TimeGranularity == runtimev1.TimeGrain_TIME_GRAIN_WEEK && mv.FirstDayOfWeek > 1 { - offset := 8 - mv.FirstDayOfWeek - shift = fmt.Sprintf("%d day", offset) - } else if q.TimeGranularity == runtimev1.TimeGrain_TIME_GRAIN_YEAR && mv.FirstMonthOfYear > 1 { - offset := 13 - mv.FirstMonthOfYear - shift = fmt.Sprintf("%d month", offset) - } - - sql := "" - if shift == "" { - sql = fmt.Sprintf( - ` - SELECT - toTimeZone(date_trunc('%[1]s', toTimeZone(%[2]s::TIMESTAMP, '%[7]s'))::TIMESTAMP, '%[7]s') as %[3]s, - %[4]s - FROM %[5]s - WHERE %[6]s - GROUP BY %[3]s - %[8]s - ORDER BY %[3]s`, - dateTruncSpecifier, // 1 - safeName(mv.TimeDimension), // 2 - tsAlias, // 3 - strings.Join(selectCols, ", "), // 4 - escapeMetricsViewTable(drivers.DialectClickHouse, mv), // 5 - whereClause, // 6 - timezone, // 7 - havingClause, // 8 - ) - } else { - sql = fmt.Sprintf( - ` - SELECT - toTimeZone(date_trunc('%[1]s', toTimeZone(%[2]s::TIMESTAMP, '%[7]s') + INTERVAL %[8]s)::TIMESTAMP - (INTERVAL %[8]s), '%[7]s') as %[3]s, - %[4]s - FROM %[5]s - WHERE %[6]s - GROUP BY %[3]s - %[9]s - ORDER BY %[3]s`, - dateTruncSpecifier, // 1 - safeName(mv.TimeDimension), // 2 - tsAlias, // 3 - strings.Join(selectCols, ", "), // 4 - escapeMetricsViewTable(drivers.DialectClickHouse, mv), // 5 - whereClause, // 6 - timezone, // 7 - shift, // 8 - havingClause, // 9 - ) - } - - return sql -} - -func (q *MetricsViewTimeSeries) buildDuckDBSQL(mv *runtimev1.MetricsViewSpec, tsAlias string, selectCols []string, whereClause, havingClause, timezone string) string { - dateTruncSpecifier := drivers.DialectDuckDB.ConvertToDateTruncSpecifier(q.TimeGranularity) - - shift := "" // shift to accommodate FirstDayOfWeek or FirstMonthOfYear - if q.TimeGranularity == runtimev1.TimeGrain_TIME_GRAIN_WEEK && mv.FirstDayOfWeek > 1 { - offset := 8 - mv.FirstDayOfWeek - shift = fmt.Sprintf("%d DAY", offset) - } else if q.TimeGranularity == runtimev1.TimeGrain_TIME_GRAIN_YEAR && mv.FirstMonthOfYear > 1 { - offset := 13 - mv.FirstMonthOfYear - shift = fmt.Sprintf("%d MONTH", offset) - } - - sql := "" - if shift == "" { - if q.TimeGranularity == runtimev1.TimeGrain_TIME_GRAIN_HOUR || - q.TimeGranularity == runtimev1.TimeGrain_TIME_GRAIN_MINUTE || - q.TimeGranularity == runtimev1.TimeGrain_TIME_GRAIN_SECOND { - sql = fmt.Sprintf( - ` - SELECT - time_bucket(INTERVAL '1 %[1]s', %[2]s::TIMESTAMPTZ, '%[7]s') as %[3]s, - %[4]s - FROM %[5]s - WHERE %[6]s - GROUP BY 1 - %[8]s - ORDER BY 1`, - dateTruncSpecifier, // 1 - safeName(mv.TimeDimension), // 2 - tsAlias, // 3 - strings.Join(selectCols, ", "), // 4 - escapeMetricsViewTable(drivers.DialectDuckDB, mv), // 5 - whereClause, // 6 - timezone, // 7 - havingClause, // 8 - ) - } else { // date_trunc is faster than time_bucket for year, month, week - sql = fmt.Sprintf( - ` - SELECT - timezone('%[7]s', date_trunc('%[1]s', timezone('%[7]s', %[2]s::TIMESTAMPTZ))) as %[3]s, - %[4]s - FROM %[5]s - WHERE %[6]s - GROUP BY 1 - %[8]s - ORDER BY 1`, - dateTruncSpecifier, // 1 - safeName(mv.TimeDimension), // 2 - tsAlias, // 3 - strings.Join(selectCols, ", "), // 4 - escapeMetricsViewTable(drivers.DialectDuckDB, mv), // 5 - whereClause, // 6 - timezone, // 7 - havingClause, // 8 - ) - } - } else { - sql = fmt.Sprintf( - ` - SELECT - timezone('%[7]s', date_trunc('%[1]s', timezone('%[7]s', %[2]s::TIMESTAMPTZ) + INTERVAL %[8]s) - (INTERVAL %[8]s)) as %[3]s, - %[4]s - FROM %[5]s - WHERE %[6]s - GROUP BY 1 - %[9]s - ORDER BY 1`, - dateTruncSpecifier, // 1 - safeName(mv.TimeDimension), // 2 - tsAlias, // 3 - strings.Join(selectCols, ", "), // 4 - escapeMetricsViewTable(drivers.DialectDuckDB, mv), // 5 - whereClause, // 6 - timezone, // 7 - shift, // 8 - havingClause, // 9 - ) - } - - return sql -} - func generateNullRecords(schema *runtimev1.StructType) *structpb.Struct { nullStruct := structpb.Struct{Fields: make(map[string]*structpb.Value, len(schema.Fields))} for _, f := range schema.Fields { @@ -602,25 +288,13 @@ func addTo(t time.Time, d duration.Duration, tz *time.Location) time.Time { return d.Add(t.In(tz)).In(time.UTC) } -func (q *MetricsViewTimeSeries) rewriteToMetricsViewQuery() (*metricsview.Query, bool, error) { +func (q *MetricsViewTimeSeries) rewriteToMetricsViewQuery(timeDimension string) (*metricsview.Query, error) { qry := &metricsview.Query{MetricsView: q.MetricsViewName} for _, m := range q.MeasureNames { qry.Measures = append(qry.Measures, metricsview.Measure{Name: m}) } - for _, m := range q.InlineMeasures { - res := metricsview.Measure{Name: m.Name} - switch strings.ToLower(m.Expression) { - case "count(*)": - res.Compute = &metricsview.MeasureCompute{Count: true} - default: - return nil, false, fmt.Errorf("inline measure expression is not supported") - } - - qry.Measures = append(qry.Measures, res) - } - res := &metricsview.TimeRange{} if q.TimeStart != nil { res.Start = q.TimeStart.AsTime() @@ -647,14 +321,14 @@ func (q *MetricsViewTimeSeries) rewriteToMetricsViewQuery() (*metricsview.Query, if len(q.Sort) == 0 { qry.Sort = append(qry.Sort, metricsview.Sort{ - Name: q.MetricsView.TimeDimension, + Name: timeDimension, Desc: false, }) } - if q.Filter != nil { // backwards backwards compatibility + if q.Filter != nil { // Backwards compatibility if q.Where != nil { - return nil, false, fmt.Errorf("both filter and where is provided") + return nil, fmt.Errorf("both filter and where is provided") } q.Where = convertFilterToExpression(q.Filter) } @@ -668,10 +342,10 @@ func (q *MetricsViewTimeSeries) rewriteToMetricsViewQuery() (*metricsview.Query, } qry.Dimensions = append(qry.Dimensions, metricsview.Dimension{ - Name: q.MetricsView.TimeDimension, + Name: timeDimension, Compute: &metricsview.DimensionCompute{ TimeFloor: &metricsview.DimensionComputeTimeFloor{ - Dimension: q.MetricsView.TimeDimension, + Dimension: timeDimension, Grain: metricsview.TimeGrainFromProto(q.TimeGranularity), }, }, @@ -679,5 +353,5 @@ func (q *MetricsViewTimeSeries) rewriteToMetricsViewQuery() (*metricsview.Query, qry.TimeZone = q.TimeZone - return qry, true, nil + return qry, nil } diff --git a/runtime/queries/metricsview_timeseries_test.go b/runtime/queries/metricsview_timeseries_test.go index a466f766d28..687d1674e1b 100644 --- a/runtime/queries/metricsview_timeseries_test.go +++ b/runtime/queries/metricsview_timeseries_test.go @@ -8,7 +8,6 @@ import ( "testing" runtimev1 "github.com/rilldata/rill/proto/gen/rill/runtime/v1" - "github.com/rilldata/rill/runtime" "github.com/rilldata/rill/runtime/pkg/expressionpb" "github.com/rilldata/rill/runtime/queries" "github.com/rilldata/rill/runtime/testruntime" @@ -65,23 +64,16 @@ func TestMetricsViewsTimeseriesAgainstClickHouse(t *testing.T) { func TestMetricsViewsTimeseries_month_grain(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_year"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"max_clicks"}, MetricsViewName: "timeseries_year", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2023-01-01T00:00:00Z"), TimeEnd: parseTime(t, "2024-01-01T00:00:00Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_MONTH, Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -115,16 +107,9 @@ func TestMetricsViewsTimeseries_month_grain(t *testing.T) { func TestMetricsViewsTimeseries_month_grain_IST(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_year"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"max_clicks"}, MetricsViewName: "timeseries_year", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2022-12-31T18:30:00Z"), TimeEnd: parseTime(t, "2024-01-31T18:30:00Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_MONTH, @@ -132,7 +117,7 @@ func TestMetricsViewsTimeseries_month_grain_IST(t *testing.T) { Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -168,16 +153,9 @@ func TestMetricsViewsTimeseries_month_grain_IST(t *testing.T) { func TestMetricsViewsTimeseries_quarter_grain_IST(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_year"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"max_clicks"}, MetricsViewName: "timeseries_year", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2022-12-31T18:30:00Z"), TimeEnd: parseTime(t, "2024-01-31T18:30:00Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_QUARTER, @@ -185,7 +163,7 @@ func TestMetricsViewsTimeseries_quarter_grain_IST(t *testing.T) { Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -207,16 +185,9 @@ func TestMetricsViewsTimeseries_quarter_grain_IST(t *testing.T) { func TestMetricsViewsTimeseries_year_grain_IST(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_year"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"max_clicks"}, MetricsViewName: "timeseries_year", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2022-12-31T18:30:00Z"), TimeEnd: parseTime(t, "2024-12-31T00:00:00Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_YEAR, @@ -224,7 +195,7 @@ func TestMetricsViewsTimeseries_year_grain_IST(t *testing.T) { Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -238,23 +209,16 @@ func TestMetricsViewsTimeseries_year_grain_IST(t *testing.T) { func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Continuous_Weekly(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_dst_backwards"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"total_records"}, MetricsViewName: "timeseries_dst_backwards", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2023-10-28T04:00:00.000Z"), TimeEnd: parseTime(t, "2023-11-19T05:00:00.000Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_WEEK, TimeZone: "America/New_York", Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -272,24 +236,16 @@ func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Continuous_Weekly(t *tes func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Continuous_WeeklyOnSaturday(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_dst_backwards"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - - mv.GetSpec().FirstDayOfWeek = 6 q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"total_records"}, - MetricsViewName: "timeseries_dst_backwards", - MetricsView: mv.Spec, + MetricsViewName: "timeseries_dst_backwards_fdow6", TimeStart: parseTime(t, "2023-10-28T04:00:00.000Z"), TimeEnd: parseTime(t, "2023-11-19T05:00:00.000Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_WEEK, TimeZone: "America/New_York", Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -307,23 +263,16 @@ func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Continuous_WeeklyOnSatur func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Continuous_Daily(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_dst_backwards"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"total_records"}, MetricsViewName: "timeseries_dst_backwards", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2023-11-03T04:00:00.000Z"), TimeEnd: parseTime(t, "2023-11-07T05:00:00.000Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_DAY, TimeZone: "America/New_York", Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -341,12 +290,6 @@ func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Continuous_Daily(t *test func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Sparse_Daily(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_dst_backwards"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"total_records"}, Where: expressionpb.In( @@ -354,14 +297,13 @@ func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Sparse_Daily(t *testing. []*runtimev1.Expression{expressionpb.Value(toStructpbValue(t, "sparse_day"))}, ), MetricsViewName: "timeseries_dst_backwards", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2023-11-03T04:00:00.000Z"), TimeEnd: parseTime(t, "2023-11-07T05:00:00.000Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_DAY, TimeZone: "America/New_York", Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -383,23 +325,16 @@ func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Sparse_Daily(t *testing. func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Continuous_Second(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_dst_backwards"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"total_records"}, MetricsViewName: "timeseries_dst_backwards", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2023-11-05T05:00:00.000Z"), TimeEnd: parseTime(t, "2023-11-05T05:00:01.000Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_SECOND, TimeZone: "America/New_York", Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -410,7 +345,6 @@ func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Continuous_Second(t *tes q = &queries.MetricsViewTimeSeries{ MeasureNames: []string{"total_records"}, MetricsViewName: "timeseries_dst_backwards", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2023-11-05T06:00:00.000Z"), TimeEnd: parseTime(t, "2023-11-05T06:00:01.000Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_SECOND, @@ -429,23 +363,16 @@ func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Continuous_Second(t *tes func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Continuous_Minute(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_dst_backwards"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"total_records"}, MetricsViewName: "timeseries_dst_backwards", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2023-11-05T05:00:00.000Z"), TimeEnd: parseTime(t, "2023-11-05T05:01:00.000Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_MINUTE, TimeZone: "America/New_York", Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -456,7 +383,6 @@ func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Continuous_Minute(t *tes q = &queries.MetricsViewTimeSeries{ MeasureNames: []string{"total_records"}, MetricsViewName: "timeseries_dst_backwards", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2023-11-05T06:00:00.000Z"), TimeEnd: parseTime(t, "2023-11-05T06:01:00.000Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_MINUTE, @@ -475,23 +401,16 @@ func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Continuous_Minute(t *tes func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Continuous_Hourly(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_dst_backwards"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"total_records"}, MetricsViewName: "timeseries_dst_backwards", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2023-11-05T03:00:00.000Z"), TimeEnd: parseTime(t, "2023-11-05T08:00:00.000Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_HOUR, TimeZone: "America/New_York", Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -511,12 +430,6 @@ func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Continuous_Hourly(t *tes func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Sparse_Hourly(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_dst_backwards"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"total_records"}, Where: expressionpb.In( @@ -524,14 +437,13 @@ func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Sparse_Hourly(t *testing []*runtimev1.Expression{expressionpb.Value(toStructpbValue(t, "sparse_hour"))}, ), MetricsViewName: "timeseries_dst_backwards", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2023-11-05T03:00:00.000Z"), TimeEnd: parseTime(t, "2023-11-05T08:00:00.000Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_HOUR, TimeZone: "America/New_York", Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -556,23 +468,16 @@ func TestMetricsViewTimeSeries_DayLightSavingsBackwards_Sparse_Hourly(t *testing func TestMetricsViewTimeSeries_DayLightSavingsForwards_Continuous_Weekly(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_dst_forwards"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"total_records"}, MetricsViewName: "timeseries_dst_forwards", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2023-02-26T05:00:00.000Z"), TimeEnd: parseTime(t, "2023-03-26T04:00:00.000Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_WEEK, TimeZone: "America/New_York", Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -590,23 +495,16 @@ func TestMetricsViewTimeSeries_DayLightSavingsForwards_Continuous_Weekly(t *test func TestMetricsViewTimeSeries_DayLightSavingsForwards_Continuous_Daily(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_dst_forwards"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"total_records"}, MetricsViewName: "timeseries_dst_forwards", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2023-03-10T05:00:00.000Z"), TimeEnd: parseTime(t, "2023-03-14T04:00:00.000Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_DAY, TimeZone: "America/New_York", Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -624,12 +522,6 @@ func TestMetricsViewTimeSeries_DayLightSavingsForwards_Continuous_Daily(t *testi func TestMetricsViewTimeSeries_DayLightSavingsForwards_Sparse_Daily(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_dst_forwards"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"total_records"}, Where: expressionpb.In( @@ -637,14 +529,13 @@ func TestMetricsViewTimeSeries_DayLightSavingsForwards_Sparse_Daily(t *testing.T []*runtimev1.Expression{expressionpb.Value(toStructpbValue(t, "sparse_day"))}, ), MetricsViewName: "timeseries_dst_forwards", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2023-03-10T05:00:00.000Z"), TimeEnd: parseTime(t, "2023-03-14T04:00:00.000Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_DAY, TimeZone: "America/New_York", Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -666,23 +557,16 @@ func TestMetricsViewTimeSeries_DayLightSavingsForwards_Sparse_Daily(t *testing.T func TestMetricsViewTimeSeries_DayLightSavingsForwards_Continuous_Hourly(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_dst_forwards"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"total_records"}, MetricsViewName: "timeseries_dst_forwards", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2023-03-12T04:00:00.000Z"), TimeEnd: parseTime(t, "2023-03-12T09:00:00.000Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_HOUR, TimeZone: "America/New_York", Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -702,12 +586,6 @@ func TestMetricsViewTimeSeries_DayLightSavingsForwards_Continuous_Hourly(t *test func TestMetricsViewTimeSeries_DayLightSavingsForwards_Sparse_Hourly(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_dst_forwards"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"total_records"}, Where: expressionpb.In( @@ -715,14 +593,13 @@ func TestMetricsViewTimeSeries_DayLightSavingsForwards_Sparse_Hourly(t *testing. []*runtimev1.Expression{expressionpb.Value(toStructpbValue(t, "sparse_hour"))}, ), MetricsViewName: "timeseries_dst_forwards", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2023-03-12T04:00:00.000Z"), TimeEnd: parseTime(t, "2023-03-12T09:00:00.000Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_HOUR, TimeZone: "America/New_York", Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -747,16 +624,9 @@ func TestMetricsViewTimeSeries_DayLightSavingsForwards_Sparse_Hourly(t *testing. func TestMetricsViewTimeSeries_having_clause(t *testing.T) { rt, instanceID := testruntime.NewInstanceForProject(t, "timeseries") - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "timeseries_gaps"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - q := &queries.MetricsViewTimeSeries{ MeasureNames: []string{"sum_imps"}, MetricsViewName: "timeseries_gaps", - MetricsView: mv.Spec, TimeStart: parseTime(t, "2019-01-01T00:00:00Z"), TimeEnd: parseTime(t, "2019-01-07T00:00:00Z"), TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_DAY, @@ -781,7 +651,7 @@ func TestMetricsViewTimeSeries_having_clause(t *testing.T) { }, Limit: 250, } - err = q.Resolve(context.Background(), rt, instanceID, 0) + err := q.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) require.NotEmpty(t, q.Result) rows := q.Result.Data @@ -816,17 +686,10 @@ func TestMetricsTimeseries_measure_filters_same_name(t *testing.T) { err := ctr.Resolve(context.Background(), rt, instanceID, 0) require.NoError(t, err) - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "ad_bids_metrics"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - lmt := int64(25) q := &queries.MetricsViewTimeSeries{ MetricsViewName: "ad_bids_metrics", MeasureNames: []string{"bid_price"}, - MetricsView: mv.Spec, TimeStart: timestamppb.New(ctr.Result.Min.AsTime().Truncate(time.Hour)), TimeEnd: ctr.Result.Max, TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_DAY, diff --git a/runtime/queries/metricsview_toplist.go b/runtime/queries/metricsview_toplist.go index 46303168362..56e6ac62dfc 100644 --- a/runtime/queries/metricsview_toplist.go +++ b/runtime/queries/metricsview_toplist.go @@ -5,31 +5,30 @@ import ( "encoding/json" "fmt" "io" + "os" "strings" runtimev1 "github.com/rilldata/rill/proto/gen/rill/runtime/v1" "github.com/rilldata/rill/runtime" "github.com/rilldata/rill/runtime/drivers" + "github.com/rilldata/rill/runtime/metricsview" "google.golang.org/protobuf/types/known/timestamppb" ) type MetricsViewToplist struct { - MetricsViewName string `json:"metrics_view_name,omitempty"` - DimensionName string `json:"dimension_name,omitempty"` - MeasureNames []string `json:"measure_names,omitempty"` - InlineMeasures []*runtimev1.InlineMeasure `json:"inline_measures,omitempty"` - TimeStart *timestamppb.Timestamp `json:"time_start,omitempty"` - TimeEnd *timestamppb.Timestamp `json:"time_end,omitempty"` - Limit *int64 `json:"limit,omitempty"` - Offset int64 `json:"offset,omitempty"` - Sort []*runtimev1.MetricsViewSort `json:"sort,omitempty"` - Where *runtimev1.Expression `json:"where,omitempty"` - Having *runtimev1.Expression `json:"having,omitempty"` - MetricsView *runtimev1.MetricsViewSpec `json:"-"` - ResolvedMVSecurity *runtime.ResolvedMetricsViewSecurity `json:"security"` - - // backwards compatibility - Filter *runtimev1.MetricsViewFilter `json:"filter,omitempty"` + MetricsViewName string `json:"metrics_view_name,omitempty"` + DimensionName string `json:"dimension_name,omitempty"` + MeasureNames []string `json:"measure_names,omitempty"` + TimeStart *timestamppb.Timestamp `json:"time_start,omitempty"` + TimeEnd *timestamppb.Timestamp `json:"time_end,omitempty"` + Limit *int64 `json:"limit,omitempty"` + Offset int64 `json:"offset,omitempty"` + Sort []*runtimev1.MetricsViewSort `json:"sort,omitempty"` + Where *runtimev1.Expression `json:"where,omitempty"` + Filter *runtimev1.MetricsViewFilter `json:"filter,omitempty"` // backwards compatibility + Having *runtimev1.Expression `json:"having,omitempty"` + SecurityAttributes map[string]any `json:"security_attributes,omitempty"` + SecurityPolicy *runtimev1.MetricsViewSpec_SecurityV2 `json:"security_policy,omitempty"` Result *runtimev1.MetricsViewToplistResponse `json:"-"` } @@ -67,229 +66,167 @@ func (q *MetricsViewToplist) UnmarshalResult(v any) error { } func (q *MetricsViewToplist) Resolve(ctx context.Context, rt *runtime.Runtime, instanceID string, priority int) error { - olap, release, err := rt.OLAP(ctx, instanceID, q.MetricsView.Connector) - if err != nil { - return err + ds := []*runtimev1.MetricsViewAggregationDimension{{Name: q.DimensionName}} + ms := make([]*runtimev1.MetricsViewAggregationMeasure, len(q.MeasureNames)) + for i, m := range q.MeasureNames { + ms[i] = &runtimev1.MetricsViewAggregationMeasure{Name: m} } - defer release() - if olap.Dialect() != drivers.DialectDuckDB && olap.Dialect() != drivers.DialectDruid && olap.Dialect() != drivers.DialectClickHouse && olap.Dialect() != drivers.DialectPinot { - return fmt.Errorf("not available for dialect '%s'", olap.Dialect()) + mv, security, err := resolveMVAndSecurityFromAttributes(ctx, rt, instanceID, q.MetricsViewName, q.SecurityAttributes, q.SecurityPolicy, ds, ms) + if err != nil { + return err } - if q.MetricsView.TimeDimension == "" && (q.TimeStart != nil || q.TimeEnd != nil) { - return fmt.Errorf("metrics view '%s' does not have a time dimension", q.MetricsViewName) + qry, err := q.rewriteToMetricsViewQuery(false) + if err != nil { + return fmt.Errorf("error rewriting to metrics query: %w", err) } - // backwards compatibility - if q.Filter != nil { - if q.Where != nil { - return fmt.Errorf("both filter and where is provided") - } - q.Where = convertFilterToExpression(q.Filter) + e, err := metricsview.NewExecutor(ctx, rt, instanceID, mv, security, priority) + if err != nil { + return err } + defer e.Close() - // Build query - sql, args, err := q.buildMetricsTopListSQL(q.MetricsView, olap.Dialect(), q.ResolvedMVSecurity) + res, _, err := e.Query(ctx, qry, nil) if err != nil { - return fmt.Errorf("error building query: %w", err) + return err } + defer res.Close() - // Execute - meta, data, err := metricsQuery(ctx, olap, priority, sql, args) + data, err := rowsToData(res) if err != nil { return err } q.Result = &runtimev1.MetricsViewToplistResponse{ - Meta: meta, + Meta: structTypeToMetricsViewColumn(res.Schema), Data: data, } - return nil } func (q *MetricsViewToplist) Export(ctx context.Context, rt *runtime.Runtime, instanceID string, w io.Writer, opts *runtime.ExportOptions) error { - olap, release, err := rt.OLAP(ctx, instanceID, q.MetricsView.Connector) + ds := []*runtimev1.MetricsViewAggregationDimension{{Name: q.DimensionName}} + ms := make([]*runtimev1.MetricsViewAggregationMeasure, len(q.MeasureNames)) + for i, m := range q.MeasureNames { + ms[i] = &runtimev1.MetricsViewAggregationMeasure{Name: m} + } + + mv, security, err := resolveMVAndSecurityFromAttributes(ctx, rt, instanceID, q.MetricsViewName, q.SecurityAttributes, q.SecurityPolicy, ds, ms) if err != nil { return err } - defer release() - - switch olap.Dialect() { - case drivers.DialectDuckDB: - if opts.Format == runtimev1.ExportFormat_EXPORT_FORMAT_CSV || opts.Format == runtimev1.ExportFormat_EXPORT_FORMAT_PARQUET { - if q.MetricsView.TimeDimension == "" && (q.TimeStart != nil || q.TimeEnd != nil) { - return fmt.Errorf("metrics view '%s' does not have a time dimension", q.MetricsViewName) - } - - sql, args, err := q.buildMetricsTopListSQL(q.MetricsView, olap.Dialect(), q.ResolvedMVSecurity) - if err != nil { - return err - } - - filename := q.generateFilename() - if err := DuckDBCopyExport(ctx, w, opts, sql, args, filename, olap, opts.Format); err != nil { - return err - } - } else { - if err := q.generalExport(ctx, rt, instanceID, w, opts); err != nil { - return err - } - } - case drivers.DialectDruid, drivers.DialectClickHouse, drivers.DialectPinot: - if err := q.generalExport(ctx, rt, instanceID, w, opts); err != nil { - return err - } - default: - return fmt.Errorf("not available for dialect '%s'", olap.Dialect()) - } - - return nil -} -func (q *MetricsViewToplist) generalExport(ctx context.Context, rt *runtime.Runtime, instanceID string, w io.Writer, opts *runtime.ExportOptions) error { - err := q.Resolve(ctx, rt, instanceID, opts.Priority) + qry, err := q.rewriteToMetricsViewQuery(true) if err != nil { - return err + return fmt.Errorf("error rewriting to metrics query: %w", err) } - if opts.PreWriteHook != nil { - err = opts.PreWriteHook(q.generateFilename()) - if err != nil { - return err - } + e, err := metricsview.NewExecutor(ctx, rt, instanceID, mv, security, opts.Priority) + if err != nil { + return err } + defer e.Close() + var format drivers.FileFormat switch opts.Format { - case runtimev1.ExportFormat_EXPORT_FORMAT_UNSPECIFIED: - return fmt.Errorf("unspecified format") case runtimev1.ExportFormat_EXPORT_FORMAT_CSV: - return WriteCSV(q.Result.Meta, q.Result.Data, w) + format = drivers.FileFormatCSV case runtimev1.ExportFormat_EXPORT_FORMAT_XLSX: - return WriteXLSX(q.Result.Meta, q.Result.Data, w) + format = drivers.FileFormatXLSX case runtimev1.ExportFormat_EXPORT_FORMAT_PARQUET: - return WriteParquet(q.Result.Meta, q.Result.Data, w) + format = drivers.FileFormatParquet + default: + return fmt.Errorf("unsupported format: %s", opts.Format.String()) } - return nil -} + path, err := e.Export(ctx, qry, nil, format) + if err != nil { + return err + } + defer func() { _ = os.Remove(path) }() -func (q *MetricsViewToplist) generateFilename() string { - filename := strings.ReplaceAll(q.MetricsViewName, `"`, `_`) - filename += "_" + q.DimensionName - if q.TimeStart != nil || q.TimeEnd != nil || q.Where != nil || q.Having != nil { - filename += "_filtered" + filename := q.generateFilename() + err = opts.PreWriteHook(filename) + if err != nil { + return err } - return filename -} -func (q *MetricsViewToplist) buildMetricsTopListSQL(mv *runtimev1.MetricsViewSpec, dialect drivers.Dialect, policy *runtime.ResolvedMetricsViewSecurity) (string, []any, error) { - ms, err := resolveMeasures(mv, q.InlineMeasures, q.MeasureNames) + f, err := os.Open(path) if err != nil { - return "", nil, err + return err } + defer f.Close() - dim, err := metricsViewDimension(mv, q.DimensionName) + _, err = io.Copy(w, f) if err != nil { - return "", nil, err + return err } - var selectCols []string - dimSel, unnestClause := dialect.DimensionSelect(mv.Database, mv.DatabaseSchema, mv.Table, dim) - selectCols = append(selectCols, dimSel) + return nil +} + +func (q *MetricsViewToplist) rewriteToMetricsViewQuery(export bool) (*metricsview.Query, error) { + qry := &metricsview.Query{MetricsView: q.MetricsViewName} - for _, m := range ms { - expr := fmt.Sprintf(`%s as "%s"`, m.Expression, m.Name) - selectCols = append(selectCols, expr) - } + qry.Dimensions = append(qry.Dimensions, metricsview.Dimension{Name: q.DimensionName}) - whereClause := "1=1" - args := []any{} - if mv.TimeDimension != "" { - td := safeName(mv.TimeDimension) - if dialect == drivers.DialectDuckDB { - td = fmt.Sprintf("%s::TIMESTAMP", td) - } + for _, m := range q.MeasureNames { + qry.Measures = append(qry.Measures, metricsview.Measure{Name: m}) + } + if q.TimeStart != nil || q.TimeEnd != nil { + res := &metricsview.TimeRange{} if q.TimeStart != nil { - whereClause += fmt.Sprintf(" AND %s >= ?", td) - args = append(args, q.TimeStart.AsTime()) + res.Start = q.TimeStart.AsTime() } if q.TimeEnd != nil { - whereClause += fmt.Sprintf(" AND %s < ?", td) - args = append(args, q.TimeEnd.AsTime()) + res.End = q.TimeEnd.AsTime() } + qry.TimeRange = res } - if q.Where != nil { - builder := &ExpressionBuilder{ - mv: mv, - dialect: dialect, - } - clause, clauseArgs, err := builder.buildExpression(q.Where) - if err != nil { - return "", nil, err - } - if strings.TrimSpace(clause) != "" { - whereClause += fmt.Sprintf(" AND (%s)", clause) - } - args = append(args, clauseArgs...) + if q.Limit != nil { + qry.Limit = q.Limit } - if policy != nil && policy.RowFilter != "" { - whereClause += fmt.Sprintf(" AND (%s)", policy.RowFilter) + if q.Offset != 0 { + qry.Offset = &q.Offset } - havingClause := "" - if q.Having != nil { - var havingClauseArgs []any - builder := &ExpressionBuilder{ - mv: mv, - dialect: dialect, - having: true, - } - havingClause, havingClauseArgs, err = builder.buildExpression(q.Having) - if err != nil { - return "", nil, err - } - if strings.TrimSpace(havingClause) != "" { - havingClause = " HAVING " + havingClause - } - args = append(args, havingClauseArgs...) + for _, s := range q.Sort { + qry.Sort = append(qry.Sort, metricsview.Sort{ + Name: s.Name, + Desc: !s.Ascending, + }) } - sortingCriteria := make([]string, 0, len(q.Sort)) - for _, s := range q.Sort { - sortCriterion := safeName(s.Name) - if !s.Ascending { - sortCriterion += " DESC" - } - if dialect == drivers.DialectDuckDB { - sortCriterion += " NULLS LAST" + if q.Filter != nil { // Backwards compatibility + if q.Where != nil { + return nil, fmt.Errorf("both filter and where is provided") } - sortingCriteria = append(sortingCriteria, sortCriterion) + q.Where = convertFilterToExpression(q.Filter) } - orderClause := "" - if len(sortingCriteria) > 0 { - orderClause = "ORDER BY " + strings.Join(sortingCriteria, ", ") + + if q.Where != nil { + qry.Where = metricsview.NewExpressionFromProto(q.Where) } - var limitClause string - if q.Limit != nil { - limitClause = fmt.Sprintf("LIMIT %d", *q.Limit) + if q.Having != nil { + qry.Having = metricsview.NewExpressionFromProto(q.Having) } - sql := fmt.Sprintf("SELECT %s FROM %s %s WHERE %s GROUP BY 1 %s %s %s OFFSET %d", - strings.Join(selectCols, ", "), - escapeMetricsViewTable(dialect, mv), - unnestClause, - whereClause, - havingClause, - orderClause, - limitClause, - q.Offset, - ) - - return sql, args, nil + qry.Label = export + + return qry, nil +} + +func (q *MetricsViewToplist) generateFilename() string { + filename := strings.ReplaceAll(q.MetricsViewName, `"`, `_`) + filename += "_" + q.DimensionName + if q.TimeStart != nil || q.TimeEnd != nil || q.Where != nil || q.Having != nil { + filename += "_filtered" + } + return filename } diff --git a/runtime/queries/metricsview_toplist_test.go b/runtime/queries/metricsview_toplist_test.go index 1317d921427..98b2699ef41 100644 --- a/runtime/queries/metricsview_toplist_test.go +++ b/runtime/queries/metricsview_toplist_test.go @@ -6,7 +6,6 @@ import ( "testing" runtimev1 "github.com/rilldata/rill/proto/gen/rill/runtime/v1" - "github.com/rilldata/rill/runtime" "github.com/rilldata/rill/runtime/queries" "github.com/rilldata/rill/runtime/testruntime" "github.com/stretchr/testify/require" @@ -56,18 +55,11 @@ func TestMetricsViewsToplist_measure_filters(t *testing.T) { diff := ctr.Result.Max.AsTime().Sub(ctr.Result.Min.AsTime()) maxTime := ctr.Result.Min.AsTime().Add(diff / 2) - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "ad_bids_metrics"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - lmt := int64(250) q := &queries.MetricsViewToplist{ MetricsViewName: "ad_bids_metrics", DimensionName: "dom", MeasureNames: []string{"measure_1"}, - MetricsView: mv.Spec, TimeStart: ctr.Result.Min, TimeEnd: timestamppb.New(maxTime), Having: &runtimev1.Expression{ @@ -91,7 +83,7 @@ func TestMetricsViewsToplist_measure_filters(t *testing.T) { }, Sort: []*runtimev1.MetricsViewSort{ { - Name: "domain", + Name: "dom", Ascending: false, }, }, @@ -119,18 +111,11 @@ func TestMetricsViewsToplist_measure_filters_same_name(t *testing.T) { diff := ctr.Result.Max.AsTime().Sub(ctr.Result.Min.AsTime()) maxTime := ctr.Result.Min.AsTime().Add(diff / 2) - ctrl, err := rt.Controller(context.Background(), instanceID) - require.NoError(t, err) - r, err := ctrl.Get(context.Background(), &runtimev1.ResourceName{Kind: runtime.ResourceKindMetricsView, Name: "ad_bids_metrics"}, false) - require.NoError(t, err) - mv := r.GetMetricsView() - lmt := int64(250) q := &queries.MetricsViewToplist{ MetricsViewName: "ad_bids_metrics", DimensionName: "dom", MeasureNames: []string{"bid_price"}, - MetricsView: mv.Spec, TimeStart: ctr.Result.Min, TimeEnd: timestamppb.New(maxTime), Having: &runtimev1.Expression{ diff --git a/runtime/queries/metricsview_totals.go b/runtime/queries/metricsview_totals.go index 9993766f23f..a96fd34bc3f 100644 --- a/runtime/queries/metricsview_totals.go +++ b/runtime/queries/metricsview_totals.go @@ -5,27 +5,22 @@ import ( "encoding/json" "fmt" "io" - "strings" runtimev1 "github.com/rilldata/rill/proto/gen/rill/runtime/v1" "github.com/rilldata/rill/runtime" - "github.com/rilldata/rill/runtime/drivers" + "github.com/rilldata/rill/runtime/metricsview" "google.golang.org/protobuf/types/known/timestamppb" ) type MetricsViewTotals struct { - MetricsViewName string `json:"metrics_view_name,omitempty"` - MeasureNames []string `json:"measure_names,omitempty"` - InlineMeasures []*runtimev1.InlineMeasure `json:"inline_measures,omitempty"` - TimeStart *timestamppb.Timestamp `json:"time_start,omitempty"` - TimeEnd *timestamppb.Timestamp `json:"time_end,omitempty"` - Where *runtimev1.Expression `json:"where,omitempty"` - Having *runtimev1.Expression `json:"having,omitempty"` - MetricsView *runtimev1.MetricsViewSpec `json:"-"` - ResolvedMVSecurity *runtime.ResolvedMetricsViewSecurity `json:"security"` - - // backwards compatibility - Filter *runtimev1.MetricsViewFilter `json:"filter,omitempty"` + MetricsViewName string `json:"metrics_view_name,omitempty"` + MeasureNames []string `json:"measure_names,omitempty"` + TimeStart *timestamppb.Timestamp `json:"time_start,omitempty"` + TimeEnd *timestamppb.Timestamp `json:"time_end,omitempty"` + Where *runtimev1.Expression `json:"where,omitempty"` + Filter *runtimev1.MetricsViewFilter `json:"filter,omitempty"` // backwards compatibility + SecurityAttributes map[string]any `json:"security_attributes,omitempty"` + SecurityPolicy *runtimev1.MetricsViewSpec_SecurityV2 `json:"security_policy,omitempty"` Result *runtimev1.MetricsViewTotalsResponse `json:"-"` } @@ -63,44 +58,44 @@ func (q *MetricsViewTotals) UnmarshalResult(v any) error { } func (q *MetricsViewTotals) Resolve(ctx context.Context, rt *runtime.Runtime, instanceID string, priority int) error { - olap, release, err := rt.OLAP(ctx, instanceID, q.MetricsView.Connector) - if err != nil { - return err + var ms []*runtimev1.MetricsViewAggregationMeasure + for _, m := range q.MeasureNames { + ms = append(ms, &runtimev1.MetricsViewAggregationMeasure{Name: m}) } - defer release() - if olap.Dialect() != drivers.DialectDuckDB && olap.Dialect() != drivers.DialectDruid && olap.Dialect() != drivers.DialectClickHouse && olap.Dialect() != drivers.DialectPinot { - return fmt.Errorf("not available for dialect '%s'", olap.Dialect()) + mv, security, err := resolveMVAndSecurityFromAttributes(ctx, rt, instanceID, q.MetricsViewName, q.SecurityAttributes, q.SecurityPolicy, nil, ms) + if err != nil { + return err } - if q.MetricsView.TimeDimension == "" && (q.TimeStart != nil || q.TimeEnd != nil) { - return fmt.Errorf("metrics view '%s' does not have a time dimension", q.MetricsViewName) + qry, err := q.rewriteToMetricsViewQuery(false) + if err != nil { + return fmt.Errorf("error rewriting to metrics query: %w", err) } - // backwards compatibility - if q.Filter != nil { - if q.Where != nil { - return fmt.Errorf("both filter and where is provided") - } - q.Where = convertFilterToExpression(q.Filter) + e, err := metricsview.NewExecutor(ctx, rt, instanceID, mv, security, priority) + if err != nil { + return err } + defer e.Close() - ql, args, err := q.buildMetricsTotalsSQL(q.MetricsView, olap.Dialect(), q.ResolvedMVSecurity) + res, _, err := e.Query(ctx, qry, nil) if err != nil { - return fmt.Errorf("error building query: %w", err) + return err } + defer res.Close() - meta, data, err := metricsQuery(ctx, olap, priority, ql, args) + data, err := rowsToData(res) if err != nil { return err } if len(data) == 0 { - return fmt.Errorf("no rows received from totals query") + return fmt.Errorf("no data returned") } q.Result = &runtimev1.MetricsViewTotalsResponse{ - Meta: meta, + Meta: structTypeToMetricsViewColumn(res.Schema), Data: data[0], } @@ -111,59 +106,36 @@ func (q *MetricsViewTotals) Export(ctx context.Context, rt *runtime.Runtime, ins return ErrExportNotSupported } -func (q *MetricsViewTotals) buildMetricsTotalsSQL(mv *runtimev1.MetricsViewSpec, dialect drivers.Dialect, policy *runtime.ResolvedMetricsViewSecurity) (string, []any, error) { - ms, err := resolveMeasures(mv, q.InlineMeasures, q.MeasureNames) - if err != nil { - return "", nil, err - } +func (q *MetricsViewTotals) rewriteToMetricsViewQuery(exporting bool) (*metricsview.Query, error) { + qry := &metricsview.Query{MetricsView: q.MetricsViewName} - selectCols := []string{} - for _, m := range ms { - expr := fmt.Sprintf(`%s as "%s"`, m.Expression, m.Name) - selectCols = append(selectCols, expr) + for _, m := range q.MeasureNames { + qry.Measures = append(qry.Measures, metricsview.Measure{Name: m}) } - whereClause := "1=1" - args := []any{} - if mv.TimeDimension != "" { - td := safeName(mv.TimeDimension) - if dialect == drivers.DialectDuckDB { - td = fmt.Sprintf("%s::TIMESTAMP", td) - } + if q.TimeStart != nil || q.TimeEnd != nil { + res := &metricsview.TimeRange{} if q.TimeStart != nil { - whereClause += fmt.Sprintf(" AND %s >= ?", td) - args = append(args, q.TimeStart.AsTime()) + res.Start = q.TimeStart.AsTime() } if q.TimeEnd != nil { - whereClause += fmt.Sprintf(" AND %s < ?", td) - args = append(args, q.TimeEnd.AsTime()) + res.End = q.TimeEnd.AsTime() } + qry.TimeRange = res } - if q.Where != nil { - builder := &ExpressionBuilder{ - mv: mv, - dialect: dialect, - } - clause, clauseArgs, err := builder.buildExpression(q.Where) - if err != nil { - return "", nil, err - } - if strings.TrimSpace(clause) != "" { - whereClause += fmt.Sprintf(" AND (%s)", clause) + if q.Filter != nil { // Backwards compatibility + if q.Where != nil { + return nil, fmt.Errorf("both filter and where is provided") } - args = append(args, clauseArgs...) + q.Where = convertFilterToExpression(q.Filter) } - if policy != nil && policy.RowFilter != "" { - whereClause += fmt.Sprintf(" AND (%s)", policy.RowFilter) + if q.Where != nil { + qry.Where = metricsview.NewExpressionFromProto(q.Where) } - sql := fmt.Sprintf( - "SELECT %s FROM %s WHERE %s", - strings.Join(selectCols, ", "), - escapeMetricsViewTable(dialect, mv), - whereClause, - ) - return sql, args, nil + qry.Label = exporting + + return qry, nil } diff --git a/runtime/queries/protoutil.go b/runtime/queries/protoutil.go index e03d9e5b5b7..1dcaec2631f 100644 --- a/runtime/queries/protoutil.go +++ b/runtime/queries/protoutil.go @@ -13,6 +13,18 @@ func safeFieldType(t *runtimev1.StructType, i int) *runtimev1.Type { return nil } +// safeFieldTypeName returns the type of the field with name n, or nil if no type is found. +func safeFieldTypeName(t *runtimev1.StructType, n string) *runtimev1.Type { + if t != nil { + for _, f := range t.Fields { + if f.Name == n { + return f.Type + } + } + } + return nil +} + // sizeProtoMessage returns size of serialized proto message func sizeProtoMessage(m proto.Message) int64 { bytes, err := proto.Marshal(m) diff --git a/runtime/queries/sqlutil.go b/runtime/queries/sqlutil.go index 63adb3096ed..63cadec2911 100644 --- a/runtime/queries/sqlutil.go +++ b/runtime/queries/sqlutil.go @@ -31,30 +31,6 @@ func tempName(prefix string) string { return prefix + strings.ReplaceAll(uuid.New().String(), "-", "") } -func convertToDruidTimeFloorSpecifier(specifier runtimev1.TimeGrain) string { - switch specifier { - case runtimev1.TimeGrain_TIME_GRAIN_MILLISECOND: - return "PT0.001S" - case runtimev1.TimeGrain_TIME_GRAIN_SECOND: - return "PT1S" - case runtimev1.TimeGrain_TIME_GRAIN_MINUTE: - return "PT1M" - case runtimev1.TimeGrain_TIME_GRAIN_HOUR: - return "PT1H" - case runtimev1.TimeGrain_TIME_GRAIN_DAY: - return "P1D" - case runtimev1.TimeGrain_TIME_GRAIN_WEEK: - return "P1W" - case runtimev1.TimeGrain_TIME_GRAIN_MONTH: - return "P1M" - case runtimev1.TimeGrain_TIME_GRAIN_QUARTER: - return "P3M" - case runtimev1.TimeGrain_TIME_GRAIN_YEAR: - return "P1Y" - } - panic(fmt.Errorf("unconvertable time grain specifier: %v", specifier)) -} - func toTimeGrain(val string) runtimev1.TimeGrain { switch strings.ToUpper(val) { case "MILLISECOND": diff --git a/runtime/server/downloads.go b/runtime/server/downloads.go index 040b98e48e9..7d90209eddd 100644 --- a/runtime/server/downloads.go +++ b/runtime/server/downloads.go @@ -153,34 +153,6 @@ func (s *Server) downloadHandler(w http.ResponseWriter, req *http.Request) { case *runtimev1.Query_MetricsViewToplistRequest: r := v.MetricsViewToplistRequest - mv, security, err := resolveMVAndSecurityFromAttributes(req.Context(), s.runtime, request.InstanceId, r.MetricsViewName, attrs, policy) - if err != nil { - if errors.Is(err, ErrForbidden) { - http.Error(w, "action not allowed", http.StatusUnauthorized) - return - } - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if !checkFieldAccess(r.DimensionName, security) { - http.Error(w, "action not allowed", http.StatusUnauthorized) - return - } - - // validate measures access - for _, m := range r.MeasureNames { - if !checkFieldAccess(m, security) { - http.Error(w, "action not allowed", http.StatusUnauthorized) - return - } - } - - err = validateInlineMeasures(r.InlineMeasures) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - var limitPtr *int64 limit := s.resolveExportLimit(cfg, request.Limit, r.Limit) if limit != 0 { @@ -188,18 +160,15 @@ func (s *Server) downloadHandler(w http.ResponseWriter, req *http.Request) { } q = &queries.MetricsViewToplist{ - MetricsViewName: r.MetricsViewName, - DimensionName: r.DimensionName, - MeasureNames: r.MeasureNames, - InlineMeasures: r.InlineMeasures, - TimeStart: r.TimeStart, - TimeEnd: r.TimeEnd, - Sort: r.Sort, - Where: r.Where, - Having: r.Having, - Limit: limitPtr, - MetricsView: mv, - ResolvedMVSecurity: security, + MetricsViewName: r.MetricsViewName, + DimensionName: r.DimensionName, + MeasureNames: r.MeasureNames, + TimeStart: r.TimeStart, + TimeEnd: r.TimeEnd, + Sort: r.Sort, + Where: r.Where, + Having: r.Having, + Limit: limitPtr, } case *runtimev1.Query_MetricsViewRowsRequest: r := v.MetricsViewRowsRequest @@ -234,34 +203,15 @@ func (s *Server) downloadHandler(w http.ResponseWriter, req *http.Request) { case *runtimev1.Query_MetricsViewTimeSeriesRequest: r := v.MetricsViewTimeSeriesRequest - mv, security, err := resolveMVAndSecurityFromAttributes(req.Context(), s.runtime, request.InstanceId, r.MetricsViewName, attrs, policy) - if err != nil { - if errors.Is(err, ErrForbidden) { - http.Error(w, "action not allowed", http.StatusUnauthorized) - return - } - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - err = validateInlineMeasures(r.InlineMeasures) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - q = &queries.MetricsViewTimeSeries{ - MetricsViewName: r.MetricsViewName, - MeasureNames: r.MeasureNames, - InlineMeasures: r.InlineMeasures, - TimeStart: r.TimeStart, - TimeEnd: r.TimeEnd, - TimeGranularity: r.TimeGranularity, - Where: r.Where, - Having: r.Having, - TimeZone: r.TimeZone, - MetricsView: mv, - ResolvedMVSecurity: security, + MetricsViewName: r.MetricsViewName, + MeasureNames: r.MeasureNames, + TimeStart: r.TimeStart, + TimeEnd: r.TimeEnd, + TimeGranularity: r.TimeGranularity, + Where: r.Where, + Having: r.Having, + TimeZone: r.TimeZone, } case *runtimev1.Query_MetricsViewComparisonRequest: r := v.MetricsViewComparisonRequest diff --git a/runtime/server/observability_utils.go b/runtime/server/observability_utils.go index af225b9e5eb..fa7de3129ae 100644 --- a/runtime/server/observability_utils.go +++ b/runtime/server/observability_utils.go @@ -28,18 +28,6 @@ func filterCount(m *runtimev1.Expression) int { return c } -func marshalInlineMeasure(ms []*runtimev1.InlineMeasure) []string { - if len(ms) == 0 { - return make([]string, 0) - } - - names := make([]string, len(ms)) - for i := 0; i < len(ms); i++ { - names[i] = ms[i].Name - } - return nil -} - func marshalMetricsViewAggregationDimension(ms []*runtimev1.MetricsViewAggregationDimension) []string { if len(ms) == 0 { return make([]string, 0) diff --git a/runtime/server/queries_metrics.go b/runtime/server/queries_metrics.go index 95ef121cfef..1a30a7f3fff 100644 --- a/runtime/server/queries_metrics.go +++ b/runtime/server/queries_metrics.go @@ -2,8 +2,6 @@ package server import ( "context" - "fmt" - "regexp" runtimev1 "github.com/rilldata/rill/proto/gen/rill/runtime/v1" "github.com/rilldata/rill/runtime" @@ -84,7 +82,6 @@ func (s *Server) MetricsViewToplist(ctx context.Context, req *runtimev1.MetricsV attribute.String("args.time_end", safeTimeStr(req.TimeEnd)), attribute.Int("args.filter_count", filterCount(req.Where)), attribute.StringSlice("args.sort.names", marshalMetricsViewSort(req.Sort)), - attribute.StringSlice("args.inline_measures", marshalInlineMeasure(req.InlineMeasures)), ) s.addInstanceRequestAttributes(ctx, req.InstanceId) @@ -93,48 +90,24 @@ func (s *Server) MetricsViewToplist(ctx context.Context, req *runtimev1.MetricsV return nil, ErrForbidden } - mv, security, err := resolveMVAndSecurity(ctx, s.runtime, req.InstanceId, req.MetricsViewName) - if err != nil { - return nil, err - } - - if !checkFieldAccess(req.DimensionName, security) { - return nil, ErrForbidden - } - - // validate measures access - for _, m := range req.MeasureNames { - if !checkFieldAccess(m, security) { - return nil, ErrForbidden - } - } - - err = validateInlineMeasures(req.InlineMeasures) - if err != nil { - return nil, err - } - if req.Limit == 0 { req.Limit = 100 } q := &queries.MetricsViewToplist{ - MetricsViewName: req.MetricsViewName, - DimensionName: req.DimensionName, - MeasureNames: req.MeasureNames, - InlineMeasures: req.InlineMeasures, - TimeStart: req.TimeStart, - TimeEnd: req.TimeEnd, - Limit: &req.Limit, - Offset: req.Offset, - Sort: req.Sort, - Where: req.Where, - Having: req.Having, - MetricsView: mv, - ResolvedMVSecurity: security, - Filter: req.Filter, + MetricsViewName: req.MetricsViewName, + DimensionName: req.DimensionName, + MeasureNames: req.MeasureNames, + TimeStart: req.TimeStart, + TimeEnd: req.TimeEnd, + Limit: &req.Limit, + Offset: req.Offset, + Sort: req.Sort, + Where: req.Where, + Having: req.Having, + Filter: req.Filter, } - err = s.runtime.Query(ctx, req.InstanceId, q, int(req.Priority)) + err := s.runtime.Query(ctx, req.InstanceId, q, int(req.Priority)) if err != nil { return nil, err } @@ -211,7 +184,6 @@ func (s *Server) MetricsViewTimeSeries(ctx context.Context, req *runtimev1.Metri attribute.String("args.instance_id", req.InstanceId), attribute.String("args.metric_view", req.MetricsViewName), attribute.StringSlice("args.measures", req.MeasureNames), - attribute.StringSlice("args.inline_measures.names", marshalInlineMeasure(req.InlineMeasures)), attribute.String("args.time_start", safeTimeStr(req.TimeStart)), attribute.String("args.time_end", safeTimeStr(req.TimeEnd)), attribute.String("args.time_granularity", req.TimeGranularity.String()), @@ -225,38 +197,18 @@ func (s *Server) MetricsViewTimeSeries(ctx context.Context, req *runtimev1.Metri return nil, ErrForbidden } - mv, security, err := resolveMVAndSecurity(ctx, s.runtime, req.InstanceId, req.MetricsViewName) - if err != nil { - return nil, err - } - - // validate measures access - for _, m := range req.MeasureNames { - if !checkFieldAccess(m, security) { - return nil, ErrForbidden - } - } - - err = validateInlineMeasures(req.InlineMeasures) - if err != nil { - return nil, err - } - q := &queries.MetricsViewTimeSeries{ - MetricsViewName: req.MetricsViewName, - MeasureNames: req.MeasureNames, - InlineMeasures: req.InlineMeasures, - TimeStart: req.TimeStart, - TimeEnd: req.TimeEnd, - TimeGranularity: req.TimeGranularity, - Where: req.Where, - Having: req.Having, - TimeZone: req.TimeZone, - MetricsView: mv, - ResolvedMVSecurity: security, - Filter: req.Filter, + MetricsViewName: req.MetricsViewName, + MeasureNames: req.MeasureNames, + TimeStart: req.TimeStart, + TimeEnd: req.TimeEnd, + TimeGranularity: req.TimeGranularity, + Where: req.Where, + Having: req.Having, + TimeZone: req.TimeZone, + Filter: req.Filter, } - err = s.runtime.Query(ctx, req.InstanceId, q, int(req.Priority)) + err := s.runtime.Query(ctx, req.InstanceId, q, int(req.Priority)) if err != nil { return nil, err } @@ -269,7 +221,6 @@ func (s *Server) MetricsViewTotals(ctx context.Context, req *runtimev1.MetricsVi attribute.String("args.instance_id", req.InstanceId), attribute.String("args.metric_view", req.MetricsViewName), attribute.StringSlice("args.measures", req.MeasureNames), - attribute.StringSlice("args.inline_measures.names", marshalInlineMeasure(req.InlineMeasures)), attribute.String("args.time_start", safeTimeStr(req.TimeStart)), attribute.String("args.time_end", safeTimeStr(req.TimeEnd)), attribute.Int("args.filter_count", filterCount(req.Where)), @@ -282,35 +233,15 @@ func (s *Server) MetricsViewTotals(ctx context.Context, req *runtimev1.MetricsVi return nil, ErrForbidden } - mv, security, err := resolveMVAndSecurity(ctx, s.runtime, req.InstanceId, req.MetricsViewName) - if err != nil { - return nil, err - } - - // validate measures access - for _, m := range req.MeasureNames { - if !checkFieldAccess(m, security) { - return nil, ErrForbidden - } - } - - err = validateInlineMeasures(req.InlineMeasures) - if err != nil { - return nil, err - } - q := &queries.MetricsViewTotals{ - MetricsViewName: req.MetricsViewName, - MeasureNames: req.MeasureNames, - InlineMeasures: req.InlineMeasures, - TimeStart: req.TimeStart, - TimeEnd: req.TimeEnd, - Where: req.Where, - MetricsView: mv, - ResolvedMVSecurity: security, - Filter: req.Filter, + MetricsViewName: req.MetricsViewName, + MeasureNames: req.MeasureNames, + TimeStart: req.TimeStart, + TimeEnd: req.TimeEnd, + Where: req.Where, + Filter: req.Filter, } - err = s.runtime.Query(ctx, req.InstanceId, q, int(req.Priority)) + err := s.runtime.Query(ctx, req.InstanceId, q, int(req.Priority)) if err != nil { return nil, err } @@ -462,22 +393,6 @@ func (s *Server) MetricsViewSearch(ctx context.Context, req *runtimev1.MetricsVi return q.Result, nil } -// inlineMeasureRegexp is used by validateInlineMeasures. -var inlineMeasureRegexp = regexp.MustCompile(`(?i)^COUNT\((DISTINCT)? *.+\)$`) - -// validateInlineMeasures checks that the inline measures are allowed. -// This is to prevent injection of arbitrary SQL from clients with only ReadMetrics access. -// In the future, we should consider allowing arbitrary expressions from people with wider access. -// Currently, only COUNT(*) and COUNT(DISTINCT name) is allowed. -func validateInlineMeasures(ms []*runtimev1.InlineMeasure) error { - for _, im := range ms { - if !inlineMeasureRegexp.MatchString(im.Expression) { - return fmt.Errorf("illegal inline measure expression: %q", im.Expression) - } - } - return nil -} - func resolveMVAndSecurity(ctx context.Context, rt *runtime.Runtime, instanceID, metricsViewName string) (*runtimev1.MetricsViewSpec, *runtime.ResolvedMetricsViewSecurity, error) { res, mv, err := lookupMetricsView(ctx, rt, instanceID, metricsViewName) if err != nil { @@ -533,29 +448,3 @@ func lookupMetricsView(ctx context.Context, rt *runtime.Runtime, instanceID, nam return res, spec, nil } - -func checkFieldAccess(field string, policy *runtime.ResolvedMetricsViewSecurity) bool { - if policy != nil { - if !policy.Access { - return false - } - - if len(policy.Include) > 0 { - for _, include := range policy.Include { - if include == field { - return true - } - } - } else if len(policy.Exclude) > 0 { - for _, exclude := range policy.Exclude { - if exclude == field { - return false - } - } - } else { - // if no include/exclude is specified, then all fields are allowed - return true - } - } - return true -} diff --git a/runtime/server/queries_metrics_comparison_toplist_test.go b/runtime/server/queries_metrics_comparison_toplist_test.go index c023dd4bfe1..70994ee58d9 100644 --- a/runtime/server/queries_metrics_comparison_toplist_test.go +++ b/runtime/server/queries_metrics_comparison_toplist_test.go @@ -892,12 +892,14 @@ func TestServer_MetricsViewComparison_unnested_dimension_expression_in_filter(t Exact: true, }) require.NoError(t, err) - require.Len(t, tr.Rows, 4) + require.Len(t, tr.Rows, 6) require.Equal(t, 1, len(tr.Rows[0].MeasureValues)) require.Equal(t, "instagram", tr.Rows[0].DimensionValue.GetStringValue()) - require.Equal(t, "com", tr.Rows[1].DimensionValue.GetStringValue()) - require.Equal(t, "facebook", tr.Rows[2].DimensionValue.GetStringValue()) - require.Equal(t, "msn", tr.Rows[3].DimensionValue.GetStringValue()) + require.Equal(t, "facebook", tr.Rows[1].DimensionValue.GetStringValue()) + require.Equal(t, "sports", tr.Rows[2].DimensionValue.GetStringValue()) + require.Equal(t, "com", tr.Rows[3].DimensionValue.GetStringValue()) + require.Equal(t, "news", tr.Rows[4].DimensionValue.GetStringValue()) + require.Equal(t, "msn", tr.Rows[5].DimensionValue.GetStringValue()) } func TestServer_MetricsViewComparison_unnested_dimension_expression_like_filter(t *testing.T) { @@ -940,14 +942,15 @@ func TestServer_MetricsViewComparison_unnested_dimension_expression_like_filter( for _, row := range tr.Rows { fmt.Println(row.DimensionValue.GetStringValue()) } - require.Len(t, tr.Rows, 6) + require.Len(t, tr.Rows, 7) require.Equal(t, 1, len(tr.Rows[0].MeasureValues)) require.Equal(t, "instagram", tr.Rows[0].DimensionValue.GetStringValue()) require.Equal(t, "facebook", tr.Rows[1].DimensionValue.GetStringValue()) - require.Equal(t, "com", tr.Rows[2].DimensionValue.GetStringValue()) - require.Equal(t, "google", tr.Rows[3].DimensionValue.GetStringValue()) - require.Equal(t, "news", tr.Rows[4].DimensionValue.GetStringValue()) - require.Equal(t, "msn", tr.Rows[5].DimensionValue.GetStringValue()) + require.Equal(t, "sports", tr.Rows[2].DimensionValue.GetStringValue()) + require.Equal(t, "com", tr.Rows[3].DimensionValue.GetStringValue()) + require.Equal(t, "google", tr.Rows[4].DimensionValue.GetStringValue()) + require.Equal(t, "news", tr.Rows[5].DimensionValue.GetStringValue()) + require.Equal(t, "msn", tr.Rows[6].DimensionValue.GetStringValue()) } /* @@ -1420,10 +1423,12 @@ func TestServer_MetricsViewComparison_no_comparison_unnested_dimension_expressio Exact: true, }) require.NoError(t, err) - require.Len(t, tr.Rows, 4) + require.Len(t, tr.Rows, 6) require.Equal(t, 1, len(tr.Rows[0].MeasureValues)) - require.Equal(t, "instagram", tr.Rows[0].DimensionValue.GetStringValue()) - require.Equal(t, "msn", tr.Rows[1].DimensionValue.GetStringValue()) - require.Equal(t, "facebook", tr.Rows[2].DimensionValue.GetStringValue()) - require.Equal(t, "com", tr.Rows[3].DimensionValue.GetStringValue()) + require.Equal(t, "sports", tr.Rows[0].DimensionValue.GetStringValue()) + require.Equal(t, "instagram", tr.Rows[1].DimensionValue.GetStringValue()) + require.Equal(t, "msn", tr.Rows[2].DimensionValue.GetStringValue()) + require.Equal(t, "facebook", tr.Rows[3].DimensionValue.GetStringValue()) + require.Equal(t, "news", tr.Rows[4].DimensionValue.GetStringValue()) + require.Equal(t, "com", tr.Rows[5].DimensionValue.GetStringValue()) } diff --git a/runtime/server/queries_metrics_test.go b/runtime/server/queries_metrics_test.go deleted file mode 100644 index 920ff7d6f2c..00000000000 --- a/runtime/server/queries_metrics_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package server - -import ( - "testing" - - runtimev1 "github.com/rilldata/rill/proto/gen/rill/runtime/v1" - "github.com/stretchr/testify/require" -) - -func TestInlineMeasureValidation(t *testing.T) { - require.NoError(t, validateInlineMeasures([]*runtimev1.InlineMeasure{{Expression: "COUNT(*)"}})) - require.NoError(t, validateInlineMeasures([]*runtimev1.InlineMeasure{{Expression: "COUNT(my_dim)"}})) - require.NoError(t, validateInlineMeasures([]*runtimev1.InlineMeasure{{Expression: "COUNT(DISTINCT my_dim)"}})) - require.NoError(t, validateInlineMeasures([]*runtimev1.InlineMeasure{{Expression: "count(distinct my_dim)"}})) - require.Error(t, validateInlineMeasures([]*runtimev1.InlineMeasure{{Expression: "SUM(my_dim)"}})) -} diff --git a/runtime/server/queries_metrics_timeseries_test.go b/runtime/server/queries_metrics_timeseries_test.go index 3a0c65eb94f..a81a02a51e6 100644 --- a/runtime/server/queries_metrics_timeseries_test.go +++ b/runtime/server/queries_metrics_timeseries_test.go @@ -747,15 +747,11 @@ func TestServer_MetricsViewTimeseries_export_xlsx(t *testing.T) { rt, instanceId := testruntime.NewInstanceForProject(t, "ad_bids_2rows") ctx := testCtx() - mvName := "ad_bids_metrics" - mv, security := resolveMVAndSecurity(t, rt, instanceId, mvName) q := &queries.MetricsViewTimeSeries{ - MetricsViewName: "ad_bids_metrics", - TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_DAY, - MeasureNames: []string{"measure_0"}, - MetricsView: mv, - ResolvedMVSecurity: security, + MetricsViewName: "ad_bids_metrics", + TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_DAY, + MeasureNames: []string{"measure_0"}, } var buf bytes.Buffer @@ -780,15 +776,11 @@ func TestServer_MetricsViewTimeseries_export_csv(t *testing.T) { rt, instanceId := testruntime.NewInstanceForProject(t, "ad_bids_2rows") ctx := testCtx() - mvName := "ad_bids_metrics" - mv, security := resolveMVAndSecurity(t, rt, instanceId, mvName) q := &queries.MetricsViewTimeSeries{ - MetricsViewName: "ad_bids_metrics", - TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_DAY, - MeasureNames: []string{"measure_0"}, - MetricsView: mv, - ResolvedMVSecurity: security, + MetricsViewName: "ad_bids_metrics", + TimeGranularity: runtimev1.TimeGrain_TIME_GRAIN_DAY, + MeasureNames: []string{"measure_0"}, } var buf bytes.Buffer diff --git a/runtime/server/queries_metrics_toplist_test.go b/runtime/server/queries_metrics_toplist_test.go index 60c9cb8734c..effea538773 100644 --- a/runtime/server/queries_metrics_toplist_test.go +++ b/runtime/server/queries_metrics_toplist_test.go @@ -66,47 +66,6 @@ func TestServer_MetricsViewToplist_dim_sort(t *testing.T) { require.Equal(t, 2.0, tr.Data[1].Fields["measure_2"].GetNumberValue()) } -func TestServer_MetricsViewToplist_InlineMeasures(t *testing.T) { - t.Parallel() - server, instanceId := getMetricsTestServer(t, "ad_bids_2rows") - - tr, err := server.MetricsViewToplist(testCtx(), &runtimev1.MetricsViewToplistRequest{ - InstanceId: instanceId, - MetricsViewName: "ad_bids_metrics", - DimensionName: "domain", - MeasureNames: []string{"measure_0", "tmp_measure", "measure_2"}, - InlineMeasures: []*runtimev1.InlineMeasure{ - { - Name: "tmp_measure", - Expression: "count(*)", - }, - }, - Sort: []*runtimev1.MetricsViewSort{ - { - Name: "measure_0", - Ascending: true, - }, - { - Name: "measure_2", - Ascending: true, - }, - }, - }) - require.NoError(t, err) - require.Equal(t, 2, len(tr.Data)) - require.Equal(t, 4, len(tr.Data[0].Fields)) - - require.Equal(t, "yahoo.com", tr.Data[0].Fields["domain"].GetStringValue()) - require.Equal(t, 1.0, tr.Data[0].Fields["measure_0"].GetNumberValue()) - require.Equal(t, 1.0, tr.Data[0].Fields["measure_2"].GetNumberValue()) - require.Equal(t, 1.0, tr.Data[0].Fields["tmp_measure"].GetNumberValue()) - - require.Equal(t, "msn.com", tr.Data[1].Fields["domain"].GetStringValue()) - require.Equal(t, 1.0, tr.Data[1].Fields["measure_0"].GetNumberValue()) - require.Equal(t, 2.0, tr.Data[1].Fields["measure_2"].GetNumberValue()) - require.Equal(t, 1.0, tr.Data[1].Fields["tmp_measure"].GetNumberValue()) -} - func TestServer_MetricsViewToplist_quotes(t *testing.T) { t.Parallel() server, instanceId := getMetricsTestServer(t, "ad_bids_2rows") @@ -419,10 +378,12 @@ func TestServer_MetricsViewToplist__dimension_expression_in_filter(t *testing.T) ), }) require.NoError(t, err) - require.Len(t, tr.Data, 4) + require.Len(t, tr.Data, 6) require.Equal(t, 2, len(tr.Data[0].Fields)) - require.Equal(t, "instagram", tr.Data[0].AsMap()["domain_parts"]) - require.Equal(t, "msn", tr.Data[1].AsMap()["domain_parts"]) - require.Equal(t, "facebook", tr.Data[2].AsMap()["domain_parts"]) - require.Equal(t, "com", tr.Data[3].AsMap()["domain_parts"]) + require.Equal(t, "sports", tr.Data[0].AsMap()["domain_parts"]) + require.Equal(t, "instagram", tr.Data[1].AsMap()["domain_parts"]) + require.Equal(t, "msn", tr.Data[2].AsMap()["domain_parts"]) + require.Equal(t, "facebook", tr.Data[3].AsMap()["domain_parts"]) + require.Equal(t, "news", tr.Data[4].AsMap()["domain_parts"]) + require.Equal(t, "com", tr.Data[5].AsMap()["domain_parts"]) } diff --git a/runtime/testruntime/testdata/timeseries/dashboards/timeseries_dst_backwards_fdow6.yaml b/runtime/testruntime/testdata/timeseries/dashboards/timeseries_dst_backwards_fdow6.yaml new file mode 100644 index 00000000000..5c4bbf899f7 --- /dev/null +++ b/runtime/testruntime/testdata/timeseries/dashboards/timeseries_dst_backwards_fdow6.yaml @@ -0,0 +1,28 @@ +# Visit https://docs.rilldata.com/reference/project-files to learn more about Rill project files. + +title: timeseries_dst +model: timeseries_dst_backwards_model +timeseries: timestamp +first_day_of_week: 6 +measures: + - label: Total records + expression: count(*) + name: total_records + description: Total number of records present + format_preset: humanize + valid_percent_of_total: true +dimensions: + - name: label + column: label +available_time_zones: + - America/Los_Angeles + - America/Chicago + - America/New_York + - Europe/London + - Europe/Paris + - Asia/Jerusalem + - Europe/Moscow + - Asia/Kolkata + - Asia/Shanghai + - Asia/Tokyo + - Australia/Sydney \ No newline at end of file diff --git a/web-admin/src/client/gen/index.schemas.ts b/web-admin/src/client/gen/index.schemas.ts index dd36341d542..77da371a2d7 100644 --- a/web-admin/src/client/gen/index.schemas.ts +++ b/web-admin/src/client/gen/index.schemas.ts @@ -1044,7 +1044,7 @@ export interface RpcStatus { * `NullValue` is a singleton enumeration to represent the null value for the `Value` type union. -The JSON representation for `NullValue` is JSON `null`. + The JSON representation for `NullValue` is JSON `null`. - NULL_VALUE: Null value. */ diff --git a/web-common/src/proto/gen/rill/runtime/v1/queries_pb.ts b/web-common/src/proto/gen/rill/runtime/v1/queries_pb.ts index 13487735711..8f6e9cdebd2 100644 --- a/web-common/src/proto/gen/rill/runtime/v1/queries_pb.ts +++ b/web-common/src/proto/gen/rill/runtime/v1/queries_pb.ts @@ -1420,11 +1420,6 @@ export class MetricsViewToplistRequest extends Message metricsViewName = ""; /** - * Required either measure_names or inline_measures - * * @generated from field: repeated string measure_names = 3; */ measureNames: string[] = []; - /** - * @generated from field: repeated rill.runtime.v1.InlineMeasure inline_measures = 9; - */ - inlineMeasures: InlineMeasure[] = []; - /** * Optional. Defaults to min * @@ -2304,7 +2283,6 @@ export class MetricsViewTotalsRequest extends Message { no: 1, name: "instance_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "metrics_view_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 3, name: "measure_names", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true }, - { no: 9, name: "inline_measures", kind: "message", T: InlineMeasure, repeated: true }, { no: 4, name: "time_start", kind: "message", T: Timestamp }, { no: 5, name: "time_end", kind: "message", T: Timestamp }, { no: 7, name: "where", kind: "message", T: Expression }, diff --git a/web-common/src/runtime-client/gen/index.schemas.ts b/web-common/src/runtime-client/gen/index.schemas.ts index 31e71b9f590..fde124464b7 100644 --- a/web-common/src/runtime-client/gen/index.schemas.ts +++ b/web-common/src/runtime-client/gen/index.schemas.ts @@ -180,7 +180,6 @@ export type QueryServiceColumnNullCountParams = { export type QueryServiceMetricsViewTotalsBody = { measureNames?: string[]; - inlineMeasures?: V1InlineMeasure[]; timeStart?: string; timeEnd?: string; where?: V1Expression; @@ -191,7 +190,6 @@ export type QueryServiceMetricsViewTotalsBody = { export type QueryServiceMetricsViewToplistBody = { dimensionName?: string; measureNames?: string[]; - inlineMeasures?: V1InlineMeasure[]; timeStart?: string; timeEnd?: string; limit?: string; @@ -205,7 +203,6 @@ export type QueryServiceMetricsViewToplistBody = { export type QueryServiceMetricsViewTimeSeriesBody = { measureNames?: string[]; - inlineMeasures?: V1InlineMeasure[]; timeStart?: string; timeEnd?: string; timeGranularity?: V1TimeGrain; @@ -544,6 +541,12 @@ export interface V1TimeSeriesValue { records?: V1TimeSeriesValueRecords; } +export interface V1TimeSeriesTimeRange { + start?: string; + end?: string; + interval?: V1TimeGrain; +} + export interface V1TimeSeriesResponse { results?: V1TimeSeriesValue[]; spark?: V1TimeSeriesValue[]; @@ -572,12 +575,6 @@ export const V1TimeGrain = { TIME_GRAIN_YEAR: "TIME_GRAIN_YEAR", } as const; -export interface V1TimeSeriesTimeRange { - start?: string; - end?: string; - interval?: V1TimeGrain; -} - export interface V1TimeRange { start?: string; end?: string; @@ -1092,7 +1089,6 @@ export interface V1MetricsViewTotalsRequest { instanceId?: string; metricsViewName?: string; measureNames?: string[]; - inlineMeasures?: V1InlineMeasure[]; timeStart?: string; timeEnd?: string; where?: V1Expression; @@ -1112,7 +1108,6 @@ export interface V1MetricsViewToplistRequest { metricsViewName?: string; dimensionName?: string; measureNames?: string[]; - inlineMeasures?: V1InlineMeasure[]; timeStart?: string; timeEnd?: string; limit?: string; @@ -1129,21 +1124,6 @@ export interface V1MetricsViewTimeSeriesResponse { data?: V1TimeSeriesValue[]; } -export interface V1MetricsViewTimeSeriesRequest { - instanceId?: string; - metricsViewName?: string; - measureNames?: string[]; - inlineMeasures?: V1InlineMeasure[]; - timeStart?: string; - timeEnd?: string; - timeGranularity?: V1TimeGrain; - where?: V1Expression; - having?: V1Expression; - timeZone?: string; - priority?: number; - filter?: V1MetricsViewFilter; -} - export interface V1MetricsViewTimeRangeResponse { timeRangeSummary?: V1TimeRangeSummary; } @@ -1210,6 +1190,20 @@ export interface V1MetricsViewFilter { exclude?: MetricsViewFilterCond[]; } +export interface V1MetricsViewTimeSeriesRequest { + instanceId?: string; + metricsViewName?: string; + measureNames?: string[]; + timeStart?: string; + timeEnd?: string; + timeGranularity?: V1TimeGrain; + where?: V1Expression; + having?: V1Expression; + timeZone?: string; + priority?: number; + filter?: V1MetricsViewFilter; +} + export interface V1MetricsViewRowsRequest { instanceId?: string; metricsViewName?: string; @@ -1289,25 +1283,6 @@ export interface V1MetricsViewComparisonMeasureAlias { alias?: string; } -export interface V1MetricsViewComparisonRequest { - instanceId?: string; - metricsViewName?: string; - dimension?: V1MetricsViewAggregationDimension; - measures?: V1MetricsViewAggregationMeasure[]; - comparisonMeasures?: string[]; - sort?: V1MetricsViewComparisonSort[]; - timeRange?: V1TimeRange; - comparisonTimeRange?: V1TimeRange; - where?: V1Expression; - having?: V1Expression; - aliases?: V1MetricsViewComparisonMeasureAlias[]; - limit?: string; - offset?: string; - priority?: number; - exact?: boolean; - filter?: V1MetricsViewFilter; -} - export interface V1MetricsViewColumn { name?: string; type?: string; @@ -1326,6 +1301,27 @@ export interface V1MetricsViewAggregationResponse { data?: V1MetricsViewAggregationResponseDataItem[]; } +export interface V1MetricsViewAggregationRequest { + instanceId?: string; + metricsView?: string; + dimensions?: V1MetricsViewAggregationDimension[]; + measures?: V1MetricsViewAggregationMeasure[]; + sort?: V1MetricsViewAggregationSort[]; + timeRange?: V1TimeRange; + comparisonTimeRange?: V1TimeRange; + timeStart?: string; + timeEnd?: string; + pivotOn?: string[]; + aliases?: V1MetricsViewComparisonMeasureAlias[]; + where?: V1Expression; + having?: V1Expression; + limit?: string; + offset?: string; + priority?: number; + filter?: V1MetricsViewFilter; + exact?: boolean; +} + export interface V1MetricsViewAggregationMeasureComputeCountDistinct { dimension?: string; } @@ -1365,25 +1361,23 @@ export interface V1MetricsViewAggregationDimension { alias?: string; } -export interface V1MetricsViewAggregationRequest { +export interface V1MetricsViewComparisonRequest { instanceId?: string; - metricsView?: string; - dimensions?: V1MetricsViewAggregationDimension[]; + metricsViewName?: string; + dimension?: V1MetricsViewAggregationDimension; measures?: V1MetricsViewAggregationMeasure[]; - sort?: V1MetricsViewAggregationSort[]; + comparisonMeasures?: string[]; + sort?: V1MetricsViewComparisonSort[]; timeRange?: V1TimeRange; comparisonTimeRange?: V1TimeRange; - timeStart?: string; - timeEnd?: string; - pivotOn?: string[]; - aliases?: V1MetricsViewComparisonMeasureAlias[]; where?: V1Expression; having?: V1Expression; + aliases?: V1MetricsViewComparisonMeasureAlias[]; limit?: string; offset?: string; priority?: number; - filter?: V1MetricsViewFilter; exact?: boolean; + filter?: V1MetricsViewFilter; } export interface V1MapType { @@ -1475,11 +1469,6 @@ export interface V1Instance { watchRepo?: boolean; } -export interface V1InlineMeasure { - name?: string; - expression?: string; -} - export type V1HistogramMethod = (typeof V1HistogramMethod)[keyof typeof V1HistogramMethod]; @@ -2082,7 +2071,7 @@ export interface V1API { * `NullValue` is a singleton enumeration to represent the null value for the `Value` type union. -The JSON representation for `NullValue` is JSON `null`. + The JSON representation for `NullValue` is JSON `null`. - NULL_VALUE: Null value. */ From edec171f0884384a3b243c1f709a734ec38c5ebe Mon Sep 17 00:00:00 2001 From: Anshul Khandelwal <12948312+k-anshul@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:13:24 +0530 Subject: [PATCH 15/19] feat: Asset cleanup (#5152) * assets cleanup * assets cleanup - prevent recent assets from getting deleted * use pagination and fail safe approach * lint fix * review comments * check for error * small change * remove inline interface --- admin/admin.go | 5 ++- admin/database/database.go | 2 + admin/database/postgres/postgres.go | 22 ++++++++++ admin/server/admin_rbac_test.go | 1 + admin/server/assets.go | 2 +- admin/server/repos.go | 2 +- admin/server/server.go | 5 +-- admin/worker/delete_unsued_assets.go | 64 ++++++++++++++++++++++++++++ admin/worker/worker.go | 3 ++ cli/cmd/admin/start.go | 24 ++++++----- cli/pkg/mock/server.go | 10 +---- 11 files changed, 114 insertions(+), 26 deletions(-) create mode 100644 admin/worker/delete_unsued_assets.go diff --git a/admin/admin.go b/admin/admin.go index f30d248b643..d4a0ad6d754 100644 --- a/admin/admin.go +++ b/admin/admin.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "cloud.google.com/go/storage" "github.com/rilldata/rill/admin/ai" "github.com/rilldata/rill/admin/database" "github.com/rilldata/rill/admin/provisioner" @@ -31,6 +32,7 @@ type Service struct { Email *email.Client Github Github AI ai.Client + Assets *storage.BucketHandle Used *usedFlusher Logger *zap.Logger opts *Options @@ -41,7 +43,7 @@ type Service struct { AutoscalerCron string } -func New(ctx context.Context, opts *Options, logger *zap.Logger, issuer *auth.Issuer, emailClient *email.Client, github Github, aiClient ai.Client) (*Service, error) { +func New(ctx context.Context, opts *Options, logger *zap.Logger, issuer *auth.Issuer, emailClient *email.Client, github Github, aiClient ai.Client, assets *storage.BucketHandle) (*Service, error) { // Init db db, err := database.Open(opts.DatabaseDriver, opts.DatabaseDSN) if err != nil { @@ -95,6 +97,7 @@ func New(ctx context.Context, opts *Options, logger *zap.Logger, issuer *auth.Is Email: emailClient, Github: github, AI: aiClient, + Assets: assets, Used: newUsedFlusher(logger, db), Logger: logger, opts: opts, diff --git a/admin/database/database.go b/admin/database/database.go index 0eb47e10192..a67d491c90a 100644 --- a/admin/database/database.go +++ b/admin/database/database.go @@ -215,7 +215,9 @@ type DB interface { DeleteExpiredVirtualFiles(ctx context.Context, retention time.Duration) error FindAsset(ctx context.Context, id string) (*Asset, error) + FindUnusedAssets(ctx context.Context, limit int) ([]*Asset, error) InsertAsset(ctx context.Context, organizationID, path, ownerID string) (*Asset, error) + DeleteAssets(ctx context.Context, ids []string) error } // Tx represents a database transaction. It can only be used to commit and rollback transactions. diff --git a/admin/database/postgres/postgres.go b/admin/database/postgres/postgres.go index ac42af78de3..9d55c441907 100644 --- a/admin/database/postgres/postgres.go +++ b/admin/database/postgres/postgres.go @@ -1580,6 +1580,28 @@ func (c *connection) InsertAsset(ctx context.Context, organizationID, path, owne return res, nil } +func (c *connection) FindUnusedAssets(ctx context.Context, limit int) ([]*database.Asset, error) { + var res []*database.Asset + // We skip unused assets created in last 6 hours to prevent race condition + // where somebody just created an asset but is yet to use it + err := c.getDB(ctx).SelectContext(ctx, &res, ` + SELECT a.* FROM assets a + WHERE a.created_on < now() - INTERVAL '6 hours' + AND NOT EXISTS + (SELECT 1 FROM projects p WHERE p.archive_asset_id = a.id) + ORDER BY a.created_on DESC LIMIT $1 + `, limit) + if err != nil { + return nil, parseErr("assets", err) + } + return res, nil +} + +func (c *connection) DeleteAssets(ctx context.Context, ids []string) error { + _, err := c.getDB(ctx).ExecContext(ctx, "DELETE FROM assets WHERE id=ANY($1)", ids) + return parseErr("asset", err) +} + // projectDTO wraps database.Project, using the pgtype package to handle types that pgx can't read directly into their native Go types. type projectDTO struct { *database.Project diff --git a/admin/server/admin_rbac_test.go b/admin/server/admin_rbac_test.go index 8b97b6f4f80..c4921412341 100644 --- a/admin/server/admin_rbac_test.go +++ b/admin/server/admin_rbac_test.go @@ -59,6 +59,7 @@ func TestAdmin_RBAC(t *testing.T) { emailClient, github, ai.NewNoop(), + nil, ) require.NoError(t, err) diff --git a/admin/server/assets.go b/admin/server/assets.go index b8eaffa0d62..69045900bae 100644 --- a/admin/server/assets.go +++ b/admin/server/assets.go @@ -63,7 +63,7 @@ func (s *Server) CreateAsset(ctx context.Context, req *adminv1.CreateAssetReques Headers: signingHeaders, Expires: time.Now().Add(15 * time.Minute), } - signedURL, err := s.assetsBucket.SignedURL(object, opts) + signedURL, err := s.admin.Assets.SignedURL(object, opts) if err != nil { return nil, err } diff --git a/admin/server/repos.go b/admin/server/repos.go index 15e10bac9a2..c6084010480 100644 --- a/admin/server/repos.go +++ b/admin/server/repos.go @@ -151,7 +151,7 @@ func (s *Server) generateV4GetObjectSignedURL(objectpath string) (string, error) Expires: time.Now().Add(15 * time.Minute), } - signedURL, err := s.assetsBucket.SignedURL(strings.TrimPrefix(u.Path, "/"), opts) + signedURL, err := s.admin.Assets.SignedURL(strings.TrimPrefix(u.Path, "/"), opts) if err != nil { return "", err } diff --git a/admin/server/server.go b/admin/server/server.go index e663255d251..18ece575a51 100644 --- a/admin/server/server.go +++ b/admin/server/server.go @@ -9,7 +9,6 @@ import ( "strings" "time" - "cloud.google.com/go/storage" grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth" "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" grpc_validator "github.com/grpc-ecosystem/go-grpc-middleware/validator" @@ -78,7 +77,6 @@ type Server struct { urls *externalURLs limiter ratelimit.Limiter activity *activity.Client - assetsBucket *storage.BucketHandle } var _ adminv1.AdminServiceServer = (*Server)(nil) @@ -87,7 +85,7 @@ var _ adminv1.AIServiceServer = (*Server)(nil) var _ adminv1.TelemetryServiceServer = (*Server)(nil) -func New(logger *zap.Logger, adm *admin.Service, issuer *runtimeauth.Issuer, limiter ratelimit.Limiter, activityClient *activity.Client, assetsBucket *storage.BucketHandle, opts *Options) (*Server, error) { +func New(logger *zap.Logger, adm *admin.Service, issuer *runtimeauth.Issuer, limiter ratelimit.Limiter, activityClient *activity.Client, opts *Options) (*Server, error) { externalURL, err := url.Parse(opts.ExternalURL) if err != nil { return nil, fmt.Errorf("failed to parse external URL: %w", err) @@ -144,7 +142,6 @@ func New(logger *zap.Logger, adm *admin.Service, issuer *runtimeauth.Issuer, lim urls: newURLRegistry(opts), limiter: limiter, activity: activityClient, - assetsBucket: assetsBucket, }, nil } diff --git a/admin/worker/delete_unsued_assets.go b/admin/worker/delete_unsued_assets.go new file mode 100644 index 00000000000..54b6374b618 --- /dev/null +++ b/admin/worker/delete_unsued_assets.go @@ -0,0 +1,64 @@ +package worker + +import ( + "context" + "errors" + "fmt" + "net/url" + "strings" + + "cloud.google.com/go/storage" + "golang.org/x/sync/errgroup" +) + +const _unusedAssetsPageSize = 100 + +func (w *Worker) deleteUnusedAssets(ctx context.Context) error { + for { + // 1. Fetch unused assets + assets, err := w.admin.DB.FindUnusedAssets(ctx, _unusedAssetsPageSize) + if err != nil { + return err + } + if len(assets) == 0 { + return nil + } + + // 2. Delete objects from cloud storage + // Limit the number of concurrent deletes to 8 + // TODO: Use batch API once google-cloud-go supports it + group, cctx := errgroup.WithContext(ctx) + group.SetLimit(8) + var ids []string + for _, asset := range assets { + ids = append(ids, asset.ID) + group.Go(func() error { + parsed, err := url.Parse(asset.Path) + if err != nil { + return fmt.Errorf("failed to parse asset path %q: %w", asset.Path, err) + } + err = w.admin.Assets.Object(strings.TrimPrefix(parsed.Path, "/")).Delete(cctx) + if err != nil && !errors.Is(err, storage.ErrObjectNotExist) { + return fmt.Errorf("failed to delete asset %q: %w", asset.Path, err) + } + return nil + }) + } + err = group.Wait() + if err != nil { + return err + } + + // 3. Delete the assets in the DB + err = w.admin.DB.DeleteAssets(ctx, ids) + if err != nil { + return err + } + + if len(assets) < _unusedAssetsPageSize { + // no more assets to delete + return nil + } + // fetch again could be more unused assets + } +} diff --git a/admin/worker/worker.go b/admin/worker/worker.go index 4f3eec1763a..2d29d23f30c 100644 --- a/admin/worker/worker.go +++ b/admin/worker/worker.go @@ -64,6 +64,9 @@ func (w *Worker) Run(ctx context.Context) error { group.Go(func() error { return w.scheduleCron(ctx, "run_autoscaler", w.runAutoscaler, w.admin.AutoscalerCron) }) + group.Go(func() error { + return w.schedule(ctx, "delete_unused_assets", w.deleteUnusedAssets, 6*time.Hour) + }) // NOTE: Add new scheduled jobs here diff --git a/cli/cmd/admin/start.go b/cli/cmd/admin/start.go index 2997eba9f6a..076e7fb9447 100644 --- a/cli/cmd/admin/start.go +++ b/cli/cmd/admin/start.go @@ -229,6 +229,17 @@ func StartCmd(ch *cmdutil.Helper) *cobra.Command { aiClient = ai.NewNoop() } + // Init AssetsBucket handle + var clientOpts []option.ClientOption + if conf.AssetsBucketGoogleCredentialsJSON != "" { + clientOpts = append(clientOpts, option.WithCredentialsJSON([]byte(conf.AssetsBucketGoogleCredentialsJSON))) + } + storageClient, err := storage.NewClient(cmd.Context(), clientOpts...) + if err != nil { + logger.Fatal("failed to create assets bucket handle", zap.Error(err)) + } + assetsBucket := storageClient.Bucket(conf.AssetsBucket) + // Parse metrics project name var metricsProjectOrg, metricsProjectName string if conf.MetricsProject != "" { @@ -253,7 +264,7 @@ func StartCmd(ch *cmdutil.Helper) *cobra.Command { MetricsProjectName: metricsProjectName, AutoscalerCron: conf.AutoscalerCron, } - adm, err := admin.New(cmd.Context(), admOpts, logger, issuer, emailClient, gh, aiClient) + adm, err := admin.New(cmd.Context(), admOpts, logger, issuer, emailClient, gh, aiClient, assetsBucket) if err != nil { logger.Fatal("error creating service", zap.Error(err)) } @@ -292,16 +303,7 @@ func StartCmd(ch *cmdutil.Helper) *cobra.Command { limiter = ratelimit.NewRedis(redis.NewClient(opts)) } - var clientOpts []option.ClientOption - if conf.AssetsBucketGoogleCredentialsJSON != "" { - clientOpts = append(clientOpts, option.WithCredentialsJSON([]byte(conf.AssetsBucketGoogleCredentialsJSON))) - } - storageClient, err := storage.NewClient(cmd.Context(), clientOpts...) - if err != nil { - logger.Fatal("failed to create assets bucket handle", zap.Error(err)) - } - - srv, err := server.New(logger, adm, issuer, limiter, activityClient, storageClient.Bucket(conf.AssetsBucket), &server.Options{ + srv, err := server.New(logger, adm, issuer, limiter, activityClient, &server.Options{ HTTPPort: conf.HTTPPort, GRPCPort: conf.GRPCPort, ExternalURL: conf.ExternalURL, diff --git a/cli/pkg/mock/server.go b/cli/pkg/mock/server.go index 71df927f6d6..8726491fec8 100644 --- a/cli/pkg/mock/server.go +++ b/cli/pkg/mock/server.go @@ -7,7 +7,6 @@ import ( "net/http" "time" - "cloud.google.com/go/storage" "github.com/google/go-github/v50/github" "github.com/rilldata/rill/admin" "github.com/rilldata/rill/admin/ai" @@ -19,7 +18,6 @@ import ( "github.com/rilldata/rill/runtime/pkg/ratelimit" runtimeauth "github.com/rilldata/rill/runtime/server/auth" "go.uber.org/zap" - "google.golang.org/api/option" ) func AdminService(ctx context.Context, logger *zap.Logger, databaseURL string) (*admin.Service, error) { @@ -49,7 +47,7 @@ func AdminService(ctx context.Context, logger *zap.Logger, databaseURL string) ( VersionCommit: "", } - adm, err := admin.New(ctx, admOpts, logger, issuer, emailClient, gh, ai.NewNoop()) + adm, err := admin.New(ctx, admOpts, logger, issuer, emailClient, gh, ai.NewNoop(), nil) if err != nil { return nil, err } @@ -84,11 +82,7 @@ func AdminServer(ctx context.Context, logger *zap.Logger, adm *admin.Service) (* } limiter := ratelimit.NewNoop() - client, err := storage.NewClient(ctx, option.WithoutAuthentication()) - if err != nil { - return nil, err - } - srv, err := server.New(logger, adm, issuer, limiter, activity.NewNoopClient(), client.Bucket("mock"), &server.Options{ + srv, err := server.New(logger, adm, issuer, limiter, activity.NewNoopClient(), &server.Options{ HTTPPort: conf.HTTPPort, GRPCPort: conf.GRPCPort, ExternalURL: conf.ExternalURL, From 9c5ff5d8b72aab2ef5d364efce16a56d482e6087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Egelund-M=C3=BCller?= Date: Fri, 28 Jun 2024 13:45:29 +0200 Subject: [PATCH 16/19] Runtime: Simplify code for unnest dimensions (#5154) * Runtime: Simplify code for unnest dimensions * Nits --- runtime/metricsview/ast.go | 58 +++++++++++++++++----------------- runtime/metricsview/astexpr.go | 8 ++--- runtime/metricsview/astsql.go | 25 ++------------- 3 files changed, 34 insertions(+), 57 deletions(-) diff --git a/runtime/metricsview/ast.go b/runtime/metricsview/ast.go index 33a4ff81342..50bb1934617 100644 --- a/runtime/metricsview/ast.go +++ b/runtime/metricsview/ast.go @@ -20,6 +20,7 @@ type AST struct { underlyingTable *string underlyingWhere *ExprNode dimFields []FieldNode + unnests []string nextIdentifier int // Contextual info for building the AST @@ -44,6 +45,7 @@ type SelectNode struct { LeftJoinSelects []*SelectNode // Sub-selects to left join onto FromSelect, to enable "per-dimension" measures JoinComparisonSelect *SelectNode // Sub-select to join onto FromSelect for comparison measures JoinComparisonType string // Type of join to use for JoinComparisonSelect + Unnests []string // Unnest expressions to add in the FROM clause Group bool // Whether the SELECT is grouped. If yes, it will group by all DimFields. Where *ExprNode // Expression for the WHERE clause TimeWhere *ExprNode // Expression for the time range to add to the WHERE clause @@ -57,11 +59,9 @@ type SelectNode struct { // The Name must always match a the name of a dimension/measure in the metrics view or a computed field specified in the request. // This means that if two columns in different places in the AST have the same Name, they're guaranteed to resolve to the same value. type FieldNode struct { - Name string - Label string - Expr string - Unnest bool - UnnestAlias string + Name string + Label string + Expr string } // ExprNode represents an expression for a WHERE clause. @@ -120,27 +120,35 @@ func NewAST(mv *runtimev1.MetricsViewSpec, sec *runtime.ResolvedMetricsViewSecur // Build dimensions to apply against the underlying SELECT. // We cache these in the AST type because when resolving expressions and adding new JOINs, we need the ability to reference these. - dimFields := make([]FieldNode, 0, len(ast.query.Dimensions)) + ast.dimFields = make([]FieldNode, 0, len(ast.query.Dimensions)) for _, qd := range ast.query.Dimensions { dim, err := ast.resolveDimension(qd, true) if err != nil { return nil, fmt.Errorf("invalid dimension %q: %w", qd.Name, err) } - var unnestAlias string + f := FieldNode{ + Name: dim.Name, + Label: dim.Label, + Expr: ast.dialect.MetricsViewDimensionExpression(dim), + } + if dim.Unnest { - unnestAlias = ast.generateIdentifier() + unnestAlias := ast.generateIdentifier() + + tblWithAlias, auto, err := ast.dialect.LateralUnnest(f.Expr, unnestAlias, f.Name) + if err != nil { + return nil, fmt.Errorf("failed to unnest field %q: %w", f.Name, err) + } + + if !auto { + ast.unnests = append(ast.unnests, tblWithAlias) + f.Expr = ast.sqlForMember(unnestAlias, f.Name) + } } - dimFields = append(dimFields, FieldNode{ - Name: dim.Name, - Label: dim.Label, - Expr: ast.dialect.MetricsViewDimensionExpression(dim), - Unnest: dim.Unnest, - UnnestAlias: unnestAlias, - }) + ast.dimFields = append(ast.dimFields, f) } - ast.dimFields = dimFields // Build underlying SELECT tbl := ast.dialect.EscapeTable(mv.Database, mv.DatabaseSchema, mv.Table) @@ -523,6 +531,7 @@ func (a *AST) buildBaseSelect(alias string, tr *TimeRange) (*SelectNode, error) n := &SelectNode{ Alias: alias, DimFields: a.dimFields, + Unnests: a.unnests, Group: true, FromTable: a.underlyingTable, Where: a.underlyingWhere, @@ -567,6 +576,7 @@ func (a *AST) buildSpineSelect(alias string, spine *Spine, tr *TimeRange) (*Sele n := &SelectNode{ Alias: alias, DimFields: a.dimFields, + Unnests: a.unnests, Group: true, FromTable: a.underlyingTable, } @@ -867,7 +877,6 @@ func (a *AST) wrapSelect(s *SelectNode, innerAlias string) { Name: f.Name, Label: f.Label, Expr: a.sqlForMember(cpy.Alias, f.Name), - // Not copying Unnest because we should only unnest once (at the innermost level). }) } @@ -886,6 +895,7 @@ func (a *AST) wrapSelect(s *SelectNode, innerAlias string) { s.LeftJoinSelects = nil s.JoinComparisonSelect = nil s.JoinComparisonType = "" + s.Unnests = nil s.Group = false s.Where = nil s.TimeWhere = nil @@ -1043,12 +1053,7 @@ func (a *AST) sqlForMeasure(m *runtimev1.MetricsViewSpec_MeasureV2, n *SelectNod b.WriteString(", ") } - expr := f.Expr - if f.Unnest { - expr = a.sqlForMember(f.UnnestAlias, f.Name) - } - - b.WriteString(expr) + b.WriteString(f.Expr) } } if len(orderFields) > 0 { @@ -1061,12 +1066,7 @@ func (a *AST) sqlForMeasure(m *runtimev1.MetricsViewSpec_MeasureV2, n *SelectNod b.WriteString(", ") } - expr := f.Expr - if f.Unnest { - expr = a.sqlForMember(f.UnnestAlias, f.Name) - } - - b.WriteString(expr) + b.WriteString(f.Expr) if orderDesc[i] { b.WriteString(" DESC") } diff --git a/runtime/metricsview/astexpr.go b/runtime/metricsview/astexpr.go index bc92443bf78..a78eee284b0 100644 --- a/runtime/metricsview/astexpr.go +++ b/runtime/metricsview/astexpr.go @@ -534,12 +534,8 @@ func (b *sqlExprBuilder) sqlForName(name string) (expr string, unnest bool, err // First, search for the dimension in the ASTs dimension fields (this also covers any computed dimension) for _, f := range b.ast.dimFields { if f.Name == name { - if f.Unnest { - // Since it's unnested, we need to reference the unnested alias. - // Note that we return "false" for "unnest" because it will already have been unnested since it's one of the dimensions included in the query, - // so we can filter against it as if it's a normal dimension. - return b.ast.sqlForMember(f.UnnestAlias, f.Name), false, nil - } + // Note that we return "false" even though it may be an unnest dimension because it will already have been unnested since it's one of the dimensions included in the query. + // So we can filter against it as if it's a normal dimension. return f.Expr, false, nil } } diff --git a/runtime/metricsview/astsql.go b/runtime/metricsview/astsql.go index ee8662d45b0..10069e18c71 100644 --- a/runtime/metricsview/astsql.go +++ b/runtime/metricsview/astsql.go @@ -1,7 +1,6 @@ package metricsview import ( - "fmt" "strconv" "strings" ) @@ -82,13 +81,8 @@ func (b *sqlBuilder) writeSelect(n *SelectNode) error { b.out.WriteString(", ") } - expr := f.Expr - if f.Unnest { - expr = b.ast.sqlForMember(f.UnnestAlias, f.Name) - } - b.out.WriteByte('(') - b.out.WriteString(expr) + b.out.WriteString(f.Expr) b.out.WriteString(") AS ") b.out.WriteString(b.ast.dialect.EscapeIdentifier(f.Name)) } @@ -109,22 +103,9 @@ func (b *sqlBuilder) writeSelect(n *SelectNode) error { b.out.WriteString(*n.FromTable) // Add unnest joins. We only and always apply these against FromPlain (ensuring they are already unnested when referenced in outer SELECTs). - for _, f := range n.DimFields { - if !f.Unnest { - continue - } - - tblWithAlias, auto, err := b.ast.dialect.LateralUnnest(f.Expr, f.UnnestAlias, f.Name) - if err != nil { - return fmt.Errorf("failed to unnest field %q: %w", f.Name, err) - } - - if auto { - continue - } - + for _, u := range n.Unnests { b.out.WriteString(", ") - b.out.WriteString(tblWithAlias) + b.out.WriteString(u) } } else if n.FromSelect != nil { b.out.WriteByte('(') From c993f23ab2697eaa868b77d91a8bb539abb19175 Mon Sep 17 00:00:00 2001 From: Egor Riashin Date: Fri, 28 Jun 2024 15:05:19 +0300 Subject: [PATCH 17/19] Histogram infinity (#5156) * histogram infinity * histogram infinity --------- Co-authored-by: Egor Ryashin --- runtime/queries/column_desc_stats.go | 22 ++++++++------------- runtime/queries/column_numeric_histogram.go | 8 ++++---- runtime/server/queries_columns_test.go | 5 +++-- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/runtime/queries/column_desc_stats.go b/runtime/queries/column_desc_stats.go index fa964ed4c3e..373700dbc7b 100644 --- a/runtime/queries/column_desc_stats.go +++ b/runtime/queries/column_desc_stats.go @@ -60,20 +60,14 @@ func (q *ColumnDescriptiveStatistics) Resolve(ctx context.Context, rt *runtime.R switch olap.Dialect() { case drivers.DialectDuckDB: descriptiveStatisticsSQL = fmt.Sprintf("SELECT "+ - "min(%s)::DOUBLE as min, "+ - "approx_quantile(%s, 0.25)::DOUBLE as q25, "+ - "approx_quantile(%s, 0.5)::DOUBLE as q50, "+ - "approx_quantile(%s, 0.75)::DOUBLE as q75, "+ - "max(%s)::DOUBLE as max, "+ - "avg(%s)::DOUBLE as mean, "+ - "stddev_pop(%s)::DOUBLE as sd "+ - "FROM %s", - sanitizedColumnName, - sanitizedColumnName, - sanitizedColumnName, - sanitizedColumnName, - sanitizedColumnName, - sanitizedColumnName, + "min(%[1]s)::DOUBLE as min, "+ + "approx_quantile(%[1]s, 0.25)::DOUBLE as q25, "+ + "approx_quantile(%[1]s, 0.5)::DOUBLE as q50, "+ + "approx_quantile(%[1]s, 0.75)::DOUBLE as q75, "+ + "max(%[1]s)::DOUBLE as max, "+ + "avg(%[1]s)::DOUBLE as mean, "+ + "'NaN'::DOUBLE as sd "+ + "FROM %[2]s WHERE NOT isinf(%[1]s) ", sanitizedColumnName, olap.Dialect().EscapeTable(q.Database, q.DatabaseSchema, q.TableName)) case drivers.DialectClickHouse: diff --git a/runtime/queries/column_numeric_histogram.go b/runtime/queries/column_numeric_histogram.go index 0ac11724561..82457cb0b00 100644 --- a/runtime/queries/column_numeric_histogram.go +++ b/runtime/queries/column_numeric_histogram.go @@ -173,10 +173,10 @@ func (q *ColumnNumericHistogram) calculateFDMethod(ctx context.Context, rt *runt WITH data_table AS ( SELECT %[1]s as %[2]s FROM %[3]s - WHERE %[2]s IS NOT NULL + WHERE %[2]s IS NOT NULL AND NOT isinf(%[2]s) ), values AS ( SELECT %[2]s as value from data_table - WHERE %[2]s IS NOT NULL + WHERE %[2]s IS NOT NULL AND NOT isinf(%[2]s) ), buckets AS ( SELECT `+rangeNumbersCol(olap.Dialect())+`::DOUBLE as bucket, @@ -293,7 +293,7 @@ func (q *ColumnNumericHistogram) calculateDiagnosticMethod(ctx context.Context, WITH data_table AS ( SELECT %[1]s as %[2]s FROM %[3]s - WHERE %[2]s IS NOT NULL + WHERE %[2]s IS NOT NULL AND NOT isinf(%[2]s) ), S AS ( SELECT min(%[2]s) as minVal, @@ -398,7 +398,7 @@ func getMinMaxRange(ctx context.Context, olap drivers.OLAPStore, columnName, dat max(%[2]s) AS max, max(%[2]s) - min(%[2]s) AS range FROM %[1]s - WHERE %[2]s IS NOT NULL + WHERE %[2]s IS NOT NULL AND NOT isinf(%[2]s) `, olap.Dialect().EscapeTable(database, databaseSchema, tableName), selectColumn, diff --git a/runtime/server/queries_columns_test.go b/runtime/server/queries_columns_test.go index fc547b055c2..ba7ffb5a821 100644 --- a/runtime/server/queries_columns_test.go +++ b/runtime/server/queries_columns_test.go @@ -3,6 +3,7 @@ package server_test import ( "context" "fmt" + "math" "testing" "time" @@ -144,7 +145,7 @@ func TestServer_ColumnDescriptiveStatistics(t *testing.T) { _, err := server.ColumnDescriptiveStatistics(testCtx(), &runtimev1.ColumnDescriptiveStatisticsRequest{InstanceId: instanceId, TableName: "test", ColumnName: "col"}) if err != nil { // "col" is a varchar column, so this should fail - require.ErrorContains(t, err, "No function matches the given name and argument types 'approx_quantile(VARCHAR, DECIMAL(3,2))'") + require.ErrorContains(t, err, "No function matches the given name and argument types 'isinf(VARCHAR)'") } res, err := server.ColumnDescriptiveStatistics(testCtx(), &runtimev1.ColumnDescriptiveStatisticsRequest{InstanceId: instanceId, TableName: "test", ColumnName: "val"}) @@ -156,7 +157,7 @@ func TestServer_ColumnDescriptiveStatistics(t *testing.T) { require.Equal(t, 1.0, res.NumericSummary.GetNumericStatistics().Q25) require.Equal(t, 1.0, res.NumericSummary.GetNumericStatistics().Q50) require.Equal(t, 4.0, res.NumericSummary.GetNumericStatistics().Q75) - require.Equal(t, 1.6, res.NumericSummary.GetNumericStatistics().Sd) + require.True(t, math.IsNaN(res.NumericSummary.GetNumericStatistics().Sd)) } func TestServer_ColumnDescriptiveStatistics_EmptyModel(t *testing.T) { From 349403ef2fe55fd1588b2c7938da8d2e6345f790 Mon Sep 17 00:00:00 2001 From: Egor Riashin Date: Fri, 28 Jun 2024 15:18:17 +0300 Subject: [PATCH 18/19] Static assets endpoint (#5063) * static assets endpoint * static assets endpoint - cache * static assets endpoint - thread safety * static assets endpoint - admin repo * static assets endpoint - admin repo * static assets endpoint - admin repo * static assets endpoint - admin repo * static assets endpoint - admin repo * static assets endpoint - admin repo * static assets endpoint - admin repo * static assets endpoint - admin repo * static assets endpoint - admin repo * static assets endpoint - admin repo * static assets endpoint - removing cache * static assets endpoint - removing cache * static assets endpoint - removing cache * static assets endpoint - removing cache * static assets endpoint - migration * static assets endpoint - migration * static assets: obscure root dir * static assets: obscure root dir * static assets: obscure root dir * static assets: obscure root dir --------- Co-authored-by: Egor Ryashin --- runtime/compilers/rillv1/parse_rillyaml.go | 8 +++ runtime/drivers/admin/admin.go | 1 + runtime/drivers/admin/repo.go | 8 ++- runtime/drivers/file/repo.go | 8 ++- runtime/drivers/registry.go | 2 + runtime/drivers/sqlite/migrations/0023.sql | 1 + runtime/drivers/sqlite/registry.go | 44 +++++++++++++-- runtime/runtime.go | 1 + runtime/server/assets.go | 63 ++++++++++++++++++++++ runtime/server/server.go | 2 + 10 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 runtime/drivers/sqlite/migrations/0023.sql create mode 100644 runtime/server/assets.go diff --git a/runtime/compilers/rillv1/parse_rillyaml.go b/runtime/compilers/rillv1/parse_rillyaml.go index 4e599bd8635..0dbf9e614a6 100644 --- a/runtime/compilers/rillv1/parse_rillyaml.go +++ b/runtime/compilers/rillv1/parse_rillyaml.go @@ -21,6 +21,7 @@ type RillYAML struct { Variables []*VariableDef Defaults map[ResourceKind]yaml.Node FeatureFlags map[string]bool + PublicPaths []string } // ConnectorDef is a subtype of RillYAML, defining connectors required by the project @@ -68,6 +69,8 @@ type rillYAML struct { Migrations yaml.Node `yaml:"migrations"` // Feature flags (preferably a map[string]bool, but can also be a []string for backwards compatibility) Features yaml.Node `yaml:"features"` + // Paths to expose over HTTP (defaults to ./public) + PublicPaths []string `yaml:"public_paths"` } // parseRillYAML parses rill.yaml @@ -161,6 +164,10 @@ func (p *Parser) parseRillYAML(ctx context.Context, path string) error { } } + if len(tmp.PublicPaths) == 0 { + tmp.PublicPaths = []string{"public"} + } + res := &RillYAML{ Title: tmp.Title, Description: tmp.Description, @@ -174,6 +181,7 @@ func (p *Parser) parseRillYAML(ctx context.Context, path string) error { ResourceKindMigration: tmp.Migrations, }, FeatureFlags: featureFlags, + PublicPaths: tmp.PublicPaths, } for i, c := range tmp.Connectors { diff --git a/runtime/drivers/admin/admin.go b/runtime/drivers/admin/admin.go index f4d5c2422cd..b4504f5d37a 100644 --- a/runtime/drivers/admin/admin.go +++ b/runtime/drivers/admin/admin.go @@ -134,6 +134,7 @@ var _ drivers.Handle = &Handle{} // a smaller subset of relevant parts of rill.yaml type rillYAML struct { IgnorePaths []string `yaml:"ignore_paths"` + PublicPaths []string `yaml:"public_paths"` } // Driver implements drivers.Handle. diff --git a/runtime/drivers/admin/repo.go b/runtime/drivers/admin/repo.go index f8d0bb08a2a..822714b7fda 100644 --- a/runtime/drivers/admin/repo.go +++ b/runtime/drivers/admin/repo.go @@ -98,10 +98,14 @@ func (h *Handle) Get(ctx context.Context, filePath string) (string, error) { } defer h.repoMu.RUnlock() - filePath = filepath.Join(h.projPath, filePath) + fp := filepath.Join(h.projPath, filePath) - b, err := os.ReadFile(filePath) + b, err := os.ReadFile(fp) if err != nil { + // obscure the root directory location + if t, ok := err.(*fs.PathError); ok { // nolint:errorlint // we specifically check for a non-wrapped error + return "", fmt.Errorf("%s %s %s", t.Op, filePath, t.Err.Error()) + } return "", err } diff --git a/runtime/drivers/file/repo.go b/runtime/drivers/file/repo.go index d3c229e2b42..659fe1504db 100644 --- a/runtime/drivers/file/repo.go +++ b/runtime/drivers/file/repo.go @@ -74,10 +74,14 @@ func (c *connection) ListRecursive(ctx context.Context, glob string, skipDirs bo // Get implements drivers.RepoStore. func (c *connection) Get(ctx context.Context, filePath string) (string, error) { - filePath = filepath.Join(c.root, filePath) + fp := filepath.Join(c.root, filePath) - b, err := os.ReadFile(filePath) + b, err := os.ReadFile(fp) if err != nil { + // obscure the root directory location + if t, ok := err.(*fs.PathError); ok { // nolint:errorlint // we specifically check for a non-wrapped error + return "", fmt.Errorf("%s %s %s", t.Op, filePath, t.Err.Error()) + } return "", err } diff --git a/runtime/drivers/registry.go b/runtime/drivers/registry.go index 441ffe601cb..eb6421c74d8 100644 --- a/runtime/drivers/registry.go +++ b/runtime/drivers/registry.go @@ -60,6 +60,8 @@ type Instance struct { EmbedCatalog bool `db:"embed_catalog"` // WatchRepo indicates whether to watch the repo for file changes and reconcile them automatically. WatchRepo bool `db:"watch_repo"` + // Paths to expose over HTTP (defaults to ./public) + PublicPaths []string `db:"public_paths"` // IgnoreInitialInvalidProjectError indicates whether to ignore an invalid project error when the instance is initially created. IgnoreInitialInvalidProjectError bool `db:"-"` } diff --git a/runtime/drivers/sqlite/migrations/0023.sql b/runtime/drivers/sqlite/migrations/0023.sql new file mode 100644 index 00000000000..ab4027dd4b6 --- /dev/null +++ b/runtime/drivers/sqlite/migrations/0023.sql @@ -0,0 +1 @@ +ALTER TABLE instances ADD COLUMN public_paths TEXT NOT NULL DEFAULT '[]'; diff --git a/runtime/drivers/sqlite/registry.go b/runtime/drivers/sqlite/registry.go index ffd1aecf6fe..50f44eedc34 100644 --- a/runtime/drivers/sqlite/registry.go +++ b/runtime/drivers/sqlite/registry.go @@ -51,7 +51,8 @@ func (c *connection) findInstances(_ context.Context, whereClause string, args . feature_flags, annotations, embed_catalog, - watch_repo + watch_repo, + public_paths FROM instances %s ORDER BY id `, whereClause) @@ -64,7 +65,7 @@ func (c *connection) findInstances(_ context.Context, whereClause string, args . var res []*drivers.Instance for rows.Next() { // sqlite doesn't support maps need to read as bytes and convert to map - var variables, projectVariables, featureFlags, annotations, connectors, projectConnectors []byte + var variables, projectVariables, featureFlags, annotations, connectors, projectConnectors, publicPaths []byte i := &drivers.Instance{} err := rows.Scan( &i.ID, @@ -85,6 +86,7 @@ func (c *connection) findInstances(_ context.Context, whereClause string, args . &annotations, &i.EmbedCatalog, &i.WatchRepo, + &publicPaths, ) if err != nil { return nil, err @@ -120,6 +122,11 @@ func (c *connection) findInstances(_ context.Context, whereClause string, args . return nil, err } + i.PublicPaths, err = arrayFromJSON[string](publicPaths) + if err != nil { + return nil, err + } + res = append(res, i) } @@ -166,6 +173,11 @@ func (c *connection) CreateInstance(_ context.Context, inst *drivers.Instance) e return err } + publicPaths, err := arrayToJSON(inst.PublicPaths) + if err != nil { + return err + } + now := time.Now() _, err = c.db.ExecContext( ctx, @@ -188,9 +200,10 @@ func (c *connection) CreateInstance(_ context.Context, inst *drivers.Instance) e feature_flags, annotations, embed_catalog, - watch_repo + watch_repo, + public_paths ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) `, inst.ID, inst.Environment, @@ -210,6 +223,7 @@ func (c *connection) CreateInstance(_ context.Context, inst *drivers.Instance) e annotations, inst.EmbedCatalog, inst.WatchRepo, + publicPaths, ) if err != nil { return err @@ -257,6 +271,11 @@ func (c *connection) EditInstance(_ context.Context, inst *drivers.Instance) err return err } + publicPaths, err := arrayToJSON(inst.PublicPaths) + if err != nil { + return err + } + now := time.Now() _, err = c.db.ExecContext( ctx, @@ -277,7 +296,8 @@ func (c *connection) EditInstance(_ context.Context, inst *drivers.Instance) err feature_flags = $14, annotations = $15, embed_catalog = $16, - watch_repo = $17 + watch_repo = $17, + public_paths = $18 WHERE id = $1 `, inst.ID, @@ -297,6 +317,7 @@ func (c *connection) EditInstance(_ context.Context, inst *drivers.Instance) err annotations, inst.EmbedCatalog, inst.WatchRepo, + publicPaths, ) if err != nil { return err @@ -329,6 +350,19 @@ func mapFromJSON[T any](data []byte) (map[string]T, error) { return m, err } +func arrayToJSON[T any](data []T) ([]byte, error) { + return json.Marshal(data) +} + +func arrayFromJSON[T any](data []byte) ([]T, error) { + if len(data) == 0 { + return []T{}, nil + } + var a []T + err := json.Unmarshal(data, &a) + return a, err +} + func unmarshalConnectors(s []byte) ([]*runtimev1.Connector, error) { if len(s) == 0 { return make([]*runtimev1.Connector, 0), nil diff --git a/runtime/runtime.go b/runtime/runtime.go index 2538e5680fc..64f7875bf7d 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -164,6 +164,7 @@ func (r *Runtime) UpdateInstanceWithRillYAML(ctx context.Context, instanceID str } inst.ProjectVariables = vars inst.FeatureFlags = rillYAML.FeatureFlags + inst.PublicPaths = rillYAML.PublicPaths return r.EditInstance(ctx, inst, restartController) } diff --git a/runtime/server/assets.go b/runtime/server/assets.go new file mode 100644 index 00000000000..f5b941c897b --- /dev/null +++ b/runtime/server/assets.go @@ -0,0 +1,63 @@ +package server + +import ( + "fmt" + "net/http" + "os" + "path/filepath" + + "github.com/rilldata/rill/runtime/pkg/httputil" + "github.com/rilldata/rill/runtime/pkg/observability" + "github.com/rilldata/rill/runtime/server/auth" + "go.opentelemetry.io/otel/attribute" +) + +func (s *Server) assetsHandler(w http.ResponseWriter, req *http.Request) error { + ctx := req.Context() + instanceID := req.PathValue("instance_id") + path := req.PathValue("path") + + observability.AddRequestAttributes(ctx, + attribute.String("args.instance_id", instanceID), + attribute.String("args.path", path), + ) + + if !auth.GetClaims(req.Context()).CanInstance(instanceID, auth.ReadObjects) { + return httputil.Errorf(http.StatusForbidden, "does not have access to assets") + } + + inst, err := s.runtime.Instance(ctx, instanceID) + if err != nil { + return err + } + + allowed := false + for _, p := range inst.PublicPaths { + // 'p' can be `/public`, `/public/`, `public/`, `public` (with os-based separators) + // match pattern `public/*` or `/public/*` + ok, err := filepath.Match(fmt.Sprintf("%s%c*", filepath.Clean(p), os.PathSeparator), path) + if err != nil { + return httputil.Error(http.StatusBadRequest, err) + } + if ok { + allowed = true + break + } + } + if !allowed { + return httputil.Error(http.StatusForbidden, fmt.Errorf("path is not allowed")) + } + + repo, release, err := s.runtime.Repo(ctx, instanceID) + if err != nil { + return err + } + defer release() + + str, err := repo.Get(ctx, path) + if err != nil { + return err + } + _, err = w.Write([]byte(str)) + return err +} diff --git a/runtime/server/server.go b/runtime/server/server.go index 4ef8349bcaf..6c0486da463 100644 --- a/runtime/server/server.go +++ b/runtime/server/server.go @@ -214,6 +214,8 @@ func (s *Server) HTTPHandler(ctx context.Context, registerAdditionalHandlers fun // Add handler for resolving component data observability.MuxHandle(httpMux, "/v1/instances/{instance_id}/components/{name}/data", observability.Middleware("runtime", s.logger, auth.HTTPMiddleware(s.aud, httputil.Handler(s.componentDataHandler)))) + // Serving static assets + observability.MuxHandle(httpMux, "/v1/instances/{instance_id}/assets/{path...}", observability.Middleware("runtime", s.logger, auth.HTTPMiddleware(s.aud, httputil.Handler(s.assetsHandler)))) // Add Prometheus if s.opts.ServePrometheus { From 2f3b78a831931d321c36b3ba62c339e71383f10b Mon Sep 17 00:00:00 2001 From: Anshul Khandelwal <12948312+k-anshul@users.noreply.github.com> Date: Fri, 28 Jun 2024 17:54:08 +0530 Subject: [PATCH 19/19] Runtime : Fix temp_dir usage for local (#5160) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * temp_dir fix for local * fix mapstructure decode * do not enable external storage when separate file passed * Apply suggestions from code review Co-authored-by: Benjamin Egelund-Müller --------- Co-authored-by: Benjamin Egelund-Müller --- cli/pkg/local/app.go | 47 +++++++----------------- runtime/drivers/athena/athena.go | 2 +- runtime/drivers/azure/azure.go | 2 +- runtime/drivers/bigquery/bigquery.go | 3 +- runtime/drivers/bigquery/sql_store.go | 42 ++++++++++++--------- runtime/drivers/clickhouse/clickhouse.go | 2 +- runtime/drivers/druid/druid.go | 2 +- runtime/drivers/duckdb/config.go | 2 +- runtime/drivers/file/file.go | 2 +- runtime/drivers/gcs/gcs.go | 2 +- runtime/drivers/redshift/redshift.go | 2 +- runtime/drivers/snowflake/snowflake.go | 26 ++++++++++--- runtime/drivers/snowflake/sql_store.go | 24 ++++++------ 13 files changed, 80 insertions(+), 78 deletions(-) diff --git a/cli/pkg/local/app.go b/cli/pkg/local/app.go index dd96346262a..b67bcd745a1 100644 --- a/cli/pkg/local/app.go +++ b/cli/pkg/local/app.go @@ -7,12 +7,10 @@ import ( "fmt" "net/http" "os" - "path" "path/filepath" "strconv" "time" - "github.com/bmatcuk/doublestar/v4" "github.com/c2h5oh/datasize" "github.com/rilldata/rill/cli/pkg/browser" "github.com/rilldata/rill/cli/pkg/cmdutil" @@ -203,26 +201,22 @@ func NewApp(ctx context.Context, opts *AppOptions) (*App, error) { // If the OLAP is the default OLAP (DuckDB in stage.db), we make it relative to the project directory (not the working directory) defaultOLAP := false - olapDSN := opts.OlapDSN olapCfg := make(map[string]string) - if opts.OlapDriver == DefaultOLAPDriver && olapDSN == DefaultOLAPDSN { + if opts.OlapDriver == DefaultOLAPDriver && opts.OlapDSN == DefaultOLAPDSN { defaultOLAP = true - olapDSN = path.Join(dbDirPath, olapDSN) - // Set path which overrides the duckdb's default behaviour to store duckdb data in data_dir// directory which is not backward compatible - olapCfg["path"] = olapDSN - val, err := isExternalStorageEnabled(dbDirPath, vars) + val, err := isExternalStorageEnabled(vars) if err != nil { return nil, err } - olapCfg["external_table_storage"] = strconv.FormatBool(val) } - // Set default DuckDB pool size to 4 - olapCfg["dsn"] = olapDSN if opts.OlapDriver == "duckdb" { + // Set default DuckDB pool size to 4 olapCfg["pool_size"] = "4" if !defaultOLAP { + // dsn is automatically computed by duckdb driver so we set only when non default dsn is passed + olapCfg["dsn"] = opts.OlapDSN olapCfg["error_on_incompatible_version"] = "true" } } @@ -621,27 +615,12 @@ func (s skipFieldZapEncoder) AddString(key, val string) { } // isExternalStorageEnabled determines if external storage can be enabled. -// we can't always enable `external_table_storage` if the project dir already has a db file -// it could have been created with older logic where every source was a table in the main db -func isExternalStorageEnabled(dbPath string, variables map[string]string) (bool, error) { - _, err := os.Stat(filepath.Join(dbPath, DefaultOLAPDSN)) - if err != nil { - // fresh project - // check if flag explicitly passed - val, ok := variables["connector.duckdb.external_table_storage"] - if !ok { - // mark enabled by default - return true, nil - } - return strconv.ParseBool(val) - } - - fsRoot := os.DirFS(dbPath) - glob := path.Clean(path.Join("./", filepath.Join("*", "version.txt"))) - - matches, err := doublestar.Glob(fsRoot, glob) - if err != nil { - return false, err - } - return len(matches) > 0, nil +func isExternalStorageEnabled(variables map[string]string) (bool, error) { + // check if flag explicitly passed + val, ok := variables["connector.duckdb.external_table_storage"] + if !ok { + // mark enabled by default + return true, nil + } + return strconv.ParseBool(val) } diff --git a/runtime/drivers/athena/athena.go b/runtime/drivers/athena/athena.go index d0e8142d71f..06c2cedac7b 100644 --- a/runtime/drivers/athena/athena.go +++ b/runtime/drivers/athena/athena.go @@ -121,7 +121,7 @@ func (c *Connection) Driver() string { // Config implements drivers.Connection. func (c *Connection) Config() map[string]any { m := make(map[string]any, 0) - _ = mapstructure.Decode(c.config, m) + _ = mapstructure.Decode(c.config, &m) return m } diff --git a/runtime/drivers/azure/azure.go b/runtime/drivers/azure/azure.go index 3e187d06d33..7e1922f5494 100644 --- a/runtime/drivers/azure/azure.go +++ b/runtime/drivers/azure/azure.go @@ -149,7 +149,7 @@ func (c *Connection) Driver() string { // Config implements drivers.Connection. func (c *Connection) Config() map[string]any { m := make(map[string]any, 0) - _ = mapstructure.Decode(c.config, m) + _ = mapstructure.Decode(c.config, &m) return m } diff --git a/runtime/drivers/bigquery/bigquery.go b/runtime/drivers/bigquery/bigquery.go index 3507be36635..952e3e87ea4 100644 --- a/runtime/drivers/bigquery/bigquery.go +++ b/runtime/drivers/bigquery/bigquery.go @@ -66,6 +66,7 @@ type driver struct{} type configProperties struct { SecretJSON string `mapstructure:"google_application_credentials"` AllowHostAccess bool `mapstructure:"allow_host_access"` + TempDir string `mapstructure:"temp_dir"` } func (d driver) Open(instanceID string, config map[string]any, client *activity.Client, logger *zap.Logger) (drivers.Handle, error) { @@ -116,7 +117,7 @@ func (c *Connection) Driver() string { // Config implements drivers.Connection. func (c *Connection) Config() map[string]any { m := make(map[string]any, 0) - _ = mapstructure.Decode(c.config, m) + _ = mapstructure.Decode(c.config, &m) return m } diff --git a/runtime/drivers/bigquery/sql_store.go b/runtime/drivers/bigquery/sql_store.go index 87113b59da5..3dc9d192361 100644 --- a/runtime/drivers/bigquery/sql_store.go +++ b/runtime/drivers/bigquery/sql_store.go @@ -149,6 +149,11 @@ func (c *Connection) QueryAsFiles(ctx context.Context, props map[string]any, opt c.logger.Debug("query took", zap.Duration("duration", time.Since(now)), observability.ZapCtx(ctx)) } + tempDir, err := os.MkdirTemp(c.config.TempDir, "bigquery") + if err != nil { + return nil, err + } + p.Target(int64(it.TotalRows), drivers.ProgressUnitRecord) return &fileIterator{ client: client, @@ -158,6 +163,7 @@ func (c *Connection) QueryAsFiles(ctx context.Context, props map[string]any, opt progress: p, totalRecords: int64(it.TotalRows), ctx: ctx, + tempDir: tempDir, }, nil } @@ -178,9 +184,9 @@ type fileIterator struct { logger *zap.Logger limitInBytes int64 progress drivers.Progress + tempDir string totalRecords int64 - tempFilePath string downloaded bool ctx context.Context // TODO :: refatcor NextBatch to take context on NextBatch @@ -188,7 +194,7 @@ type fileIterator struct { // Close implements drivers.FileIterator. func (f *fileIterator) Close() error { - return os.Remove(f.tempFilePath) + return os.RemoveAll(f.tempDir) } // Next implements drivers.FileIterator. @@ -200,10 +206,11 @@ func (f *fileIterator) Next() ([]string, error) { // storage API not available so can't read as arrow records. Read results row by row and dump in a json file. if !f.bqIter.IsAccelerated() { f.logger.Debug("downloading results in json file", observability.ZapCtx(f.ctx)) - if err := f.downloadAsJSONFile(); err != nil { + file, err := f.downloadAsJSONFile() + if err != nil { return nil, err } - return []string{f.tempFilePath}, nil + return []string{file}, nil } f.logger.Debug("downloading results in parquet file", observability.ZapCtx(f.ctx)) @@ -213,7 +220,6 @@ func (f *fileIterator) Next() ([]string, error) { return nil, err } defer fw.Close() - f.tempFilePath = fw.Name() f.downloaded = true rdr, err := f.AsArrowRecordReader() @@ -296,19 +302,18 @@ func (f *fileIterator) Format() string { return "" } -func (f *fileIterator) downloadAsJSONFile() error { +func (f *fileIterator) downloadAsJSONFile() (string, error) { tf := time.Now() defer func() { f.logger.Debug("time taken to write row in json file", zap.Duration("duration", time.Since(tf)), observability.ZapCtx(f.ctx)) }() // create a temp file - fw, err := os.CreateTemp("", "temp*.ndjson") + fw, err := os.CreateTemp(f.tempDir, "temp*.ndjson") if err != nil { - return err + return "", err } defer fw.Close() - f.tempFilePath = fw.Name() f.downloaded = true init := false @@ -320,13 +325,14 @@ func (f *fileIterator) downloadAsJSONFile() error { row := make(map[string]bigquery.Value) err := f.bqIter.Next(&row) if err != nil { - if errors.Is(err, iterator.Done) { - if !init { - return drivers.ErrNoRows - } - return nil + if !errors.Is(err, iterator.Done) { + return "", err + } + if !init { + return "", drivers.ErrNoRows } - return err + // all rows written successfully + return fw.Name(), nil } // schema and total rows is available after first call to next only @@ -356,7 +362,7 @@ func (f *fileIterator) downloadAsJSONFile() error { err = enc.Encode(row) if err != nil { - return fmt.Errorf("conversion of row to json failed with error: %w", err) + return "", fmt.Errorf("conversion of row to json failed with error: %w", err) } // If we don't have storage API access, BigQuery may return massive JSON results. (But even with storage API access, it may return JSON for small results.) @@ -365,10 +371,10 @@ func (f *fileIterator) downloadAsJSONFile() error { if rows != 0 && rows%10000 == 0 { // Check file size every 10k rows fileInfo, err := os.Stat(fw.Name()) if err != nil { - return fmt.Errorf("bigquery: failed to poll json file size: %w", err) + return "", fmt.Errorf("bigquery: failed to poll json file size: %w", err) } if fileInfo.Size() >= _jsonDownloadLimitBytes { - return fmt.Errorf("bigquery: json download exceeded limit of %d bytes (enable and provide access to the BigQuery Storage Read API to read larger results)", _jsonDownloadLimitBytes) + return "", fmt.Errorf("bigquery: json download exceeded limit of %d bytes (enable and provide access to the BigQuery Storage Read API to read larger results)", _jsonDownloadLimitBytes) } } } diff --git a/runtime/drivers/clickhouse/clickhouse.go b/runtime/drivers/clickhouse/clickhouse.go index 0f4f7a0c3f2..916e375465f 100644 --- a/runtime/drivers/clickhouse/clickhouse.go +++ b/runtime/drivers/clickhouse/clickhouse.go @@ -262,7 +262,7 @@ func (c *connection) Driver() string { // Config used to open the Connection func (c *connection) Config() map[string]any { m := make(map[string]any, 0) - _ = mapstructure.Decode(c.config, m) + _ = mapstructure.Decode(c.config, &m) return m } diff --git a/runtime/drivers/druid/druid.go b/runtime/drivers/druid/druid.go index bf1d4eda4a8..32f00c7cbca 100644 --- a/runtime/drivers/druid/druid.go +++ b/runtime/drivers/druid/druid.go @@ -195,7 +195,7 @@ func (c *connection) Driver() string { // Config used to open the Connection func (c *connection) Config() map[string]any { m := make(map[string]any, 0) - _ = mapstructure.Decode(c.config, m) + _ = mapstructure.Decode(c.config, &m) return m } diff --git a/runtime/drivers/duckdb/config.go b/runtime/drivers/duckdb/config.go index 2b4d5f8a812..fd48b53496b 100644 --- a/runtime/drivers/duckdb/config.go +++ b/runtime/drivers/duckdb/config.go @@ -81,7 +81,7 @@ func newConfig(cfgMap map[string]any) (*config, error) { // Override DSN.Path with config.Path if cfg.Path != "" { // backward compatibility, cfg.Path takes precedence over cfg.DataDir uri.Path = cfg.Path - } else if cfg.DataDir != "" { + } else if cfg.DataDir != "" && uri.Path == "" { // if some path is set in DSN, honour that path and ignore DataDir uri.Path = filepath.Join(cfg.DataDir, "main.db") } diff --git a/runtime/drivers/file/file.go b/runtime/drivers/file/file.go index 88033823b2d..2f05b68a2b2 100644 --- a/runtime/drivers/file/file.go +++ b/runtime/drivers/file/file.go @@ -148,7 +148,7 @@ type connection struct { // Config implements drivers.Connection. func (c *connection) Config() map[string]any { m := make(map[string]any, 0) - _ = mapstructure.Decode(c.driverConfig, m) + _ = mapstructure.Decode(c.driverConfig, &m) return m } diff --git a/runtime/drivers/gcs/gcs.go b/runtime/drivers/gcs/gcs.go index 1f5d804904a..ef8d74e9d0a 100644 --- a/runtime/drivers/gcs/gcs.go +++ b/runtime/drivers/gcs/gcs.go @@ -174,7 +174,7 @@ func (c *Connection) Driver() string { // Config implements drivers.Connection. func (c *Connection) Config() map[string]any { m := make(map[string]any, 0) - _ = mapstructure.Decode(c.config, m) + _ = mapstructure.Decode(c.config, &m) return m } diff --git a/runtime/drivers/redshift/redshift.go b/runtime/drivers/redshift/redshift.go index b380c27f254..60335cf99ab 100644 --- a/runtime/drivers/redshift/redshift.go +++ b/runtime/drivers/redshift/redshift.go @@ -146,7 +146,7 @@ func (c *Connection) Driver() string { // Config implements drivers.Connection. func (c *Connection) Config() map[string]any { m := make(map[string]any, 0) - _ = mapstructure.Decode(c.config, m) + _ = mapstructure.Decode(c.config, &m) return m } diff --git a/runtime/drivers/snowflake/snowflake.go b/runtime/drivers/snowflake/snowflake.go index 992def810e9..a2da7b1326e 100644 --- a/runtime/drivers/snowflake/snowflake.go +++ b/runtime/drivers/snowflake/snowflake.go @@ -4,6 +4,7 @@ import ( "context" "errors" + "github.com/mitchellh/mapstructure" "github.com/rilldata/rill/runtime/drivers" "github.com/rilldata/rill/runtime/pkg/activity" "go.uber.org/zap" @@ -48,14 +49,27 @@ var spec = drivers.Spec{ type driver struct{} +type configProperties struct { + DSN string `mapstructure:"dsn"` + ParallelFetchLimit int `mapstructure:"parallel_fetch_limit"` + TempDir string `mapstructure:"temp_dir"` +} + func (d driver) Open(instanceID string, config map[string]any, client *activity.Client, logger *zap.Logger) (drivers.Handle, error) { if instanceID == "" { return nil, errors.New("snowflake driver can't be shared") } + + conf := &configProperties{} + err := mapstructure.WeakDecode(config, conf) + if err != nil { + return nil, err + } + // actual db connection is opened during query return &connection{ - config: config, - logger: logger, + configProperties: conf, + logger: logger, }, nil } @@ -72,8 +86,8 @@ func (d driver) TertiarySourceConnectors(ctx context.Context, src map[string]any } type connection struct { - config map[string]any - logger *zap.Logger + configProperties *configProperties + logger *zap.Logger } // Migrate implements drivers.Connection. @@ -93,7 +107,9 @@ func (c *connection) Driver() string { // Config implements drivers.Connection. func (c *connection) Config() map[string]any { - return c.config + m := make(map[string]any, 0) + _ = mapstructure.Decode(c.configProperties, &m) + return m } // Close implements drivers.Connection. diff --git a/runtime/drivers/snowflake/sql_store.go b/runtime/drivers/snowflake/sql_store.go index 5f76021f46f..93387e90489 100644 --- a/runtime/drivers/snowflake/sql_store.go +++ b/runtime/drivers/snowflake/sql_store.go @@ -7,7 +7,6 @@ import ( "fmt" "io" "os" - "strconv" "sync" "time" @@ -46,18 +45,15 @@ func (c *connection) QueryAsFiles(ctx context.Context, props map[string]any, opt var dsn string if srcProps.DSN != "" { // get from src properties dsn = srcProps.DSN - } else if url, ok := c.config["dsn"].(string); ok && url != "" { // get from driver configs - dsn = url + } else if c.configProperties.DSN != "" { // get from driver configs + dsn = c.configProperties.DSN } else { return nil, fmt.Errorf("the property 'dsn' is required for Snowflake. Provide 'dsn' in the YAML properties or pass '--var connector.snowflake.dsn=...' to 'rill start'") } parallelFetchLimit := 15 - if limit, ok := c.config["parallel_fetch_limit"].(string); ok { - parallelFetchLimit, err = strconv.Atoi(limit) - if err != nil { - return nil, err - } + if c.configProperties.ParallelFetchLimit != 0 { + parallelFetchLimit = c.configProperties.ParallelFetchLimit } db, err := sql.Open("snowflake", dsn) @@ -97,6 +93,10 @@ func (c *connection) QueryAsFiles(ctx context.Context, props map[string]any, opt // the number of returned rows is unknown at this point, only the number of batches and output files p.Target(1, drivers.ProgressUnitFile) + tempDir, err := os.MkdirTemp(c.configProperties.TempDir, "snowflake") + if err != nil { + return nil, err + } return &fileIterator{ ctx: ctx, db: db, @@ -107,6 +107,7 @@ func (c *connection) QueryAsFiles(ctx context.Context, props map[string]any, opt limitInBytes: opt.TotalLimitInBytes, parallelFetchLimit: parallelFetchLimit, logger: c.logger, + tempDir: tempDir, }, nil } @@ -119,9 +120,9 @@ type fileIterator struct { progress drivers.Progress limitInBytes int64 logger *zap.Logger + tempDir string // Computed while iterating totalRecords int64 - tempFilePath string downloaded bool // Max number of batches to fetch in parallel parallelFetchLimit int @@ -129,7 +130,7 @@ type fileIterator struct { // Close implements drivers.FileIterator. func (f *fileIterator) Close() error { - return os.Remove(f.tempFilePath) + return os.RemoveAll(f.tempDir) } // Next implements drivers.FileIterator. @@ -149,12 +150,11 @@ func (f *fileIterator) Next() ([]string, error) { f.logger.Debug("downloading results in parquet file", observability.ZapCtx(f.ctx)) // create a temp file - fw, err := os.CreateTemp("", "temp*.parquet") + fw, err := os.CreateTemp(f.tempDir, "temp*.parquet") if err != nil { return nil, err } defer fw.Close() - f.tempFilePath = fw.Name() f.downloaded = true tf := time.Now()