Question

Working on a simple SQL like dsl in groovy, and I have some builder classes to create a select statement.

e.g.

class SelectClause implements Clause {

  final Object[] attrs;
  final ModTupleDSL parent;
  FromClause fromClause;

  public SelectClause(ModTupleDSL parent, Object... attrs) {
    this.parent = parent;
    this.attrs = attrs;
  }

  public FromClause from (Object relation) {
    fromClause = new FromClause(this, relation);
    return fromClause;
  }
}

and

class FromClause implements Clause {

  final Object relation;
  final SelectClause parent;
  WhereClause whereClause;

  public FromClause(SelectClause parent, Object relation) {
    this.parent = parent;
    this.relation = relation;
  }

  public WhereClause where(Closure predicate) {
    whereClause = new WhereClause(this, predicate);
    return whereClause;
  }
}

etc...

In my script I can now say this:

select all from foo where {tuple, args -> tuple.get(id) == args[0]};

However, if I put newlines into it, that's an error.

select all 
from foo 
where {tuple, args -> tuple.get(id) == args[0]};

groovy.lang.MissingMethodException: 
No signature of method: foo.Foo.from() is applicable for argument types:
 (clojure.lang.Keyword) values: [:foo]|Possible solutions: grep(), find(), 
          find(groovy.lang.Closure), grep(java.lang.Object), wait(), any()

Is this just a side effect of optional parenthesis when parsing Groovy?

On a hunch, I put backslashes at the end of each line and it seems to work. e.g.

select all \
from foo \
where {tuple, args -> tuple.get(id) == args[0]};

Is there a better way?

Was it helpful?

Solution

No. The Groovy parser will assume that a statement is complete after the first line, inserting a ; at the end unless it knows the line goes on (for example when there is an open parenthesis or when you escape the EOL).

That's the drawback of a language which doesn't require semicolons.

OTHER TIPS

I guess @Aaron is right. Alternatively you can allow the methods to be called inside a closure and delegates the method calls to your query builder:

class QueryBuilder {

  def projection
  def origin
  def condition

  def call(closure) {
    def hydrated = closure.rehydrate this, this, this
    hydrated()
    "select $projection from $origin"
  }

  def select(projection) {
    this.projection = projection
  }

  def from(origin) {
    this.origin = origin
  }

  def where(closure) {
    condition = closure
  }

  def propertyMissing(String property) { property }
}

def sql = new QueryBuilder().call {
  select all 
  from foo 
  where {tuple, args -> tuple.get(id) == args[0]};
}

assert sql == "select all from foo"

I didn't finished the where part, but i guess you got the idea

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top