views:

127

answers:

2

I'm working on a library to generate SQL from LINQ expressions (basically a modified subset of LINQ-to-SQL). I'm using discriminated unions to model the SQL expressions, but have encountered some (perceived?) limitations. I want to do something like the following (note the last line):

type SqlSourceExpression =
    | Table of string
    | Join of JoinType * SqlSourceExpression * SqlSourceExpression * SqlExpression //ie, left, right, predicate

and SqlExpression =
    | Source of SqlSourceExpression
    | OrderBy of SqlExpression * SortDirection
    | Select of SqlSourceExpression * SqlExpression * OrderBy list //can't do this

I could do the following:

type SqlOrderByExpression = SqlExpression * SortDirection

...and change the last two lines to:

    | OrderBy of SqlOrderByExpression
    | Select of SqlSourceExpression * SqlExpression * SqlOrderByExpression list

But that appears to have two problems:

  1. SqlOrderByExpression is not a SqlExpression. This makes it hard to use the visitor pattern (maybe herein lies the problem?). Which means when traversing a Select expression I can't iterate over the list of order by expressions passing each one to Visit(expr:SqlExpression).

  2. SqlOrderByExpression is merely a type alias for a tuple, so no type information is preserved. That hurts readability IMO.

Is there a better way to model this? I tried the inheritance route, but I think DUs are MUCH easier to work with (barring the noted difficulty).

A: 

I'm not sure why SqlExpression, as a discriminated union, has cases for OrderBy and Select. What's allowed to go inside an OrderBy?

I think discriminated unions are making this more difficult than it needs to be. I think SqlSourceExpression, SqlOrderByExpression and SqlSelectExpression can be different types. You can make them records if you're worried about tuples being difficult to read (this is what I'd do in your situation).

I think an SqlExpression discriminated union will come in useful when you start to represent expression trees like (1 + SUM(ISNULL(Value1, Value2))) / COUNT(*), where the syntax is more flexible.

Tim Robinson
OrderBys are actually pretty expressive. If you wanted to, you could write `order by case when table.col = value then 1 else 0 end DESC`, or even sub-selects. But in any case, I completely agree: unless there's a business requirement to handle every edge case, the OP's data model is too heavyweight for his purposes.
Juliet
Good point: SqlOrderByExpression will contain an SqlExpression, but it won't be an SqlExpression in its own right.
Tim Robinson
@Juliet - Aside from the possibility of this model being too 'heavyweight' would you model these expressions similarly?
Daniel
@Daniel: no I wouldn't. The definition of `SqlExpression` doesn't look correct, because <code>let x= OrderBy(Source(Table "Users"), ASC)</code> is valid F# but probably doesn't map to a valid SQL expression. Instead of trying to create a comprehensive SQL model, I'd start really small: pick a very tiny subset of SQL (i.e. no subqueries, ORDER BY clauses limited to a column name and direction, no derived tables, etc), then build your model from there. Once you can represent a limited subset of SQL, it will be easier to build up from there.
Juliet
+1  A: 

As you work with FP technique (DU), as you don't need to remember OOP patterns, just use high-order functions (fold, map, zippers, etc).

In case you just want to specific view for your matching code, there is active patterns:

let (|OrderByTerm|OrderByList|_|)= function
  |OrderBy x -> Some (OrderByTerm x)
  |Select (_,_,xs) -> Some (OrderByList xs)
  |_ -> None
ssp