static bool TryMakeColumnSpec(string name, feather.fbs.Type effectiveType, ref PrimitiveArray arrayDetails, string[] categoryLevels, DateTimePrecisionType precision, out ColumnSpec columnSpec, out string errorMessage)
        {
            var arrayOffset   = arrayDetails.Offset;
            var arrayLength   = arrayDetails.Length;
            var arrayNulls    = arrayDetails.NullCount;
            var arrayEncoding = arrayDetails.Encoding;

            // TODO (Dictionary Encoding)
            if (arrayEncoding != feather.fbs.Encoding.PLAIN)
            {
                throw new NotImplementedException();
            }
            // END TODO

            ColumnType type;
            bool       isNullable;

            if (!TryGetType(effectiveType, ref arrayDetails, precision, out type, out isNullable, out errorMessage))
            {
                columnSpec = default(ColumnSpec);
                return(false);
            }

            long numNullBytes = 0;

            if (isNullable)
            {
                numNullBytes = arrayLength / 8;
                if (arrayLength % 8 != 0)
                {
                    numNullBytes++;
                }
            }

            // a naive reading of the spec suggests that the null bitmask should be
            //   aligned based on the _type_ but it appears to always be long
            //   aligned.
            // this may be a bug in the spec
            int nullPadding = 0;

            if ((numNullBytes % FeatherMagic.NULL_BITMASK_ALIGNMENT) != 0)
            {
                nullPadding = FeatherMagic.NULL_BITMASK_ALIGNMENT - (int)(numNullBytes % FeatherMagic.NULL_BITMASK_ALIGNMENT);
            }
            var nullOffset = isNullable ? (arrayOffset) : -1;
            var dataOffset = !isNullable ? (arrayOffset) : (nullOffset + numNullBytes + nullPadding);

            columnSpec =
                new ColumnSpec
            {
                Name = name,
                NullBitmaskOffset = nullOffset,
                DataOffset        = dataOffset,
                Length            = arrayLength,
                Type           = type,
                CategoryLevels = categoryLevels,

                // only spin up this map if we've got categories to potentially map to
                CategoryEnumMap = categoryLevels != null ? new Dictionary <System.Type, CategoryEnumMapType>() : null
            };
            errorMessage = null;
            return(true);
        }
        static bool TryGetType(feather.fbs.Type effectiveType, ref PrimitiveArray array, DateTimePrecisionType precision, out ColumnType type, out bool isNullable, out string errorMessage)
        {
            isNullable = array.NullCount != 0;

            if (!isNullable)
            {
                switch (effectiveType)
                {
                case feather.fbs.Type.BINARY:
                    type         = ColumnType.Binary;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.BOOL:
                    type         = ColumnType.Bool;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.CATEGORY:
                    type         = ColumnType.Category;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.DATE:
                    type         = ColumnType.Date;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.DOUBLE:
                    type         = ColumnType.Double;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.FLOAT:
                    type         = ColumnType.Float;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.INT16:
                    type         = ColumnType.Int16;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.INT32:
                    type         = ColumnType.Int32;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.INT64:
                    type         = ColumnType.Int64;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.INT8:
                    type         = ColumnType.Int8;
                    errorMessage = null;
                    return(true);


                case feather.fbs.Type.TIMESTAMP:
                    switch (precision)
                    {
                    case DateTimePrecisionType.Microsecond:
                        type         = ColumnType.Timestamp_Microsecond;
                        errorMessage = null;
                        return(true);

                    case DateTimePrecisionType.Millisecond:
                        type         = ColumnType.Timestamp_Millisecond;
                        errorMessage = null;
                        return(true);

                    case DateTimePrecisionType.Nanosecond:
                        type         = ColumnType.Timestamp_Nanosecond;
                        errorMessage = null;
                        return(true);

                    case DateTimePrecisionType.Second:
                        type         = ColumnType.Timestamp_Second;
                        errorMessage = null;
                        return(true);

                    default:
                        errorMessage = $"Unknown precision {precision}";
                        type         = ColumnType.NONE;
                        return(false);
                    }

                case feather.fbs.Type.TIME:
                    switch (precision)
                    {
                    case DateTimePrecisionType.Microsecond:
                        type         = ColumnType.Time_Microsecond;
                        errorMessage = null;
                        return(true);

                    case DateTimePrecisionType.Millisecond:
                        type         = ColumnType.Time_Millisecond;
                        errorMessage = null;
                        return(true);

                    case DateTimePrecisionType.Nanosecond:
                        type         = ColumnType.Time_Nanosecond;
                        errorMessage = null;
                        return(true);

                    case DateTimePrecisionType.Second:
                        type         = ColumnType.Time_Second;
                        errorMessage = null;
                        return(true);

                    default:
                        errorMessage = $"Unknown precision {precision}";
                        type         = ColumnType.NONE;
                        return(false);
                    }

                case feather.fbs.Type.UINT16:
                    type         = ColumnType.Uint16;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.UINT32:
                    type         = ColumnType.Uint32;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.UINT64:
                    type         = ColumnType.Uint64;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.UINT8:
                    type         = ColumnType.Uint8;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.UTF8:
                    type         = ColumnType.String;
                    errorMessage = null;
                    return(true);

                default:
                    errorMessage = $"Unknown column type {array.Type}";
                    type         = ColumnType.NONE;
                    return(false);
                }
            }
            else
            {
                switch (effectiveType)
                {
                case feather.fbs.Type.BINARY:
                    type         = ColumnType.NullableBinary;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.BOOL:
                    type         = ColumnType.NullableBool;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.CATEGORY:
                    type         = ColumnType.NullableCategory;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.DATE:
                    type         = ColumnType.NullableDate;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.DOUBLE:
                    type         = ColumnType.NullableDouble;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.FLOAT:
                    type         = ColumnType.NullableFloat;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.INT16:
                    type         = ColumnType.NullableInt16;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.INT32:
                    type         = ColumnType.NullableInt32;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.INT64:
                    type         = ColumnType.NullableInt64;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.INT8:
                    type         = ColumnType.NullableInt8;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.TIMESTAMP:
                    switch (precision)
                    {
                    case DateTimePrecisionType.Microsecond:
                        type         = ColumnType.NullableTimestamp_Microsecond;
                        errorMessage = null;
                        return(true);

                    case DateTimePrecisionType.Millisecond:
                        type         = ColumnType.NullableTimestamp_Millisecond;
                        errorMessage = null;
                        return(true);

                    case DateTimePrecisionType.Nanosecond:
                        type         = ColumnType.NullableTimestamp_Nanosecond;
                        errorMessage = null;
                        return(true);

                    case DateTimePrecisionType.Second:
                        type         = ColumnType.NullableTimestamp_Second;
                        errorMessage = null;
                        return(true);

                    default:
                        errorMessage = $"Unknown precision {precision}";
                        type         = ColumnType.NONE;
                        return(false);
                    }

                case feather.fbs.Type.TIME:
                    switch (precision)
                    {
                    case DateTimePrecisionType.Microsecond:
                        type         = ColumnType.NullableTime_Microsecond;
                        errorMessage = null;
                        return(true);

                    case DateTimePrecisionType.Millisecond:
                        type         = ColumnType.NullableTime_Millisecond;
                        errorMessage = null;
                        return(true);

                    case DateTimePrecisionType.Nanosecond:
                        type         = ColumnType.NullableTime_Nanosecond;
                        errorMessage = null;
                        return(true);

                    case DateTimePrecisionType.Second:
                        type         = ColumnType.NullableTime_Second;
                        errorMessage = null;
                        return(true);

                    default:
                        errorMessage = $"Unknown precision {precision}";
                        type         = ColumnType.NONE;
                        return(false);
                    }

                case feather.fbs.Type.UINT16:
                    type         = ColumnType.NullableUint16;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.UINT32:
                    type         = ColumnType.NullableUint32;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.UINT64:
                    type         = ColumnType.NullableUint64;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.UINT8:
                    type         = ColumnType.NullableUint8;
                    errorMessage = null;
                    return(true);

                case feather.fbs.Type.UTF8:
                    type         = ColumnType.NullableString;
                    errorMessage = null;
                    return(true);

                default:
                    errorMessage = $"Unknown column type {array.Type}";
                    type         = ColumnType.NONE;
                    return(false);
                }
            }
        }