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 }