This is an automated email from the ASF dual-hosted git repository.
CurtHagenlocher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-dotnet.git
The following commit(s) were added to refs/heads/main by this push:
new a25e49b Refactors Apache.Arrow.Variant into two assemblies (#322)
a25e49b is described below
commit a25e49b411c614ec1203e8888a3df4e66a4d7c31
Author: Curt Hagenlocher <[email protected]>
AuthorDate: Mon Apr 20 20:22:41 2026 -0700
Refactors Apache.Arrow.Variant into two assemblies (#322)
## What's Changed
Refactors `Apache.Arrow.Variant` into two assemblies -- one for the base
type and one for JSON conversion. The dependency on `System.Text.Json`
moves to the second assembly.
---
Apache.Arrow.Tests.slnf | 3 +-
Apache.Arrow.sln | 160 +++++
dev/release/verify_rc.sh | 1 +
.../Apache.Arrow.Operations.csproj} | 8 +-
.../Json/VariantJsonConverter.cs | 4 +-
.../Json/VariantJsonReader.cs | 152 +++++
.../Json/VariantJsonWriter.cs | 4 +-
.../Properties/AssemblyInfo.cs | 2 +-
.../Apache.Arrow.Variant.csproj | 1 -
src/Apache.Arrow.Variant/Json/VariantJsonReader.cs | 49 --
.../Properties/AssemblyInfo.cs | 1 +
src/Apache.Arrow.Variant/VariantBuilder.cs | 720 ++-------------------
src/Apache.Arrow.Variant/VariantValueWriter.cs | 679 +++++++++++++++++++
.../Apache.Arrow.Operations.Tests.csproj} | 2 +-
.../Json/VariantDecimalJsonTests.cs | 117 ++++
.../Json}/VariantJsonTests.cs | 5 +-
.../Apache.Arrow.Variant.Benchmarks.csproj | 1 +
.../EncodingBenchmarks.cs | 13 +-
.../Apache.Arrow.Variant.Tests.csproj | 2 +
.../VariantSqlDecimalTests.cs | 85 ---
20 files changed, 1171 insertions(+), 838 deletions(-)
diff --git a/Apache.Arrow.Tests.slnf b/Apache.Arrow.Tests.slnf
index 92b57e6..0b16c89 100644
--- a/Apache.Arrow.Tests.slnf
+++ b/Apache.Arrow.Tests.slnf
@@ -7,7 +7,8 @@
"test\\Apache.Arrow.Flight.Tests\\Apache.Arrow.Flight.Tests.csproj",
"test\\Apache.Arrow.Flight.TestWeb\\Apache.Arrow.Flight.TestWeb.csproj",
"test\\Apache.Arrow.Tests\\Apache.Arrow.Tests.csproj",
- "test\\Apache.Arrow.Variant.Tests\\Apache.Arrow.Variant.Tests.csproj"
+ "test\\Apache.Arrow.Variant.Tests\\Apache.Arrow.Variant.Tests.csproj",
+
"test\\Apache.Arrow.Operations.Tests\\Apache.Arrow.Operations.Tests.csproj"
]
}
}
diff --git a/Apache.Arrow.sln b/Apache.Arrow.sln
index f14c4d5..105eb0c 100644
--- a/Apache.Arrow.sln
+++ b/Apache.Arrow.sln
@@ -35,76 +35,236 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") =
"Apache.Arrow.Variant.Tests"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") =
"Apache.Arrow.Variant.Benchmarks",
"test\Apache.Arrow.Variant.Benchmarks\Apache.Arrow.Variant.Benchmarks.csproj",
"{F3A8B7C6-D5E4-4321-9ABC-DEF012345678}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Apache.Arrow.Operations",
"src\Apache.Arrow.Operations\Apache.Arrow.Operations.csproj",
"{B4799C3C-179F-470F-A8A4-8A75D172E3E1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") =
"Apache.Arrow.Operations.Tests",
"test\Apache.Arrow.Operations.Tests\Apache.Arrow.Operations.Tests.csproj",
"{73EBE132-AE05-4C32-9525-515F4768156B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
{BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Debug|x64.Build.0 =
Debug|Any CPU
+ {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Debug|x86.Build.0 =
Debug|Any CPU
{BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Release|Any CPU.Build.0
= Release|Any CPU
+ {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Release|x64.ActiveCfg =
Release|Any CPU
+ {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Release|x64.Build.0 =
Release|Any CPU
+ {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Release|x86.ActiveCfg =
Release|Any CPU
+ {BA6B2B0D-EAAE-4183-8A39-1B9CF571F71F}.Release|x86.Build.0 =
Release|Any CPU
{9CCEC01B-E67A-4726-BE72-7B514F76163F}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
{9CCEC01B-E67A-4726-BE72-7B514F76163F}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Debug|x64.Build.0 =
Debug|Any CPU
+ {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Debug|x86.Build.0 =
Debug|Any CPU
{9CCEC01B-E67A-4726-BE72-7B514F76163F}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{9CCEC01B-E67A-4726-BE72-7B514F76163F}.Release|Any CPU.Build.0
= Release|Any CPU
+ {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Release|x64.ActiveCfg =
Release|Any CPU
+ {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Release|x64.Build.0 =
Release|Any CPU
+ {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Release|x86.ActiveCfg =
Release|Any CPU
+ {9CCEC01B-E67A-4726-BE72-7B514F76163F}.Release|x86.Build.0 =
Release|Any CPU
{742DF47D-77C5-4B84-9E0C-69645F1161EA}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
{742DF47D-77C5-4B84-9E0C-69645F1161EA}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Debug|x64.Build.0 =
Debug|Any CPU
+ {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Debug|x86.Build.0 =
Debug|Any CPU
{742DF47D-77C5-4B84-9E0C-69645F1161EA}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{742DF47D-77C5-4B84-9E0C-69645F1161EA}.Release|Any CPU.Build.0
= Release|Any CPU
+ {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Release|x64.ActiveCfg =
Release|Any CPU
+ {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Release|x64.Build.0 =
Release|Any CPU
+ {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Release|x86.ActiveCfg =
Release|Any CPU
+ {742DF47D-77C5-4B84-9E0C-69645F1161EA}.Release|x86.Build.0 =
Release|Any CPU
{D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
{D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Debug|x64.Build.0 =
Debug|Any CPU
+ {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Debug|x86.Build.0 =
Debug|Any CPU
{D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Release|Any CPU.Build.0
= Release|Any CPU
+ {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Release|x64.ActiveCfg =
Release|Any CPU
+ {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Release|x64.Build.0 =
Release|Any CPU
+ {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Release|x86.ActiveCfg =
Release|Any CPU
+ {D6443535-3740-4F6C-8001-F90EDAF4CF0C}.Release|x86.Build.0 =
Release|Any CPU
{058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
{058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Debug|x64.Build.0 =
Debug|Any CPU
+ {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Debug|x86.Build.0 =
Debug|Any CPU
{058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Release|Any CPU.Build.0
= Release|Any CPU
+ {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Release|x64.ActiveCfg =
Release|Any CPU
+ {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Release|x64.Build.0 =
Release|Any CPU
+ {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Release|x86.ActiveCfg =
Release|Any CPU
+ {058F9CFA-2A13-43B8-87D9-E69F63F9EFF0}.Release|x86.Build.0 =
Release|Any CPU
{2490AA1E-DDA4-4069-B065-79A4897B0582}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
{2490AA1E-DDA4-4069-B065-79A4897B0582}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {2490AA1E-DDA4-4069-B065-79A4897B0582}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {2490AA1E-DDA4-4069-B065-79A4897B0582}.Debug|x64.Build.0 =
Debug|Any CPU
+ {2490AA1E-DDA4-4069-B065-79A4897B0582}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {2490AA1E-DDA4-4069-B065-79A4897B0582}.Debug|x86.Build.0 =
Debug|Any CPU
{2490AA1E-DDA4-4069-B065-79A4897B0582}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{2490AA1E-DDA4-4069-B065-79A4897B0582}.Release|Any CPU.Build.0
= Release|Any CPU
+ {2490AA1E-DDA4-4069-B065-79A4897B0582}.Release|x64.ActiveCfg =
Release|Any CPU
+ {2490AA1E-DDA4-4069-B065-79A4897B0582}.Release|x64.Build.0 =
Release|Any CPU
+ {2490AA1E-DDA4-4069-B065-79A4897B0582}.Release|x86.ActiveCfg =
Release|Any CPU
+ {2490AA1E-DDA4-4069-B065-79A4897B0582}.Release|x86.Build.0 =
Release|Any CPU
{E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
{E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Debug|x64.Build.0 =
Debug|Any CPU
+ {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Debug|x86.Build.0 =
Debug|Any CPU
{E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Release|Any CPU.Build.0
= Release|Any CPU
+ {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Release|x64.ActiveCfg =
Release|Any CPU
+ {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Release|x64.Build.0 =
Release|Any CPU
+ {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Release|x86.ActiveCfg =
Release|Any CPU
+ {E4F74938-E8FF-4AC1-A495-FEE95FC1EFDF}.Release|x86.Build.0 =
Release|Any CPU
{B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
{B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Debug|x64.Build.0 =
Debug|Any CPU
+ {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Debug|x86.Build.0 =
Debug|Any CPU
{B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Release|Any CPU.Build.0
= Release|Any CPU
+ {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Release|x64.ActiveCfg =
Release|Any CPU
+ {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Release|x64.Build.0 =
Release|Any CPU
+ {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Release|x86.ActiveCfg =
Release|Any CPU
+ {B62E77D2-D0B0-4C0C-BA78-1C117DE4C299}.Release|x86.Build.0 =
Release|Any CPU
{5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
{5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Debug|x64.Build.0 =
Debug|Any CPU
+ {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Debug|x86.Build.0 =
Debug|Any CPU
{5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Release|Any CPU.Build.0
= Release|Any CPU
+ {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Release|x64.ActiveCfg =
Release|Any CPU
+ {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Release|x64.Build.0 =
Release|Any CPU
+ {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Release|x86.ActiveCfg =
Release|Any CPU
+ {5D7FF380-B7DF-4752-B415-7C08C70C9F06}.Release|x86.Build.0 =
Release|Any CPU
{DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
{DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Debug|x64.Build.0 =
Debug|Any CPU
+ {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Debug|x86.Build.0 =
Debug|Any CPU
{DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Release|Any CPU.Build.0
= Release|Any CPU
+ {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Release|x64.ActiveCfg =
Release|Any CPU
+ {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Release|x64.Build.0 =
Release|Any CPU
+ {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Release|x86.ActiveCfg =
Release|Any CPU
+ {DCC99EB1-4E60-4F0D-AEA9-C44A4C0C8B1D}.Release|x86.Build.0 =
Release|Any CPU
{2ADE087A-B424-4895-8CC5-10170D10BA62}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
{2ADE087A-B424-4895-8CC5-10170D10BA62}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {2ADE087A-B424-4895-8CC5-10170D10BA62}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {2ADE087A-B424-4895-8CC5-10170D10BA62}.Debug|x64.Build.0 =
Debug|Any CPU
+ {2ADE087A-B424-4895-8CC5-10170D10BA62}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {2ADE087A-B424-4895-8CC5-10170D10BA62}.Debug|x86.Build.0 =
Debug|Any CPU
{2ADE087A-B424-4895-8CC5-10170D10BA62}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{2ADE087A-B424-4895-8CC5-10170D10BA62}.Release|Any CPU.Build.0
= Release|Any CPU
+ {2ADE087A-B424-4895-8CC5-10170D10BA62}.Release|x64.ActiveCfg =
Release|Any CPU
+ {2ADE087A-B424-4895-8CC5-10170D10BA62}.Release|x64.Build.0 =
Release|Any CPU
+ {2ADE087A-B424-4895-8CC5-10170D10BA62}.Release|x86.ActiveCfg =
Release|Any CPU
+ {2ADE087A-B424-4895-8CC5-10170D10BA62}.Release|x86.Build.0 =
Release|Any CPU
{7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
{7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Debug|x64.Build.0 =
Debug|Any CPU
+ {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Debug|x86.Build.0 =
Debug|Any CPU
{7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Release|Any CPU.Build.0
= Release|Any CPU
+ {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Release|x64.ActiveCfg =
Release|Any CPU
+ {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Release|x64.Build.0 =
Release|Any CPU
+ {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Release|x86.ActiveCfg =
Release|Any CPU
+ {7E66CBB4-D921-41E7-A98A-7C6DEA521696}.Release|x86.Build.0 =
Release|Any CPU
{E8264B7F-B680-4A55-939B-85DB628164BB}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
{E8264B7F-B680-4A55-939B-85DB628164BB}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {E8264B7F-B680-4A55-939B-85DB628164BB}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {E8264B7F-B680-4A55-939B-85DB628164BB}.Debug|x64.Build.0 =
Debug|Any CPU
+ {E8264B7F-B680-4A55-939B-85DB628164BB}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {E8264B7F-B680-4A55-939B-85DB628164BB}.Debug|x86.Build.0 =
Debug|Any CPU
{E8264B7F-B680-4A55-939B-85DB628164BB}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{E8264B7F-B680-4A55-939B-85DB628164BB}.Release|Any CPU.Build.0
= Release|Any CPU
+ {E8264B7F-B680-4A55-939B-85DB628164BB}.Release|x64.ActiveCfg =
Release|Any CPU
+ {E8264B7F-B680-4A55-939B-85DB628164BB}.Release|x64.Build.0 =
Release|Any CPU
+ {E8264B7F-B680-4A55-939B-85DB628164BB}.Release|x86.ActiveCfg =
Release|Any CPU
+ {E8264B7F-B680-4A55-939B-85DB628164BB}.Release|x86.Build.0 =
Release|Any CPU
{EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
{EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Debug|x64.Build.0 =
Debug|Any CPU
+ {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Debug|x86.Build.0 =
Debug|Any CPU
{EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Release|Any CPU.Build.0
= Release|Any CPU
+ {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Release|x64.ActiveCfg =
Release|Any CPU
+ {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Release|x64.Build.0 =
Release|Any CPU
+ {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Release|x86.ActiveCfg =
Release|Any CPU
+ {EE364C9A-CB88-4DCE-9209-6ACBB8E6934F}.Release|x86.Build.0 =
Release|Any CPU
{0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
{0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Debug|x64.Build.0 =
Debug|Any CPU
+ {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Debug|x86.Build.0 =
Debug|Any CPU
{0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Release|Any CPU.Build.0
= Release|Any CPU
+ {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Release|x64.ActiveCfg =
Release|Any CPU
+ {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Release|x64.Build.0 =
Release|Any CPU
+ {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Release|x86.ActiveCfg =
Release|Any CPU
+ {0C495233-010C-45F8-BAB2-D9CD0B7B9861}.Release|x86.Build.0 =
Release|Any CPU
{F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
{F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Debug|x64.Build.0 =
Debug|Any CPU
+ {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Debug|x86.Build.0 =
Debug|Any CPU
{F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Release|Any CPU.Build.0
= Release|Any CPU
+ {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Release|x64.ActiveCfg =
Release|Any CPU
+ {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Release|x64.Build.0 =
Release|Any CPU
+ {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Release|x86.ActiveCfg =
Release|Any CPU
+ {F3A8B7C6-D5E4-4321-9ABC-DEF012345678}.Release|x86.Build.0 =
Release|Any CPU
+ {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
+ {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Debug|x64.Build.0 =
Debug|Any CPU
+ {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Debug|x86.Build.0 =
Debug|Any CPU
+ {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Release|Any
CPU.ActiveCfg = Release|Any CPU
+ {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Release|Any CPU.Build.0
= Release|Any CPU
+ {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Release|x64.ActiveCfg =
Release|Any CPU
+ {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Release|x64.Build.0 =
Release|Any CPU
+ {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Release|x86.ActiveCfg =
Release|Any CPU
+ {B4799C3C-179F-470F-A8A4-8A75D172E3E1}.Release|x86.Build.0 =
Release|Any CPU
+ {73EBE132-AE05-4C32-9525-515F4768156B}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
+ {73EBE132-AE05-4C32-9525-515F4768156B}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {73EBE132-AE05-4C32-9525-515F4768156B}.Debug|x64.ActiveCfg =
Debug|Any CPU
+ {73EBE132-AE05-4C32-9525-515F4768156B}.Debug|x64.Build.0 =
Debug|Any CPU
+ {73EBE132-AE05-4C32-9525-515F4768156B}.Debug|x86.ActiveCfg =
Debug|Any CPU
+ {73EBE132-AE05-4C32-9525-515F4768156B}.Debug|x86.Build.0 =
Debug|Any CPU
+ {73EBE132-AE05-4C32-9525-515F4768156B}.Release|Any
CPU.ActiveCfg = Release|Any CPU
+ {73EBE132-AE05-4C32-9525-515F4768156B}.Release|Any CPU.Build.0
= Release|Any CPU
+ {73EBE132-AE05-4C32-9525-515F4768156B}.Release|x64.ActiveCfg =
Release|Any CPU
+ {73EBE132-AE05-4C32-9525-515F4768156B}.Release|x64.Build.0 =
Release|Any CPU
+ {73EBE132-AE05-4C32-9525-515F4768156B}.Release|x86.ActiveCfg =
Release|Any CPU
+ {73EBE132-AE05-4C32-9525-515F4768156B}.Release|x86.Build.0 =
Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/dev/release/verify_rc.sh b/dev/release/verify_rc.sh
index bbb5f4b..8fcad5e 100755
--- a/dev/release/verify_rc.sh
+++ b/dev/release/verify_rc.sh
@@ -184,6 +184,7 @@ test_binary_distribution() {
reference_package "Apache.Arrow.Compression" "Apache.Arrow.Compression.Tests"
reference_package "Apache.Arrow.Flight.Sql" "Apache.Arrow.Flight.Sql.Tests"
"Apache.Arrow.Flight.TestWeb"
reference_package "Apache.Arrow.Flight.AspNetCore"
"Apache.Arrow.Flight.TestWeb"
+ reference_package "Apache.Arrow.Operations" "Apache.Arrow.Operations.Tests"
"Apache.Arrow.Variant.Tests"
reference_package "Apache.Arrow.Variant" "Apache.Arrow.Variant.Tests"
# Move src directory to ensure we are only testing against built packages
diff --git a/src/Apache.Arrow.Variant/Apache.Arrow.Variant.csproj
b/src/Apache.Arrow.Operations/Apache.Arrow.Operations.csproj
similarity index 63%
copy from src/Apache.Arrow.Variant/Apache.Arrow.Variant.csproj
copy to src/Apache.Arrow.Operations/Apache.Arrow.Operations.csproj
index 181dff9..8cc1c4c 100644
--- a/src/Apache.Arrow.Variant/Apache.Arrow.Variant.csproj
+++ b/src/Apache.Arrow.Operations/Apache.Arrow.Operations.csproj
@@ -4,14 +4,12 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
- <Description>Apache Arrow Variant encoding support for .NET. Provides
readers, writers, and object model for the Parquet Variant binary format for
semi-structured data.</Description>
+ <Description>Format-specific codecs for the Apache Arrow Variant binary
format, including JSON reader/writer.</Description>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETStandard'">
<PackageReference Include="System.Buffers" />
- <PackageReference Include="System.Data.Common" />
<PackageReference Include="System.Memory" />
- <PackageReference Include="System.Runtime.CompilerServices.Unsafe" />
</ItemGroup>
<ItemGroup>
@@ -19,4 +17,8 @@
<PackageReference Include="Microsoft.SourceLink.GitHub"
PrivateAssets="All" />
</ItemGroup>
+ <ItemGroup>
+ <ProjectReference
Include="..\Apache.Arrow.Variant\Apache.Arrow.Variant.csproj" />
+ </ItemGroup>
+
</Project>
diff --git a/src/Apache.Arrow.Variant/Json/VariantJsonConverter.cs
b/src/Apache.Arrow.Operations/Json/VariantJsonConverter.cs
similarity index 99%
rename from src/Apache.Arrow.Variant/Json/VariantJsonConverter.cs
rename to src/Apache.Arrow.Operations/Json/VariantJsonConverter.cs
index 73494bd..b1806a5 100644
--- a/src/Apache.Arrow.Variant/Json/VariantJsonConverter.cs
+++ b/src/Apache.Arrow.Operations/Json/VariantJsonConverter.cs
@@ -15,11 +15,11 @@
using System;
using System.Collections.Generic;
-using System.Data.SqlTypes;
using System.Text.Json;
using System.Text.Json.Serialization;
+using Apache.Arrow.Variant;
-namespace Apache.Arrow.Variant.Json
+namespace Apache.Arrow.Operations.Json
{
/// <summary>
/// A <see cref="JsonConverter{T}"/> for <see cref="VariantValue"/>.
diff --git a/src/Apache.Arrow.Operations/Json/VariantJsonReader.cs
b/src/Apache.Arrow.Operations/Json/VariantJsonReader.cs
new file mode 100644
index 0000000..9585008
--- /dev/null
+++ b/src/Apache.Arrow.Operations/Json/VariantJsonReader.cs
@@ -0,0 +1,152 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
+using System.Text;
+using System.Text.Json;
+using Apache.Arrow.Variant;
+
+namespace Apache.Arrow.Operations.Json
+{
+ /// <summary>
+ /// Parses a JSON string or UTF-8 bytes directly into variant binary format
+ /// (metadata + value byte arrays) without creating intermediate <see
cref="VariantValue"/> objects.
+ /// </summary>
+ public static class VariantJsonReader
+ {
+ /// <summary>
+ /// Parses a JSON string into variant binary format.
+ /// </summary>
+ /// <param name="json">The JSON string to parse.</param>
+ /// <returns>A tuple of (metadata bytes, value bytes).</returns>
+ public static (byte[] Metadata, byte[] Value) Parse(string json)
+ {
+ byte[] utf8 = Encoding.UTF8.GetBytes(json);
+ return Parse(new ReadOnlySpan<byte>(utf8));
+ }
+
+ /// <summary>
+ /// Parses UTF-8 encoded JSON bytes into variant binary format.
+ /// </summary>
+ /// <param name="utf8Json">The UTF-8 encoded JSON bytes.</param>
+ /// <returns>A tuple of (metadata bytes, value bytes).</returns>
+ public static (byte[] Metadata, byte[] Value) Parse(ReadOnlySpan<byte>
utf8Json)
+ {
+ // Pass 1: collect all field names into the metadata dictionary.
+ VariantMetadataBuilder metadataBuilder = new
VariantMetadataBuilder();
+ Utf8JsonReader collector = new Utf8JsonReader(utf8Json);
+ collector.Read();
+ CollectFieldNames(ref collector, metadataBuilder);
+
+ byte[] metadata = metadataBuilder.Build(out int[] idRemap);
+
+ // Pass 2: stream values into a VariantValueWriter using the
sorted field IDs.
+ Utf8JsonReader emitter = new Utf8JsonReader(utf8Json);
+ emitter.Read();
+ VariantValueWriter writer = new
VariantValueWriter(metadataBuilder, idRemap);
+ WriteValue(ref emitter, writer);
+ return (metadata, writer.ToArray());
+ }
+
+ private static void CollectFieldNames(ref Utf8JsonReader reader,
VariantMetadataBuilder builder)
+ {
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.StartObject:
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.EndObject)
+ return;
+ builder.Add(reader.GetString());
+ reader.Read();
+ CollectFieldNames(ref reader, builder);
+ }
+ throw new JsonException("Unterminated JSON object.");
+
+ case JsonTokenType.StartArray:
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.EndArray)
+ return;
+ CollectFieldNames(ref reader, builder);
+ }
+ throw new JsonException("Unterminated JSON array.");
+
+ default:
+ return;
+ }
+ }
+
+ private static void WriteValue(ref Utf8JsonReader reader,
VariantValueWriter writer)
+ {
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.Null:
+ writer.WriteNull();
+ return;
+
+ case JsonTokenType.True:
+ writer.WriteBoolean(true);
+ return;
+
+ case JsonTokenType.False:
+ writer.WriteBoolean(false);
+ return;
+
+ case JsonTokenType.Number:
+ if (reader.TryGetInt64(out long longValue))
+ {
+ writer.WriteIntegerCompact(longValue);
+ }
+ else
+ {
+ writer.WriteDouble(reader.GetDouble());
+ }
+ return;
+
+ case JsonTokenType.String:
+ writer.WriteString(reader.GetString());
+ return;
+
+ case JsonTokenType.StartObject:
+ writer.BeginObject();
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.EndObject)
+ break;
+ writer.WriteFieldName(reader.GetString());
+ reader.Read();
+ WriteValue(ref reader, writer);
+ }
+ writer.EndObject();
+ return;
+
+ case JsonTokenType.StartArray:
+ writer.BeginArray();
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.EndArray)
+ break;
+ WriteValue(ref reader, writer);
+ }
+ writer.EndArray();
+ return;
+
+ default:
+ throw new JsonException($"Unexpected JSON token type
{reader.TokenType}.");
+ }
+ }
+ }
+}
diff --git a/src/Apache.Arrow.Variant/Json/VariantJsonWriter.cs
b/src/Apache.Arrow.Operations/Json/VariantJsonWriter.cs
similarity index 99%
rename from src/Apache.Arrow.Variant/Json/VariantJsonWriter.cs
rename to src/Apache.Arrow.Operations/Json/VariantJsonWriter.cs
index 9287ceb..2f9e216 100644
--- a/src/Apache.Arrow.Variant/Json/VariantJsonWriter.cs
+++ b/src/Apache.Arrow.Operations/Json/VariantJsonWriter.cs
@@ -19,11 +19,11 @@ using System.Buffers;
#else
using System.IO;
#endif
-using System.Data.SqlTypes;
using System.Text;
using System.Text.Json;
+using Apache.Arrow.Variant;
-namespace Apache.Arrow.Variant.Json
+namespace Apache.Arrow.Operations.Json
{
/// <summary>
/// Writes variant binary data directly to JSON without creating
intermediate
diff --git a/src/Apache.Arrow.Variant/Properties/AssemblyInfo.cs
b/src/Apache.Arrow.Operations/Properties/AssemblyInfo.cs
similarity index 67%
copy from src/Apache.Arrow.Variant/Properties/AssemblyInfo.cs
copy to src/Apache.Arrow.Operations/Properties/AssemblyInfo.cs
index c990b57..423bbba 100644
--- a/src/Apache.Arrow.Variant/Properties/AssemblyInfo.cs
+++ b/src/Apache.Arrow.Operations/Properties/AssemblyInfo.cs
@@ -15,4 +15,4 @@
using System.Runtime.CompilerServices;
-[assembly: InternalsVisibleTo("Apache.Arrow.Variant.Tests,
PublicKey=0024000004800000940000000602000000240000525341310004000001000100e504183f6d470d6b67b6d19212be3e1f598f70c246a120194bc38130101d0c1853e4a0f2232cb12e37a7a90e707aabd38511dac4f25fcb0d691b2aa265900bf42de7f70468fc997551a40e1e0679b605aa2088a4a69e07c117e988f5b1738c570ee66997fba02485e7856a49eca5fd0706d09899b8312577cbb9034599fc92d4")]
+[assembly: InternalsVisibleTo("Apache.Arrow.Operations.Tests,
PublicKey=0024000004800000940000000602000000240000525341310004000001000100e504183f6d470d6b67b6d19212be3e1f598f70c246a120194bc38130101d0c1853e4a0f2232cb12e37a7a90e707aabd38511dac4f25fcb0d691b2aa265900bf42de7f70468fc997551a40e1e0679b605aa2088a4a69e07c117e988f5b1738c570ee66997fba02485e7856a49eca5fd0706d09899b8312577cbb9034599fc92d4")]
diff --git a/src/Apache.Arrow.Variant/Apache.Arrow.Variant.csproj
b/src/Apache.Arrow.Variant/Apache.Arrow.Variant.csproj
index 181dff9..17d7938 100644
--- a/src/Apache.Arrow.Variant/Apache.Arrow.Variant.csproj
+++ b/src/Apache.Arrow.Variant/Apache.Arrow.Variant.csproj
@@ -15,7 +15,6 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="System.Text.Json" />
<PackageReference Include="Microsoft.SourceLink.GitHub"
PrivateAssets="All" />
</ItemGroup>
diff --git a/src/Apache.Arrow.Variant/Json/VariantJsonReader.cs
b/src/Apache.Arrow.Variant/Json/VariantJsonReader.cs
deleted file mode 100644
index f1a8700..0000000
--- a/src/Apache.Arrow.Variant/Json/VariantJsonReader.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements. See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You under the Apache License, Version 2.0
-// (the "License"); you may not use this file except in compliance with
-// the License. You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-using System;
-using System.Text;
-
-namespace Apache.Arrow.Variant.Json
-{
- /// <summary>
- /// Parses a JSON string or UTF-8 bytes directly into variant binary format
- /// (metadata + value byte arrays) without creating intermediate <see
cref="VariantValue"/> objects.
- /// </summary>
- public static class VariantJsonReader
- {
- /// <summary>
- /// Parses a JSON string into variant binary format.
- /// </summary>
- /// <param name="json">The JSON string to parse.</param>
- /// <returns>A tuple of (metadata bytes, value bytes).</returns>
- public static (byte[] Metadata, byte[] Value) Parse(string json)
- {
- byte[] utf8 = Encoding.UTF8.GetBytes(json);
- return Parse(new ReadOnlySpan<byte>(utf8));
- }
-
- /// <summary>
- /// Parses UTF-8 encoded JSON bytes into variant binary format.
- /// </summary>
- /// <param name="utf8Json">The UTF-8 encoded JSON bytes.</param>
- /// <returns>A tuple of (metadata bytes, value bytes).</returns>
- public static (byte[] Metadata, byte[] Value) Parse(ReadOnlySpan<byte>
utf8Json)
- {
- VariantBuilder builder = new VariantBuilder();
- return builder.EncodeFromJson(utf8Json);
- }
- }
-}
diff --git a/src/Apache.Arrow.Variant/Properties/AssemblyInfo.cs
b/src/Apache.Arrow.Variant/Properties/AssemblyInfo.cs
index c990b57..6386c23 100644
--- a/src/Apache.Arrow.Variant/Properties/AssemblyInfo.cs
+++ b/src/Apache.Arrow.Variant/Properties/AssemblyInfo.cs
@@ -16,3 +16,4 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Apache.Arrow.Variant.Tests,
PublicKey=0024000004800000940000000602000000240000525341310004000001000100e504183f6d470d6b67b6d19212be3e1f598f70c246a120194bc38130101d0c1853e4a0f2232cb12e37a7a90e707aabd38511dac4f25fcb0d691b2aa265900bf42de7f70468fc997551a40e1e0679b605aa2088a4a69e07c117e988f5b1738c570ee66997fba02485e7856a49eca5fd0706d09899b8312577cbb9034599fc92d4")]
+[assembly: InternalsVisibleTo("Apache.Arrow.Operations,
PublicKey=0024000004800000940000000602000000240000525341310004000001000100e504183f6d470d6b67b6d19212be3e1f598f70c246a120194bc38130101d0c1853e4a0f2232cb12e37a7a90e707aabd38511dac4f25fcb0d691b2aa265900bf42de7f70468fc997551a40e1e0679b605aa2088a4a69e07c117e988f5b1738c570ee66997fba02485e7856a49eca5fd0706d09899b8312577cbb9034599fc92d4")]
diff --git a/src/Apache.Arrow.Variant/VariantBuilder.cs
b/src/Apache.Arrow.Variant/VariantBuilder.cs
index 648b1d0..9e40120 100644
--- a/src/Apache.Arrow.Variant/VariantBuilder.cs
+++ b/src/Apache.Arrow.Variant/VariantBuilder.cs
@@ -14,12 +14,7 @@
// limitations under the License.
using System;
-using System.Buffers.Binary;
using System.Collections.Generic;
-using System.Data.SqlTypes;
-using System.IO;
-using System.Text;
-using System.Text.Json;
namespace Apache.Arrow.Variant
{
@@ -29,38 +24,19 @@ namespace Apache.Arrow.Variant
/// </summary>
public sealed class VariantBuilder
{
- /// <summary>
- /// Maximum number of offset entries (count + 1) to allocate on the
stack.
- /// 256 ints = 1024 bytes, well within safe stack limits.
- /// </summary>
- private const int StackAllocThreshold = 256;
-
-#if !NET8_0_OR_GREATER
- private byte[] _buffer;
-#endif
- private readonly Stack<MemoryStream> _streamPool = new
Stack<MemoryStream>();
-
/// <summary>
/// Encodes a <see cref="VariantValue"/> to the variant binary format.
/// </summary>
/// <returns>A tuple of (metadata bytes, value bytes).</returns>
public (byte[] Metadata, byte[] Value) Encode(VariantValue variant)
{
- EnsureBuffer();
-
- // Phase 1: collect all field names recursively.
VariantMetadataBuilder metadataBuilder = new
VariantMetadataBuilder();
CollectFieldNames(variant, metadataBuilder);
-
- // Phase 2: build sorted metadata + get the ID remap.
byte[] metadata = metadataBuilder.Build(out int[] idRemap);
- // Phase 3: encode the value.
- using (MemoryStream ms = new MemoryStream())
- {
- WriteValue(variant, metadataBuilder, idRemap, ms);
- return (metadata, ms.ToArray());
- }
+ VariantValueWriter writer = new
VariantValueWriter(metadataBuilder, idRemap);
+ WriteValue(writer, variant);
+ return (metadata, writer.ToArray());
}
private static void CollectFieldNames(VariantValue variant,
VariantMetadataBuilder builder)
@@ -82,724 +58,102 @@ namespace Apache.Arrow.Variant
}
}
- private void WriteValue(VariantValue variant, VariantMetadataBuilder
metadataBuilder, int[] idRemap, MemoryStream ms)
+ private static void WriteValue(VariantValueWriter writer, VariantValue
variant)
{
if (variant.IsNull)
{
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.NullType));
+ writer.WriteNull();
return;
}
if (variant.IsBoolean)
{
- VariantPrimitiveType pt = variant.AsBoolean()
- ? VariantPrimitiveType.BooleanTrue
- : VariantPrimitiveType.BooleanFalse;
- ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(pt));
+ writer.WriteBoolean(variant.AsBoolean());
return;
}
if (variant.IsObject)
{
- WriteObject(variant, metadataBuilder, idRemap, ms);
+ writer.BeginObject();
+ foreach (KeyValuePair<string, VariantValue> field in
variant.AsObject())
+ {
+ writer.WriteFieldName(field.Key);
+ WriteValue(writer, field.Value);
+ }
+ writer.EndObject();
return;
}
if (variant.IsArray)
{
- WriteArray(variant, metadataBuilder, idRemap, ms);
+ writer.BeginArray();
+ foreach (VariantValue element in variant.AsArray())
+ {
+ WriteValue(writer, element);
+ }
+ writer.EndArray();
return;
}
- // Primitive types
switch (variant.PrimitiveType)
{
case VariantPrimitiveType.Int8:
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int8));
- ms.WriteByte((byte)variant.AsInt8());
+ writer.WriteInt8(variant.AsInt8());
break;
-
case VariantPrimitiveType.Int16:
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int16));
- WriteInt16(ms, variant.AsInt16());
+ writer.WriteInt16(variant.AsInt16());
break;
-
case VariantPrimitiveType.Int32:
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int32));
- WriteInt32(ms, variant.AsInt32());
+ writer.WriteInt32(variant.AsInt32());
break;
-
case VariantPrimitiveType.Int64:
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int64));
- WriteInt64(ms, variant.AsInt64());
+ writer.WriteInt64(variant.AsInt64());
break;
-
case VariantPrimitiveType.Float:
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Float));
- WriteFloat(ms, variant.AsFloat());
+ writer.WriteFloat(variant.AsFloat());
break;
-
case VariantPrimitiveType.Double:
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Double));
- WriteDouble(ms, variant.AsDouble());
+ writer.WriteDouble(variant.AsDouble());
break;
-
case VariantPrimitiveType.Decimal4:
- WriteDecimal4(ms, variant.AsDecimal());
+ writer.WriteDecimal4(variant.AsDecimal());
break;
-
case VariantPrimitiveType.Decimal8:
- WriteDecimal8(ms, variant.AsDecimal());
+ writer.WriteDecimal8(variant.AsDecimal());
break;
-
case VariantPrimitiveType.Decimal16:
- SqlDecimal sd = variant.AsSqlDecimal();
- WriteSqlDecimal16(ms, sd);
+ writer.WriteDecimal16(variant.AsSqlDecimal());
break;
-
case VariantPrimitiveType.Date:
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Date));
- WriteInt32(ms, variant.AsDateDays());
+ writer.WriteDateDays(variant.AsDateDays());
break;
-
case VariantPrimitiveType.Timestamp:
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Timestamp));
- WriteInt64(ms, variant.AsTimestampMicros());
+ writer.WriteTimestampMicros(variant.AsTimestampMicros());
break;
-
case VariantPrimitiveType.TimestampNtz:
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.TimestampNtz));
- WriteInt64(ms, variant.AsTimestampNtzMicros());
+
writer.WriteTimestampNtzMicros(variant.AsTimestampNtzMicros());
break;
-
case VariantPrimitiveType.TimeNtz:
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.TimeNtz));
- WriteInt64(ms, variant.AsTimeNtzMicros());
+ writer.WriteTimeNtzMicros(variant.AsTimeNtzMicros());
break;
-
case VariantPrimitiveType.TimestampTzNanos:
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.TimestampTzNanos));
- WriteInt64(ms, variant.AsTimestampTzNanos());
+ writer.WriteTimestampTzNanos(variant.AsTimestampTzNanos());
break;
-
case VariantPrimitiveType.TimestampNtzNanos:
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.TimestampNtzNanos));
- WriteInt64(ms, variant.AsTimestampNtzNanos());
+
writer.WriteTimestampNtzNanos(variant.AsTimestampNtzNanos());
break;
-
case VariantPrimitiveType.String:
- WriteString(ms, variant.AsString());
+ writer.WriteString(variant.AsString());
break;
-
case VariantPrimitiveType.Binary:
- WriteBinary(ms, variant.AsBinary());
+ writer.WriteBinary(variant.AsBinary());
break;
-
case VariantPrimitiveType.Uuid:
- WriteUuid(ms, variant.AsUuid());
+ writer.WriteUuid(variant.AsUuid());
break;
-
default:
throw new NotSupportedException($"Unsupported primitive
type: {variant.PrimitiveType}");
}
}
-
- private void WriteString(MemoryStream ms, string value)
- {
- int byteCount = Encoding.UTF8.GetByteCount(value);
- if (byteCount <= 63)
- {
- // Short string
-
ms.WriteByte(VariantEncodingHelper.MakeShortStringHeader(byteCount));
- }
- else
- {
- // Long string (primitive type 16)
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.String));
- WriteInt32(ms, byteCount);
- }
-
- // Encode UTF-8 directly into the MemoryStream's buffer to avoid a
temporary byte[] allocation.
- int dataPos = (int)ms.Position;
- int needed = dataPos + byteCount;
- if (needed > ms.Length)
- ms.SetLength(needed);
- Encoding.UTF8.GetBytes(value, 0, value.Length, ms.GetBuffer(),
dataPos);
- ms.Position = needed;
- }
-
- private void WriteBinary(MemoryStream ms, byte[] data)
- {
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Binary));
- WriteInt32(ms, data.Length);
- ms.Write(data, 0, data.Length);
- }
-
- private void WriteUuid(MemoryStream ms, Guid guid)
- {
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Uuid));
-#if NET8_0_OR_GREATER
- Span<byte> buf = stackalloc byte[16];
- guid.TryWriteBytes(buf, bigEndian: true, out int _);
- ms.Write(buf);
-#else
- byte[] native = guid.ToByteArray();
- // Convert from .NET mixed-endian to big-endian (RFC 4122)
- _buffer[0] = native[3]; _buffer[1] = native[2]; _buffer[2] =
native[1]; _buffer[3] = native[0];
- _buffer[4] = native[5]; _buffer[5] = native[4];
- _buffer[6] = native[7]; _buffer[7] = native[6];
- Buffer.BlockCopy(native, 8, _buffer, 8, 8);
- ms.Write(_buffer, 0, 16);
-#endif
- }
-
- private void WriteDecimal4(MemoryStream ms, decimal value)
- {
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Decimal4));
-#if NET8_0_OR_GREATER
- Span<int> bits = stackalloc int[4];
- decimal.GetBits(value, bits);
-#else
- int[] bits = decimal.GetBits(value);
-#endif
- byte scale = (byte)((bits[3] >> 16) & 0x7F);
- bool negative = (bits[3] & unchecked((int)0x80000000)) != 0;
- int unscaled = bits[0];
- if (negative) unscaled = -unscaled;
- ms.WriteByte(scale);
- WriteInt32(ms, unscaled);
- }
-
- private void WriteDecimal8(MemoryStream ms, decimal value)
- {
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Decimal8));
-#if NET8_0_OR_GREATER
- Span<int> bits = stackalloc int[4];
- decimal.GetBits(value, bits);
-#else
- int[] bits = decimal.GetBits(value);
-#endif
- byte scale = (byte)((bits[3] >> 16) & 0x7F);
- bool negative = (bits[3] & unchecked((int)0x80000000)) != 0;
- long unscaled = ((long)bits[1] << 32) | (uint)bits[0];
- if (negative) unscaled = -unscaled;
- ms.WriteByte(scale);
- WriteInt64(ms, unscaled);
- }
-
- private void WriteSqlDecimal16(MemoryStream ms, SqlDecimal value)
- {
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Decimal16));
-
- bool positive = value.IsPositive;
- byte scale = (byte)value.Scale;
- int[] data = value.Data;
-
- // SqlDecimal.Data: [0]=least-significant, [3]=most-significant
- // Convert to lo/hi longs (little-endian)
- long lo = ((long)(uint)data[1] << 32) | (uint)data[0];
- long hi = ((long)(uint)data[3] << 32) | (uint)data[2];
-
- if (!positive)
- {
- // Two's complement negate 128-bit
- lo = ~lo;
- hi = ~hi;
- ulong uLo = (ulong)lo + 1;
- if (uLo == 0) hi++;
- lo = (long)uLo;
- }
-
- ms.WriteByte(scale);
- WriteInt64(ms, lo);
- WriteInt64(ms, hi);
- }
-
- private void WriteObject(VariantValue variant, VariantMetadataBuilder
metadataBuilder, int[] idRemap, MemoryStream ms)
- {
- IReadOnlyDictionary<string, VariantValue> fields =
variant.AsObject();
- int fieldCount = fields.Count;
-
- // Build sorted array of (sortedFieldId, value) pairs.
- KeyValuePair<int, VariantValue>[] sortedFields = new
KeyValuePair<int, VariantValue>[fieldCount];
- int fieldIndex = 0;
- foreach (KeyValuePair<string, VariantValue> field in fields)
- {
- int originalId = metadataBuilder.GetId(field.Key);
- int sortedId = idRemap[originalId];
- sortedFields[fieldIndex++] = new KeyValuePair<int,
VariantValue>(sortedId, field.Value);
- }
- Array.Sort(sortedFields, static (a, b) => a.Key.CompareTo(b.Key));
-
- // Encode all field values into a single stream, recording offsets.
- MemoryStream valuesMs = RentStream();
- int offsetCount = fieldCount + 1;
- Span<int> offsets = offsetCount <= StackAllocThreshold
- ? stackalloc int[offsetCount]
- : new int[offsetCount];
- offsets[0] = 0;
- for (int i = 0; i < fieldCount; i++)
- {
- WriteValue(sortedFields[i].Value, metadataBuilder, idRemap,
valuesMs);
- offsets[i + 1] = (int)valuesMs.Position;
- }
-
- // Determine sizes. sortedFields is sorted by Key, so max is the
last element.
- int maxFieldId = fieldCount > 0 ? sortedFields[fieldCount - 1].Key
: 0;
-
- int fieldIdSize = fieldCount > 0 ?
VariantEncodingHelper.ByteWidthForValue(maxFieldId) : 1;
- int offsetSize =
VariantEncodingHelper.ByteWidthForValue(Math.Max(1, offsets[fieldCount]));
- bool isLarge = fieldCount > 255;
-
- // Write header.
- ms.WriteByte(VariantEncodingHelper.MakeObjectHeader(fieldIdSize,
offsetSize, isLarge));
-
- // Write field count.
- if (isLarge)
- {
- WriteInt32(ms, fieldCount);
- }
- else
- {
- ms.WriteByte((byte)fieldCount);
- }
-
- // Write field IDs.
- for (int i = 0; i < fieldCount; i++)
- {
- WriteSmallInt(ms, sortedFields[i].Key, fieldIdSize);
- }
-
- // Write offsets.
- for (int i = 0; i <= fieldCount; i++)
- {
- WriteSmallInt(ms, offsets[i], offsetSize);
- }
-
- // Write field values from the shared buffer.
- ms.Write(valuesMs.GetBuffer(), 0, (int)valuesMs.Position);
- ReturnStream(valuesMs);
- }
-
- private void WriteArray(VariantValue variant, VariantMetadataBuilder
metadataBuilder, int[] idRemap, MemoryStream ms)
- {
- IReadOnlyList<VariantValue> elements = variant.AsArray();
- int elementCount = elements.Count;
-
- // Encode all elements into a single stream, recording offsets.
- MemoryStream valuesMs = RentStream();
- int offsetCount = elementCount + 1;
- Span<int> offsets = offsetCount <= StackAllocThreshold
- ? stackalloc int[offsetCount]
- : new int[offsetCount];
- offsets[0] = 0;
- for (int i = 0; i < elementCount; i++)
- {
- WriteValue(elements[i], metadataBuilder, idRemap, valuesMs);
- offsets[i + 1] = (int)valuesMs.Position;
- }
-
- int offsetSize =
VariantEncodingHelper.ByteWidthForValue(Math.Max(1, offsets[elementCount]));
- bool isLarge = elementCount > 255;
-
- // Write header.
- ms.WriteByte(VariantEncodingHelper.MakeArrayHeader(offsetSize,
isLarge));
-
- // Write element count.
- if (isLarge)
- {
- WriteInt32(ms, elementCount);
- }
- else
- {
- ms.WriteByte((byte)elementCount);
- }
-
- // Write offsets.
- for (int i = 0; i <= elementCount; i++)
- {
- WriteSmallInt(ms, offsets[i], offsetSize);
- }
-
- // Write element values from the shared buffer.
- ms.Write(valuesMs.GetBuffer(), 0, (int)valuesMs.Position);
- ReturnStream(valuesMs);
- }
-
- // ---------------------------------------------------------------
- // Primitive write helpers
- // ---------------------------------------------------------------
-
- private void EnsureBuffer()
- {
-#if !NET8_0_OR_GREATER
- if (_buffer == null) _buffer = new byte[16];
-#endif
- }
-
- private MemoryStream RentStream()
- {
- if (_streamPool.Count > 0)
- {
- MemoryStream ms = _streamPool.Pop();
- ms.SetLength(0);
- return ms;
- }
- return new MemoryStream();
- }
-
- private void ReturnStream(MemoryStream ms)
- {
- _streamPool.Push(ms);
- }
-
- private void WriteSmallInt(MemoryStream ms, int value, int byteWidth)
- {
-#if NET8_0_OR_GREATER
- Span<byte> buf = stackalloc byte[4];
- VariantEncodingHelper.WriteLittleEndianInt(buf, value, byteWidth);
- ms.Write(buf.Slice(0, byteWidth));
-#else
- VariantEncodingHelper.WriteLittleEndianInt(_buffer, value,
byteWidth);
- ms.Write(_buffer, 0, byteWidth);
-#endif
- }
-
- private void WriteInt16(MemoryStream ms, short value)
- {
-#if NET8_0_OR_GREATER
- Span<byte> buf = stackalloc byte[2];
- BinaryPrimitives.WriteInt16LittleEndian(buf, value);
- ms.Write(buf);
-#else
- BinaryPrimitives.WriteInt16LittleEndian(_buffer, value);
- ms.Write(_buffer, 0, 2);
-#endif
- }
-
- private void WriteInt32(MemoryStream ms, int value)
- {
-#if NET8_0_OR_GREATER
- Span<byte> buf = stackalloc byte[4];
- BinaryPrimitives.WriteInt32LittleEndian(buf, value);
- ms.Write(buf);
-#else
- BinaryPrimitives.WriteInt32LittleEndian(_buffer, value);
- ms.Write(_buffer, 0, 4);
-#endif
- }
-
- private void WriteInt64(MemoryStream ms, long value)
- {
-#if NET8_0_OR_GREATER
- Span<byte> buf = stackalloc byte[8];
- BinaryPrimitives.WriteInt64LittleEndian(buf, value);
- ms.Write(buf);
-#else
- BinaryPrimitives.WriteInt64LittleEndian(_buffer, value);
- ms.Write(_buffer, 0, 8);
-#endif
- }
-
- private void WriteFloat(MemoryStream ms, float value)
- {
-#if NET8_0_OR_GREATER
- Span<byte> buf = stackalloc byte[4];
- BinaryPrimitives.WriteSingleLittleEndian(buf, value);
- ms.Write(buf);
-#else
- int bits = System.Runtime.CompilerServices.Unsafe.As<float,
int>(ref value);
- BinaryPrimitives.WriteInt32LittleEndian(_buffer, bits);
- ms.Write(_buffer, 0, 4);
-#endif
- }
-
- private void WriteDouble(MemoryStream ms, double value)
- {
-#if NET8_0_OR_GREATER
- Span<byte> buf = stackalloc byte[8];
- BinaryPrimitives.WriteDoubleLittleEndian(buf, value);
- ms.Write(buf);
-#else
- long bits = BitConverter.DoubleToInt64Bits(value);
- BinaryPrimitives.WriteInt64LittleEndian(_buffer, bits);
- ms.Write(_buffer, 0, 8);
-#endif
- }
-
- // ---------------------------------------------------------------
- // Streaming JSON encoding
- // ---------------------------------------------------------------
-
- /// <summary>
- /// Encodes UTF-8 JSON bytes directly to the variant binary format
- /// without creating intermediate <see cref="VariantValue"/> objects.
- /// </summary>
- /// <param name="utf8Json">The UTF-8 encoded JSON bytes.</param>
- /// <returns>A tuple of (metadata bytes, value bytes).</returns>
- public (byte[] Metadata, byte[] Value)
EncodeFromJson(ReadOnlySpan<byte> utf8Json)
- {
- EnsureBuffer();
-
- // Pass 1: collect all field names.
- VariantMetadataBuilder metadataBuilder = new
VariantMetadataBuilder();
- Utf8JsonReader reader1 = new Utf8JsonReader(utf8Json);
- reader1.Read();
- CollectFieldNamesFromJson(ref reader1, metadataBuilder);
-
- // Build sorted metadata + get the ID remap.
- byte[] metadata = metadataBuilder.Build(out int[] idRemap);
-
- // Pass 2: encode the value.
- Utf8JsonReader reader2 = new Utf8JsonReader(utf8Json);
- reader2.Read();
- using (MemoryStream ms = new MemoryStream())
- {
- WriteJsonValue(ref reader2, metadataBuilder, idRemap, ms);
- return (metadata, ms.ToArray());
- }
- }
-
- private static void CollectFieldNamesFromJson(ref Utf8JsonReader
reader, VariantMetadataBuilder builder)
- {
- switch (reader.TokenType)
- {
- case JsonTokenType.StartObject:
- while (reader.Read())
- {
- if (reader.TokenType == JsonTokenType.EndObject)
- return;
- // PropertyName token
- builder.Add(reader.GetString());
- reader.Read();
- CollectFieldNamesFromJson(ref reader, builder);
- }
- throw new JsonException("Unterminated JSON object.");
-
- case JsonTokenType.StartArray:
- while (reader.Read())
- {
- if (reader.TokenType == JsonTokenType.EndArray)
- return;
- CollectFieldNamesFromJson(ref reader, builder);
- }
- throw new JsonException("Unterminated JSON array.");
-
- default:
- // Primitive value (null, bool, number, string) — no field
names.
- return;
- }
- }
-
- private void WriteJsonValue(ref Utf8JsonReader reader,
VariantMetadataBuilder metadataBuilder, int[] idRemap, MemoryStream ms)
- {
- switch (reader.TokenType)
- {
- case JsonTokenType.Null:
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.NullType));
- return;
-
- case JsonTokenType.True:
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.BooleanTrue));
- return;
-
- case JsonTokenType.False:
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.BooleanFalse));
- return;
-
- case JsonTokenType.Number:
- WriteJsonNumber(ref reader, ms);
- return;
-
- case JsonTokenType.String:
- WriteString(ms, reader.GetString());
- return;
-
- case JsonTokenType.StartObject:
- WriteJsonObject(ref reader, metadataBuilder, idRemap, ms);
- return;
-
- case JsonTokenType.StartArray:
- WriteJsonArray(ref reader, metadataBuilder, idRemap, ms);
- return;
-
- default:
- throw new JsonException($"Unexpected JSON token type
{reader.TokenType}.");
- }
- }
-
- private void WriteJsonNumber(ref Utf8JsonReader reader, MemoryStream
ms)
- {
- if (reader.TryGetInt64(out long longValue))
- {
- if (longValue >= sbyte.MinValue && longValue <= sbyte.MaxValue)
- {
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int8));
- ms.WriteByte((byte)(sbyte)longValue);
- }
- else if (longValue >= short.MinValue && longValue <=
short.MaxValue)
- {
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int16));
- WriteInt16(ms, (short)longValue);
- }
- else if (longValue >= int.MinValue && longValue <=
int.MaxValue)
- {
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int32));
- WriteInt32(ms, (int)longValue);
- }
- else
- {
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int64));
- WriteInt64(ms, longValue);
- }
- }
- else
- {
-
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Double));
- WriteDouble(ms, reader.GetDouble());
- }
- }
-
- private void WriteJsonObject(ref Utf8JsonReader reader,
VariantMetadataBuilder metadataBuilder, int[] idRemap, MemoryStream ms)
- {
- // Encode all field values into a shared stream, tracking
positions.
- MemoryStream valuesMs = RentStream();
- List<int> fieldIds = new List<int>(16);
- List<int> valueStarts = new List<int>(16);
-
- while (reader.Read())
- {
- if (reader.TokenType == JsonTokenType.EndObject)
- break;
-
- string name = reader.GetString();
- fieldIds.Add(idRemap[metadataBuilder.GetId(name)]);
-
- reader.Read();
- valueStarts.Add((int)valuesMs.Position);
- WriteJsonValue(ref reader, metadataBuilder, idRemap, valuesMs);
- }
-
- int fieldCount = fieldIds.Count;
-
- // Sentinel marks end of last value.
- valueStarts.Add((int)valuesMs.Position);
-
- // Build sort indices so we can write fields in sorted ID order.
-#if NET8_0_OR_GREATER
- Span<int> sortOrder = fieldCount <= StackAllocThreshold
- ? stackalloc int[fieldCount]
- : new int[fieldCount];
- for (int i = 0; i < fieldCount; i++)
- {
- sortOrder[i] = i;
- }
- sortOrder.Sort((a, b) => fieldIds[a].CompareTo(fieldIds[b]));
-#else
- int[] sortOrder = new int[fieldCount];
- for (int i = 0; i < fieldCount; i++)
- {
- sortOrder[i] = i;
- }
- Array.Sort(sortOrder, (a, b) =>
fieldIds[a].CompareTo(fieldIds[b]));
-#endif
-
- // Build offsets in sorted order.
- int offsetCount = fieldCount + 1;
- Span<int> offsets = offsetCount <= StackAllocThreshold
- ? stackalloc int[offsetCount]
- : new int[offsetCount];
- offsets[0] = 0;
- for (int i = 0; i < fieldCount; i++)
- {
- int idx = sortOrder[i];
- offsets[i + 1] = offsets[i] + (valueStarts[idx + 1] -
valueStarts[idx]);
- }
-
- // Determine sizes.
- int maxFieldId = fieldCount > 0 ? fieldIds[sortOrder[fieldCount -
1]] : 0;
- int fieldIdSize = fieldCount > 0 ?
VariantEncodingHelper.ByteWidthForValue(maxFieldId) : 1;
- int offsetSize =
VariantEncodingHelper.ByteWidthForValue(Math.Max(1, offsets[fieldCount]));
- bool isLarge = fieldCount > 255;
-
- // Write header.
- ms.WriteByte(VariantEncodingHelper.MakeObjectHeader(fieldIdSize,
offsetSize, isLarge));
-
- // Write field count.
- if (isLarge)
- {
- WriteInt32(ms, fieldCount);
- }
- else
- {
- ms.WriteByte((byte)fieldCount);
- }
-
- // Write field IDs in sorted order.
- for (int i = 0; i < fieldCount; i++)
- {
- WriteSmallInt(ms, fieldIds[sortOrder[i]], fieldIdSize);
- }
-
- // Write offsets.
- for (int i = 0; i <= fieldCount; i++)
- {
- WriteSmallInt(ms, offsets[i], offsetSize);
- }
-
- // Write field values in sorted order.
- byte[] valueBuffer = valuesMs.GetBuffer();
- for (int i = 0; i < fieldCount; i++)
- {
- int idx = sortOrder[i];
- int start = valueStarts[idx];
- int length = valueStarts[idx + 1] - start;
- ms.Write(valueBuffer, start, length);
- }
- ReturnStream(valuesMs);
- }
-
- private void WriteJsonArray(ref Utf8JsonReader reader,
VariantMetadataBuilder metadataBuilder, int[] idRemap, MemoryStream ms)
- {
- MemoryStream valuesMs = RentStream();
- List<int> offsets = new List<int>(16);
- offsets.Add(0);
-
- while (reader.Read())
- {
- if (reader.TokenType == JsonTokenType.EndArray)
- break;
-
- WriteJsonValue(ref reader, metadataBuilder, idRemap, valuesMs);
- offsets.Add((int)valuesMs.Position);
- }
-
- int elementCount = offsets.Count - 1;
- int offsetSize =
VariantEncodingHelper.ByteWidthForValue(Math.Max(1, offsets[elementCount]));
- bool isLarge = elementCount > 255;
-
- // Write header.
- ms.WriteByte(VariantEncodingHelper.MakeArrayHeader(offsetSize,
isLarge));
-
- // Write element count.
- if (isLarge)
- {
- WriteInt32(ms, elementCount);
- }
- else
- {
- ms.WriteByte((byte)elementCount);
- }
-
- // Write offsets.
- for (int i = 0; i <= elementCount; i++)
- {
- WriteSmallInt(ms, offsets[i], offsetSize);
- }
-
- // Write element values.
- ms.Write(valuesMs.GetBuffer(), 0, (int)valuesMs.Position);
- ReturnStream(valuesMs);
- }
}
}
diff --git a/src/Apache.Arrow.Variant/VariantValueWriter.cs
b/src/Apache.Arrow.Variant/VariantValueWriter.cs
new file mode 100644
index 0000000..f5b9498
--- /dev/null
+++ b/src/Apache.Arrow.Variant/VariantValueWriter.cs
@@ -0,0 +1,679 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
+using System.Buffers;
+using System.Buffers.Binary;
+using System.Collections.Generic;
+using System.Data.SqlTypes;
+using System.IO;
+using System.Text;
+
+namespace Apache.Arrow.Variant
+{
+ /// <summary>
+ /// Streams variant value bytes directly from primitive calls, without
+ /// requiring an intermediate <see cref="VariantValue"/>. Use this to
+ /// implement encoders from arbitrary input formats (JSON, CBOR, etc.).
+ /// </summary>
+ /// <remarks>
+ /// Usage pattern:
+ /// <list type="number">
+ /// <item>Create a <see cref="VariantMetadataBuilder"/> and <see
cref="VariantMetadataBuilder.Add(string)"/> every field name that will
appear.</item>
+ /// <item>Call <see cref="VariantMetadataBuilder.Build(out int[])"/> to
produce the metadata bytes and the ID remap.</item>
+ /// <item>Create a <see cref="VariantValueWriter"/> with the metadata
builder and remap, emit the value via the <c>Write*</c> / <c>Begin*</c> /
<c>End*</c> methods, then call <see cref="ToArray"/>.</item>
+ /// </list>
+ /// </remarks>
+ public sealed class VariantValueWriter
+ {
+ private const int StackAllocThreshold = 256;
+
+ private readonly VariantMetadataBuilder _metadata;
+ private readonly int[] _idRemap;
+ private readonly MemoryStream _root = new MemoryStream();
+ private readonly Stack<Frame> _frameStack = new Stack<Frame>();
+ private readonly Stack<MemoryStream> _streamPool = new
Stack<MemoryStream>();
+ private Frame _frame;
+
+#if !NET8_0_OR_GREATER
+ private readonly byte[] _scratch = new byte[16];
+#endif
+
+ /// <summary>
+ /// Creates a writer that produces value bytes referencing the given
metadata.
+ /// </summary>
+ /// <param name="metadata">The metadata builder used to resolve field
names to IDs.</param>
+ /// <param name="idRemap">The remap returned by <see
cref="VariantMetadataBuilder.Build(out int[])"/>.</param>
+ public VariantValueWriter(VariantMetadataBuilder metadata, int[]
idRemap)
+ {
+ _metadata = metadata ?? throw new
ArgumentNullException(nameof(metadata));
+ _idRemap = idRemap ?? throw new
ArgumentNullException(nameof(idRemap));
+
+ if (_metadata.Count != _idRemap.Length)
+ {
+ throw new ArgumentException(
+ "the idRemap array length must match the metadata builder
count used to create it.",
+ nameof(idRemap));
+ }
+ }
+
+ /// <summary>
+ /// Returns the encoded value bytes. All opened objects and arrays
must be closed.
+ /// </summary>
+ public byte[] ToArray()
+ {
+ if (_frame != null)
+ {
+ throw new InvalidOperationException("Unclosed object or array
at the top of the writer.");
+ }
+ return _root.ToArray();
+ }
+
+ // ---------------------------------------------------------------
+ // Object / array scope
+ // ---------------------------------------------------------------
+
+ /// <summary>Begins writing an object. Pair with <see
cref="EndObject"/>.</summary>
+ public void BeginObject()
+ {
+ BeforeWriteValue();
+ _frameStack.Push(_frame);
+ ObjectFrame frame = new ObjectFrame { Buffer = RentStream() };
+ frame.FieldIds = ArrayPool<int>.Shared.Rent(InitialFrameCapacity);
+ frame.ValueStarts =
ArrayPool<int>.Shared.Rent(InitialFrameCapacity);
+ _frame = frame;
+ }
+
+ /// <summary>
+ /// Writes the name of the next field in the current object. Must be
called
+ /// before every field value (a primitive, nested object, or nested
array).
+ /// The name must already exist in the <see
cref="VariantMetadataBuilder"/>.
+ /// </summary>
+ public void WriteFieldName(string name)
+ {
+ if (!(_frame is ObjectFrame objFrame))
+ {
+ throw new InvalidOperationException("WriteFieldName may only
be called inside an object scope.");
+ }
+ if (objFrame.PendingValue)
+ {
+ throw new InvalidOperationException("A value must be written
for the previous field before writing the next field name.");
+ }
+ int fieldId = _idRemap[_metadata.GetId(name)];
+ AppendInt(ref objFrame.FieldIds, ref objFrame.FieldIdCount,
fieldId);
+ objFrame.PendingValue = true;
+ }
+
+ /// <summary>Ends the current object scope.</summary>
+ public void EndObject()
+ {
+ if (!(_frame is ObjectFrame objFrame))
+ {
+ throw new InvalidOperationException("EndObject called without
matching BeginObject.");
+ }
+ if (objFrame.PendingValue)
+ {
+ throw new InvalidOperationException("Missing value for the
last field name before EndObject.");
+ }
+
+ _frame = _frameStack.Pop();
+ MemoryStream output = _frame != null ? _frame.Buffer : _root;
+ WriteObjectBody(output, objFrame);
+ ArrayPool<int>.Shared.Return(objFrame.FieldIds);
+ ArrayPool<int>.Shared.Return(objFrame.ValueStarts);
+ ReturnStream(objFrame.Buffer);
+ }
+
+ /// <summary>Begins writing an array. Pair with <see
cref="EndArray"/>.</summary>
+ public void BeginArray()
+ {
+ BeforeWriteValue();
+ _frameStack.Push(_frame);
+ ArrayFrame frame = new ArrayFrame { Buffer = RentStream() };
+ frame.ValueStarts =
ArrayPool<int>.Shared.Rent(InitialFrameCapacity);
+ _frame = frame;
+ }
+
+ /// <summary>Ends the current array scope.</summary>
+ public void EndArray()
+ {
+ if (!(_frame is ArrayFrame arrFrame))
+ {
+ throw new InvalidOperationException("EndArray called without
matching BeginArray.");
+ }
+
+ _frame = _frameStack.Pop();
+ MemoryStream output = _frame != null ? _frame.Buffer : _root;
+ WriteArrayBody(output, arrFrame);
+ ArrayPool<int>.Shared.Return(arrFrame.ValueStarts);
+ ReturnStream(arrFrame.Buffer);
+ }
+
+ // ---------------------------------------------------------------
+ // Primitive writes
+ // ---------------------------------------------------------------
+
+ /// <summary>Writes a null value.</summary>
+ public void WriteNull()
+ {
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.NullType));
+ }
+
+ /// <summary>Writes a boolean value.</summary>
+ public void WriteBoolean(bool value)
+ {
+ MemoryStream ms = BeforeWriteValue();
+ ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(
+ value ? VariantPrimitiveType.BooleanTrue :
VariantPrimitiveType.BooleanFalse));
+ }
+
+ /// <summary>Writes an 8-bit signed integer.</summary>
+ public void WriteInt8(sbyte value)
+ {
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int8));
+ ms.WriteByte((byte)value);
+ }
+
+ /// <summary>Writes a 16-bit signed integer.</summary>
+ public void WriteInt16(short value)
+ {
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int16));
+ WriteInt16LE(ms, value);
+ }
+
+ /// <summary>Writes a 32-bit signed integer.</summary>
+ public void WriteInt32(int value)
+ {
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int32));
+ WriteInt32LE(ms, value);
+ }
+
+ /// <summary>Writes a 64-bit signed integer.</summary>
+ public void WriteInt64(long value)
+ {
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Int64));
+ WriteInt64LE(ms, value);
+ }
+
+ /// <summary>
+ /// Writes an integer using the narrowest of Int8/Int16/Int32/Int64
that fits.
+ /// Useful for size-minimising encoders such as JSON.
+ /// </summary>
+ public void WriteIntegerCompact(long value)
+ {
+ if (value >= sbyte.MinValue && value <= sbyte.MaxValue)
+ {
+ WriteInt8((sbyte)value);
+ }
+ else if (value >= short.MinValue && value <= short.MaxValue)
+ {
+ WriteInt16((short)value);
+ }
+ else if (value >= int.MinValue && value <= int.MaxValue)
+ {
+ WriteInt32((int)value);
+ }
+ else
+ {
+ WriteInt64(value);
+ }
+ }
+
+ /// <summary>Writes a 32-bit IEEE 754 float.</summary>
+ public void WriteFloat(float value)
+ {
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Float));
+ WriteFloatLE(ms, value);
+ }
+
+ /// <summary>Writes a 64-bit IEEE 754 double.</summary>
+ public void WriteDouble(double value)
+ {
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Double));
+ WriteDoubleLE(ms, value);
+ }
+
+ /// <summary>Writes a Decimal4 (precision ≤ 9) value.</summary>
+ public void WriteDecimal4(decimal value)
+ {
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Decimal4));
+#if NET8_0_OR_GREATER
+ Span<int> bits = stackalloc int[4];
+ decimal.GetBits(value, bits);
+#else
+ int[] bits = decimal.GetBits(value);
+#endif
+ byte scale = (byte)((bits[3] >> 16) & 0x7F);
+ bool negative = (bits[3] & unchecked((int)0x80000000)) != 0;
+ int unscaled = bits[0];
+ if (negative) unscaled = -unscaled;
+ ms.WriteByte(scale);
+ WriteInt32LE(ms, unscaled);
+ }
+
+ /// <summary>Writes a Decimal8 (precision ≤ 18) value.</summary>
+ public void WriteDecimal8(decimal value)
+ {
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Decimal8));
+#if NET8_0_OR_GREATER
+ Span<int> bits = stackalloc int[4];
+ decimal.GetBits(value, bits);
+#else
+ int[] bits = decimal.GetBits(value);
+#endif
+ byte scale = (byte)((bits[3] >> 16) & 0x7F);
+ bool negative = (bits[3] & unchecked((int)0x80000000)) != 0;
+ long unscaled = ((long)bits[1] << 32) | (uint)bits[0];
+ if (negative) unscaled = -unscaled;
+ ms.WriteByte(scale);
+ WriteInt64LE(ms, unscaled);
+ }
+
+ /// <summary>Writes a Decimal16 (precision ≤ 38) value stored as <see
cref="SqlDecimal"/>.</summary>
+ public void WriteDecimal16(SqlDecimal value)
+ {
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Decimal16));
+
+ bool positive = value.IsPositive;
+ byte scale = (byte)value.Scale;
+ int[] data = value.Data;
+
+ // SqlDecimal.Data: [0]=least-significant, [3]=most-significant
+ long lo = ((long)(uint)data[1] << 32) | (uint)data[0];
+ long hi = ((long)(uint)data[3] << 32) | (uint)data[2];
+
+ if (!positive)
+ {
+ // Two's complement negate 128-bit
+ lo = ~lo;
+ hi = ~hi;
+ ulong uLo = (ulong)lo + 1;
+ if (uLo == 0) hi++;
+ lo = (long)uLo;
+ }
+
+ ms.WriteByte(scale);
+ WriteInt64LE(ms, lo);
+ WriteInt64LE(ms, hi);
+ }
+
+ /// <summary>Writes a string. Uses the short-string encoding when the
UTF-8 byte length is ≤ 63.</summary>
+ public void WriteString(string value)
+ {
+ if (value == null) throw new ArgumentNullException(nameof(value));
+ MemoryStream ms = BeforeWriteValue();
+ int byteCount = Encoding.UTF8.GetByteCount(value);
+ if (byteCount <= 63)
+ {
+
ms.WriteByte(VariantEncodingHelper.MakeShortStringHeader(byteCount));
+ }
+ else
+ {
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.String));
+ WriteInt32LE(ms, byteCount);
+ }
+
+ // Encode UTF-8 directly into the MemoryStream's buffer.
+ int dataPos = (int)ms.Position;
+ int needed = dataPos + byteCount;
+ if (needed > ms.Length)
+ {
+ ms.SetLength(needed);
+ }
+ Encoding.UTF8.GetBytes(value, 0, value.Length, ms.GetBuffer(),
dataPos);
+ ms.Position = needed;
+ }
+
+ /// <summary>Writes a binary blob.</summary>
+ public void WriteBinary(byte[] data)
+ {
+ if (data == null) throw new ArgumentNullException(nameof(data));
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Binary));
+ WriteInt32LE(ms, data.Length);
+ ms.Write(data, 0, data.Length);
+ }
+
+ /// <summary>Writes a UUID.</summary>
+ public void WriteUuid(Guid value)
+ {
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Uuid));
+#if NET8_0_OR_GREATER
+ Span<byte> buf = stackalloc byte[16];
+ value.TryWriteBytes(buf, bigEndian: true, out int _);
+ ms.Write(buf);
+#else
+ byte[] native = value.ToByteArray();
+ // Convert from .NET mixed-endian to big-endian (RFC 4122).
+ _scratch[0] = native[3]; _scratch[1] = native[2]; _scratch[2] =
native[1]; _scratch[3] = native[0];
+ _scratch[4] = native[5]; _scratch[5] = native[4];
+ _scratch[6] = native[7]; _scratch[7] = native[6];
+ Buffer.BlockCopy(native, 8, _scratch, 8, 8);
+ ms.Write(_scratch, 0, 16);
+#endif
+ }
+
+ /// <summary>Writes a date as days since the Unix epoch.</summary>
+ public void WriteDateDays(int days)
+ {
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Date));
+ WriteInt32LE(ms, days);
+ }
+
+ /// <summary>Writes a timestamp (tz-adjusted microseconds since the
Unix epoch).</summary>
+ public void WriteTimestampMicros(long micros)
+ {
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.Timestamp));
+ WriteInt64LE(ms, micros);
+ }
+
+ /// <summary>Writes a timestamp-without-timezone (microseconds since
the Unix epoch).</summary>
+ public void WriteTimestampNtzMicros(long micros)
+ {
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.TimestampNtz));
+ WriteInt64LE(ms, micros);
+ }
+
+ /// <summary>Writes a time-without-timezone value (microseconds since
midnight).</summary>
+ public void WriteTimeNtzMicros(long micros)
+ {
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.TimeNtz));
+ WriteInt64LE(ms, micros);
+ }
+
+ /// <summary>Writes a timestamp with timezone (nanoseconds since the
Unix epoch).</summary>
+ public void WriteTimestampTzNanos(long nanos)
+ {
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.TimestampTzNanos));
+ WriteInt64LE(ms, nanos);
+ }
+
+ /// <summary>Writes a timestamp without timezone (nanoseconds since
the Unix epoch).</summary>
+ public void WriteTimestampNtzNanos(long nanos)
+ {
+ MemoryStream ms = BeforeWriteValue();
+
ms.WriteByte(VariantEncodingHelper.MakePrimitiveHeader(VariantPrimitiveType.TimestampNtzNanos));
+ WriteInt64LE(ms, nanos);
+ }
+
+ // ---------------------------------------------------------------
+ // Internal bookkeeping
+ // ---------------------------------------------------------------
+
+ private MemoryStream BeforeWriteValue()
+ {
+ if (_frame is ObjectFrame objFrame)
+ {
+ if (!objFrame.PendingValue)
+ {
+ throw new InvalidOperationException("A field name is
required before writing an object field value. Call WriteFieldName first.");
+ }
+ AppendInt(ref objFrame.ValueStarts, ref
objFrame.ValueStartCount, (int)objFrame.Buffer.Position);
+ objFrame.PendingValue = false;
+ return objFrame.Buffer;
+ }
+ if (_frame is ArrayFrame arrFrame)
+ {
+ AppendInt(ref arrFrame.ValueStarts, ref
arrFrame.ValueStartCount, (int)arrFrame.Buffer.Position);
+ return arrFrame.Buffer;
+ }
+ return _root;
+ }
+
+ private static void AppendInt(ref int[] array, ref int count, int
value)
+ {
+ if (count == array.Length)
+ {
+ int[] grown = ArrayPool<int>.Shared.Rent(array.Length * 2);
+ Array.Copy(array, 0, grown, 0, count);
+ ArrayPool<int>.Shared.Return(array);
+ array = grown;
+ }
+ array[count++] = value;
+ }
+
+ private void WriteObjectBody(MemoryStream output, ObjectFrame frame)
+ {
+ int fieldCount = frame.FieldIdCount;
+ // Sentinel marks the end of the last value in the frame buffer.
+ AppendInt(ref frame.ValueStarts, ref frame.ValueStartCount,
(int)frame.Buffer.Position);
+
+ int[] fieldIds = frame.FieldIds;
+ int[] valueStarts = frame.ValueStarts;
+
+ // Sort indices so fields are emitted in sorted-field-id order.
+#if NET8_0_OR_GREATER
+ Span<int> sortOrder = fieldCount <= StackAllocThreshold
+ ? stackalloc int[fieldCount]
+ : new int[fieldCount];
+ for (int i = 0; i < fieldCount; i++)
+ {
+ sortOrder[i] = i;
+ }
+ int[] fieldIdsLocal = fieldIds;
+ sortOrder.Sort((a, b) =>
fieldIdsLocal[a].CompareTo(fieldIdsLocal[b]));
+#else
+ int[] sortOrder = new int[fieldCount];
+ for (int i = 0; i < fieldCount; i++)
+ {
+ sortOrder[i] = i;
+ }
+ int[] fieldIdsLocal = fieldIds;
+ Array.Sort(sortOrder, (a, b) =>
fieldIdsLocal[a].CompareTo(fieldIdsLocal[b]));
+#endif
+
+ int offsetCount = fieldCount + 1;
+ Span<int> offsets = offsetCount <= StackAllocThreshold
+ ? stackalloc int[offsetCount]
+ : new int[offsetCount];
+ offsets[0] = 0;
+ for (int i = 0; i < fieldCount; i++)
+ {
+ int idx = sortOrder[i];
+ offsets[i + 1] = offsets[i] + (valueStarts[idx + 1] -
valueStarts[idx]);
+ }
+
+ int maxFieldId = fieldCount > 0 ? fieldIds[sortOrder[fieldCount -
1]] : 0;
+ int fieldIdSize = fieldCount > 0 ?
VariantEncodingHelper.ByteWidthForValue(maxFieldId) : 1;
+ int offsetSize =
VariantEncodingHelper.ByteWidthForValue(Math.Max(1, offsets[fieldCount]));
+ bool isLarge = fieldCount > 255;
+
+
output.WriteByte(VariantEncodingHelper.MakeObjectHeader(fieldIdSize,
offsetSize, isLarge));
+
+ if (isLarge)
+ {
+ WriteInt32LE(output, fieldCount);
+ }
+ else
+ {
+ output.WriteByte((byte)fieldCount);
+ }
+
+ for (int i = 0; i < fieldCount; i++)
+ {
+ WriteSmallInt(output, fieldIds[sortOrder[i]], fieldIdSize);
+ }
+
+ for (int i = 0; i <= fieldCount; i++)
+ {
+ WriteSmallInt(output, offsets[i], offsetSize);
+ }
+
+ byte[] valueBuffer = frame.Buffer.GetBuffer();
+ for (int i = 0; i < fieldCount; i++)
+ {
+ int idx = sortOrder[i];
+ int start = valueStarts[idx];
+ int length = valueStarts[idx + 1] - start;
+ output.Write(valueBuffer, start, length);
+ }
+ }
+
+ private void WriteArrayBody(MemoryStream output, ArrayFrame frame)
+ {
+ int elementCount = frame.ValueStartCount;
+ // Sentinel marks the end of the last element.
+ AppendInt(ref frame.ValueStarts, ref frame.ValueStartCount,
(int)frame.Buffer.Position);
+ int[] valueStarts = frame.ValueStarts;
+
+ int offsetSize =
VariantEncodingHelper.ByteWidthForValue(Math.Max(1, valueStarts[elementCount]));
+ bool isLarge = elementCount > 255;
+
+ output.WriteByte(VariantEncodingHelper.MakeArrayHeader(offsetSize,
isLarge));
+
+ if (isLarge)
+ {
+ WriteInt32LE(output, elementCount);
+ }
+ else
+ {
+ output.WriteByte((byte)elementCount);
+ }
+
+ for (int i = 0; i <= elementCount; i++)
+ {
+ WriteSmallInt(output, valueStarts[i], offsetSize);
+ }
+
+ output.Write(frame.Buffer.GetBuffer(), 0,
(int)frame.Buffer.Position);
+ }
+
+ private MemoryStream RentStream()
+ {
+ if (_streamPool.Count > 0)
+ {
+ MemoryStream ms = _streamPool.Pop();
+ ms.SetLength(0);
+ return ms;
+ }
+ return new MemoryStream();
+ }
+
+ private void ReturnStream(MemoryStream ms)
+ {
+ _streamPool.Push(ms);
+ }
+
+ private void WriteSmallInt(MemoryStream ms, int value, int byteWidth)
+ {
+#if NET8_0_OR_GREATER
+ Span<byte> buf = stackalloc byte[4];
+ VariantEncodingHelper.WriteLittleEndianInt(buf, value, byteWidth);
+ ms.Write(buf.Slice(0, byteWidth));
+#else
+ VariantEncodingHelper.WriteLittleEndianInt(_scratch, value,
byteWidth);
+ ms.Write(_scratch, 0, byteWidth);
+#endif
+ }
+
+ private void WriteInt16LE(MemoryStream ms, short value)
+ {
+#if NET8_0_OR_GREATER
+ Span<byte> buf = stackalloc byte[2];
+ BinaryPrimitives.WriteInt16LittleEndian(buf, value);
+ ms.Write(buf);
+#else
+ BinaryPrimitives.WriteInt16LittleEndian(_scratch, value);
+ ms.Write(_scratch, 0, 2);
+#endif
+ }
+
+ private void WriteInt32LE(MemoryStream ms, int value)
+ {
+#if NET8_0_OR_GREATER
+ Span<byte> buf = stackalloc byte[4];
+ BinaryPrimitives.WriteInt32LittleEndian(buf, value);
+ ms.Write(buf);
+#else
+ BinaryPrimitives.WriteInt32LittleEndian(_scratch, value);
+ ms.Write(_scratch, 0, 4);
+#endif
+ }
+
+ private void WriteInt64LE(MemoryStream ms, long value)
+ {
+#if NET8_0_OR_GREATER
+ Span<byte> buf = stackalloc byte[8];
+ BinaryPrimitives.WriteInt64LittleEndian(buf, value);
+ ms.Write(buf);
+#else
+ BinaryPrimitives.WriteInt64LittleEndian(_scratch, value);
+ ms.Write(_scratch, 0, 8);
+#endif
+ }
+
+ private void WriteFloatLE(MemoryStream ms, float value)
+ {
+#if NET8_0_OR_GREATER
+ Span<byte> buf = stackalloc byte[4];
+ BinaryPrimitives.WriteSingleLittleEndian(buf, value);
+ ms.Write(buf);
+#else
+ int bits = System.Runtime.CompilerServices.Unsafe.As<float,
int>(ref value);
+ BinaryPrimitives.WriteInt32LittleEndian(_scratch, bits);
+ ms.Write(_scratch, 0, 4);
+#endif
+ }
+
+ private void WriteDoubleLE(MemoryStream ms, double value)
+ {
+#if NET8_0_OR_GREATER
+ Span<byte> buf = stackalloc byte[8];
+ BinaryPrimitives.WriteDoubleLittleEndian(buf, value);
+ ms.Write(buf);
+#else
+ long bits = BitConverter.DoubleToInt64Bits(value);
+ BinaryPrimitives.WriteInt64LittleEndian(_scratch, bits);
+ ms.Write(_scratch, 0, 8);
+#endif
+ }
+
+ private const int InitialFrameCapacity = 16;
+
+ private abstract class Frame
+ {
+ public MemoryStream Buffer;
+ public int[] ValueStarts;
+ public int ValueStartCount;
+ }
+
+ private sealed class ObjectFrame : Frame
+ {
+ public int[] FieldIds;
+ public int FieldIdCount;
+ public bool PendingValue;
+ }
+
+ private sealed class ArrayFrame : Frame
+ {
+ }
+ }
+}
diff --git a/test/Apache.Arrow.Variant.Tests/Apache.Arrow.Variant.Tests.csproj
b/test/Apache.Arrow.Operations.Tests/Apache.Arrow.Operations.Tests.csproj
similarity index 91%
copy from test/Apache.Arrow.Variant.Tests/Apache.Arrow.Variant.Tests.csproj
copy to test/Apache.Arrow.Operations.Tests/Apache.Arrow.Operations.Tests.csproj
index 1e62a24..e23b7c4 100644
--- a/test/Apache.Arrow.Variant.Tests/Apache.Arrow.Variant.Tests.csproj
+++ b/test/Apache.Arrow.Operations.Tests/Apache.Arrow.Operations.Tests.csproj
@@ -27,7 +27,7 @@
</ItemGroup>
<ItemGroup>
- <ProjectReference
Include="..\..\src\Apache.Arrow.Variant\Apache.Arrow.Variant.csproj" />
+ <ProjectReference
Include="..\..\src\Apache.Arrow.Operations\Apache.Arrow.Operations.csproj" />
</ItemGroup>
</Project>
diff --git a/test/Apache.Arrow.Operations.Tests/Json/VariantDecimalJsonTests.cs
b/test/Apache.Arrow.Operations.Tests/Json/VariantDecimalJsonTests.cs
new file mode 100644
index 0000000..c65d120
--- /dev/null
+++ b/test/Apache.Arrow.Operations.Tests/Json/VariantDecimalJsonTests.cs
@@ -0,0 +1,117 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System.Collections.Generic;
+using System.Data.SqlTypes;
+using System.Text.Json;
+using Apache.Arrow.Operations.Json;
+using Apache.Arrow.Variant;
+using Xunit;
+
+namespace Apache.Arrow.Operations.Tests
+{
+ public class VariantDecimalJsonTests
+ {
+ // Minimal metadata: version 1, 0 dictionary entries.
+ private static readonly byte[] MinimalMetadata = new byte[] { 0x01,
0x00 };
+
+ // Header bytes: (primitiveType << 2) | basicType, basicType=0 for
Primitive
+ // Decimal4=8 -> 0x20, Decimal8=9 -> 0x24, Decimal16=10 -> 0x28
+ private const byte Decimal16Header = 0x28;
+
+ // ---------------------------------------------------------------
+ // JSON writer: large Decimal16 produces valid JSON number
+ // ---------------------------------------------------------------
+
+ [Fact]
+ public void JsonWriter_LargeDecimal16_WritesValidJson()
+ {
+ byte[] value = new byte[]
+ {
+ Decimal16Header,
+ 0x00, // scale = 0
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00
+ };
+
+ string json = VariantJsonWriter.ToJson(MinimalMetadata, value);
+ Assert.Equal("79228162514264337593543950336", json);
+ }
+
+ // ---------------------------------------------------------------
+ // JSON converter: VariantValue with SqlDecimal storage writes valid
JSON
+ // ---------------------------------------------------------------
+
+ [Fact]
+ public void JsonConverter_SqlDecimalStorage_WritesValidJson()
+ {
+ SqlDecimal sd =
SqlDecimal.Parse("99999999999999999999999999999999999999");
+ VariantValue vv = VariantValue.FromSqlDecimal(sd);
+
+ string json = VariantJsonWriter.ToJson(vv);
+ Assert.Equal("99999999999999999999999999999999999999", json);
+ }
+
+ // ---------------------------------------------------------------
+ // JSON writer for negative large Decimal16
+ // ---------------------------------------------------------------
+
+ [Fact]
+ public void JsonWriter_NegativeLargeDecimal16_WritesValidJson()
+ {
+ SqlDecimal sd = SqlDecimal.Parse("-79228162514264337593543950336");
+ VariantValue vv = VariantValue.FromSqlDecimal(sd);
+ VariantBuilder builder = new VariantBuilder();
+ (byte[] metadata, byte[] value) = builder.Encode(vv);
+
+ string json = VariantJsonWriter.ToJson(metadata, value);
+ Assert.Equal("-79228162514264337593543950336", json);
+ }
+
+ // ---------------------------------------------------------------
+ // JSON writer for small Decimal16 (decimal path)
+ // ---------------------------------------------------------------
+
+ [Fact]
+ public void JsonWriter_SmallDecimal16_WritesNumberValue()
+ {
+ VariantValue vv = VariantValue.FromDecimal16(42.5m);
+ VariantBuilder builder = new VariantBuilder();
+ (byte[] metadata, byte[] value) = builder.Encode(vv);
+
+ string json = VariantJsonWriter.ToJson(metadata, value);
+ Assert.Equal("42.5", json);
+ }
+
+ // ---------------------------------------------------------------
+ // JSON converter serialization of nested SqlDecimal
+ // ---------------------------------------------------------------
+
+ [Fact]
+ public void JsonConverter_ObjectWithSqlDecimal_WritesValidJson()
+ {
+ SqlDecimal sd =
SqlDecimal.Parse("99999999999999999999999999999999999999");
+ VariantValue obj = VariantValue.FromObject(new Dictionary<string,
VariantValue>
+ {
+ { "val", VariantValue.FromSqlDecimal(sd) },
+ });
+
+ JsonSerializerOptions options = new JsonSerializerOptions();
+ options.Converters.Add(VariantJsonConverter.Instance);
+ string json = JsonSerializer.Serialize(obj, options);
+ Assert.Equal("{\"val\":99999999999999999999999999999999999999}",
json);
+ }
+ }
+}
diff --git a/test/Apache.Arrow.Variant.Tests/VariantJsonTests.cs
b/test/Apache.Arrow.Operations.Tests/Json/VariantJsonTests.cs
similarity index 99%
rename from test/Apache.Arrow.Variant.Tests/VariantJsonTests.cs
rename to test/Apache.Arrow.Operations.Tests/Json/VariantJsonTests.cs
index b784634..65a26dc 100644
--- a/test/Apache.Arrow.Variant.Tests/VariantJsonTests.cs
+++ b/test/Apache.Arrow.Operations.Tests/Json/VariantJsonTests.cs
@@ -17,10 +17,11 @@ using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.Text.Json;
-using Apache.Arrow.Variant.Json;
+using Apache.Arrow.Operations.Json;
+using Apache.Arrow.Variant;
using Xunit;
-namespace Apache.Arrow.Variant.Tests
+namespace Apache.Arrow.Operations.Tests
{
public class VariantJsonTests
{
diff --git
a/test/Apache.Arrow.Variant.Benchmarks/Apache.Arrow.Variant.Benchmarks.csproj
b/test/Apache.Arrow.Variant.Benchmarks/Apache.Arrow.Variant.Benchmarks.csproj
index 1a3757a..1108a2e 100644
---
a/test/Apache.Arrow.Variant.Benchmarks/Apache.Arrow.Variant.Benchmarks.csproj
+++
b/test/Apache.Arrow.Variant.Benchmarks/Apache.Arrow.Variant.Benchmarks.csproj
@@ -11,6 +11,7 @@
<ItemGroup>
<ProjectReference
Include="..\..\src\Apache.Arrow.Variant\Apache.Arrow.Variant.csproj" />
+ <ProjectReference
Include="..\..\src\Apache.Arrow.Operations\Apache.Arrow.Operations.csproj" />
</ItemGroup>
</Project>
diff --git a/test/Apache.Arrow.Variant.Benchmarks/EncodingBenchmarks.cs
b/test/Apache.Arrow.Variant.Benchmarks/EncodingBenchmarks.cs
index 73c7c96..7451355 100644
--- a/test/Apache.Arrow.Variant.Benchmarks/EncodingBenchmarks.cs
+++ b/test/Apache.Arrow.Variant.Benchmarks/EncodingBenchmarks.cs
@@ -17,7 +17,7 @@ using System.Buffers;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
-using Apache.Arrow.Variant.Json;
+using Apache.Arrow.Operations.Json;
using BenchmarkDotNet.Attributes;
namespace Apache.Arrow.Variant.Benchmarks
@@ -130,28 +130,25 @@ namespace Apache.Arrow.Variant.Benchmarks
}
// ---------------------------------------------------------------
- // EncodeFromJson: UTF-8 JSON → binary (streaming, no VariantValue)
+ // VariantJsonReader: UTF-8 JSON → binary (streaming, no VariantValue)
// ---------------------------------------------------------------
[Benchmark]
public byte[] EncodeFromJson_FlatObject()
{
- VariantBuilder builder = new VariantBuilder();
- return builder.EncodeFromJson(_flatJson).Value;
+ return VariantJsonReader.Parse(_flatJson).Value;
}
[Benchmark]
public byte[] EncodeFromJson_NestedObject()
{
- VariantBuilder builder = new VariantBuilder();
- return builder.EncodeFromJson(_nestedJson).Value;
+ return VariantJsonReader.Parse(_nestedJson).Value;
}
[Benchmark]
public byte[] EncodeFromJson_LargeArray()
{
- VariantBuilder builder = new VariantBuilder();
- return builder.EncodeFromJson(_arrayJson).Value;
+ return VariantJsonReader.Parse(_arrayJson).Value;
}
// ---------------------------------------------------------------
diff --git a/test/Apache.Arrow.Variant.Tests/Apache.Arrow.Variant.Tests.csproj
b/test/Apache.Arrow.Variant.Tests/Apache.Arrow.Variant.Tests.csproj
index 1e62a24..4471cfd 100644
--- a/test/Apache.Arrow.Variant.Tests/Apache.Arrow.Variant.Tests.csproj
+++ b/test/Apache.Arrow.Variant.Tests/Apache.Arrow.Variant.Tests.csproj
@@ -24,10 +24,12 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.skippablefact" />
+ <PackageReference Include="System.Text.Json" />
</ItemGroup>
<ItemGroup>
<ProjectReference
Include="..\..\src\Apache.Arrow.Variant\Apache.Arrow.Variant.csproj" />
+ <ProjectReference
Include="..\..\src\Apache.Arrow.Operations\Apache.Arrow.Operations.csproj" />
</ItemGroup>
</Project>
diff --git a/test/Apache.Arrow.Variant.Tests/VariantSqlDecimalTests.cs
b/test/Apache.Arrow.Variant.Tests/VariantSqlDecimalTests.cs
index bf1cad1..ce81ec4 100644
--- a/test/Apache.Arrow.Variant.Tests/VariantSqlDecimalTests.cs
+++ b/test/Apache.Arrow.Variant.Tests/VariantSqlDecimalTests.cs
@@ -16,8 +16,6 @@
using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
-using System.Text.Json;
-using Apache.Arrow.Variant.Json;
using Xunit;
namespace Apache.Arrow.Variant.Tests
@@ -278,39 +276,6 @@ namespace Apache.Arrow.Variant.Tests
Assert.Equal(SqlDecimal.Parse("79228162514264337593543950336"),
result);
}
- // ---------------------------------------------------------------
- // JSON writer: large Decimal16 produces valid JSON number
- // ---------------------------------------------------------------
-
- [Fact]
- public void JsonWriter_LargeDecimal16_WritesValidJson()
- {
- byte[] value = new byte[]
- {
- Decimal16Header,
- 0x00, // scale = 0
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00
- };
-
- string json = VariantJsonWriter.ToJson(MinimalMetadata, value);
- Assert.Equal("79228162514264337593543950336", json);
- }
-
- // ---------------------------------------------------------------
- // JSON converter: VariantValue with SqlDecimal storage writes valid
JSON
- // ---------------------------------------------------------------
-
- [Fact]
- public void JsonConverter_SqlDecimalStorage_WritesValidJson()
- {
- SqlDecimal sd =
SqlDecimal.Parse("99999999999999999999999999999999999999");
- VariantValue vv = VariantValue.FromSqlDecimal(sd);
-
- string json = VariantJsonWriter.ToJson(vv);
- Assert.Equal("99999999999999999999999999999999999999", json);
- }
-
// ---------------------------------------------------------------
// Round-trip with materialization
// ---------------------------------------------------------------
@@ -500,37 +465,6 @@ namespace Apache.Arrow.Variant.Tests
Assert.Equal(79228162514264337593543950335m, vv.AsDecimal());
}
- // ---------------------------------------------------------------
- // JSON writer for negative large Decimal16
- // ---------------------------------------------------------------
-
- [Fact]
- public void JsonWriter_NegativeLargeDecimal16_WritesValidJson()
- {
- SqlDecimal sd = SqlDecimal.Parse("-79228162514264337593543950336");
- VariantValue vv = VariantValue.FromSqlDecimal(sd);
- VariantBuilder builder = new VariantBuilder();
- (byte[] metadata, byte[] value) = builder.Encode(vv);
-
- string json = VariantJsonWriter.ToJson(metadata, value);
- Assert.Equal("-79228162514264337593543950336", json);
- }
-
- // ---------------------------------------------------------------
- // JSON writer for small Decimal16 (decimal path)
- // ---------------------------------------------------------------
-
- [Fact]
- public void JsonWriter_SmallDecimal16_WritesNumberValue()
- {
- VariantValue vv = VariantValue.FromDecimal16(42.5m);
- VariantBuilder builder = new VariantBuilder();
- (byte[] metadata, byte[] value) = builder.Encode(vv);
-
- string json = VariantJsonWriter.ToJson(metadata, value);
- Assert.Equal("42.5", json);
- }
-
// ---------------------------------------------------------------
// GetSqlDecimal on non-decimal primitive throws
// ---------------------------------------------------------------
@@ -606,25 +540,6 @@ namespace Apache.Arrow.Variant.Tests
Assert.Equal(1.23m, elements[2].AsDecimal());
}
- // ---------------------------------------------------------------
- // JSON converter serialization of nested SqlDecimal
- // ---------------------------------------------------------------
-
- [Fact]
- public void JsonConverter_ObjectWithSqlDecimal_WritesValidJson()
- {
- SqlDecimal sd =
SqlDecimal.Parse("99999999999999999999999999999999999999");
- VariantValue obj = VariantValue.FromObject(new Dictionary<string,
VariantValue>
- {
- { "val", VariantValue.FromSqlDecimal(sd) },
- });
-
- JsonSerializerOptions options = new JsonSerializerOptions();
- options.Converters.Add(VariantJsonConverter.Instance);
- string json = JsonSerializer.Serialize(obj, options);
- Assert.Equal("{\"val\":99999999999999999999999999999999999999}",
json);
- }
-
// ---------------------------------------------------------------
// Scale preservation round-trip
// ---------------------------------------------------------------