public ArchiveTable(JObject json, ParquetReader reader, ArchiveTableInformation tableInformation, string name) :
            base(json)
        {
            Name    = name;
            _reader = new RememberingParquetReader(reader);
            IsSaved = false;

            AddColumns(tableInformation);
        }
        internal ArchiveTable(JObject json, Archive.Archive archive, Guid sessionId, ArchiveTableInformation tableInformation) :
            base(json)
        {
            IsSaved    = true;
            _archive   = archive;
            _zipEntry  = tableInformation.ZipEntry;
            _sessionId = sessionId;

            if (tableInformation.Time == null)
            {
                throw new ArgumentException("Table does not have a column named 'Time'");
            }

            var streamTask = archive.OpenFile(_zipEntry);

            streamTask.Wait();
            using (var reader = new ParquetReader(streamTask.Result))
            {
                _reader = new RememberingParquetReader(reader);
            }

            AddColumns(tableInformation);
        }
        private async Task <ArchiveTableInformation> ParseTableInformation(JObject json, Archive.Archive archive, Guid sessionId)
        {
            // Find the properties in the JSON
            ArchiveStructure.GetUserMeta(json, out var meta, out var user);
            var pathArr = meta["attachments"].ToObject <string[]>() ?? throw new ArgumentException("Table is missing 'attachments'");
            var path    = "" + sessionId + pathArr[0];

            // Find the file in the archive
            var zipEntry = archive.FindFile(path) ?? throw new ZipException($"Table file '{path}' not found in archive");

            var tableInformation = new ArchiveTableInformation
            {
                ZipEntry = zipEntry,
                Columns  = new List <DataField>(),
                Uri      = path,
                Units    = new List <string>(),
                TimeUnit = "",
            };

            // Open the table file
            using (var stream = await archive.OpenFile(zipEntry))
                using (var reader = new ParquetReader(stream))
                {
                    var fields   = reader.Schema.GetDataFields();
                    var rawUnits = new string[0];
                    if (meta.ContainsKey("units"))
                    {
                        rawUnits = meta["units"].Select(unit => unit.Value <string>()).ToArray();
                    }

                    // Find all the column information
                    int idx = 0;
                    foreach (var field in fields)
                    {
                        // Fetch unit for current column
                        string currUnit = "";
                        if (idx < rawUnits.Length)
                        {
                            currUnit = rawUnits[idx];
                        }
                        idx++;

                        if (field.Name.Equals("time", StringComparison.OrdinalIgnoreCase))
                        {
                            tableInformation.Time     = field;
                            tableInformation.TimeUnit = currUnit;
                        }
                        else
                        {
                            tableInformation.Columns.Add(field);
                            tableInformation.Units.Add(currUnit);
                        }
                    }
                }

            if (meta.ContainsKey("is_world_clock"))
            {
                tableInformation.IsWorldSynchronized = meta["is_world_clock"].Value <bool>();
            }

            // Return the collected info
            return(tableInformation);
        }
        void AddColumns(ArchiveTableInformation tableInformation)
        {
            // TODO(sigurdal): Handle nullable data? column.HasNulls
            var timeInfo  = tableInformation.Time;
            var tableFile = _sessionId + "://" + tableInformation.Uri;
            var uri2      = tableFile + "/" + timeInfo.Name;
            var time      = new TableTimeIndex(timeInfo.Name, GenerateLoader <long>(_reader, timeInfo), tableInformation.IsWorldSynchronized, uri2, tableInformation.TimeUnit);

            for (var index = 0; index < tableInformation.Columns.Count; index++)
            {
                var    column = tableInformation.Columns[index];
                string unit   = null;
                if (index < tableInformation.Units?.Count)
                {
                    unit = tableInformation.Units[index];
                }

                var uri = tableFile + "/" + column.Name;

                if (column.DataType == DataType.String)
                {
                    this.AddColumn(column.Name, GenerateLoader <string>(_reader, column), time, uri, unit);
                    continue;
                }

                if (column.HasNulls)
                {
                    throw new NotImplementedException("Nullable columns are not yet implemented.");
                }

                switch (column.DataType)
                {
                case DataType.Boolean:
                    this.AddColumn(column.Name, GenerateLoader <bool>(_reader, column), time, uri, unit);
                    break;

                case DataType.Byte:
                    this.AddColumn(column.Name, GenerateLoader <byte>(_reader, column), time, uri, unit);
                    break;

                case DataType.Int32:
                    this.AddColumn(column.Name, GenerateLoader <int>(_reader, column), time, uri, unit);
                    break;

                case DataType.Int64:
                    this.AddColumn(column.Name, GenerateLoader <long>(_reader, column), time, uri, unit);
                    break;

                case DataType.Float:
                    this.AddColumn(column.Name, GenerateLoader <float>(_reader, column), time, uri, unit);
                    break;

                case DataType.Double:
                    this.AddColumn(column.Name, GenerateLoader <double>(_reader, column), time, uri, unit);
                    break;

                default:
                    throw new InvalidOperationException($"Cannot read {column.DataType} columns");
                }
            }
        }