/// <summary> /// A helper method to query and write metadata to the stream. /// </summary> /// <param name="writer">A binary writer, which if metadata exists for the /// indicated column the base stream will be positioned just past the end of /// the written metadata table of contents, and if metadata does not exist /// remains unchanged</param> /// <param name="schema">The schema to query for metadat</param> /// <param name="col">The column we are attempting to get metadata for</param> /// <param name="ch">The channel to which we write any diagnostic information</param> /// <returns>The offset of the metadata table of contents, or 0 if there was /// no metadata</returns> private long WriteMetadata(BinaryWriter writer, Schema schema, int col, IChannel ch) { _host.AssertValue(writer); _host.AssertValue(schema); _host.Assert(0 <= col && col < schema.Count); int count = 0; WriteMetadataCoreDelegate del = WriteMetadataCore <int>; MethodInfo methInfo = del.GetMethodInfo().GetGenericMethodDefinition(); object[] args = new object[] { writer.BaseStream, schema, col, null, null, null }; List <long> offsets = new List <long>(); offsets.Add(writer.BaseStream.Position); var metadataInfos = new List <Tuple <string, IValueCodec, CompressionKind> >(); var kinds = new HashSet <string>(); // Write all metadata blocks for this column to the file, one after the other, keeping // track of the location and size of each for when we write the metadata table of contents. // (To be clear, this specific layout is not required by the format.) foreach (var metaColumn in schema[col].Metadata.Schema) { _host.Check(!string.IsNullOrEmpty(metaColumn.Name), "Metadata with null or empty kind detected, disallowed"); _host.Check(metaColumn.Type != null, "Metadata with null type detected, disallowed"); if (!kinds.Add(metaColumn.Name)) { throw _host.Except("Metadata with duplicate kind '{0}' encountered, disallowed", metaColumn.Name, schema[col].Name); } args[3] = metaColumn.Name; args[4] = metaColumn.Type; IValueCodec codec = (IValueCodec)methInfo.MakeGenericMethod(metaColumn.Type.RawType).Invoke(this, args); if (codec == null) { // Nothing was written. ch.Warning("Could not get codec for type {0}, dropping column '{1}' index {2} metadata kind '{3}'", metaColumn.Type, schema[col].Name, col, metaColumn.Name); continue; } offsets.Add(writer.BaseStream.Position); _host.CheckIO(offsets[offsets.Count - 1] > offsets[offsets.Count - 2], "Bad offsets detected during write"); metadataInfos.Add(Tuple.Create(metaColumn.Name, codec, (CompressionKind)args[5])); count++; } if (metadataInfos.Count == 0) { _host.CheckIO(writer.BaseStream.Position == offsets[0], "unexpected offset after no writing of metadata"); return(0); } // Write the metadata table of contents just past the end of the last metadata block. // *** Metadata TOC format *** // LEB128 int: Number of metadata TOC entries // Metadata TOC entries: As many of these as indicated by the count above long expectedPosition = offsets[metadataInfos.Count]; writer.WriteLeb128Int((ulong)metadataInfos.Count); expectedPosition += Utils.Leb128IntLength((ulong)metadataInfos.Count); for (int i = 0; i < metadataInfos.Count; ++i) { // *** Metadata TOC entry format *** // string: metadata kind // codec definition: metadata codec // CompressionKind(byte): block compression strategy // long: Offset into the stream of the start of the metadata block // LEB128 int: Byte size of the metadata block in the file writer.Write(metadataInfos[i].Item1); int stringLen = Encoding.UTF8.GetByteCount(metadataInfos[i].Item1); expectedPosition += Utils.Leb128IntLength((ulong)stringLen) + stringLen; _host.CheckIO(writer.BaseStream.Position == expectedPosition, "unexpected offsets after metadata table of contents kind"); expectedPosition += _factory.WriteCodec(writer.BaseStream, metadataInfos[i].Item2); _host.CheckIO(writer.BaseStream.Position == expectedPosition, "unexpected offsets after metadata table of contents type description"); writer.Write((byte)metadataInfos[i].Item3); expectedPosition++; writer.Write(offsets[i]); expectedPosition += sizeof(long); long blockSize = offsets[i + 1] - offsets[i]; writer.WriteLeb128Int((ulong)blockSize); expectedPosition += Utils.Leb128IntLength((ulong)blockSize); _host.CheckIO(writer.BaseStream.Position == expectedPosition, "unexpected offsets after metadata table of contents location"); } _host.Assert(metadataInfos.Count == offsets.Count - 1); return(offsets[metadataInfos.Count]); }