Example #1
0
        private NumberListColumn <int> BuildSampleColumn()
        {
            List <ArraySlice <int> > expected = new List <ArraySlice <int> >();

            expected.Add(new ArraySlice <int>(new int[] { 5, 6, 7 }));
            expected.Add(new ArraySlice <int>(new int[] { 2, 3, 4, 5 }));
            expected.Add(new ArraySlice <int>(Enumerable.Range(0, 8192).ToArray()));

            NumberListColumn <int> column = new NumberListColumn <int>();
            NumberListColumn <int> roundTripped;

            // Set each list, and verify they are correct when read back
            for (int i = 0; i < expected.Count; ++i)
            {
                column[i].SetTo(expected[i]);
            }

            for (int i = 0; i < expected.Count; ++i)
            {
                CollectionReadVerifier.VerifySame(expected[i], column[i]);
            }

            // Round trip and verify they deserialize correctly (note, these will be in a shared array now)
            roundTripped = TreeSerializer.RoundTrip(column, TreeFormat.Binary);
            for (int i = 0; i < expected.Count; ++i)
            {
                CollectionReadVerifier.VerifySame(expected[i], column[i]);
            }

            return(roundTripped);
        }
Example #2
0
        public void NumberListColumn_NullableCases()
        {
            // StringColumns are extremely common in object models,
            // so having very compact representations for common cases
            // is really important to file size for small databases.

            GenericNumberListColumn <int> column = new GenericNumberListColumn <int>(Nullability.DefaultToNull);
            TreeDiagnostics diagnostics;

            // Empty: { }
            diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary);
            Assert.True(diagnostics.Length <= 2);

            // All null: { IsNull: { Count: 100, Capacity: 100 } }
            for (int i = 0; i < 100; ++i)
            {
                column[i] = null;
            }

            CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false));
            diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary);
            Assert.True(1 == diagnostics.Children.Count);
            Assert.True(diagnostics.Length <= 13);

            // All empty: Only nulls false written
            List <int> empty = new List <int>();

            for (int i = 0; i < 100; ++i)
            {
                column[i] = empty;
            }

            CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false));
            diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary);
            Assert.True(1 == diagnostics.Children.Count);
            Assert.True(diagnostics.Length <= 13);

            // No nulls, No Empty: 4b + 2.125b / value (613b) + 4 pages x 4b (16b) + overhead (~10b)
            List <int> single = new List <int>();

            single.Add(1);

            for (int i = 0; i < 100; ++i)
            {
                column[i] = single;
            }

            CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false));
            diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary);
            Assert.True(1 == diagnostics.Children.Count);
            Assert.True(diagnostics.Length <= 640);

            // Nulls and Non-Nulls; both parts must be written
            column[50] = null;

            CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false));
            diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary);
            Assert.True(2 == diagnostics.Children.Count);
            Assert.True(diagnostics.Length <= 670);
        }
        public void StringColumn_LongValuesAndMerging()
        {
            StringColumn  column   = new StringColumn();
            List <string> expected = new List <string>();
            StringColumn  roundTripped;

            // Test values just at and above LargeValue limit
            expected.Add(new string(' ', 2047));
            expected.Add(null);
            expected.Add(string.Empty);
            expected.Add("Normal");
            expected.Add(new string(' ', 2048));

            for (int i = 0; i < expected.Count; ++i)
            {
                column[i] = expected[i];
            }

            // Verify values properly captured
            CollectionReadVerifier.VerifySame(expected, column);

            // Proactively Trim (before serialization) and verify values not corrupted
            column.Trim();
            CollectionReadVerifier.VerifySame(expected, column);

            // Verify roundtripped column and column not corrupted by serialization
            roundTripped = TreeSerializer.RoundTrip(column, TreeFormat.Binary);
            CollectionReadVerifier.VerifySame(expected, roundTripped);
            CollectionReadVerifier.VerifySame(expected, column);

            // Set a short value to long and a long value to short, and add another value
            expected[0] = new string(':', 2400);
            expected[2] = "MuchShorter";
            expected.Add("Simple");

            for (int i = 0; i < expected.Count; ++i)
            {
                column[i] = expected[i];
            }

            // Verify values read back correctly immediately
            CollectionReadVerifier.VerifySame(expected, column);

            // Verify values re-roundtrip again properly (merging old and new immutable values)
            roundTripped = TreeSerializer.RoundTrip(column, TreeFormat.Binary);
            CollectionReadVerifier.VerifySame(expected, roundTripped);
            CollectionReadVerifier.VerifySame(expected, column);

            // Add a value causing a gap; verify count, new value returned, values in gap defaulted properly
            column[100] = "Centennial";

            Assert.Equal(101, column.Count);
            Assert.Equal("Centennial", column[100]);
            Assert.Null(column[99]);
        }
        public void DistinctColumn_Conversion()
        {
            int defaultValue = -1;
            Func <DistinctColumn <int> > ctor = () => new DistinctColumn <int>(new NumberColumn <int>(defaultValue), defaultValue);

            DistinctColumn <int> column   = ctor();
            List <int>           expected = new List <int>();

            // Verify empty and set up to map values by default
            Assert.Empty(column);
            Assert.True(column.IsMappingValues);

            // Round trip and verify it resets back to mapping correctly
            column = TreeSerializer.RoundTrip(column, ctor, TreeFormat.Binary);
            Assert.Empty(column);
            Assert.True(column.IsMappingValues);

            // Add 1,000 values with 10 distinct values
            for (int i = 0; i < 1000; ++i)
            {
                int value = i % 10;
                column[i] = value;
                expected.Add(value);
            }

            // Verify column is mapping, has 11 unique values (default + 10), and matches expected array
            Assert.True(column.IsMappingValues);
            Assert.Equal(11, column.DistinctCount);
            CollectionReadVerifier.VerifySame(expected, column);

            // Round trip; verify mapped column rehydrates properly
            column = TreeSerializer.RoundTrip(column, ctor, TreeFormat.Binary);
            CollectionReadVerifier.VerifySame(expected, column);

            // Add enough values to force the column to convert
            for (int i = 1000; i < 1300; ++i)
            {
                column[i] = i;
                expected.Add(i);
            }

            Assert.False(column.IsMappingValues);
            Assert.Equal(-1, column.DistinctCount);
            CollectionReadVerifier.VerifySame(expected, column);

            // Round-trip; verify individual values column rehydrates properly
            column = TreeSerializer.RoundTrip(column, ctor, TreeFormat.Binary);
            CollectionReadVerifier.VerifySame(expected, column);

            // Test RemoveFromEnd on unmapped form of column
            column.RemoveFromEnd(100);
            expected.RemoveRange(expected.Count - 100, 100);
            CollectionReadVerifier.VerifySame(expected, column);
        }
Example #5
0
        public void StringColumn_EmptyCases()
        {
            // StringColumns are extremely common in object models,
            // so having very compact representations for common cases
            // is really important to file size for small databases.

            StringColumn    column = new StringColumn();
            TreeDiagnostics diagnostics;

            // Empty: { }
            diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary);
            Assert.True(diagnostics.Length <= 2);

            // All null: { IsNull: { Count: 100, Capacity: 100 } }
            for (int i = 0; i < 100; ++i)
            {
                column[i] = null;
            }

            CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false));
            diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary);
            Assert.True(1 == diagnostics.Children.Count);
            Assert.True(diagnostics.Length <= 13);

            // All empty: Only nulls false written
            for (int i = 0; i < 100; ++i)
            {
                column[i] = "";
            }

            CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false));
            diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary);
            Assert.True(1 == diagnostics.Children.Count);
            Assert.True(diagnostics.Length <= 13);

            // No nulls, No Empty: 3b / value (2b end + 1b text) + 4 pages x 4b + 20b overhead
            for (int i = 0; i < 100; ++i)
            {
                column[i] = "-";
            }

            CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false));
            diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary);
            Assert.True(1 == diagnostics.Children.Count);
            Assert.True(diagnostics.Length <= 336);

            // Nulls and Non-Nulls; both parts must be written
            column[50] = null;

            CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, TreeFormat.Binary, testDoubleDispose: false));
            diagnostics = TreeSerializer.Diagnostics(column, TreeFormat.Binary);
            Assert.True(2 == diagnostics.Children.Count);
            Assert.True(diagnostics.Length <= 336 + 40);
        }
        public void DistinctColumn_Remap()
        {
            int defaultValue = -1;
            Func <DistinctColumn <int> > ctor = () => new DistinctColumn <int>(new NumberColumn <int>(defaultValue), defaultValue);

            DistinctColumn <int> column   = ctor();
            List <int>           expected = new List <int>();

            // Add 1,000 values with 10 distinct values
            for (int i = 0; i < 1000; ++i)
            {
                int value = 1 + i % 10;
                column[i] = value;
                expected.Add(value);
            }

            // Verify column is mapping, has 11 unique values (default + 10), and matches expected array
            Assert.True(column.IsMappingValues);
            Assert.Equal(11, column.DistinctCount);
            CollectionReadVerifier.VerifySame(expected, column);

            // Verify Trim does not remap
            column.Trim();
            Assert.True(column.IsMappingValues);
            Assert.Equal(11, column.DistinctCount);
            CollectionReadVerifier.VerifySame(expected, column);

            // Set rows to just three (middle) values
            for (int i = 0; i < 1000; ++i)
            {
                int value = 3 + i % 3;
                column[i]   = value;
                expected[i] = value;
            }

            // Trim. Verify column finds unused values and removes them, and column values read back correctly
            column.Trim();
            Assert.True(column.IsMappingValues);
            Assert.Equal(4, column.DistinctCount);
            CollectionReadVerifier.VerifySame(expected, column);
        }
Example #7
0
        public void StringColumn_Basics()
        {
            Column.Basics <string>(
                () => new StringColumn(),
                null,
                "AnotherValue",
                (i) => i.ToString()
                );

            // Add enough to force conversion of values
            List <string> expected = new List <string>();
            StringColumn  column   = new StringColumn();

            for (int i = 0; i < 300; ++i)
            {
                column[i] = "A";
                expected.Add("A");
            }

            CollectionReadVerifier.VerifySame(expected, column, true);
        }
Example #8
0
        public static void Basics <T>(Func <IColumn <T> > builder, T defaultValue, T otherValue, Func <int, T> valueProvider)
        {
            IColumn <T> column   = builder();
            List <T>    expected = new List <T>();

            // ICollection basics
            Assert.False(column.IsReadOnly);
            Assert.False(column.IsSynchronized);
            Assert.Null(column.SyncRoot);

            Assert.Equal(typeof(T), column.Type);

            // Empty behavior
            Assert.Equal(0, column.Count);
            Assert.Equal(defaultValue, column[0]);
            Assert.Equal(defaultValue, column[10]);
            Assert.Equal(0, column.Count);

            // Empty roundtrip works
            CollectionReadVerifier.VerifySame(expected, TreeSerializer.RoundTrip(column, builder, TreeFormat.Binary));
            CollectionReadVerifier.VerifySame(expected, TreeSerializer.RoundTrip(column, builder, TreeFormat.Json));

            // Empty trim works
            column.Trim();

            // Append values
            for (int i = 0; i < 50; ++i)
            {
                T value = valueProvider(i);
                column.Add(value);
                expected.Add(value);
            }

            // Verify count, values, indexer, enumerators
            Assert.Equal(expected.Count, column.Count);
            CollectionReadVerifier.VerifySame <T>(expected, column);

            // Verify item type supports equatability (needed for IndexOf, Contains to work)
            Assert.True(otherValue.Equals(otherValue));
            Assert.Equal(otherValue.GetHashCode(), otherValue.GetHashCode());
            Assert.False(otherValue.Equals(defaultValue));
            Assert.NotEqual(otherValue.GetHashCode(), defaultValue?.GetHashCode() ?? 0);
            Assert.False(otherValue.Equals(1.45d));

            // Contains / IndexOf
            T notInList = valueProvider(50);

            if (!expected.Contains(notInList))
            {
                Assert.DoesNotContain(notInList, column);
                Assert.Equal(-1, column.IndexOf(notInList));
            }

            Assert.Contains(valueProvider(1), column);
            Assert.Equal(1, column.IndexOf(valueProvider(1)));

            // CopyTo
            T[] other = new T[column.Count + 1];
            column.CopyTo(other, 1);
            Assert.Equal(column[0], other[1]);

            // CopyTo preconditions
            if (!Debugger.IsAttached)
            {
                Assert.Throws <ArgumentNullException>(() => column.CopyTo(null, 0));
                Assert.Throws <ArgumentException>(() => column.CopyTo(other, 2));
                Assert.Throws <ArgumentOutOfRangeException>(() => column.CopyTo(other, -1));
                Assert.Throws <ArgumentException>(() => column.CopyTo((Array)(new decimal[column.Count]), 0));
            }

            // CopyTo (untyped)
            other = new T[column.Count];
            column.CopyTo((Array)other, 0);
            CollectionReadVerifier.VerifySame(other, column, quick: true);

            // Change existing value
            column[1] = otherValue;
            Assert.Equal(otherValue, column[1]);

            // Set value back to default, back to non-default
            column[1] = defaultValue;
            Assert.Equal(defaultValue, column[1]);
            column[1] = valueProvider(1);
            Assert.Equal(valueProvider(1), column[1]);

            // Add() without instance
            T item = column.Add();

            Assert.Equal(defaultValue, item);

            // Copy values via CopyItem
            column.CopyItem(1, column, 2);
            Assert.Equal(column[1], column[2]);
            column[1] = valueProvider(1);

            // CopyItem type checking
            if (!Debugger.IsAttached)
            {
                Assert.Throws <ArgumentException>(() => column.CopyItem(1, new NumberColumn <Decimal>(0.0m), 0));
            }

            // Append so resize is required
            column[100] = valueProvider(100);

            // Verify old values were kept, middle defaulted, last one set
            for (int i = 0; i < column.Count; ++i)
            {
                T value = (i < 50 || i == 100 ? valueProvider(i) : defaultValue);
                Assert.Equal(value, column[i]);
            }

            // Verify serialization round trip via all current serialization mechanisms
            CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, builder, TreeFormat.Binary), quick: true);
            CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, builder, TreeFormat.Json), quick: true);

            // Verify column is properly skippable (required to allow flexible file format schema)
            TreeSerializer.VerifySkip(column, TreeFormat.Binary);
            TreeSerializer.VerifySkip(column, TreeFormat.Json);

            // Verify original values are still there post-serialization (ensure column not corrupted by serialization)
            for (int i = 0; i < column.Count; ++i)
            {
                T value = (i < 50 || i == 100 ? valueProvider(i) : defaultValue);
                Assert.Equal(value, column[i]);
            }

            // Swap two non-default values, verify swapped, swap back
            column.Swap(10, 20);
            Assert.Equal(valueProvider(10), column[20]);
            Assert.Equal(valueProvider(20), column[10]);
            column.Swap(10, 20);
            Assert.Equal(valueProvider(10), column[10]);
            Assert.Equal(valueProvider(20), column[20]);

            // Swap a default with a non-default value, verify swapped, swap back
            column.Swap(30, 60);
            Assert.Equal(valueProvider(30), column[60]);
            Assert.Equal(defaultValue, column[30]);
            column.Swap(30, 60);
            Assert.Equal(valueProvider(30), column[30]);
            Assert.Equal(defaultValue, column[60]);

            // Verify RemoveFromEnd for only default values works
            column.RemoveFromEnd(column.Count - 60);
            Assert.Equal(60, column.Count);
            Assert.Equal(defaultValue, column[60]);

            // Verify RemoveFromEnd down to non-default values works
            column.RemoveFromEnd(60 - 10);
            Assert.Equal(10, column.Count);

            for (int i = 0; i < 60; ++i)
            {
                T value = (i < 10 ? valueProvider(i) : defaultValue);
                Assert.Equal(value, column[i]);
            }

            // Append a default value big enough another resize would be required
            //  Verify the count is tracked correctly, previous items are initialized to default
            column[201] = defaultValue;
            Assert.Equal(202, column.Count);
            Assert.Equal(defaultValue, column[200]);

            // Verify serialization handles 'many defaults at end' properly
            CollectionReadVerifier.VerifySame(column, TreeSerializer.RoundTrip(column, builder, TreeFormat.Binary), quick: true);

            // Verify Trim doesn't throw
            column.Trim();

            // Verify clear resets count and that previously set values are back to default if accessed
            column.Clear();
            Assert.Equal(0, column.Count);
            Assert.Equal(defaultValue, column[0]);
            Assert.Equal(defaultValue, column[1]);

            // Add one default value (inner array may still not be allocated), then try RemoveFromEnd
            column[0] = defaultValue;
            column.RemoveFromEnd(1);
            Assert.Equal(0, column.Count);

            if (!Debugger.IsAttached)
            {
                // Verify indexer range check (< 0 only; columns auto-size for bigger values)
                Assert.Throws <IndexOutOfRangeException>(() => column[-1]);

                // Verify Remove throws (not expected to be implemented)
                Assert.Throws <NotSupportedException>(() => column.Remove(defaultValue));
            }
        }
Example #9
0
        public static void VerifyCollection <T>(ICollection <T> row, Func <int, T> valueProvider)
        {
            List <T> expected = new List <T>();

            row.Clear();

            // Lists should not report ReadOnly
            Assert.False(row.IsReadOnly);

            // Set up 5 values each
            for (int i = 0; i < 5; ++i)
            {
                T value = valueProvider(i);
                row.Add(value);
                expected.Add(value);
            }

            // Verify inner lists
            CollectionReadVerifier.VerifySame(expected, row);

            // Find a value not in this particular set
            T notInExpected = default(T);

            for (int i = 5; i < 20; ++i)
            {
                notInExpected = valueProvider(i);
                if (!expected.Contains(notInExpected))
                {
                    break;
                }
            }

            // Verify count correct
            Assert.Equal(expected.Count, row.Count);

            for (int i = 0; i < expected.Count; ++i)
            {
                // Check Contains
                Assert.True(row.Contains(expected[i]) == true);
            }

            Assert.False(row.Contains(notInExpected));

            // Test CopyTo
            T[] other = new T[row.Count + 1];
            row.CopyTo(other, 1);

            for (int i = 0; i < expected.Count; ++i)
            {
                Assert.Equal(expected[i], other[i + 1]);
            }

            // CopyTo bounds
            if (!Debugger.IsAttached)
            {
                Assert.Throws <ArgumentNullException>(() => row.CopyTo(null, 0));
                Assert.Throws <ArgumentException>(() => row.CopyTo(other, 2));
                Assert.Throws <ArgumentOutOfRangeException>(() => row.CopyTo(other, -1));
            }

            // Append an item; verify appended, count changed
            T ten = valueProvider(10);

            row.Add(ten);
            expected.Add(ten);
            CollectionReadVerifier.VerifySame(expected, row);

            // Test Remove, RemoveAt
            Assert.False(row.Remove(notInExpected));
            Assert.True(row.Remove(ten));
            expected.Remove(ten);
            CollectionReadVerifier.VerifySame(expected, row);

            // Clear; verify empty, read-only static instance
            row.Clear();
            expected.Clear();
            Assert.Empty(row);

            // Test Add until resize required; verify old elements copied to larger array properly
            for (int i = 0; i < 50; ++i)
            {
                T value = valueProvider(i);
                row.Add(value);
                expected.Add(value);
            }

            CollectionReadVerifier.VerifySame(expected, row);
        }