[ 
https://issues.apache.org/jira/browse/GROOVY-9159?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16884948#comment-16884948
 ] 

Guillaume Laforge commented on GROOVY-9159:
-------------------------------------------

It's probably too early to discuss syntax as you said in the notes that syntax 
is not set in stone for now, but I'm wondering how those successive parts of 
the query wouldn't be considered by the parser (and the developer reading code) 
as distinct statements.

So for example here:
def result =
        from persons p
        where p.age > 15 && p.age <= 35
        select p.name
In Groovy, it would usually be parsed as:
def result = from persons p;
where p.age > 15 && p.age <= 35;
select p.name;
Does it mean it requires a special support at the parser level to eagerly parse 
this query?

How much does it interfere with our _command-chain_ DSL syntax?

> [GEP] Support LINQ, aka GINQ
> ----------------------------
>
>                 Key: GROOVY-9159
>                 URL: https://issues.apache.org/jira/browse/GROOVY-9159
>             Project: Groovy
>          Issue Type: New Feature
>            Reporter: Daniel Sun
>            Priority: Major
>              Labels: features
>             Fix For: 4.x
>
>
> h2. *Ⅰ. Background*
> In order to make querying different types of data sources convenient, we need 
> a unified querying interface, i.e. GINQ
> h2. *Ⅱ. Solution*
> h3. *Basic rationale*
>  *Groovy User* ==_writes GINQ code_==> *Parrot Parser* ==generates AST==> 
> *GINQ Transformation* ==_transforms AST to {{Queryable}} method 
> invocations_==> *Bytecode Writer*
> h3. *transforms AST to {{Queryable}} method invocations will be designed for 
> different cases*
> # target objects are all collections
> {{Queryable}} method invocations are implemented by Java 8+ stream method 
> invocations
> # target objects are all DB related objects
> {{Queryable}} method invocations are implemented by *JOOQ* method 
> invocations( [https://github.com/jOOQ/jOOQ] ), which would be implemented as 
> a {{GINQ provider}} in a seperate sub-project(e.g. {{groovy-linq-jooq}}). 
> _Note: *JOOQ* is licensed under *APL2* too_( 
> [https://github.com/jOOQ/jOOQ/blob/master/LICENSE] )
> # target objects are XML, CSV, etc. related objects, or even mixed types of 
> objects
> We can treate the case as a special sub-case of case 1
> h3. *Interfaces & implementations*
> {code:java}
> interface Queryable<T> {
>       <U> Queryable<Tuple2<T, U>> innerJoin(Queryable<? extends U> queryable, 
> BiPredicate<? super T, ? super U> joiner);
>       <U> Queryable<Tuple2<T, U>> leftJoin(Queryable<? extends U> queryable, 
> BiPredicate<? super T, ? super U> joiner);
>       <U> Queryable<Tuple2<T, U>> rightJoin(Queryable<? extends U> queryable, 
> BiPredicate<? super T, ? super U> joiner);
>       Queryable<T> where(Predicate<? super T> filter);
>       <K, R> Queryable<R> groupBy(Function<? super T, ? extends K> mapper);
>       <U> Queryable<U> having(Predicate<? super U> filter);
>       Queryable<T> orderBy(BiPredicate<? super T, ? super T> predicate);
>       Queryable<T> limit(int size);
>       Queryable<T> offset(int index);
>       <U> Queryable<U> select(Function<? super T, ? extends U> mapper);
>       Queryable<T> union(Queryable<T> queryable);
>       Queryable<T> unionAll(Queryable<T> queryable);
>       Queryable<T> intersect(Queryable<T> queryable);
>       Queryable<T> minus(Queryable<T> queryable);
>       List<T> toList();
>       // TODO more methods.
> }
> {code}
> {code:java}
> class QueryableCollection<T> implements Queryable<T> {
>       static Queryable<T> from(Iterable<? extends T> ds) {
>               QueryableCollection<T> result = null;
>               /* TODO create QueryableCollection instance*/
>               return result ;
>       }
>       // TODO implements interface methods
> }
> {code}
> h3. *Note:*
> {color:#d04437}1. The exact syntax might be altered before introduction, 
> currently working on the general principle.{color}
>  2.GINQ will reuse most of standard SQL syntax, which can make the learning 
> curve smooth and avoid infringing the patent of Microsoft.
>  3. All GINQ related keywords are real keywords, e.g. {{from}}, {{where}}, 
> {{select}}, which is the approach applied by C#. In order to avoid breaking 
> existing source code as possible as we can, we should add the new keywords to 
> identifiers.
>  4. In order to support type inference better, {{select}} clause is placed at 
> the end of GINQ expression.
>  5. {{select P1, P2 ... Pn}} is a simplifed syntax of {{select 
> Tuple.tuple(P1, P2 ... Pn)}} and will create a {{List}} of {{Tuple}} 
> sub-class instances when and only when {{n >= 2}}
> h2. *Ⅲ. EBNF*
> h3.   TBD
> h2. *Ⅳ. Examples*
> h3. 1. Filtering
> {code:java}
> @groovy.transform.EqualsAndHashCode
> class Person {
>       String name
>       int age
> }
> def persons = [new Person(name: 'Daniel', age: 35), new Person(name: 'Peter', 
> age: 10), new Person(name: 'Alice', age: 22)]
> {code}
> h4. 1.1
> {code:java}
> def result =
>       from persons p
>       where p.age > 15 && p.age <= 35
>       select p.name
> assert ['Daniel', 'Alice'] == result
> {code}
> {code:java}
> // interface
> from(persons).where(p -> p.age > 15 && p.age <= 35).select(p -> 
> p.name).toList()
> {code}
> {code:java}
> // collection implementation
> persons.stream().filter(p -> p.age > 15 && p.age <= 35).map(p -> 
> p.name).collect(Collectors.toList())
> {code}
> h4. 1.2
> {code:java}
> def result =
>       from persons p
>       where p.age > 15 && p.age <= 35
>       select p
> assert [new Person(name: 'Daniel', age: 35), new Person(name: 'Alice', age: 
> 22)] == result
> {code}
> {code:java}
> // interface
> from(persons).where(p -> p.age > 15 && p.age <= 35).select(p -> p).toList()
> {code}
> {code:java}
> // collection implementation
> persons.stream().filter(p -> p.age > 15 && p.age <= 
> 35).collect(Collectors.toList())
> {code}
> h4. 1.3
> {code:java}
> def numbers = [1, 2, 3]
> def result =
>       from numbers t
>       where t <= 2
>       select t
> assert [1, 2] == result
> {code}
> {code:java}
> // interface
> from(numbers).where(t -> t <= 2).select(t -> t).toList()
> {code}
> {code:java}
> // collection implementation
> numbers.stream().filter(t -> t <= 2).collect(Collectors.toList())
> {code}
> h3. 2. Joining
> {code:java}
> import static groovy.lang.Tuple.*
> @groovy.transform.EqualsAndHashCode
> class Person {
>       String name
>       int age
>       City city
> }
> @groovy.transform.EqualsAndHashCode
> class City {
>       String name
> }
> def persons = [new Person(name: 'Daniel', age: 35, city: new 
> City('Shanghai')), new Person(name: 'Peter', age: 10, city: new 
> City('Beijing')), new Person(name: 'Alice', age: 22, city: new 
> City('Hangzhou'))]
> def cities = [new City('Shanghai'), new City('Beijing'), new 
> City('Guangzhou')]
> {code}
> h4. 2.1
> {code:java}
> // inner join
> def result =
>       from persons p 
>       innerjoin cities c on p.city.name == c.name
>       select p.name, c.name
> assert [tuple('Daniel', 'Shanghai'), tuple('Peter', 'Beijing')] == result
> {code}
> {code:java}
> // interface
> from(persons).innerJoin(from(cities), (p, c) -> p.city.name == 
> c.name).select((p, c) -> tuple(p.name, c.name)).toList()
> {code}
> {code:java}
> // collection implementation
> persons.stream()
>       .flatMap(p -> cities.stream().filter(c -> p.city.name == c.name).map(c 
> -> tuple(p.name, c.name)))
>       .collect(Collectors.toList())
> {code}
> h4. 2.2
> {code:java}
> def result =
>       from persons p, cities c
>       where p.city.name == c.name
>       select p.name, c.name
> assert [tuple('Daniel', 'Shanghai'), tuple('Peter', 'Beijing')] == result
> {code}
> {code:java}
> // interface
> from(persons).innerJoin(from(cities), (p, c) -> p.city.name == 
> c.name).select((p, c) -> tuple(p.name, c.name)).toList()
> {code}
> {code:java}
> // collection implementation
> persons.stream()
>       .flatMap(p -> cities.stream().filter(c -> p.city.name == c.name).map(c 
> -> tuple(p.name, c.name)))
>       .collect(Collectors.toList())
> {code}
> h4. 2.3
> {code:java}
> def result =
>       from persons p, cities c
>       where p.city == c
>       select p.name
> assert ['Daniel', 'Peter'] == result
> {code}
> {code:java}
> // interface
> from(persons).innerJoin(from(cities), (p, c) -> p.city == c).select((p, c) -> 
> p.name).toList()
> {code}
> {code:java}
> // collection implementation
> persons.stream()
>       .flatMap(p -> cities.stream().filter(c -> p.city == c).map(c -> p.name))
>       .collect(Collectors.toList())
> {code}
> h4. 2.4
> {code:java}
> // left outer join
> def result =
>       from persons p 
>       leftjoin cities c on p.city.name == c.name //  same with left outer join
>       select p.name, c.name
> assert [tuple('Daniel', 'Shanghai'), tuple('Peter', 'Beijing'), 
> tuple('Alice', null)] == result
> {code}
> h4. 2.5
> {code:java}
> // right outer join
> def result =
>       from persons p 
>       rightjoin cities c on p.city.name == c.name //  same with right outer 
> join
>       select p.name, c.name
> assert [tuple('Daniel', 'Shanghai'), tuple('Peter', 'Beijing'), tuple(null, 
> 'Guangzhou')] == result
> {code}
> h3. 3. Projection
> {code:java}
> import static groovy.lang.Tuple.*
> @groovy.transform.EqualsAndHashCode
> class Person {
>       String name
>       int age
> }
> def persons = [new Person(name: 'Daniel', age: 35), new Person(name: 'Peter', 
> age: 10), new Person(name: 'Alice', age: 22)]
> {code}
> h4. 3.1
> {code:java}
> def result =
>       from persons p
>       select p.name
> assert ['Daniel', 'Peter', 'Alice'] == result
> {code}
> h4. 3.2
> {code:java}
> def result =
>       from persons p
>       select p.name, p.age
> assert [tuple('Daniel', 35), tuple('Peter', 10), tuple('Alice', 22)] == result
> {code}
> h4. 3.3
> {code:java}
> def result =
>       from persons p
>       select [name: p.name, age: p.age]
> assert [ [name: 'Daniel', age: 35], [name: 'Peter', age: 10], [name: 'Alice', 
> age: 22] ] == result
> {code}
> h4. 3.4
> {code:java}
> def result =
>       from persons p
>       select new Person(name: p.name, age: p.age)
> assert persons == result
> {code}
> h4. 3.5
> {code:java}
> def result =
>       from persons p
>       select p
> assert persons == result
> {code}
> h3. 4. Grouping
> {code:java}
> import static groovy.lang.Tuple.*
> @groovy.transform.EqualsAndHashCode
> class Person {
>       String name
>       int age
>       String gender
> }
> def persons = [new Person(name: 'Daniel', age: 35, gender: 'Male'), new 
> Person(name: 'Peter', age: 10, gender: 'Male'), new Person(name: 'Alice', 
> age: 22, gender: 'Female')]
> {code}
> h4. 4.1
> {code:java}
> def result =
>       from persons p
>       group by p.gender
>       select p.gender, max(p.age)
> assert [tuple('Male', 35), tuple('Female', 22)] == result
> {code}
> h3. 5. Having
> {code:java}
> import static groovy.lang.Tuple.*
> @groovy.transform.EqualsAndHashCode
> class Person {
>       String name
>       int age
>       String gender
> }
> def persons = [new Person(name: 'Daniel', age: 35, gender: 'Male'), new 
> Person(name: 'Peter', age: 10, gender: 'Male'), new Person(name: 'Alice', 
> age: 22, gender: 'Female')]
> {code}
> h4. 5.1
> {code:java}
> def result =
>       from persons p
>       group by p.gender
>       having p.gender == 'Male'
>       select p.gender, MAX(p.age)
> assert [tuple('Male', 35)] == result
> {code}
> h3. 6. Sorting
> {code:java}
> @groovy.transform.EqualsAndHashCode
> class Person {
>       String name
>       int age
> }
> def persons = [new Person(name: 'Daniel', age: 35), new Person(name: 'Peter', 
> age: 10), new Person(name: 'Alice', age: 22)]
> {code}
> h4. 6.1
> {code:java}
> def result =
>       from persons p
>       order by p.age
>       select p.name
> assert ['Peter', 'Alice', 'Daniel'] == result
> {code}
> h4. 6.2
> {code:java}
> def result =
>       from persons p
>       order by p.age desc
>       select p.name
> assert ['Daniel', 'Alice', 'Peter'] == result
> {code}
> h3. 7. Pagination
> {code:java}
> def numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
> {code}
> h4. 7.1
> {code:java}
> def result =
>       from numbers n
>       limit 5 offset 2
>       select n
> assert [2, 3, 4, 5, 6] == result
> {code}
> h4. 7.2
> {code:java}
> def result =
>       from numbers n
>       limit 5
>       select n
> assert [0, 1, 2, 3, 4] == result
> {code}
> h3. 8. Nested Queries
> {code:java}
> def numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
> {code}
> h4. 8.1
> {code:java}
> def result =
>       from (
>               from numbers n
>               where n <= 5
>               select n
>       ) v
>       limit 5 offset 2
>       select v
> assert [2, 3, 4, 5] == result
> {code}
> h3. 9. WITH-Clause
> {code:java}
> def numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
> {code}
> h4. 9.1
> {code:java}
> def result =
>       with v as (
>               from numbers n
>               where n <= 5
>               select n
>       )
>       from v
>       limit 5 offset 2
>       select v
> assert [2, 3, 4, 5] == result
> {code}
> h3. 10. Union
> {code:java}
> def numbers1 = [0, 1, 2]
> def numbers2 = [2, 3, 4]
> {code}
> h4. 10.1
> {code:java}
> def result =
>       from numbers1 n
>       select n
>       unionall
>       from numbers2 n
>       select n
> assert [0, 1, 2, 2, 3, 4] == result
> {code}
> h4. 10.2
> {code:java}
> def result =
>       from numbers1 n
>       select n
>       union
>       from numbers2 n
>       select n
>       
> assert [0, 1, 2, 3, 4] == result
> {code}



--
This message was sent by Atlassian JIRA
(v7.6.14#76016)

Reply via email to