feat(json2): support JSON2 paths in SQL functions (#9007)

feat(query): support JSON2 paths in SQL functions

Update the DataFusion fork to expose scalar function planning hooks.

Infer JSON2 path output types from scalar, aggregate, and window function signatures, while preserving the default Utf8View behavior for functions that accept arbitrary inputs.

Add unit and sqlness coverage for type conflicts, mixed typed and untyped JSON paths, filters, aggregates, and window functions.

Signed-off-by: luofucong <luofc@foxmail.com>
This commit is contained in:
LFC
2026-09-03 04:19:55 +00:00
committed by GitHub
parent 534ab31297
commit 15317a131b
7 changed files with 691 additions and 96 deletions
Generated
+34 -34
View File
@@ -3757,7 +3757,7 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
[[package]]
name = "datafusion"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"arrow-schema 58.3.0",
@@ -3811,7 +3811,7 @@ dependencies = [
[[package]]
name = "datafusion-catalog"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"async-trait",
@@ -3835,7 +3835,7 @@ dependencies = [
[[package]]
name = "datafusion-catalog-listing"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"async-trait",
@@ -3857,7 +3857,7 @@ dependencies = [
[[package]]
name = "datafusion-common"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"ahash 0.8.12",
"arrow 58.3.0",
@@ -3881,7 +3881,7 @@ dependencies = [
[[package]]
name = "datafusion-common-runtime"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"futures",
"log",
@@ -3891,7 +3891,7 @@ dependencies = [
[[package]]
name = "datafusion-datasource"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"async-compression",
@@ -3925,7 +3925,7 @@ dependencies = [
[[package]]
name = "datafusion-datasource-arrow"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"arrow-ipc 58.3.0",
@@ -3948,7 +3948,7 @@ dependencies = [
[[package]]
name = "datafusion-datasource-csv"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"async-trait",
@@ -3970,7 +3970,7 @@ dependencies = [
[[package]]
name = "datafusion-datasource-json"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"async-trait",
@@ -3993,7 +3993,7 @@ dependencies = [
[[package]]
name = "datafusion-datasource-parquet"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"async-trait",
@@ -4022,12 +4022,12 @@ dependencies = [
[[package]]
name = "datafusion-doc"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
[[package]]
name = "datafusion-execution"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"arrow-buffer 58.3.0",
@@ -4049,7 +4049,7 @@ dependencies = [
[[package]]
name = "datafusion-expr"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"async-trait",
@@ -4071,7 +4071,7 @@ dependencies = [
[[package]]
name = "datafusion-expr-common"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"datafusion-common",
@@ -4083,7 +4083,7 @@ dependencies = [
[[package]]
name = "datafusion-functions"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"arrow-buffer 58.3.0",
@@ -4114,7 +4114,7 @@ dependencies = [
[[package]]
name = "datafusion-functions-aggregate"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"ahash 0.8.12",
"arrow 58.3.0",
@@ -4135,7 +4135,7 @@ dependencies = [
[[package]]
name = "datafusion-functions-aggregate-common"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"ahash 0.8.12",
"arrow 58.3.0",
@@ -4147,7 +4147,7 @@ dependencies = [
[[package]]
name = "datafusion-functions-nested"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"arrow-ord 58.3.0",
@@ -4171,7 +4171,7 @@ dependencies = [
[[package]]
name = "datafusion-functions-table"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"async-trait",
@@ -4186,7 +4186,7 @@ dependencies = [
[[package]]
name = "datafusion-functions-window"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"datafusion-common",
@@ -4203,7 +4203,7 @@ dependencies = [
[[package]]
name = "datafusion-functions-window-common"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"datafusion-common",
"datafusion-physical-expr-common",
@@ -4212,7 +4212,7 @@ dependencies = [
[[package]]
name = "datafusion-macros"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"datafusion-doc",
"quote",
@@ -4222,7 +4222,7 @@ dependencies = [
[[package]]
name = "datafusion-optimizer"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"chrono",
@@ -4271,7 +4271,7 @@ dependencies = [
[[package]]
name = "datafusion-physical-expr"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"ahash 0.8.12",
"arrow 58.3.0",
@@ -4294,7 +4294,7 @@ dependencies = [
[[package]]
name = "datafusion-physical-expr-adapter"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"datafusion-common",
@@ -4308,7 +4308,7 @@ dependencies = [
[[package]]
name = "datafusion-physical-expr-common"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"ahash 0.8.12",
"arrow 58.3.0",
@@ -4324,7 +4324,7 @@ dependencies = [
[[package]]
name = "datafusion-physical-optimizer"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"datafusion-common",
@@ -4342,7 +4342,7 @@ dependencies = [
[[package]]
name = "datafusion-physical-plan"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"ahash 0.8.12",
"arrow 58.3.0",
@@ -4373,7 +4373,7 @@ dependencies = [
[[package]]
name = "datafusion-proto"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"chrono",
@@ -4400,7 +4400,7 @@ dependencies = [
[[package]]
name = "datafusion-proto-common"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"datafusion-common",
@@ -4410,7 +4410,7 @@ dependencies = [
[[package]]
name = "datafusion-pruning"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"datafusion-common",
@@ -4426,7 +4426,7 @@ dependencies = [
[[package]]
name = "datafusion-session"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"async-trait",
"datafusion-common",
@@ -4439,7 +4439,7 @@ dependencies = [
[[package]]
name = "datafusion-sql"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"arrow 58.3.0",
"bigdecimal 0.4.8",
@@ -4457,7 +4457,7 @@ dependencies = [
[[package]]
name = "datafusion-substrait"
version = "53.1.0"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=4c8a6bf28347b346348136247eb67dfb933f1147#4c8a6bf28347b346348136247eb67dfb933f1147"
source = "git+https://github.com/GreptimeTeam/datafusion.git?rev=144225dd11e38960de75786fde69159cb27b8af3#144225dd11e38960de75786fde69159cb27b8af3"
dependencies = [
"async-recursion",
"async-trait",
+15 -15
View File
@@ -353,21 +353,21 @@ git = "https://github.com/GreptimeTeam/greptime-meter.git"
rev = "5618e779cf2bb4755b499c630fba4c35e91898cb"
[patch.crates-io]
datafusion = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "4c8a6bf28347b346348136247eb67dfb933f1147" }
datafusion-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "4c8a6bf28347b346348136247eb67dfb933f1147" }
datafusion-datasource = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "4c8a6bf28347b346348136247eb67dfb933f1147" }
datafusion-expr = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "4c8a6bf28347b346348136247eb67dfb933f1147" }
datafusion-expr-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "4c8a6bf28347b346348136247eb67dfb933f1147" }
datafusion-functions = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "4c8a6bf28347b346348136247eb67dfb933f1147" }
datafusion-functions-aggregate-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "4c8a6bf28347b346348136247eb67dfb933f1147" }
datafusion-functions-window-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "4c8a6bf28347b346348136247eb67dfb933f1147" }
datafusion-optimizer = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "4c8a6bf28347b346348136247eb67dfb933f1147" }
datafusion-physical-expr = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "4c8a6bf28347b346348136247eb67dfb933f1147" }
datafusion-physical-expr-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "4c8a6bf28347b346348136247eb67dfb933f1147" }
datafusion-physical-plan = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "4c8a6bf28347b346348136247eb67dfb933f1147" }
datafusion-proto = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "4c8a6bf28347b346348136247eb67dfb933f1147" }
datafusion-sql = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "4c8a6bf28347b346348136247eb67dfb933f1147" }
datafusion-substrait = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "4c8a6bf28347b346348136247eb67dfb933f1147" }
datafusion = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "144225dd11e38960de75786fde69159cb27b8af3" }
datafusion-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "144225dd11e38960de75786fde69159cb27b8af3" }
datafusion-datasource = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "144225dd11e38960de75786fde69159cb27b8af3" }
datafusion-expr = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "144225dd11e38960de75786fde69159cb27b8af3" }
datafusion-expr-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "144225dd11e38960de75786fde69159cb27b8af3" }
datafusion-functions = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "144225dd11e38960de75786fde69159cb27b8af3" }
datafusion-functions-aggregate-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "144225dd11e38960de75786fde69159cb27b8af3" }
datafusion-functions-window-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "144225dd11e38960de75786fde69159cb27b8af3" }
datafusion-optimizer = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "144225dd11e38960de75786fde69159cb27b8af3" }
datafusion-physical-expr = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "144225dd11e38960de75786fde69159cb27b8af3" }
datafusion-physical-expr-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "144225dd11e38960de75786fde69159cb27b8af3" }
datafusion-physical-plan = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "144225dd11e38960de75786fde69159cb27b8af3" }
datafusion-proto = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "144225dd11e38960de75786fde69159cb27b8af3" }
datafusion-sql = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "144225dd11e38960de75786fde69159cb27b8af3" }
datafusion-substrait = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "144225dd11e38960de75786fde69159cb27b8af3" }
sqlparser = { git = "https://github.com/GreptimeTeam/sqlparser-rs.git", rev = "2aefa08a8d69c96eec2d6d6703598a009bba6e4c" } # on branch v0.61.x
[profile.release]
+345 -29
View File
@@ -20,21 +20,25 @@ use common_function::scalars::udf::create_udf;
use datafusion_common::arrow::datatypes::DataType;
use datafusion_common::{Column, DFSchema, Result, ScalarValue, TableReference};
use datafusion_expr::expr::{BinaryExpr, ScalarFunction};
use datafusion_expr::planner::{ExprPlanner, PlannerResult, RawBinaryExpr};
use datafusion_expr::{Expr, ExprSchemable, Operator, ScalarUDF};
use datafusion_expr::planner::{
ExprPlanner, PlannerResult, RawAggregateExpr, RawBinaryExpr, RawScalarExpr, RawWindowExpr,
};
use datafusion_expr::type_coercion::functions::{UDFCoercionExt, fields_with_udf};
use datafusion_expr::{Expr, ExprSchemable, Operator, ScalarUDF, WindowFunctionDefinition};
use datatypes::extension::json::is_json2_extension_type;
use either::Either;
use sqlparser::ast::BinaryOperator;
/// Rewrites JSON-aware SQL expressions into DataFusion expressions.
///
/// This planner handles two cases:
/// This planner handles three cases:
/// - Rewrites compound identifiers on JSON extension columns into `json_get` function.
/// For example, `select a.b.c` => `select json_get(a, "b.c")`.
/// - Pushes an "expected type" argument into the `json_get` function when it participates in a
/// binary operator. So that `json_get` knows the wanted data type when dealing with variant
/// JSON values.
/// For example, `select json_get(a, "b.c") + 1` => `select json_get(a, "b.c", NULL::Int64) + 1`.
/// - Infers the expected type from scalar, aggregate, and window function signatures.
/// For example, `select abs(a.b.c)` => `select abs(json_get(a, "b.c", NULL::Float64))`.
#[derive(Debug)]
pub(crate) struct JsonExprPlanner;
@@ -50,9 +54,7 @@ impl ExprPlanner for JsonExprPlanner {
mut right,
} = expr;
if extract_untyped_json_get(&mut left).is_none()
&& extract_untyped_json_get(&mut right).is_none()
{
if !is_untyped_json_get(&left) && !is_untyped_json_get(&right) {
return Ok(PlannerResult::Original(RawBinaryExpr { op, left, right }));
}
@@ -62,17 +64,16 @@ impl ExprPlanner for JsonExprPlanner {
let left_type = left.get_type(schema)?;
let right_type = right.get_type(schema)?;
let left = push_json_get_type_arg(left, right_type)?;
let right = push_json_get_type_arg(right, left_type)?;
match (left, right) {
(Either::Left(left), Either::Left(right)) => {
Ok(PlannerResult::Original(RawBinaryExpr { op, left, right }))
}
(left, right) => Ok(PlannerResult::Planned(Expr::BinaryExpr(BinaryExpr::new(
Box::new(left.into_inner()),
let left_changed = push_json_get_type_arg(&mut left, &right_type)?;
let right_changed = push_json_get_type_arg(&mut right, &left_type)?;
if left_changed || right_changed {
Ok(PlannerResult::Planned(Expr::BinaryExpr(BinaryExpr::new(
Box::new(left),
expr_op,
Box::new(right.into_inner()),
)))),
Box::new(right),
))))
} else {
Ok(PlannerResult::Original(RawBinaryExpr { op, left, right }))
}
}
@@ -101,31 +102,235 @@ impl ExprPlanner for JsonExprPlanner {
),
)))
}
/// Rewrites JSON2 arguments without taking over the final function planning.
///
/// `Original` carries the possibly modified raw expression to subsequent planners and then
/// DataFusion's default function construction. Returning `Planned` would short-circuit both.
fn plan_scalar(&self, mut expr: RawScalarExpr) -> Result<PlannerResult<RawScalarExpr>> {
push_function_arg_types(expr.func.as_ref(), &mut expr.args)?;
Ok(PlannerResult::Original(expr))
}
/// Rewrites JSON2 arguments while preserving subsequent aggregate planning.
fn plan_aggregate(
&self,
mut expr: RawAggregateExpr,
) -> Result<PlannerResult<RawAggregateExpr>> {
push_function_arg_types(expr.func.as_ref(), &mut expr.args)?;
Ok(PlannerResult::Original(expr))
}
/// Rewrites JSON2 arguments while preserving subsequent window planning.
fn plan_window(&self, mut expr: RawWindowExpr) -> Result<PlannerResult<RawWindowExpr>> {
match &expr.func_def {
WindowFunctionDefinition::AggregateUDF(func) => {
push_function_arg_types(func.as_ref(), &mut expr.args)?;
}
WindowFunctionDefinition::WindowUDF(func) => {
push_function_arg_types(func.as_ref(), &mut expr.args)?;
}
}
Ok(PlannerResult::Original(expr))
}
}
enum JsonGetTypeResolution {
Fallback,
Typed(Vec<(usize, DataType)>),
}
/// Infers static output types for untyped `json_get` arguments from a function signature.
///
/// DataFusion requires every expression to have one Arrow data type during planning. A JSON path
/// may contain heterogeneous values across rows, but it cannot expose those values as different
/// Arrow types in one result column. Preserving their runtime types would require a single
/// Variant-like data type and Variant-aware functions instead. Maybe we can wait for
/// https://github.com/apache/datafusion/issues/16116
///
/// This helper uses the function's coercion rules to select a supported output type, then appends
/// a typed NULL argument to each relevant `json_get`. The typed argument makes `json_get` project
/// compatible JSON values to that type and return NULL for incompatible values. Functions that
/// accept json_get's default `Utf8View` output keep the two-argument form so later rewrites can
/// still push down an outer cast.
fn push_function_arg_types<F>(func: &F, args: &mut [Expr]) -> Result<()>
where
F: UDFCoercionExt,
{
if !args.iter().any(is_untyped_json_get) {
return Ok(());
}
let fields = args.iter().map(function_arg_field).collect::<Vec<_>>();
match infer_json_get_types(func, args, &fields) {
JsonGetTypeResolution::Fallback => {
let Some(data_type) = fallback_json_get_type(func, args, &fields) else {
return Ok(());
};
for arg in args.iter_mut() {
if is_untyped_json_get(arg) {
let _ = push_json_get_type_arg(arg, &data_type)?;
}
}
}
JsonGetTypeResolution::Typed(types) => {
for (index, data_type) in types {
let _ = push_json_get_type_arg(&mut args[index], &data_type)?;
}
}
}
Ok(())
}
fn infer_json_get_types<F>(func: &F, args: &[Expr], fields: &[Arc<Field>]) -> JsonGetTypeResolution
where
F: UDFCoercionExt,
{
// Only untyped json_get arguments use Null placeholders; preserve every other known argument
// type. fields_with_udf performs contextual coercion rather than reverse inference from a
// signature alone. Numeric signatures may preserve all-Null inputs, while Comparable
// signatures may default them to Utf8. For example, retaining the Float64 peer in
// coalesce(json_get(...), 1.0) lets DataFusion resolve json_get to Float64 instead of Utf8.
//
// This is a best-effort probe: a failure does not mean the actual function call is invalid, so
// try concrete JSON types before leaving final validation to DataFusion's default planner.
let Ok(coerced) = fields_with_udf(fields, func) else {
return JsonGetTypeResolution::Fallback;
};
let mut inferred_types = Vec::with_capacity(coerced.len());
for (index, (arg, field)) in args.iter().zip(coerced).enumerate() {
if !is_untyped_json_get(arg) || field.data_type().is_null() {
continue;
}
let Some(data_type) = json_get_output_type(field.data_type()) else {
return JsonGetTypeResolution::Fallback;
};
inferred_types.push((index, data_type));
}
if inferred_types.is_empty() {
JsonGetTypeResolution::Fallback
} else {
JsonGetTypeResolution::Typed(inferred_types)
}
}
fn fallback_json_get_type<F>(func: &F, args: &[Expr], fields: &[Arc<Field>]) -> Option<DataType>
where
F: UDFCoercionExt,
{
// Prefer json_get's default Utf8View type. If the function rejects strings but accepts numeric
// values, prefer Float64 so both integers and fractions remain usable.
let mut candidate_fields = fields.to_vec();
for data_type in [
DataType::Utf8View,
DataType::Float64,
DataType::Int64,
DataType::Boolean,
] {
for (index, arg) in args.iter().enumerate() {
if is_untyped_json_get(arg) {
candidate_fields[index] = Arc::new(
fields[index]
.as_ref()
.clone()
.with_data_type(data_type.clone()),
);
}
}
if fields_with_udf(&candidate_fields, func).is_ok() {
return Some(data_type);
}
}
None
}
fn function_arg_field(expr: &Expr) -> Arc<Field> {
let data_type = if is_untyped_json_get(expr) {
DataType::Null
} else if let Some(data_type) = extract_json_get_type(expr) {
data_type
} else {
// Treat unresolved expressions as untyped NULL. This lets signatures such as `power`
// infer a JSON type, while functions such as `coalesce` can leave it untyped for default
// planning. This is only best-effort: overloaded or user-defined functions may select a
// different signature for NULL than for the expression's actual type.
// TODO(LFC): Use the input schema once DataFusion passes it to ExprPlanner::plan_*().
expr.get_type(&DFSchema::empty()).unwrap_or(DataType::Null)
};
Arc::new(Field::new("", data_type, true))
}
fn json_get_output_type(data_type: &DataType) -> Option<DataType> {
let output_type = match data_type {
DataType::Boolean => DataType::Boolean,
data_type if data_type.is_integer() => DataType::Int64,
data_type if data_type.is_floating() => DataType::Float64,
DataType::Decimal128(_, _) | DataType::Decimal256(_, _) => DataType::Float64,
data_type if data_type.is_string() => DataType::Utf8View,
_ => return None,
};
Some(output_type)
}
macro_rules! is_untyped_json_get_func {
($func:expr) => {
$func
.func
.name()
.eq_ignore_ascii_case(JsonGetWithType::NAME)
&& $func.args.len() == 2
};
}
macro_rules! is_typed_json_get_func {
($func:expr) => {
$func
.func
.name()
.eq_ignore_ascii_case(JsonGetWithType::NAME)
&& $func.args.len() == 3
};
}
fn extract_untyped_json_get(expr: &mut Expr) -> Option<&mut ScalarFunction> {
match expr {
Expr::ScalarFunction(f)
if f.func.name().eq_ignore_ascii_case(JsonGetWithType::NAME) && f.args.len() == 2 =>
{
Some(f)
}
Expr::ScalarFunction(f) if is_untyped_json_get_func!(f) => Some(f),
_ => None,
}
}
fn push_json_get_type_arg(mut expr: Expr, mut data_type: DataType) -> Result<Either<Expr, Expr>> {
let Some(json_get) = extract_untyped_json_get(&mut expr) else {
return Ok(Either::Left(expr));
fn extract_json_get_type(expr: &Expr) -> Option<DataType> {
match expr {
Expr::ScalarFunction(f) if is_typed_json_get_func!(f) => f
.args
.get(2)
.and_then(|x| x.as_literal())
.map(|x| x.data_type()),
_ => None,
}
}
fn is_untyped_json_get(expr: &Expr) -> bool {
matches!(
expr,
Expr::ScalarFunction(f) if is_untyped_json_get_func!(f)
)
}
fn push_json_get_type_arg(expr: &mut Expr, data_type: &DataType) -> Result<bool> {
let Some(json_get) = extract_untyped_json_get(expr) else {
return Ok(false);
};
// The two-argument form already returns Utf8View. Keep it so JsonGetRewriter can still absorb
// a cast added by subsequent function coercion.
if data_type.is_string() {
data_type = DataType::Utf8View;
return Ok(false);
}
let with_type = ScalarValue::try_new_null(&data_type).map(|x| Expr::Literal(x, None))?;
let with_type = ScalarValue::try_new_null(data_type).map(|x| Expr::Literal(x, None))?;
json_get.args.push(with_type);
Ok(Either::Right(expr))
Ok(true)
}
fn parse_sql_op(op: &BinaryOperator) -> Option<Operator> {
@@ -153,6 +358,11 @@ fn parse_sql_op(op: &BinaryOperator) -> Option<Operator> {
#[cfg(test)]
mod tests {
use arrow_schema::Fields;
use datafusion::functions_aggregate::count::count_udaf;
use datafusion::functions_aggregate::sum::sum_udaf;
use datafusion_expr::WindowFrame;
use datafusion_functions::core::coalesce;
use datafusion_functions::math::{abs, power};
use datatypes::extension::json::Json2ExtensionType;
use super::*;
@@ -281,4 +491,110 @@ mod tests {
Ok(())
}
#[test]
fn test_plan_functions() -> Result<()> {
let planner = JsonExprPlanner;
let json_get = || json_get_expr(Expr::Column(Column::new_unqualified("j")), "a.b");
let PlannerResult::Original(scalar) = planner.plan_scalar(RawScalarExpr {
func: abs(),
args: vec![json_get()],
})?
else {
unreachable!();
};
assert_eq!(
Some(DataType::Float64),
extract_json_get_type(&scalar.args[0])
);
let PlannerResult::Original(scalar) = planner.plan_scalar(RawScalarExpr {
func: power(),
args: vec![
json_get(),
Expr::Column(Column::new_unqualified("exponent")),
],
})?
else {
unreachable!();
};
assert_eq!(
Some(DataType::Float64),
extract_json_get_type(&scalar.args[0])
);
let PlannerResult::Original(aggregate) = planner.plan_aggregate(RawAggregateExpr {
func: sum_udaf(),
args: vec![json_get()],
distinct: false,
filter: None,
order_by: vec![],
null_treatment: None,
})?
else {
unreachable!();
};
assert_eq!(
Some(DataType::Float64),
extract_json_get_type(&aggregate.args[0])
);
let PlannerResult::Original(count) = planner.plan_aggregate(RawAggregateExpr {
func: count_udaf(),
args: vec![json_get()],
distinct: false,
filter: None,
order_by: vec![],
null_treatment: None,
})?
else {
unreachable!();
};
assert_eq!(None, extract_json_get_type(&count.args[0]));
let PlannerResult::Original(window) = planner.plan_window(RawWindowExpr {
func_def: WindowFunctionDefinition::AggregateUDF(sum_udaf()),
args: vec![json_get()],
partition_by: vec![],
order_by: vec![],
window_frame: WindowFrame::new(None),
filter: None,
null_treatment: None,
distinct: false,
})?
else {
unreachable!();
};
assert_eq!(
Some(DataType::Float64),
extract_json_get_type(&window.args[0])
);
Ok(())
}
#[test]
fn test_plan_function_with_mixed_json_get_types() -> Result<()> {
let planner = JsonExprPlanner;
let json_get = || json_get_expr(Expr::Column(Column::new_unqualified("j")), "a.b");
let mut typed = json_get();
push_json_get_type_arg(&mut typed, &DataType::Float64)?;
let PlannerResult::Original(scalar) = planner.plan_scalar(RawScalarExpr {
func: coalesce(),
args: vec![json_get(), typed],
})?
else {
unreachable!();
};
assert_eq!(
Some(DataType::Float64),
extract_json_get_type(&scalar.args[0])
);
assert_eq!(
Some(DataType::Float64),
extract_json_get_type(&scalar.args[1])
);
Ok(())
}
}
@@ -328,19 +328,6 @@ select j.a.b + 1 from json2_table order by ts;
| 11 |
+------------------------------------------------------------+
select abs(j.a.b) from json2_table order by ts;
Error: 3000(PlanQuery), Failed to plan SQL: Error during planning: Function 'abs' expects NativeType::Numeric but received NativeType::String No function matches the given name and argument types 'abs(Utf8View)'. You might need to add explicit type casts.
Candidate functions:
abs(Numeric(1))
-- "j.c" is of type "String", "abs" is expected to be all "null"s.
select abs(j.c) from json2_table order by ts;
Error: 3000(PlanQuery), Failed to plan SQL: Error during planning: Function 'abs' expects NativeType::Numeric but received NativeType::String No function matches the given name and argument types 'abs(Utf8View)'. You might need to add explicit type casts.
Candidate functions:
abs(Numeric(1))
select j.d from json2_table order by ts;
+-----------------------------------+
@@ -70,11 +70,6 @@ select json_get(j, '$.') from json2_table order by ts;
select j.a.b + 1 from json2_table order by ts;
select abs(j.a.b) from json2_table order by ts;
-- "j.c" is of type "String", "abs" is expected to be all "null"s.
select abs(j.c) from json2_table order by ts;
select j.d from json2_table order by ts;
drop table json2_table;
@@ -0,0 +1,230 @@
create table json2_function_table (
ts timestamp time index,
j json2,
exponent double
) with (
'append_mode' = 'true',
'sst_format' = 'flat'
);
Affected Rows: 0
insert into json2_function_table values
(1, '{"metrics":{"value":1,"enabled":true},"label":"one","tags":["a"]}', 2),
(2, '{"metrics":{"value":-2.5,"enabled":false},"label":2,"tags":{"source":"b"}}', 2);
Affected Rows: 2
admin flush_table('json2_function_table');
+-------------------------------------------+
| ADMIN flush_table('json2_function_table') |
+-------------------------------------------+
| 0 |
+-------------------------------------------+
insert into json2_function_table values
(3, '{"metrics":{"value":"three","enabled":"yes"},"label":true,"tags":[1,2]}', 2),
(4, '{"metrics":{"value":true},"label":{"name":"four"},"tags":"four"}', 2),
(5, '{"metrics":{"value":{"nested":5}},"label":["five"],"tags":null}', 2),
(6, '{"metrics":{"value":[6]},"label":null}', 2);
Affected Rows: 4
admin flush_table('json2_function_table');
+-------------------------------------------+
| ADMIN flush_table('json2_function_table') |
+-------------------------------------------+
| 0 |
+-------------------------------------------+
admin compact_table('json2_function_table', 'swcs', '86400');
+--------------------------------------------------------------+
| ADMIN compact_table('json2_function_table', 'swcs', '86400') |
+--------------------------------------------------------------+
| 0 |
+--------------------------------------------------------------+
insert into json2_function_table values
(7, '{"metrics":{"value":null},"label":"seven","extra":7}', 2),
(8, '{"metrics":"opaque","label":8,"extra":false}', 2);
Affected Rows: 2
admin flush_table('json2_function_table');
+-------------------------------------------+
| ADMIN flush_table('json2_function_table') |
+-------------------------------------------+
| 0 |
+-------------------------------------------+
insert into json2_function_table values
(9, '{"other":9,"label":{"name":"nine"}}', 2),
(10, '{"metrics":{"value":10},"label":"ten","extra":[10]}', 2);
Affected Rows: 2
-- Arrow casts Boolean values to Float64 as true = 1.0 and false = 0.0.
select abs(j.metrics.value) as abs_value from json2_function_table order by ts;
+-----------+
| abs_value |
+-----------+
| 1.0 |
| 2.5 |
| |
| 1.0 |
| |
| |
| |
| |
| |
| 10.0 |
+-----------+
select power(j.label, 2) * 2 as scaled_label from json2_function_table order by ts;
+--------------+
| scaled_label |
+--------------+
| |
| 8.0 |
| 2.0 |
| |
| |
| |
| |
| 128.0 |
| |
| |
+--------------+
select power(j.metrics.value, exponent) as powered_value from json2_function_table order by ts;
+---------------+
| powered_value |
+---------------+
| 1.0 |
| 6.25 |
| |
| 1.0 |
| |
| |
| |
| |
| |
| 100.0 |
+---------------+
select coalesce(j.metrics.value, exponent) as value_or_exponent from json2_function_table order by ts;
+-------------------+
| value_or_exponent |
+-------------------+
| 1.0 |
| -2.5 |
| 2.0 |
| 1.0 |
| 2.0 |
| 2.0 |
| 2.0 |
| 2.0 |
| 2.0 |
| 10.0 |
+-------------------+
select coalesce(
j.metrics.value,
j.label::double
) as mixed_value from json2_function_table order by ts;
+-------------+
| mixed_value |
+-------------+
| 1.0 |
| -2.5 |
| 1.0 |
| 1.0 |
| |
| |
| |
| 8.0 |
| |
| 10.0 |
+-------------+
select ts, j.label as label, signum(j.metrics.value) as value_sign
from json2_function_table
order by ts;
+-------------------------+-----------------+------------+
| ts | label | value_sign |
+-------------------------+-----------------+------------+
| 1970-01-01T00:00:00.001 | one | 1.0 |
| 1970-01-01T00:00:00.002 | 2 | -1.0 |
| 1970-01-01T00:00:00.003 | true | |
| 1970-01-01T00:00:00.004 | {"name":"four"} | 1.0 |
| 1970-01-01T00:00:00.005 | ["five"] | |
| 1970-01-01T00:00:00.006 | | |
| 1970-01-01T00:00:00.007 | seven | |
| 1970-01-01T00:00:00.008 | 8 | |
| 1970-01-01T00:00:00.009 | {"name":"nine"} | |
| 1970-01-01T00:00:00.010 | ten | 1.0 |
+-------------------------+-----------------+------------+
select ts, j.label as label
from json2_function_table
where iszero(j.metrics.value) = false
order by ts;
+-------------------------+-----------------+
| ts | label |
+-------------------------+-----------------+
| 1970-01-01T00:00:00.001 | one |
| 1970-01-01T00:00:00.002 | 2 |
| 1970-01-01T00:00:00.004 | {"name":"four"} |
| 1970-01-01T00:00:00.010 | ten |
+-------------------------+-----------------+
select count(j.metrics.value) as value_count from json2_function_table;
+-------------+
| value_count |
+-------------+
| 7 |
+-------------+
select sum(j.metrics.value) as value_sum from json2_function_table;
+-----------+
| value_sum |
+-----------+
| 9.5 |
+-----------+
select lag(j.metrics.value) over (order by ts) as previous_value
from json2_function_table
order by ts;
+----------------+
| previous_value |
+----------------+
| |
| 1 |
| -2.5 |
| three |
| true |
| {"nested":5} |
| [6] |
| |
| |
| |
+----------------+
drop table json2_function_table;
Affected Rows: 0
@@ -0,0 +1,67 @@
create table json2_function_table (
ts timestamp time index,
j json2,
exponent double
) with (
'append_mode' = 'true',
'sst_format' = 'flat'
);
insert into json2_function_table values
(1, '{"metrics":{"value":1,"enabled":true},"label":"one","tags":["a"]}', 2),
(2, '{"metrics":{"value":-2.5,"enabled":false},"label":2,"tags":{"source":"b"}}', 2);
admin flush_table('json2_function_table');
insert into json2_function_table values
(3, '{"metrics":{"value":"three","enabled":"yes"},"label":true,"tags":[1,2]}', 2),
(4, '{"metrics":{"value":true},"label":{"name":"four"},"tags":"four"}', 2),
(5, '{"metrics":{"value":{"nested":5}},"label":["five"],"tags":null}', 2),
(6, '{"metrics":{"value":[6]},"label":null}', 2);
admin flush_table('json2_function_table');
admin compact_table('json2_function_table', 'swcs', '86400');
insert into json2_function_table values
(7, '{"metrics":{"value":null},"label":"seven","extra":7}', 2),
(8, '{"metrics":"opaque","label":8,"extra":false}', 2);
admin flush_table('json2_function_table');
insert into json2_function_table values
(9, '{"other":9,"label":{"name":"nine"}}', 2),
(10, '{"metrics":{"value":10},"label":"ten","extra":[10]}', 2);
-- Arrow casts Boolean values to Float64 as true = 1.0 and false = 0.0.
select abs(j.metrics.value) as abs_value from json2_function_table order by ts;
select power(j.label, 2) * 2 as scaled_label from json2_function_table order by ts;
select power(j.metrics.value, exponent) as powered_value from json2_function_table order by ts;
select coalesce(j.metrics.value, exponent) as value_or_exponent from json2_function_table order by ts;
select coalesce(
j.metrics.value,
j.label::double
) as mixed_value from json2_function_table order by ts;
select ts, j.label as label, signum(j.metrics.value) as value_sign
from json2_function_table
order by ts;
select ts, j.label as label
from json2_function_table
where iszero(j.metrics.value) = false
order by ts;
select count(j.metrics.value) as value_count from json2_function_table;
select sum(j.metrics.value) as value_sum from json2_function_table;
select lag(j.metrics.value) over (order by ts) as previous_value
from json2_function_table
order by ts;
drop table json2_function_table;