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 }