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

Daniel Sun commented on GROOVY-9159:
------------------------------------

[~glaforge]  GINQ related keywords, e.g. {{from}}, {{where}}, {{select}}, etc., 
will be language keywords(as C# did). In order not to break existing source 
code as possible as we can, the new keywords will be added as identifiers too. 
So I think the code in the Gaelyk project should work as before.

As for the switch to enable the GINQ, I did also think about alternatives for 
lots of time but found they were verbose and strange to users, e.g.
 # Mark the scope with {{from}} and {{morf}}
{code:java}
    from persons p
    where p.age in [15, 25, 35]
    select p.name as personName
    morf // end of GINQ, inspired by Shell case statement
{code}

 # All GINQ related keywords are uppercase, but {{in}} and {{as}} will have 
uppercase versions, which is redundant and harmful to language
{code:java}
    FROM persons p
    WHERE p.age IN [15, 25, 35]
    SELECT p.name AS personName
{code}

So after I revised the GEP for many times, I finally propose the current 
syntax, which is elegant and easy to learn.

> [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