mirror of
https://github.com/microsoft/HybridRow.git
synced 2026-01-21 10:23:13 +00:00
636 lines
24 KiB
C#
636 lines
24 KiB
C#
// ------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// ------------------------------------------------------------
|
|
|
|
#pragma warning disable SA1137 // Elements should have the same indentation
|
|
|
|
namespace Microsoft.Azure.Cosmos.Serialization.HybridRowGenerator
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using Microsoft.Azure.Cosmos.Core;
|
|
using Microsoft.Azure.Cosmos.Core.Utf8;
|
|
using Microsoft.Azure.Cosmos.Serialization.HybridRow;
|
|
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts;
|
|
using Microsoft.Azure.Cosmos.Serialization.HybridRow.Schemas;
|
|
|
|
public class HybridRowValueGenerator
|
|
{
|
|
private readonly RandomGenerator rand;
|
|
private readonly HybridRowGeneratorConfig config;
|
|
|
|
public HybridRowValueGenerator(RandomGenerator rand, HybridRowGeneratorConfig config)
|
|
{
|
|
this.rand = rand;
|
|
this.config = config;
|
|
}
|
|
|
|
public static bool DynamicTypeArgumentEquals(LayoutResolver resolver, object left, object right, TypeArgument typeArg)
|
|
{
|
|
if (object.ReferenceEquals(left, null))
|
|
{
|
|
return object.ReferenceEquals(null, right);
|
|
}
|
|
|
|
if (object.ReferenceEquals(null, right))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
switch (typeArg.Type.LayoutCode)
|
|
{
|
|
case LayoutCode.Null:
|
|
case LayoutCode.Boolean:
|
|
case LayoutCode.Int8:
|
|
case LayoutCode.Int16:
|
|
case LayoutCode.Int32:
|
|
case LayoutCode.Int64:
|
|
case LayoutCode.UInt8:
|
|
case LayoutCode.UInt16:
|
|
case LayoutCode.UInt32:
|
|
case LayoutCode.UInt64:
|
|
case LayoutCode.VarInt:
|
|
case LayoutCode.VarUInt:
|
|
case LayoutCode.Float32:
|
|
case LayoutCode.Float64:
|
|
case LayoutCode.Float128:
|
|
case LayoutCode.Decimal:
|
|
case LayoutCode.DateTime:
|
|
case LayoutCode.UnixDateTime:
|
|
case LayoutCode.Guid:
|
|
case LayoutCode.MongoDbObjectId:
|
|
case LayoutCode.Utf8:
|
|
return object.Equals(left, right);
|
|
case LayoutCode.Binary:
|
|
return ((byte[])left).SequenceEqual((byte[])right);
|
|
|
|
case LayoutCode.ObjectScope:
|
|
case LayoutCode.ImmutableObjectScope:
|
|
{
|
|
Dictionary<Utf8String, object> leftDict = (Dictionary<Utf8String, object>)left;
|
|
Dictionary<Utf8String, object> rightDict = (Dictionary<Utf8String, object>)right;
|
|
if (leftDict.Count != rightDict.Count)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// TODO: add properties to an object scope.
|
|
return true;
|
|
}
|
|
|
|
case LayoutCode.TypedArrayScope:
|
|
case LayoutCode.ImmutableTypedArrayScope:
|
|
{
|
|
List<object> leftList = (List<object>)left;
|
|
List<object> rightList = (List<object>)right;
|
|
if (leftList.Count != rightList.Count)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < leftList.Count; i++)
|
|
{
|
|
if (!HybridRowValueGenerator.DynamicTypeArgumentEquals(resolver, leftList[i], rightList[i], typeArg.TypeArgs[0]))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case LayoutCode.TypedSetScope:
|
|
case LayoutCode.ImmutableTypedSetScope:
|
|
{
|
|
List<object> leftList = (List<object>)left;
|
|
List<object> rightList = (List<object>)right;
|
|
if (leftList.Count != rightList.Count)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
List<object> working = new List<object>(leftList);
|
|
foreach (object rightItem in rightList)
|
|
{
|
|
int i = HybridRowValueGenerator.SetContains(resolver, working, rightItem, typeArg);
|
|
if (i == -1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
working.RemoveAt(i);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case LayoutCode.TypedMapScope:
|
|
case LayoutCode.ImmutableTypedMapScope:
|
|
{
|
|
List<List<object>> leftList = (List<List<object>>)left;
|
|
List<List<object>> rightList = (List<List<object>>)right;
|
|
if (leftList.Count != rightList.Count)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
List<List<object>> working = new List<List<object>>(leftList);
|
|
foreach (List<object> rightItem in rightList)
|
|
{
|
|
int i = HybridRowValueGenerator.MapContains(resolver, working, rightItem[0], typeArg);
|
|
if (i == -1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
List<object> leftItem = working[i];
|
|
if (!HybridRowValueGenerator.DynamicTypeArgumentEquals(resolver, leftItem[1], rightItem[1], typeArg.TypeArgs[1]))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
working.RemoveAt(i);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case LayoutCode.TupleScope:
|
|
case LayoutCode.ImmutableTupleScope:
|
|
case LayoutCode.TypedTupleScope:
|
|
case LayoutCode.ImmutableTypedTupleScope:
|
|
case LayoutCode.TaggedScope:
|
|
case LayoutCode.ImmutableTaggedScope:
|
|
case LayoutCode.Tagged2Scope:
|
|
case LayoutCode.ImmutableTagged2Scope:
|
|
{
|
|
List<object> leftList = (List<object>)left;
|
|
List<object> rightList = (List<object>)right;
|
|
if (leftList.Count != rightList.Count)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Contract.Assert(leftList.Count == typeArg.TypeArgs.Count);
|
|
for (int i = 0; i < leftList.Count; i++)
|
|
{
|
|
if (!HybridRowValueGenerator.DynamicTypeArgumentEquals(resolver, leftList[i], rightList[i], typeArg.TypeArgs[i]))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case LayoutCode.NullableScope:
|
|
case LayoutCode.ImmutableNullableScope:
|
|
{
|
|
Contract.Assert(typeArg.TypeArgs.Count == 1);
|
|
return HybridRowValueGenerator.DynamicTypeArgumentEquals(resolver, left, right, typeArg.TypeArgs[0]);
|
|
}
|
|
|
|
case LayoutCode.Schema:
|
|
case LayoutCode.ImmutableSchema:
|
|
{
|
|
SchemaId schemaId = typeArg.TypeArgs.SchemaId;
|
|
Contract.Assert(schemaId != SchemaId.Invalid);
|
|
Layout layout = resolver.Resolve(schemaId);
|
|
Contract.Assert(layout != null);
|
|
|
|
Dictionary<Utf8String, object> leftDict = (Dictionary<Utf8String, object>)left;
|
|
Dictionary<Utf8String, object> rightDict = (Dictionary<Utf8String, object>)right;
|
|
if (leftDict.Count != rightDict.Count)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (KeyValuePair<Utf8String, object> pair in leftDict)
|
|
{
|
|
if (!rightDict.TryGetValue(pair.Key, out object rightValue))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Contract.Requires(layout.TryFind(pair.Key, out LayoutColumn c));
|
|
return HybridRowValueGenerator.DynamicTypeArgumentEquals(
|
|
resolver,
|
|
pair.Value,
|
|
rightValue,
|
|
new TypeArgument(c.Type, c.TypeArgs));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
Contract.Assert(false, $"Unknown type will be ignored: {typeArg}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static T GenerateExclusive<T>(Func<T> op, IEnumerable<T> exclusions, int maxCandidates)
|
|
{
|
|
HashSet<T> exclusionSet = new HashSet<T>(exclusions);
|
|
T candidate;
|
|
|
|
int retryCount = 0;
|
|
do
|
|
{
|
|
if (retryCount >= maxCandidates)
|
|
{
|
|
throw new Exception(
|
|
$"Max Candidates Reached: {maxCandidates} : " +
|
|
string.Join(",", from e in exclusions select e.ToString()));
|
|
}
|
|
|
|
candidate = op();
|
|
retryCount++;
|
|
}
|
|
while (exclusionSet.Contains(candidate));
|
|
|
|
return candidate;
|
|
}
|
|
|
|
public string GenerateIdentifier()
|
|
{
|
|
int length = this.config.IdentifierLength.Next(this.rand);
|
|
CharDistribution distribution = this.config.IdentifierCharacters;
|
|
return this.GenerateString(length, distribution);
|
|
}
|
|
|
|
public unsafe string GenerateString(int length, CharDistribution distribution)
|
|
{
|
|
if (length == 0)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
char* result = stackalloc char[length];
|
|
int trim;
|
|
do
|
|
{
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
result[i] = distribution.Next(this.rand);
|
|
}
|
|
|
|
// Drop characters until we are under the encoded length.
|
|
trim = length;
|
|
while ((trim > 0) && Encoding.UTF8.GetByteCount(result, trim) > length)
|
|
{
|
|
trim--;
|
|
result[trim] = '\0';
|
|
}
|
|
}
|
|
while (trim == 0);
|
|
|
|
// Pad with zero's if the resulting encoding is too short.
|
|
while (Encoding.UTF8.GetByteCount(result, trim) < length)
|
|
{
|
|
trim++;
|
|
}
|
|
|
|
Contract.Assert(Encoding.UTF8.GetByteCount(result, trim) == length);
|
|
return new string(result, 0, trim);
|
|
}
|
|
|
|
public byte[] GenerateBinary(int length)
|
|
{
|
|
byte[] result = new byte[length];
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
result[i] = this.rand.NextUInt8();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public SchemaId GenerateSchemaId()
|
|
{
|
|
return new SchemaId(this.config.SchemaIds.Next(this.rand));
|
|
}
|
|
|
|
public StorageKind GenerateStorageKind()
|
|
{
|
|
return (StorageKind)this.config.FieldStorage.Next(this.rand);
|
|
}
|
|
|
|
public TypeKind GenerateTypeKind()
|
|
{
|
|
return (TypeKind)this.config.FieldType.Next(this.rand);
|
|
}
|
|
|
|
public string GenerateComment()
|
|
{
|
|
int length = this.config.CommentLength.Next(this.rand);
|
|
CharDistribution distribution = this.config.UnicodeCharacters;
|
|
return this.GenerateString(length, distribution);
|
|
}
|
|
|
|
public bool GenerateBool()
|
|
{
|
|
return this.rand.NextInt32(0, 1) != 0;
|
|
}
|
|
|
|
public object GenerateLayoutType(
|
|
LayoutResolver resolver,
|
|
TypeArgument typeArg,
|
|
bool nullable = false,
|
|
int length = 0,
|
|
StorageKind storage = StorageKind.Sparse)
|
|
{
|
|
switch (typeArg.Type.LayoutCode)
|
|
{
|
|
case LayoutCode.Null:
|
|
return nullable && this.GenerateBool() ? (object)null : NullValue.Default;
|
|
case LayoutCode.Boolean:
|
|
return nullable && this.GenerateBool() ? (object)null : this.GenerateBool();
|
|
case LayoutCode.Int8:
|
|
return nullable && this.GenerateBool() ? (object)null : this.rand.NextInt8();
|
|
case LayoutCode.Int16:
|
|
return nullable && this.GenerateBool() ? (object)null : this.rand.NextInt16();
|
|
case LayoutCode.Int32:
|
|
return nullable && this.GenerateBool() ? (object)null : this.rand.NextInt32();
|
|
case LayoutCode.Int64:
|
|
return nullable && this.GenerateBool() ? (object)null : this.rand.NextInt64();
|
|
case LayoutCode.UInt8:
|
|
return nullable && this.GenerateBool() ? (object)null : this.rand.NextUInt8();
|
|
case LayoutCode.UInt16:
|
|
return nullable && this.GenerateBool() ? (object)null : this.rand.NextUInt16();
|
|
case LayoutCode.UInt32:
|
|
return nullable && this.GenerateBool() ? (object)null : this.rand.NextUInt32();
|
|
case LayoutCode.UInt64:
|
|
return nullable && this.GenerateBool() ? (object)null : this.rand.NextUInt64();
|
|
case LayoutCode.VarInt:
|
|
return nullable && this.GenerateBool() ? (object)null : this.rand.NextInt64();
|
|
case LayoutCode.VarUInt:
|
|
return nullable && this.GenerateBool() ? (object)null : this.rand.NextUInt64();
|
|
case LayoutCode.Float32:
|
|
return nullable && this.GenerateBool() ? (object)null : (float)this.rand.NextInt32();
|
|
case LayoutCode.Float64:
|
|
return nullable && this.GenerateBool() ? (object)null : (double)this.rand.NextInt64();
|
|
case LayoutCode.Float128:
|
|
return nullable && this.GenerateBool() ? (object)null : new Float128(this.rand.NextInt64(), this.rand.NextInt64());
|
|
case LayoutCode.Decimal:
|
|
return nullable && this.GenerateBool()
|
|
? (object)null
|
|
: new decimal(
|
|
this.rand.NextInt32(),
|
|
this.rand.NextInt32(),
|
|
this.rand.NextInt32(),
|
|
this.GenerateBool(),
|
|
this.rand.NextUInt8(0, 28));
|
|
case LayoutCode.DateTime:
|
|
{
|
|
Contract.Assert(DateTime.MinValue.Ticks == 0);
|
|
long ticks = unchecked((long)(this.rand.NextUInt64() % (ulong)(DateTime.MaxValue.Ticks + 1)));
|
|
return nullable && this.GenerateBool() ? (object)null : new DateTime(ticks);
|
|
}
|
|
|
|
case LayoutCode.UnixDateTime:
|
|
{
|
|
return nullable && this.GenerateBool() ? (object)null : new UnixDateTime(this.rand.NextInt64());
|
|
}
|
|
|
|
case LayoutCode.Guid:
|
|
return nullable && this.GenerateBool()
|
|
? (object)null
|
|
: new Guid(
|
|
this.rand.NextInt32(),
|
|
this.rand.NextInt16(),
|
|
this.rand.NextInt16(),
|
|
this.rand.NextUInt8(),
|
|
this.rand.NextUInt8(),
|
|
this.rand.NextUInt8(),
|
|
this.rand.NextUInt8(),
|
|
this.rand.NextUInt8(),
|
|
this.rand.NextUInt8(),
|
|
this.rand.NextUInt8(),
|
|
this.rand.NextUInt8());
|
|
|
|
case LayoutCode.MongoDbObjectId:
|
|
{
|
|
return nullable && this.GenerateBool() ? (object)null : new MongoDbObjectId(this.rand.NextUInt32(), this.rand.NextUInt64());
|
|
}
|
|
|
|
case LayoutCode.Utf8:
|
|
{
|
|
if (nullable && this.GenerateBool())
|
|
{
|
|
return null;
|
|
}
|
|
|
|
switch (storage)
|
|
{
|
|
case StorageKind.Variable:
|
|
length = this.rand.NextInt32(0, length == 0 ? this.config.StringValueLength.Next(this.rand) : length);
|
|
break;
|
|
case StorageKind.Sparse:
|
|
length = this.config.StringValueLength.Next(this.rand);
|
|
break;
|
|
}
|
|
|
|
CharDistribution distribution = this.config.UnicodeCharacters;
|
|
return Utf8String.TranscodeUtf16(this.GenerateString(length, distribution));
|
|
}
|
|
|
|
case LayoutCode.Binary:
|
|
{
|
|
if (nullable && this.GenerateBool())
|
|
{
|
|
return null;
|
|
}
|
|
|
|
switch (storage)
|
|
{
|
|
case StorageKind.Variable:
|
|
length = this.rand.NextInt32(0, length == 0 ? this.config.StringValueLength.Next(this.rand) : length);
|
|
break;
|
|
case StorageKind.Sparse:
|
|
length = this.config.StringValueLength.Next(this.rand);
|
|
break;
|
|
}
|
|
|
|
return this.GenerateBinary(length);
|
|
}
|
|
|
|
case LayoutCode.ObjectScope:
|
|
case LayoutCode.ImmutableObjectScope:
|
|
{
|
|
if (nullable && this.GenerateBool())
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Dictionary<Utf8String, object> dict = new Dictionary<Utf8String, object>(SamplingUtf8StringComparer.Default);
|
|
|
|
// TODO: add properties to an object scope.
|
|
return dict;
|
|
}
|
|
|
|
case LayoutCode.TypedArrayScope:
|
|
case LayoutCode.ImmutableTypedArrayScope:
|
|
{
|
|
if (nullable && this.GenerateBool())
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Contract.Assert(typeArg.TypeArgs.Count == 1);
|
|
length = this.config.CollectionValueLength.Next(this.rand);
|
|
List<object> arrayValue = new List<object>(length);
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
arrayValue.Add(this.GenerateLayoutType(resolver, typeArg.TypeArgs[0]));
|
|
}
|
|
|
|
return arrayValue;
|
|
}
|
|
|
|
case LayoutCode.TypedSetScope:
|
|
case LayoutCode.ImmutableTypedSetScope:
|
|
{
|
|
if (nullable && this.GenerateBool())
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Contract.Assert(typeArg.TypeArgs.Count == 1);
|
|
length = this.config.CollectionValueLength.Next(this.rand);
|
|
List<object> setValue = new List<object>(length);
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
object value = this.GenerateLayoutType(resolver, typeArg.TypeArgs[0]);
|
|
if (HybridRowValueGenerator.SetContains(resolver, setValue, value, typeArg) == -1)
|
|
{
|
|
setValue.Add(value);
|
|
}
|
|
}
|
|
|
|
return setValue;
|
|
}
|
|
|
|
case LayoutCode.TypedMapScope:
|
|
case LayoutCode.ImmutableTypedMapScope:
|
|
{
|
|
if (nullable && this.GenerateBool())
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Contract.Assert(typeArg.TypeArgs.Count == 2);
|
|
length = this.config.CollectionValueLength.Next(this.rand);
|
|
List<List<object>> mapValue = new List<List<object>>(length);
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
object key = this.GenerateLayoutType(resolver, typeArg.TypeArgs[0]);
|
|
if (HybridRowValueGenerator.MapContains(resolver, mapValue, key, typeArg) == -1)
|
|
{
|
|
object value = this.GenerateLayoutType(resolver, typeArg.TypeArgs[1]);
|
|
mapValue.Add(new List<object> { key, value });
|
|
}
|
|
}
|
|
|
|
return mapValue;
|
|
}
|
|
|
|
case LayoutCode.TupleScope:
|
|
case LayoutCode.ImmutableTupleScope:
|
|
case LayoutCode.TypedTupleScope:
|
|
case LayoutCode.ImmutableTypedTupleScope:
|
|
case LayoutCode.TaggedScope:
|
|
case LayoutCode.ImmutableTaggedScope:
|
|
case LayoutCode.Tagged2Scope:
|
|
case LayoutCode.ImmutableTagged2Scope:
|
|
{
|
|
if (nullable && this.GenerateBool())
|
|
{
|
|
return null;
|
|
}
|
|
|
|
List<object> tupleValue = new List<object>(typeArg.TypeArgs.Count);
|
|
foreach (TypeArgument t in typeArg.TypeArgs)
|
|
{
|
|
tupleValue.Add(this.GenerateLayoutType(resolver, t));
|
|
}
|
|
|
|
return tupleValue;
|
|
}
|
|
|
|
case LayoutCode.NullableScope:
|
|
case LayoutCode.ImmutableNullableScope:
|
|
{
|
|
if (this.GenerateBool())
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return this.GenerateLayoutType(resolver, typeArg.TypeArgs[0]);
|
|
}
|
|
|
|
case LayoutCode.Schema:
|
|
case LayoutCode.ImmutableSchema:
|
|
{
|
|
if (nullable && this.GenerateBool())
|
|
{
|
|
return null;
|
|
}
|
|
|
|
SchemaId schemaId = typeArg.TypeArgs.SchemaId;
|
|
Contract.Assert(schemaId != SchemaId.Invalid);
|
|
Layout layout = resolver.Resolve(schemaId);
|
|
Contract.Assert(layout != null);
|
|
|
|
Dictionary<Utf8String, object> dict = new Dictionary<Utf8String, object>(SamplingUtf8StringComparer.Default);
|
|
foreach (LayoutColumn c in layout.Columns)
|
|
{
|
|
dict[c.Path] = this.GenerateLayoutType(
|
|
resolver,
|
|
new TypeArgument(c.Type, c.TypeArgs),
|
|
length: c.Size,
|
|
storage: c.Storage);
|
|
}
|
|
|
|
return dict;
|
|
}
|
|
|
|
default:
|
|
Contract.Assert(false, $"Unknown type will be ignored: {typeArg}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static int SetContains(LayoutResolver resolver, List<object> set, object right, TypeArgument typeArg)
|
|
{
|
|
for (int i = 0; i < set.Count; i++)
|
|
{
|
|
object left = set[i];
|
|
if (HybridRowValueGenerator.DynamicTypeArgumentEquals(resolver, left, right, typeArg.TypeArgs[0]))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
private static int MapContains(LayoutResolver resolver, List<List<object>> map, object right, TypeArgument typeArg)
|
|
{
|
|
for (int i = 0; i < map.Count; i++)
|
|
{
|
|
List<object> pair = map[i];
|
|
Contract.Assert(pair.Count == 2);
|
|
if (HybridRowValueGenerator.DynamicTypeArgumentEquals(resolver, pair[0], right, typeArg.TypeArgs[0]))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
}
|