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 }