groovy-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Daniel Sun (JIRA)" <j...@apache.org>
Subject [jira] [Updated] (GROOVY-9159) [GEP] Support LINQ, aka GINQ
Date Sat, 10 Aug 2019 17:37:00 GMT

     [ https://issues.apache.org/jira/browse/GROOVY-9159?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]

Daniel Sun updated GROOVY-9159:
-------------------------------
    Description: 
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]
) 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*
 * {{Queryable}}
 [https://github.com/danielsun1106/groovy-linq/blob/master/src/main/java/groovy/linq/Queryable.java]
 * {{QueryableCollection}}
 [https://github.com/danielsun1106/groovy-linq/blob/master/src/main/java/groovy/linq/QueryableCollection.java]

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(name: 'Shanghai')), new
Person(name: 'Peter', age: 10, city: new City(name: 'Beijing')), new Person(name: 'Alice',
age: 22, city: new City(name: 'Hangzhou'))]

def cities = [new City(name: 'Shanghai'), new City(name: 'Beijing'), new City(name: '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}
{code:java}
// interface
from(persons).leftjoin(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()
            .map(c -> p.city.name == c.name ? c : GinqConstant.NULL)
            .reduce(new LinkedList(), (r, e) -> {
                int size = r.size()
                if (0 == size) {
                    r.add(e)
                    return r
                }
                
                int lastIndex = size - 1
                Object lastElement = r.get(lastIndex)
                
                if (GinqConstant.NULL != e) {
                    if (GinqConstant.NULL == lastElement) {
                        r.set(lastIndex, e)
                    } else {
                        r.add(e)
                    }
                }
                
                return r
            }).stream()
                .map(c -> Tuple.tuple(p.name, GinqConstant.NULL == c ? GinqConstant.NULL
: c.name)))
                .collect(Collectors.toList())
{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
	groupby 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
	groupby 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
	orderby p.age
	select p.name

assert ['Peter', 'Alice', 'Daniel'] == result
{code}
h4. 6.2
{code:java}
def result =
	from persons p
	orderby 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 2, 5
	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 2, 5
	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 2, 5
	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}

  was:
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]
) 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*
*  {{Queryable}}
https://github.com/danielsun1106/groovy-linq/blob/master/src/main/java/groovy/linq/Queryable.java
*  {{QueryableCollection}}
https://github.com/danielsun1106/groovy-linq/blob/master/src/main/java/groovy/linq/QueryableCollection.java

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(name: 'Shanghai')), new
Person(name: 'Peter', age: 10, city: new City(name: 'Beijing')), new Person(name: 'Alice',
age: 22, city: new City(name: 'Hangzhou'))]

def cities = [new City(name: 'Shanghai'), new City(name: 'Beijing'), new City(name: '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}
{code:java}
// interface
from(persons).leftjoin(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()
            .map(c -> p.city.name == c.name ? c : GinqConstant.NULL)
            .reduce(new LinkedList(), (r, e) -> {
                int size = r.size()
                if (0 == size) {
                    r.add(e)
                    return r
                }
                
                int lastIndex = size - 1
                Object lastElement = r.get(lastIndex)
                
                if (GinqConstant.NULL != e) {
                    if (GinqConstant.NULL == lastElement) {
                        r.set(lastIndex, e)
                    } else {
                        r.add(e)
                    }
                }
                
                return r
            }).stream()
                .map(c -> Tuple.tuple(p.name, GinqConstant.NULL == c ? GinqConstant.NULL
: c.name)))
                .collect(Collectors.toList())
{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
	groupby 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
	groupby 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
	orderby p.age
	select p.name

assert ['Peter', 'Alice', 'Daniel'] == result
{code}
h4. 6.2
{code:java}
def result =
	from persons p
	orderby 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 2, 5
	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 2, 5
	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 2, 5
	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}


> [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]
) 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*
>  * {{Queryable}}
>  [https://github.com/danielsun1106/groovy-linq/blob/master/src/main/java/groovy/linq/Queryable.java]
>  * {{QueryableCollection}}
>  [https://github.com/danielsun1106/groovy-linq/blob/master/src/main/java/groovy/linq/QueryableCollection.java]
> 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(name: 'Shanghai')),
new Person(name: 'Peter', age: 10, city: new City(name: 'Beijing')), new Person(name: 'Alice',
age: 22, city: new City(name: 'Hangzhou'))]
> def cities = [new City(name: 'Shanghai'), new City(name: 'Beijing'), new City(name: '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}
> {code:java}
> // interface
> from(persons).leftjoin(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()
>             .map(c -> p.city.name == c.name ? c : GinqConstant.NULL)
>             .reduce(new LinkedList(), (r, e) -> {
>                 int size = r.size()
>                 if (0 == size) {
>                     r.add(e)
>                     return r
>                 }
>                 
>                 int lastIndex = size - 1
>                 Object lastElement = r.get(lastIndex)
>                 
>                 if (GinqConstant.NULL != e) {
>                     if (GinqConstant.NULL == lastElement) {
>                         r.set(lastIndex, e)
>                     } else {
>                         r.add(e)
>                     }
>                 }
>                 
>                 return r
>             }).stream()
>                 .map(c -> Tuple.tuple(p.name, GinqConstant.NULL == c ? GinqConstant.NULL
: c.name)))
>                 .collect(Collectors.toList())
> {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
> 	groupby 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
> 	groupby 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
> 	orderby p.age
> 	select p.name
> assert ['Peter', 'Alice', 'Daniel'] == result
> {code}
> h4. 6.2
> {code:java}
> def result =
> 	from persons p
> 	orderby 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 2, 5
> 	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 2, 5
> 	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 2, 5
> 	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)

Mime
View raw message