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 org.testng.Assert;
 25 import org.testng.annotations.Test;
 26 
 27 import jdk.incubator.code.Op;
 28 import jdk.incubator.code.Quoted;
 29 import jdk.incubator.code.Value;
 30 import jdk.incubator.code.type.MethodRef;
 31 import jdk.incubator.code.interpreter.Interpreter;
 32 import java.lang.invoke.MethodHandles;
 33 import jdk.incubator.code.type.JavaType;
 34 import jdk.incubator.code.TypeElement;
 35 import java.util.stream.Stream;
 36 
 37 import static jdk.incubator.code.type.MethodRef.method;
 38 import static jdk.incubator.code.op.CoreOp.*;
 39 import static jdk.incubator.code.type.FunctionType.functionType;
 40 
 41 /*
 42  * @test
 43  * @modules jdk.incubator.code
 44  * @run testng TestLinqUsingQuoted
 45  */
 46 
 47 public class TestLinqUsingQuoted {
 48 
 49     // Query interfaces
 50 
 51     public interface Queryable {
 52         TypeElement elementType();
 53 
 54         // Queryable<T> -> Queryable<U>
 55         FuncOp expression();
 56 
 57         QueryProvider provider();
 58 
 59         // T -> boolean
 60         // Predicate<T>
 61         default Queryable where(Quoted f) {
 62             // @@@@ validate
 63             ClosureOp c = (ClosureOp) f.op();
 64             return insertQuery(elementType(), "where", c);
 65         }
 66 
 67         // T -> R
 68         // Function<T, R>
 69         default Queryable select(Quoted f) {
 70             // @@@@ validate
 71             ClosureOp c = (ClosureOp) f.op();
 72             return insertQuery(c.invokableType().returnType(), "select", c);
 73         }
 74 
 75         private Queryable insertQuery(TypeElement et, String name, ClosureOp c) {
 76             QueryProvider qp = provider();
 77 
 78             FuncOp currentQueryExpression = expression();
 79             FuncOp nextQueryExpression = currentQueryExpression.transform((block, op) -> {
 80                 if (op instanceof ReturnOp rop && rop.ancestorBody() == currentQueryExpression.body()) {
 81                     Value query = block.context().getValue(rop.returnValue());
 82 
 83                     Op.Result quotedLambda = block.op(quoted(block.parentBody(), qblock -> c));
 84 
 85                     MethodRef md = method(qp.queryableType(), name,
 86                             functionType(qp.queryableType(), QuotedOp.QUOTED_TYPE));
 87                     Op.Result queryable = block.op(invoke(md, query, quotedLambda));
 88 
 89                     block.op(_return(queryable));
 90                 } else {
 91                     block.op(op);
 92                 }
 93                 return block;
 94             });
 95 
 96             return qp.createQuery(et, nextQueryExpression);
 97         }
 98 
 99         // Iterate
100         // Queryable -> Stream
101         default QueryResult elements() {
102             TypeElement resultType = JavaType.parameterized(JavaType.type(Stream.class), (JavaType) elementType());
103             return insertQueryResult("elements", resultType);
104         }
105 
106         // Count
107         // Queryable -> Long
108         default QueryResult count() {
109             return insertQueryResult("count", JavaType.LONG);
110         }
111 
112         private QueryResult insertQueryResult(String name, TypeElement resultType) {
113             QueryProvider qp = provider();
114 
115             // Copy function expression, replacing return type
116             FuncOp currentQueryExpression = expression();
117             FuncOp nextQueryExpression = func("queryresult",
118                     functionType(qp.queryResultType(), currentQueryExpression.invokableType().parameterTypes()))
119                     .body(b -> b.inline(currentQueryExpression, b.parameters(), (block, query) -> {
120                         MethodRef md = method(qp.queryableType(), name, functionType(qp.queryResultType()));
121                         Op.Result queryResult = block.op(invoke(md, query));
122 
123                         block.op(_return(queryResult));
124                     }));
125             return qp.createQueryResult(resultType, nextQueryExpression);
126         }
127     }
128 
129     public interface QueryResult {
130         TypeElement resultType();
131 
132         // Queryable -> QueryResult
133         FuncOp expression();
134 
135         Object execute();
136     }
137 
138     public interface QueryProvider {
139         TypeElement queryableType();
140 
141         TypeElement queryResultType();
142 
143         Queryable createQuery(TypeElement elementType, FuncOp expression);
144 
145         QueryResult createQueryResult(TypeElement resultType, FuncOp expression);
146 
147         Queryable newQuery(TypeElement elementType);
148     }
149 
150 
151     // Query implementation
152 
153     public static final class TestQueryable implements Queryable {
154         final TypeElement elementType;
155         final TestQueryProvider provider;
156         final FuncOp expression;
157 
158         TestQueryable(TypeElement elementType, TestQueryProvider provider) {
159             this.elementType = elementType;
160             this.provider = provider;
161 
162             // Initial expression is an identity function
163             var funType = functionType(provider().queryableType(), provider().queryableType());
164             this.expression = func("query", funType)
165                     .body(b -> b.op(_return(b.parameters().get(0))));
166         }
167 
168         TestQueryable(TypeElement elementType, TestQueryProvider provider, FuncOp expression) {
169             this.elementType = elementType;
170             this.provider = provider;
171             this.expression = expression;
172         }
173 
174         @Override
175         public TypeElement elementType() {
176             return elementType;
177         }
178 
179         @Override
180         public FuncOp expression() {
181             return expression;
182         }
183 
184         @Override
185         public QueryProvider provider() {
186             return provider;
187         }
188     }
189 
190     public record TestQueryResult(TypeElement resultType, FuncOp expression) implements QueryResult {
191         @Override
192         public Object execute() {
193             // @@@ Compile/translate the expression and execute it
194             throw new UnsupportedOperationException();
195         }
196     }
197 
198     public static final class TestQueryProvider implements QueryProvider {
199         final TypeElement queryableType;
200         final TypeElement queryResultType;
201 
202         TestQueryProvider() {
203             this.queryableType = JavaType.type(Queryable.class);
204             this.queryResultType = JavaType.type(QueryResult.class);
205         }
206 
207         @Override
208         public TypeElement queryableType() {
209             return queryableType;
210         }
211 
212         @Override
213         public TypeElement queryResultType() {
214             return queryResultType;
215         }
216 
217         @Override
218         public TestQueryable createQuery(TypeElement elementType, FuncOp expression) {
219             return new TestQueryable(elementType, this, expression);
220         }
221 
222         @Override
223         public QueryResult createQueryResult(TypeElement resultType, FuncOp expression) {
224             return new TestQueryResult(resultType, expression);
225         }
226 
227         @Override
228         public Queryable newQuery(TypeElement elementType) {
229             return new TestQueryable(elementType, this);
230         }
231     }
232 
233 
234     static class Customer {
235         // 1st column
236         String contactName;
237         // 2nd column
238         String phone;
239         // 3rd column
240         String city;
241     }
242 
243     @Test
244     public void testSimpleQuery() {
245         QueryProvider qp = new TestQueryProvider();
246 
247         QueryResult qr = qp.newQuery(JavaType.type(Customer.class))
248                 // c -> c.city.equals("London")
249                 .where((Customer c) -> c.city.equals("London"))
250                 // c -> c.contactName
251                 .select((Customer c) -> c.contactName).elements();
252 
253         qr.expression().writeTo(System.out);
254 
255         QueryResult qr2 = (QueryResult) Interpreter.invoke(MethodHandles.lookup(),
256                 qr.expression(), qp.newQuery(JavaType.type(Customer.class)));
257 
258         qr2.expression().writeTo(System.out);
259 
260         Assert.assertEquals(qr.expression().toText(), qr2.expression().toText());
261     }
262 }