Syntax Trees using Composite/Intepreter Pattern Part 2

Part 1

Syntax Trees

In the GOF Design Patterns book you can see an example of using the composite pattern in the interpreter chapter, the way we will implement the syntax tree will be very similar. If you are not familiar with either the composite or interpreter patterns I suggest a trip to the data & object factory patterns site

Achieving what were after

The first step is to begin defining what were after, I write a quick simple unit test to get the ball rolling:

[Test]
public void Should_add_single_where_clause_with_string_value_on_WhereSection()
{
    string expected = "WHERE (col1 = 'test')";

    SelectQueryBuilder queryBuilder = new SelectQueryBuilder();

    EqualToWhere where1 = new EqualToWhere("col1", "test");
    queryBuilder.AddWhere(where1);

    string output = queryBuilder.WhereSection();
    Assert.AreEqual(expected, output);
}

We need to undertake a few steps to get this required result to compile:

  1. We need to add a new class “EqualToWhere”
  2. SelectQueryBuilder needs to be changed to support a AddWhere which can accept the new object
  3. SelectQueryBuilder needs a new method that we can use to check our result “WhereSection()”

Creating the new class

The EqualToWhere class will be what’s called a leaf node and will accept leftside and rightside arguments and will write out SQL via the Output method:

public class EqualToWhere
{
 public EqualToWhere(string leftSide, object rightSide)
 {
 	LeftSide = leftSide;
 	RightSide = rightSide;
 }

	public string Output()
 {
     return "";
        }
}

Changing SelectQuerybuilder’s AddWhere method

private EqualToWhere where;
public void AddWhere(EqualToWhere where)
{
 this.where = where;
}

Adding new WhereSection method to SelectQueryBuilder

I could have chosen not to do this step and instead used the existing BuildQuery method however I didn’t want to start breaking the rest of the object when I’m only interested in the where clauses.

public string WhereSection()
{
 return "WHERE " + where.Output();
}

Getting the test to pass

After we compile and run the tests we get a red cross our next task is to get a green tick, so if we change the Output method in EqualToWhere to this:

public string Output()
{
 StringBuilder output = new StringBuilder();
 output.Append("(");
 output.Append(LeftSide);
 output.Append(" = ");

	switch (RightSide.GetType().Name)
 {
 	case "String": output.Append("'" + ((string)RightSide).Replace("'", "''") + "'"); break;
 	case "DateTime": output.Append("'" + ((DateTime)RightSide).ToString("yyyy/MM/dd hh:mm:ss") + "'"); break;
 	case "DBNull": output.Append("NULL"); break;
 	case "Boolean": output.Append((bool)RightSide ? "1" : "0"); break;
 	case "SqlLiteral": output.Append(((SqlLiteral)RightSide).Value); break;
 	default: output.Append(RightSide.ToString()); break;
 }

	output.Append(")");
 return output.ToString();
}

Once this is compiled and the test ran again we should see green, great success!

Now for the refactoring

From the above we can establish that we need to have a common base class that we can use as the basis for our classes, this will include the standard Output method so we have a common interface no matter if we have a leaf class as in the example or a composite class which will be shown in the next part.

So we use an extract superclass refactoring:

public abstract class AbstractWhere
{
 public abstract string Output();

	protected string FormatForSqlFrom(object input)
 {
 	string SqlValue = string.Empty;

		switch (input.GetType().Name)
 	{
 		case "String": SqlValue = "'" + ((string)input).Replace("'", "''") + "'"; break;
 		case "DateTime": SqlValue = "'" + ((DateTime)input).ToString("yyyy/MM/dd hh:mm:ss") + "'"; break;
 		case "DBNull": SqlValue = "NULL"; break;
 		case "Boolean": SqlValue = ((bool)input ? "1" : "0"); break;
 		case "SqlLiteral": SqlValue = (((SqlLiteral)input).Value); break;
 		default: SqlValue = input.ToString(); break;
 	}

		return SqlValue;
 }
}

I also did a pull up method refactoring as I could predict that in the future derived classes will need to format objects to be used in writing out SQL and therefore this prevents duplication. Once the changes were made I went back to my unit tests to make sure I hadn’t broken anything, green again! We now have the common interface that the classes will use (essentially the Output method that returns SQL), we can now make the SelectQueryBuilder use the common interface rather than a specific derived version i.e. EqualToWhere that it references now, so lets make the change:

private AbstractWhere where
public void AddWhere(AbstractWhere where)
{
 this.where = where;
}

That’s reduced the coupling and given us greater extensibility, we can now pass in any object that adheres to our common interface (AbstractWhere). I do a re-run of the unit tests, still green were well on our way!

Creation of other leaf node classes

Now at the moment we have only 1 leaf node that can create the SQL to an equal operator against 2 operands, lets add another operator object TDD style, and to mix it up we’ll check against a date object on the right operand:

[Test]
public void Should_add_single_lessthan_where_clause_with_date_value_on_WhereSection()
{
     string expected = "WHERE (col1 < '2008/01/21 20:00:00')";
     SelectQueryBuilder queryBuilder = new SelectQueryBuilder();

     LessThanWhere where1 = new LessThanWhere("col1", new DateTime(2008,1,21,20,0,0));
 queryBuilder.AddWhere(where1);

     string output = queryBuilder.WhereSection();

     Assert.AreEqual(expected, output);
}

Spotted a small bug the ToString method for the DateTime formatting needed to be “yyyy/MM/dd HH:mm:ss” rather than “yyyy/MM/dd hh:mm:ss” a clear example of the usefulness of unit testing! 🙂 Our LessThanWhere class looks like this:

public class LessThanWhere
{
	public LessThanWhere(string leftSide, object rightSide)
	{
		LeftSide = leftSide;
		RightSide = rightSide;
	}

	public override string Output()
	{
		StringBuilder output = new StringBuilder();
		output.Append("(");
		output.Append(LeftSide);
		output.Append(" < ");
		output.Append(FormatForSqlFrom(RightSide));
		output.Append(")");
		return output.ToString();
	}
}

So I re-check my unit test and get it to pass, you can see the only change from the EqualToWhere class being the operator in the output method, this would be an ideal candidate to refactor the duplication with perhaps a BinaryWhere superclass using a template method, I’ll leave that as an optional user exercise 😉

Next steps

I will introduce composite classes that bring together the 2 leaf nodes we have created above to allow us to create logical comparisons, nested expressions and also prove the extensibility of what we have.

Part 3

Advertisements

2 thoughts on “Syntax Trees using Composite/Intepreter Pattern Part 2

  1. Pingback: Syntax Trees using Composite/Intepreter Pattern Part 3 « Journal of a software dev
  2. Pingback: Syntax Trees using Composite/Intepreter Pattern Part 1 « Journal of a software dev

Comments are closed.