[ https://issues.apache.org/jira/browse/GROOVY-9159?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16885074#comment-16885074 ]
Daniel Sun commented on GROOVY-9159: ------------------------------------ [~glaforge] GINQ is much more complicated than what command chain expression can support, so it will has its own syntax(following the steps of C#). Also, I believe our parrot parser will handle the new syntax properly at the parser level and not interfere with command chain expression 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)