1 /*
  2  * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 import jdk.incubator.code.dialect.java.JavaOp;
 25 import org.testng.Assert;
 26 import org.testng.annotations.Test;
 27 
 28 import jdk.incubator.code.Op;
 29 import jdk.incubator.code.Quoted;
 30 import jdk.incubator.code.Value;
 31 import jdk.incubator.code.dialect.java.MethodRef;
 32 import jdk.incubator.code.interpreter.Interpreter;
 33 import java.lang.invoke.MethodHandles;
 34 import jdk.incubator.code.dialect.java.JavaType;
 35 import jdk.incubator.code.TypeElement;
 36 import java.util.stream.Stream;
 37 
 38 import static jdk.incubator.code.dialect.java.MethodRef.method;
 39 import static jdk.incubator.code.dialect.core.CoreOp.*;
 40 import static jdk.incubator.code.dialect.core.CoreType.functionType;
 41 
 42 /*
 43  * @test
 44  * @modules jdk.incubator.code
 45  * @run testng TestLinqUsingQuoted
 46  */
 47 
 48 public class TestLinqUsingQuoted {
 49 
 50     // Query interfaces
 51 
 52     public interface Queryable {
 53         TypeElement elementType();
 54 
 55         // Queryable<T> -> Queryable<U>
 56         FuncOp expression();
 57 
 58         QueryProvider provider();
 59 
 60         // T -> boolean
 61         // Predicate<T>
 62         default Queryable where(Quoted f) {
 63             // @@@@ validate
 64             ClosureOp c = (ClosureOp) f.op();
 65             return insertQuery(elementType(), "where", c);
 66         }
 67 
 68         // T -> R
 69         // Function<T, R>
 70         default Queryable select(Quoted f) {
 71             // @@@@ validate
 72             ClosureOp c = (ClosureOp) f.op();
 73             return insertQuery(c.invokableType().returnType(), "select", c);
 74         }
 75 
 76         private Queryable insertQuery(TypeElement et, String name, ClosureOp c) {
 77             QueryProvider qp = provider();
 78 
 79             FuncOp currentQueryExpression = expression();
 80             FuncOp nextQueryExpression = currentQueryExpression.transform((block, op) -> {
 81                 if (op instanceof ReturnOp rop && rop.ancestorBody() == currentQueryExpression.body()) {
 82                     Value query = block.context().getValue(rop.returnValue());
 83 
 84                     Op.Result quotedLambda = block.op(quoted(block.parentBody(), qblock -> c));
 85 
 86                     MethodRef md = method(qp.queryableType(), name,
 87                             functionType(qp.queryableType(), QuotedOp.QUOTED_TYPE));
 88                     Op.Result queryable = block.op(JavaOp.invoke(md, query, quotedLambda));
 89 
 90                     block.op(return_(queryable));
 91                 } else {
 92                     block.op(op);
 93                 }
 94                 return block;
 95             });
 96 
 97             return qp.createQuery(et, nextQueryExpression);
 98         }
 99 
100         // Iterate
101         // Queryable -> Stream
102         default QueryResult elements() {
103             TypeElement resultType = JavaType.parameterized(JavaType.type(Stream.class), (JavaType) elementType());
104             return insertQueryResult("elements", resultType);
105         }
106 
107         // Count
108         // Queryable -> Long
109         default QueryResult count() {
110             return insertQueryResult("count", JavaType.LONG);
111         }
112 
113         private QueryResult insertQueryResult(String name, TypeElement resultType) {
114             QueryProvider qp = provider();
115 
116             // Copy function expression, replacing return type
117             FuncOp currentQueryExpression = expression();
118             FuncOp nextQueryExpression = func("queryresult",
119                     functionType(qp.queryResultType(), currentQueryExpression.invokableType().parameterTypes()))
120                     .body(b -> b.inline(currentQueryExpression, b.parameters(), (block, query) -> {
121                         MethodRef md = method(qp.queryableType(), name, functionType(qp.queryResultType()));
122                         Op.Result queryResult = block.op(JavaOp.invoke(md, query));
123 
124                         block.op(return_(queryResult));
125                     }));
126             return qp.createQueryResult(resultType, nextQueryExpression);
127         }
128     }
129 
130     public interface QueryResult {
131         TypeElement resultType();
132 
133         // Queryable -> QueryResult
134         FuncOp expression();
135 
136         Object execute();
137     }
138 
139     public interface QueryProvider {
140         TypeElement queryableType();
141 
142         TypeElement queryResultType();
143 
144         Queryable createQuery(TypeElement elementType, FuncOp expression);
145 
146         QueryResult createQueryResult(TypeElement resultType, FuncOp expression);
147 
148         Queryable newQuery(TypeElement elementType);
149     }
150 
151 
152     // Query implementation
153 
154     public static final class TestQueryable implements Queryable {
155         final TypeElement elementType;
156         final TestQueryProvider provider;
157         final FuncOp expression;
158 
159         TestQueryable(TypeElement elementType, TestQueryProvider provider) {
160             this.elementType = elementType;
161             this.provider = provider;
162 
163             // Initial expression is an identity function
164             var funType = functionType(provider().queryableType(), provider().queryableType());
165             this.expression = func("query", funType)
166                     .body(b -> b.op(return_(b.parameters().get(0))));
167         }
168 
169         TestQueryable(TypeElement elementType, TestQueryProvider provider, FuncOp expression) {
170             this.elementType = elementType;
171             this.provider = provider;
172             this.expression = expression;
173         }
174 
175         @Override
176         public TypeElement elementType() {
177             return elementType;
178         }
179 
180         @Override
181         public FuncOp expression() {
182             return expression;
183         }
184 
185         @Override
186         public QueryProvider provider() {
187             return provider;
188         }
189     }
190 
191     public record TestQueryResult(TypeElement resultType, FuncOp expression) implements QueryResult {
192         @Override
193         public Object execute() {
194             // @@@ Compile/translate the expression and execute it
195             throw new UnsupportedOperationException();
196         }
197     }
198 
199     public static final class TestQueryProvider implements QueryProvider {
200         final TypeElement queryableType;
201         final TypeElement queryResultType;
202 
203         TestQueryProvider() {
204             this.queryableType = JavaType.type(Queryable.class);
205             this.queryResultType = JavaType.type(QueryResult.class);
206         }
207 
208         @Override
209         public TypeElement queryableType() {
210             return queryableType;
211         }
212 
213         @Override
214         public TypeElement queryResultType() {
215             return queryResultType;
216         }
217 
218         @Override
219         public TestQueryable createQuery(TypeElement elementType, FuncOp expression) {
220             return new TestQueryable(elementType, this, expression);
221         }
222 
223         @Override
224         public QueryResult createQueryResult(TypeElement resultType, FuncOp expression) {
225             return new TestQueryResult(resultType, expression);
226         }
227 
228         @Override
229         public Queryable newQuery(TypeElement elementType) {
230             return new TestQueryable(elementType, this);
231         }
232     }
233 
234 
235     static class Customer {
236         // 1st column
237         String contactName;
238         // 2nd column
239         String phone;
240         // 3rd column
241         String city;
242     }
243 
244     @Test
245     public void testSimpleQuery() {
246         QueryProvider qp = new TestQueryProvider();
247 
248         QueryResult qr = qp.newQuery(JavaType.type(Customer.class))
249                 // c -> c.city.equals("London")
250                 .where((Customer c) -> c.city.equals("London"))
251                 // c -> c.contactName
252                 .select((Customer c) -> c.contactName).elements();
253 
254         qr.expression().writeTo(System.out);
255 
256         QueryResult qr2 = (QueryResult) Interpreter.invoke(MethodHandles.lookup(),
257                 qr.expression(), qp.newQuery(JavaType.type(Customer.class)));
258 
259         qr2.expression().writeTo(System.out);
260 
261         Assert.assertEquals(qr.expression().toText(), qr2.expression().toText());
262     }
263 }