This is the twentieth part of the SQLxD series. For your convenience you can find other parts in the table of contents in Part 1 – XML Transformation
We can extract nodes, transform them into rows, filter, join, group, order, and transform them. It is high time to glue all the things together in order to have engine working.
For connecting cells and expressions we will use transformers. They execute things in correct order and allow stacking of different operations.
1 2 3 4 5 6 7 8 9 10 11 12 |
using System.Collections.Generic; using Model; using QueryLogic.Transformers.CellTransformers; namespace QueryLogic.Transformers.RowTransformers { public interface IRowTransformer { IEnumerable<ICellTransformer> CellTransformers { get; } Row Calculate(Row row); } } |
We start with transformer for row. We can see that it transforms cells of row. We can implement identity transformer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
using System.Collections.Generic; using Model; using QueryLogic.Transformers.CellTransformers; namespace QueryLogic.Transformers.RowTransformers { public class IdentityRowTransformer : IRowTransformer { public Row Calculate(Row row) { return row; } public IEnumerable<ICellTransformer> CellTransformers { get { return new ICellTransformer[0]; } } protected bool Equals(IdentityRowTransformer other) { return true; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != GetType()) return false; return Equals((IdentityRowTransformer)obj); } public override int GetHashCode() { return 0; } } } |
We can also implement proper row transformer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
using System.Collections.Generic; using System.Linq; using Model; using QueryLogic.Transformers.CellTransformers; namespace QueryLogic.Transformers.RowTransformers { public class CellsRowTransformer : IRowTransformer { public CellsRowTransformer(IEnumerable<ICellTransformer> cellTransformers) { CellTransformers = cellTransformers; } public IEnumerable<ICellTransformer> CellTransformers { get; private set; } public Row Calculate(Row row) { var result = new List<Cell>(); foreach (ICellTransformer cellTransformer in CellTransformers) { result.Add(cellTransformer.TransformCell(row)); } return new Row(result); } protected bool Equals(CellsRowTransformer other) { return CellTransformers.SequenceEqual(other.CellTransformers); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != GetType()) return false; return Equals((CellsRowTransformer)obj); } public override int GetHashCode() { return (CellTransformers != null ? CellTransformers.GetHashCode() : 0); } } } |
Now cell transformer:
1 2 3 4 5 6 7 8 9 10 11 |
using Model; namespace QueryLogic.Transformers.CellTransformers { public interface ICellTransformer { ColumnHeader Source { get; } ColumnHeader Result { get; } Cell TransformCell(Row sourceRow); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
using Model; using QueryLogic.Expressions.CellExpressions; namespace QueryLogic.Transformers.CellTransformers { public class CellTransformer : ICellTransformer { public CellTransformer(ICellExpression cellExpression) { CellExpression = cellExpression; } public CellTransformer(ICellExpression cellExpression, string resultColumn) : this(cellExpression) { Result = new ColumnHeader("", resultColumn); } public ICellExpression CellExpression { get; private set; } public ColumnHeader Source { get { return CellExpression.Source; } } public ColumnHeader Result { get; private set; } public Cell TransformCell(Row sourceRow) { Cell tmp = CellExpression.Calculate(sourceRow); Result = new ColumnHeader(tmp.ColumnHeader.Schema, Result != null ? Result.Name : tmp.ColumnHeader.Name); return new Cell(Result, tmp.Value); } protected bool Equals(CellTransformer other) { return Equals(CellExpression, other.CellExpression) && string.Equals(Result, other.Result); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != GetType()) return false; return Equals((CellTransformer)obj); } public override int GetHashCode() { unchecked { return ((CellExpression != null ? CellExpression.GetHashCode() : 0) * 397) ^ (Result != null ? Result.GetHashCode() : 0); } } } } |
We can see that transformer takes expression and calculates result by using it. This is the thing which glues all other mechanisms together: we pass row to row transformer which uses cell expression to transform cell and return new row.
And now tests:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
using Model; using NUnit.Framework; using QueryLogic.Transformers.RowTransformers; namespace QueryLogic.Test.Transformers.RowTransformers { [TestFixture] public class IdentityRowTransformerTests { [Test] public void Calculate_ShouldReturnSameRow() { // Arrage var row = new Row(); row.AddCell(new Cell(new ColumnHeader("schema", "Column1"), "value1")); row.AddCell(new Cell(new ColumnHeader("schema", "Column2"), "value2")); var transformer = new IdentityRowTransformer(); var expected = new Row(row.Cells); // Act Row actual = transformer.Calculate(row); // Assert Assert.That(actual, Is.EqualTo(expected)); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
using System.Collections.Generic; using Model; using NUnit.Framework; using QueryLogic.Expressions.CellExpressions; using QueryLogic.Transformers.CellTransformers; using QueryLogic.Transformers.RowTransformers; using Rhino.Mocks; namespace QueryLogic.Test.Transformers.RowTransformers { [TestFixture] internal class CellsRowTransformerTests { [Test] public void Calculate_ShouldReturnTransformedColumn() { // Arrage var row = new Row(); var columnHeader = new ColumnHeader("schema", "column"); var cell = new Cell(columnHeader, "value"); row.AddCell(cell); var cellExpressionMock = MockRepository.GenerateStub<ICellExpression>(); cellExpressionMock.Stub(s => s.Calculate(null)).IgnoreArguments().Return(cell); var cellTransformers = new List<ICellTransformer> { new CellTransformer(cellExpressionMock), }; var transformer = new CellsRowTransformer(cellTransformers); IEnumerable<Cell> expected = row.Cells; // Act IEnumerable<Cell> actual = transformer.Calculate(row).Cells; // Assert CollectionAssert.AreEquivalent(expected, actual); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
using Model; using NUnit.Framework; using QueryLogic.Expressions.CellExpressions; using QueryLogic.Transformers.CellTransformers; using Rhino.Mocks; namespace QueryLogic.Test.Transformers.CellTransformers { [TestFixture] public class CellTransformerTests { [Test] public void TransformCell_ShouldReturnCellWithValueAndName() { // Arrange var cell = new Cell("testSchema", "testName", "testValue"); var expressionMock = MockRepository.GenerateMock<ICellExpression>(); expressionMock.Stub(m => m.Calculate(null)).IgnoreArguments().Return(cell); var transformer = new CellTransformer(expressionMock, "resultName"); var expected = new Cell("testSchema", "resultName", "testValue"); // Act Cell actual = transformer.TransformCell(new Row(new []{cell})); // Assert Assert.That(actual, Is.EqualTo(expected)); } } } |
And we are ready to implement last operator: SELECT.