/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.server.test.it.routines; import com.foundationdb.qp.loadableplan.DirectObjectCursor; import com.foundationdb.qp.loadableplan.DirectObjectPlan; import com.foundationdb.qp.loadableplan.LoadableDirectObjectPlan; import com.foundationdb.qp.operator.QueryBindings; import com.foundationdb.qp.operator.QueryContext; import com.foundationdb.qp.operator.BindingNotSetException; import com.foundationdb.server.error.AkibanInternalException; import com.foundationdb.server.error.QueryCanceledException; import java.sql.Types; import java.util.*; import java.util.concurrent.*; import java.io.*; /** A loadable direct object plan that returns asynchronous results. * Needs to use copy mode to get results out as it goes. * <code><pre> CALL sqlj.install_jar('target/fdb-sql-layer-x.y.z-tests.jar', 'testjar', 0); CREATE PROCEDURE system.`exec`(IN cmd VARCHAR(1024)) LANGUAGE java PARAMETER STYLE foundationdb_loadable_plan EXTERNAL NAME 'testjar:com.foundationdb.server.test.it.routines.TestDirectAsync'; CALL system.`exec`('tail', '-f', '/tmp/fdb-sql-layer/layer.log'); * </pre></code> */ public class TestDirectAsync extends LoadableDirectObjectPlan { @Override public DirectObjectPlan plan() { return new DirectObjectPlan() { @Override public DirectObjectCursor cursor(QueryContext context, QueryBindings bindings) { return new TestDirectObjectCursor(context, bindings); } @Override public OutputMode getOutputMode() { return OutputMode.COPY_WITH_NEWLINE; } }; } public static class TestDirectObjectCursor extends DirectObjectCursor { QueryContext context; QueryBindings bindings; Process process; BlockingQueue<String> output; public TestDirectObjectCursor(QueryContext context, QueryBindings bindings) { this.context = context; this.bindings = bindings; } @Override public void open() { List<String> command = new ArrayList<>(); for (int i = 0; i < 100; i++) { String carg; try { carg = bindings.getValue(i).getString(); //carg = bindings.getValue(i).getString(); } catch (BindingNotSetException ex) { break; } command.add(carg); } ProcessBuilder pb = new ProcessBuilder(command); pb.redirectErrorStream(true); try { process = pb.start(); } catch (IOException ex) { throw new AkibanInternalException("Could not launch", ex); } output = new LinkedBlockingQueue<>(); new Thread() { private final BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream())); @Override public void run() { while (true) { try { String line = input.readLine(); if (line == null) { output.add(EOF); break; } output.add(line); } catch (IOException ex) { // Could log the error, I suppose. break; } } } }.start(); } @Override public List<String> next() { String line; try { line = output.poll(TIMEOUT, TimeUnit.MILLISECONDS); } catch (InterruptedException ex) { throw new QueryCanceledException(context.getSession()); } if (line == null) return Collections.<String>emptyList(); else if (line == EOF) return null; else return Collections.singletonList(line); } @Override public void close() { process.destroy(); } } @Override public int[] jdbcTypes() { return TYPES; } private static final int[] TYPES = new int[] { Types.VARCHAR }; private static final long TIMEOUT = 500; private static final String EOF = "*EOF*"; }