blob: 4537c3cb69e84ff443a8198d7bfc07bf1d304b5f [file]
// 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.Collections.Generic;
using Apache.Arrow.Scalars.Variant;
namespace Apache.Arrow.Operations.Shredding
{
/// <summary>
/// Analyzes a batch of <see cref="VariantValue"/>s and infers an optimal
/// <see cref="ShredSchema"/> for shredding them.
/// </summary>
public sealed class ShredSchemaInferer
{
/// <summary>
/// Infers a shredding schema by analyzing the given values.
/// </summary>
/// <param name="values">The variant values to analyze.</param>
/// <param name="options">Options controlling depth, frequency, and type consistency thresholds.</param>
/// <returns>An inferred <see cref="ShredSchema"/>.</returns>
public ShredSchema Infer(IEnumerable<VariantValue> values, ShredOptions options = null)
{
if (values == null) throw new ArgumentNullException(nameof(values));
if (options == null) options = ShredOptions.Default;
TypeStats stats = new TypeStats();
int totalCount = 0;
foreach (VariantValue value in values)
{
CollectStats(value, stats, 0, options.MaxDepth);
totalCount++;
}
if (totalCount == 0)
{
return ShredSchema.Unshredded();
}
return BuildSchema(stats, totalCount, options, 0);
}
private void CollectStats(VariantValue value, TypeStats stats, int depth, int maxDepth)
{
ShredType type = VariantShredder.GetShredType(value);
stats.TypeCounts.TryGetValue(type, out int count);
stats.TypeCounts[type] = count + 1;
if (type == ShredType.Object && depth <= maxDepth && value.IsObject)
{
if (stats.ObjectFieldStats == null)
{
stats.ObjectFieldStats = new Dictionary<string, TypeStats>();
}
foreach (KeyValuePair<string, VariantValue> field in value.AsObject())
{
if (!stats.ObjectFieldStats.TryGetValue(field.Key, out TypeStats fieldStats))
{
fieldStats = new TypeStats();
stats.ObjectFieldStats[field.Key] = fieldStats;
}
CollectStats(field.Value, fieldStats, depth + 1, maxDepth);
}
}
else if (type == ShredType.Array && depth <= maxDepth && value.IsArray)
{
if (stats.ArrayElementStats == null)
{
stats.ArrayElementStats = new TypeStats();
}
foreach (VariantValue element in value.AsArray())
{
CollectStats(element, stats.ArrayElementStats, depth + 1, maxDepth);
}
}
}
private ShredSchema BuildSchema(TypeStats stats, int totalCount, ShredOptions options, int depth)
{
// Find the dominant type.
ShredType dominantType = ShredType.None;
int dominantCount = 0;
int nonNullCount = 0;
foreach (KeyValuePair<ShredType, int> entry in stats.TypeCounts)
{
if (entry.Key != ShredType.None)
{
nonNullCount += entry.Value;
if (entry.Value > dominantCount)
{
dominantCount = entry.Value;
dominantType = entry.Key;
}
}
}
if (nonNullCount == 0)
{
return ShredSchema.Unshredded();
}
// Check type consistency.
double consistency = (double)dominantCount / nonNullCount;
if (consistency < options.MinTypeConsistency)
{
return ShredSchema.Unshredded();
}
if (dominantType == ShredType.Object && stats.ObjectFieldStats != null)
{
return BuildObjectSchema(stats, totalCount, dominantCount, options, depth);
}
if (dominantType == ShredType.Array && stats.ArrayElementStats != null)
{
return BuildArraySchema(stats, dominantCount, options, depth);
}
// Object/Array without collected sub-stats (e.g., maxDepth reached) — can't shred further.
if (dominantType == ShredType.Object || dominantType == ShredType.Array)
{
return ShredSchema.Unshredded();
}
// Primitive type — shred as that type.
return ShredSchema.Primitive(dominantType);
}
private ShredSchema BuildObjectSchema(TypeStats stats, int totalCount, int objectCount, ShredOptions options, int depth)
{
Dictionary<string, ShredSchema> fields = new Dictionary<string, ShredSchema>();
foreach (KeyValuePair<string, TypeStats> fieldEntry in stats.ObjectFieldStats)
{
// Check field frequency: how often does this field appear relative to the number of objects?
int fieldAppearances = 0;
foreach (KeyValuePair<ShredType, int> tc in fieldEntry.Value.TypeCounts)
{
fieldAppearances += tc.Value;
}
double frequency = (double)fieldAppearances / objectCount;
if (frequency < options.MinFieldFrequency)
{
continue;
}
ShredSchema fieldSchema = BuildSchema(fieldEntry.Value, fieldAppearances, options, depth + 1);
fields[fieldEntry.Key] = fieldSchema;
}
if (fields.Count == 0)
{
return ShredSchema.Unshredded();
}
return ShredSchema.ForObject(fields);
}
private ShredSchema BuildArraySchema(TypeStats stats, int arrayCount, ShredOptions options, int depth)
{
// Count total elements across all arrays.
int totalElements = 0;
foreach (KeyValuePair<ShredType, int> entry in stats.ArrayElementStats.TypeCounts)
{
totalElements += entry.Value;
}
if (totalElements == 0)
{
return ShredSchema.Unshredded();
}
ShredSchema elementSchema = BuildSchema(stats.ArrayElementStats, totalElements, options, depth + 1);
return ShredSchema.ForArray(elementSchema);
}
private sealed class TypeStats
{
public Dictionary<ShredType, int> TypeCounts { get; } = new Dictionary<ShredType, int>();
public Dictionary<string, TypeStats> ObjectFieldStats { get; set; }
public TypeStats ArrayElementStats { get; set; }
}
}
}