// ------------------------------------------------------------ // 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 leftDict = (Dictionary)left; Dictionary rightDict = (Dictionary)right; if (leftDict.Count != rightDict.Count) { return false; } // TODO: add properties to an object scope. return true; } case LayoutCode.TypedArrayScope: case LayoutCode.ImmutableTypedArrayScope: { List leftList = (List)left; List rightList = (List)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 leftList = (List)left; List rightList = (List)right; if (leftList.Count != rightList.Count) { return false; } List working = new List(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> leftList = (List>)left; List> rightList = (List>)right; if (leftList.Count != rightList.Count) { return false; } List> working = new List>(leftList); foreach (List rightItem in rightList) { int i = HybridRowValueGenerator.MapContains(resolver, working, rightItem[0], typeArg); if (i == -1) { return false; } List 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 leftList = (List)left; List rightList = (List)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 leftDict = (Dictionary)left; Dictionary rightDict = (Dictionary)right; if (leftDict.Count != rightDict.Count) { return false; } foreach (KeyValuePair 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(Func op, IEnumerable exclusions, int maxCandidates) { HashSet exclusionSet = new HashSet(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 dict = new Dictionary(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 arrayValue = new List(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 setValue = new List(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> mapValue = new List>(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 { 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 tupleValue = new List(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 dict = new Dictionary(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 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> map, object right, TypeArgument typeArg) { for (int i = 0; i < map.Count; i++) { List pair = map[i]; Contract.Assert(pair.Count == 2); if (HybridRowValueGenerator.DynamicTypeArgumentEquals(resolver, pair[0], right, typeArg.TypeArgs[0])) { return i; } } return -1; } } }