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