All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.flink.table.client.cli.CliClient Maven / Gradle / Ivy

There is a newer version: 1.5.1
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.flink.table.client.cli;

import org.apache.flink.table.client.SqlClientException;
import org.apache.flink.table.client.cli.SqlCommandParser.SqlCommandCall;
import org.apache.flink.table.client.gateway.DatabaseDesc;
import org.apache.flink.table.client.gateway.Executor;
import org.apache.flink.table.client.gateway.ProgramTargetDescriptor;
import org.apache.flink.table.client.gateway.ResultDescriptor;
import org.apache.flink.table.client.gateway.SessionContext;
import org.apache.flink.table.client.gateway.SqlExecutionException;
import org.apache.flink.table.client.gateway.TableDesc;
import org.apache.flink.table.client.gateway.TypedResult;
import org.apache.flink.table.client.gateway.local.result.DynamicResult;
import org.apache.flink.types.Row;
import org.apache.flink.util.Preconditions;

import org.jline.reader.EndOfFileException;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.MaskingCallback;
import org.jline.reader.UserInterruptException;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
import org.jline.utils.InfoCmp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOError;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * SQL CLI client.
 */
public class CliClient {

	private static final Logger LOG = LoggerFactory.getLogger(CliClient.class);

	private final Executor executor;

	private final SessionContext context;

	private final Terminal terminal;

	private final LineReader lineReader;

	private final String prompt;

	private boolean isRunning;

	private static final int PLAIN_TERMINAL_WIDTH = 80;

	private static final int PLAIN_TERMINAL_HEIGHT = 30;

	private static final int SOURCE_MAX_SIZE = 50_000;

	public CliClient(SessionContext context, Executor executor) {
		this.context = context;
		this.executor = executor;

		try {
			// initialize terminal
			terminal = TerminalBuilder.builder()
				.name(CliStrings.CLI_NAME)
				.build();
			// make space from previous output and test the writer
			terminal.writer().println();
			terminal.writer().flush();
		} catch (IOException e) {
			throw new SqlClientException("Error opening command line interface.", e);
		}

		// initialize line lineReader
		lineReader = LineReaderBuilder.builder()
			.terminal(terminal)
			.appName(CliStrings.CLI_NAME)
			.parser(new SqlMultiLineParser())
			.completer(new SqlCompleter(context, executor))
			.build();
		// this option is disabled for now for correct backslash escaping
		// a "SELECT '\'" query should return a string with a backslash
		lineReader.option(LineReader.Option.DISABLE_EVENT_EXPANSION, true);
		// set strict "typo" distance between words when doing code completion
		lineReader.setVariable(LineReader.ERRORS, 1);
		// perform code completion case insensitive
		lineReader.option(LineReader.Option.CASE_INSENSITIVE, true);

		// create prompt
		prompt = new AttributedStringBuilder()
			.style(AttributedStyle.DEFAULT.foreground(AttributedStyle.GREEN))
			.append("Flink SQL")
			.style(AttributedStyle.DEFAULT)
			.append("> ")
			.toAnsi();
	}

	public Terminal getTerminal() {
		return terminal;
	}

	public SessionContext getContext() {
		return context;
	}

	public void clearTerminal() {
		if (isPlainTerminal()) {
			for (int i = 0; i < 200; i++) { // large number of empty lines
				terminal.writer().println();
			}
		} else {
			terminal.puts(InfoCmp.Capability.clear_screen);
		}
	}

	public boolean isPlainTerminal() {
		// check if terminal width can be determined
		// e.g. IntelliJ IDEA terminal supports only a plain terminal
		return terminal.getWidth() == 0 && terminal.getHeight() == 0;
	}

	public int getWidth() {
		if (isPlainTerminal()) {
			return PLAIN_TERMINAL_WIDTH;
		}
		return terminal.getWidth();
	}

	public int getHeight() {
		if (isPlainTerminal()) {
			return PLAIN_TERMINAL_HEIGHT;
		}
		return terminal.getHeight();
	}

	public Executor getExecutor() {
		return executor;
	}

	/**
	 * Opens the interactive CLI shell.
	 */
	public void open() {
		isRunning = true;

		// print welcome
		terminal.writer().append(CliStrings.MESSAGE_WELCOME);

		// begin reading loop
		while (isRunning) {
			// make some space to previous command
			terminal.writer().append("\n");
			terminal.flush();

			final String line;
			try {
				line = lineReader.readLine(prompt, null, (MaskingCallback) null, null);
			} catch (UserInterruptException e) {
				// user cancelled line with Ctrl+C
				continue;
			} catch (EndOfFileException | IOError e) {
				// user cancelled application with Ctrl+D or kill
				break;
			} catch (Throwable t) {
				throw new SqlClientException("Could not read from command line.", t);
			}
			if (line == null) {
				continue;
			}

			final List statements = splitSemiColon(line);
			for (String statement : statements) {
				final Optional cmdCall = parseCommand(statement);

				if (!cmdCall.isPresent()) {
					// if we found a invalid command, issue error message and break
					break;
				}

				cmdCall.ifPresent(this::callCommand);
			}
		}
	}

	public void submitSQLFile(URL sqlFile) {
		isRunning = true;

		final String content;
		try {
			final Path path = Paths.get(sqlFile.toURI());
			byte[] encoded = Files.readAllBytes(path);
			content = new String(encoded, Charset.defaultCharset());
		} catch (IOException | URISyntaxException e) {
			printExecutionException(e);
			return;
		}

		final List statements = splitSemiColon(content);
		for (String statement : statements) {
			final Optional cmdCall = parseCommand(statement);

			if (!cmdCall.isPresent()) {
				break;
			}

			cmdCall.ifPresent(this::callCommand);
		}
	}

	/**
	 * Submits a SQL update statement and prints status information and/or errors on the terminal.
	 *
	 * @param statement SQL update statement
	 * @return flag to indicate if the submission was successful or not
	 */
	public boolean submitUpdate(String statement) {
		terminal.writer().println(CliStrings.messageInfo(CliStrings.MESSAGE_WILL_EXECUTE).toAnsi());
		terminal.writer().println(new AttributedString(statement).toString());
		terminal.flush();

		final Optional parsedStatement = parseCommand(statement);
		// only support INSERT INTO
		return parsedStatement.map(cmdCall -> {
			switch (cmdCall.command) {
				case INSERT_INTO:
				case DELETE_FROM:
					return callSqlUpdate(cmdCall);
				default:
					printError(CliStrings.MESSAGE_UNSUPPORTED_SQL);
					return false;
			}
		}).orElse(false);
	}

	// --------------------------------------------------------------------------------------------

	private Optional parseCommand(String line) {
		final Optional parsedLine = SqlCommandParser.parse(line);
		if (!parsedLine.isPresent()) {
			printError(CliStrings.MESSAGE_UNKNOWN_SQL);
		}
		return parsedLine;
	}

	private void callCommand(SqlCommandCall cmdCall) {
		switch (cmdCall.command) {
			case QUIT:
				callQuit();
				break;
			case CLEAR:
				callClear();
				break;
			case RESET:
				callReset();
				break;
			case SET:
				callSet(cmdCall);
				break;
			case HELP:
				callHelp();
				break;
			case SHOW_CATALOGS:
				callShowCatalogs();
				break;
			case SHOW_DATABASES:
				callShowDatabases();
				break;
			case SHOW_TABLES:
				callShowTables();
				break;
			case SHOW_VIEWS:
				callShowViews();
				break;
			case SHOW_FUNCTIONS:
				callShowFunctions();
				break;
			case SHOW_PARTITIONS:
				callShowPartitons(cmdCall);
				break;
			case SHOW_CREATE_TABLE:
				callShowCreateTable(cmdCall);
				break;
			case USE:
				callUseDatabase(cmdCall);
				break;
			case USE_CATALOG:
				callUseCatalog(cmdCall);
				break;
			case DESCRIBE:
			case DESC:
				callDescribe(cmdCall);
				break;
			case DESCRIBE_DATABASE:
			case DESC_DATABASE:
				callDescribeDatabase(cmdCall);
				break;
			case EXPLAIN:
				callExplain(cmdCall);
				break;
			case SELECT:
				callSelect(cmdCall);
				break;
			case INSERT_INTO:
			case INSERT_OVERWRITE:
			case DELETE_FROM:
				callSqlUpdate(cmdCall);
				break;
			case CREATE_TABLE:
				callCreateTable(cmdCall);
				break;
			case DROP_TABLE:
				callDropTable(cmdCall);
				break;
			case CREATE_VIEW:
				callCreateView(cmdCall);
				break;
			case DROP_VIEW:
				callDropView(cmdCall);
				break;
			case CREATE_FUNCTION:
				callCreateFunction(cmdCall);
				break;
			case DROP_FUNCTION:
				callDropFunction(cmdCall);
				break;
			case CREATE_DATABASE:
				callCreateDatabase(cmdCall);
				break;
			case DROP_DATABASE:
				callDropDatabase(cmdCall);
				break;
			case SOURCE:
				callSource(cmdCall);
				break;
			case ALTER_DATABASE:
				callAlterDatabase(cmdCall);
				break;
			case ALTER_TABLE:
				callAlterTable(cmdCall);
				break;
			case BOND:
				callBond(cmdCall);
				break;
			case ANALYZE:
				callAnalyze(cmdCall);
				break;
			case IMPORT:
				callImport(cmdCall);
				break;
			default:
				throw new SqlClientException("Unsupported command: " + cmdCall.command);
		}
	}

	private Optional> callBondCommand(SqlCommandCall cmdCall) {
		switch (cmdCall.command) {
			case SELECT:
				return callBondSelect(cmdCall);
			default:
				throw new SqlClientException("Unsupported command: " + cmdCall.command);
		}
	}

	private void callQuit() {
		printInfo(CliStrings.MESSAGE_QUIT);
		isRunning = false;
	}

	private void callClear() {
		clearTerminal();
	}

	private void callReset() {
		context.resetSessionProperties();
		printInfo(CliStrings.MESSAGE_RESET);
	}

	private void callSet(SqlCommandCall cmdCall) {
		// show all properties
		if (cmdCall.operands.length == 0) {
			final Map properties;
			try {
				properties = executor.getSessionProperties(context);
			} catch (SqlExecutionException e) {
				printExecutionException(e);
				return;
			}
			if (properties.isEmpty()) {
				terminal.writer().println(CliStrings.messageInfo(CliStrings.MESSAGE_EMPTY).toAnsi());
			} else {
				properties
					.entrySet()
					.stream()
					.map((e) -> e.getKey() + "=" + e.getValue())
					.sorted()
					.forEach((p) -> terminal.writer().println(p));
			}
		}
		// set a property
		else {
			context.setSessionProperty(cmdCall.operands[0], cmdCall.operands[1]);

			// if we change execution type (batch -> streaming or streaming -> batch
			// we will use a new TableEnvironment for the following queries.
			terminal.writer().println(CliStrings.messageInfo(CliStrings.MESSAGE_SET).toAnsi());
		}
		terminal.flush();
	}

	private void callHelp() {
		terminal.writer().println(CliStrings.MESSAGE_HELP);
		terminal.flush();
	}

	private void callShowCatalogs() {
		final List catalogs;
		try {
			catalogs = executor.listCatalogs(context);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return;
		}
		if (catalogs.isEmpty()) {
			terminal.writer().println(CliStrings.messageInfo(CliStrings.MESSAGE_EMPTY).toAnsi());
		} else {
			catalogs.forEach((v) -> terminal.writer().println(v));
		}
		terminal.flush();
	}

	private void callShowDatabases() {
		final List dbs;
		try {
			dbs = executor.listDatabases(context);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return;
		}
		if (dbs.isEmpty()) {
			terminal.writer().println(CliStrings.messageInfo(CliStrings.MESSAGE_EMPTY).toAnsi());
		} else {
			dbs.forEach((v) -> terminal.writer().println(v));
		}
		terminal.flush();
	}

	private void callShowTables() {
		final List tables;
		try {
			tables = executor.listTables(context);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return;
		}
		if (tables.isEmpty()) {
			terminal.writer().println(CliStrings.messageInfo(CliStrings.MESSAGE_EMPTY).toAnsi());
		} else {
			tables.forEach((v) -> terminal.writer().println(v));
		}
		terminal.flush();
	}

	private void callShowViews() {
		final List views;
		try {
			views = executor.listViews(context);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return;
		}
		if (views.isEmpty()) {
			terminal.writer().println(CliStrings.messageInfo(CliStrings.MESSAGE_EMPTY).toAnsi());
		} else {
			views.forEach((v) -> terminal.writer().println(v));
		}
		terminal.flush();
	}

	private void callShowFunctions() {
		final List functions;
		try {
			functions = executor.listUserDefinedFunctions(context);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return;
		}
		if (functions.isEmpty()) {
			terminal.writer().println(CliStrings.messageInfo(CliStrings.MESSAGE_EMPTY).toAnsi());
		} else {
			functions.forEach((v) -> terminal.writer().println(v));
		}
		terminal.flush();
	}

	private void callShowPartitons(SqlCommandCall cmdCall) {
		try {
			List partitions = executor.listPartitions(context, cmdCall.operands[0]);
			if (null == partitions || partitions.isEmpty()) {
				terminal.writer().println(CliStrings.messageInfo(CliStrings.MESSAGE_EMPTY).toAnsi());
			} else {
				partitions.forEach(v -> terminal.writer().println(v));
			}
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return;
		}
		terminal.flush();
	}

	private void callShowCreateTable(SqlCommandCall cmdCall) {
		try {
			String createTableDDL = executor.showCreateTable(context, cmdCall.operands[0]);
			if (null == createTableDDL || createTableDDL.isEmpty()) {
				terminal.writer().println(CliStrings.messageInfo(CliStrings.MESSAGE_EMPTY).toAnsi());
			} else {
				terminal.writer().println(createTableDDL);
			}
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return;
		}
		terminal.flush();
	}

	private void callUseDatabase(SqlCommandCall cmdCall) {
		try {
			executor.setDefaultDatabase(context, cmdCall.operands[0]);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return;
		}
		terminal.flush();
	}

	private void callUseCatalog(SqlCommandCall cmdCall) {
		try {
			executor.setDefaultCatalog(context, cmdCall.operands[0]);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return;
		}
		terminal.flush();
	}

	private void callDescribe(SqlCommandCall cmdCall) {
		final TableDesc tableDesc;
		try {
			tableDesc = executor.describeTable(context, cmdCall.operands[0]);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return;
		}
		terminal.writer().println(tableDesc.toString());
		terminal.flush();
	}

	private void callDescribeDatabase(SqlCommandCall cmdCall) {
		try {
			DatabaseDesc desc = executor.describeDatabase(context, cmdCall.operands[0]);
			terminal.writer().println(desc.toString());
			terminal.flush();
		} catch (SqlExecutionException e) {
			printExecutionException(e);
		}
	}

	private void callExplain(SqlCommandCall cmdCall) {
		final String explanation;
		try {
			explanation = executor.explainStatement(context, cmdCall.operands[0]);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return;
		}
		terminal.writer().println(explanation);
		terminal.flush();
	}

	private void callSelect(SqlCommandCall cmdCall) {
		final long start = System.currentTimeMillis();
		final ResultDescriptor resultDesc;
		try {
			resultDesc = executor.executeQuery(context, cmdCall.operands[0]);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return;
		}
		final long end = System.currentTimeMillis();

		// support a traditional and scrolling non-interactive view
		if (executor.getOrCreateExecutionContext(context).getMergedEnvironment().getExecution().isNonInteractiveViewMode()) {
			printRows(resultDesc, start, end);
			this.executor.cancelQuery(context, resultDesc.getResultId(), resultDesc.getStatement());
		} else {
			final CliResultView view;
			if (resultDesc.isMaterialized()) {
				view = new CliTableResultView(this, resultDesc);
			} else {
				view = new CliChangelogResultView(this, resultDesc);
			}

			// enter view
			try {
				view.open();

				// view left
				printInfo(CliStrings.MESSAGE_RESULT_QUIT);
			} catch (SqlExecutionException e) {
				printExecutionException(e);
			}
		}
	}

	private boolean callSqlUpdate(SqlCommandCall cmdCall) {
		printInfo(CliStrings.MESSAGE_SUBMITTING_STATEMENT);

		try {
			final ProgramTargetDescriptor programTarget = executor.executeUpdate(context, cmdCall.operands[0]);
			terminal.writer().println(CliStrings.messageInfo(CliStrings.MESSAGE_STATEMENT_SUBMITTED).toAnsi());
			terminal.writer().println(programTarget.toString());
			terminal.flush();
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return false;
		}
		return true;
	}

	private void callCreateDatabase(SqlCommandCall cmdCall) {
		try {
			executor.createDatabase(context, cmdCall.operands[0]);
			printInfo(CliStrings.MESSAGE_DATABASE_CREATED);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
		}
	}

	private void callCreateTable(SqlCommandCall cmdCall) {
		try {
			executor.createTable(context, cmdCall.operands[0]);
			printInfo(CliStrings.MESSAGE_TABLE_CREATE);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
		}
	}

	private void callAlterTable(SqlCommandCall cmdCall) {
		try {
			executor.alterTable(context, cmdCall.operands[0]);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
		}
	}

	private void callDropTable(SqlCommandCall cmdCall) {
		try {
			// perform and validate change
			executor.dropTable(context, cmdCall.operands[0]);
			printInfo(CliStrings.MESSAGE_TABLE_REMOVED);
		} catch (SqlExecutionException e) {
			printExecutionException(CliStrings.MESSAGE_TABLE_NOT_REMOVED, e);
		}
	}

	private void callCreateView(SqlCommandCall cmdCall) {
		try {
			executor.createView(context, cmdCall.operands[0]);
			printInfo(CliStrings.MESSAGE_VIEW_CREATED);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
		}
	}

	private void callDropView(SqlCommandCall cmdCall) {
		try {
			// perform and validate change
			executor.dropView(context, cmdCall.operands[0]);
			printInfo(CliStrings.MESSAGE_VIEW_REMOVED);
		} catch (SqlExecutionException e) {
			printExecutionException(CliStrings.MESSAGE_VIEW_NOT_REMOVED, e);
		}
	}

	private void callCreateFunction(SqlCommandCall cmdCall) {
		try {
			executor.createFunction(context, cmdCall.operands[0]);
			printInfo(CliStrings.MESSAGE_FUNCTION_CREATE);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
		}
	}

	private void callDropDatabase(SqlCommandCall cmdCall) {
		try {
			executor.dropDatabase(context, cmdCall.operands[0]);
			printInfo(CliStrings.MESSAGE_DATABASE_DROP);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
		}
	}

	private void callDropFunction(SqlCommandCall cmdCall) {
		try {
			// perform and validate change
			executor.dropFunction(context, cmdCall.operands[0]);
			printInfo(CliStrings.MESSAGE_FUNCTION_REMOVED);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
		}
	}

	private void callSource(SqlCommandCall cmdCall) {
		final String pathString = cmdCall.operands[0];

		// load file
		final String stmt;
		try {
			final Path path = Paths.get(pathString);
			byte[] encoded = Files.readAllBytes(path);
			stmt = new String(encoded, Charset.defaultCharset());
		} catch (IOException e) {
			printExecutionException(e);
			return;
		}

		// limit the output a bit
		if (stmt.length() > SOURCE_MAX_SIZE) {
			printExecutionError(CliStrings.MESSAGE_MAX_SIZE_EXCEEDED);
			return;
		}

		terminal.writer().println(CliStrings.messageInfo(CliStrings.MESSAGE_WILL_EXECUTE).toAnsi());
		terminal.writer().println(new AttributedString(stmt).toString());
		terminal.flush();

		// try to run it
		final List statements = splitSemiColon(stmt);
		for (String statement : statements) {
			final Optional call = parseCommand(statement);

			if (!call.isPresent()) {
				break;
			}

			call.ifPresent(this::callCommand);
		}
	}

	private void callBond(SqlCommandCall cmdCall) {
		final long start = System.currentTimeMillis();

		// bond only support table mode:
		if (this.getContext().getEnvironment().getExecution().isChangelogMode()) {
			printExecutionError("BOND only support table mode, please change result-mode to 'table'.");
			return;
		}

		final String pathString = cmdCall.operands[0];
		final String jobName;

		// load file
		final String stmt;
		try {
			final Path path = Paths.get(pathString);
			byte[] encoded = Files.readAllBytes(path);
			stmt = new String(encoded, Charset.defaultCharset());
			jobName = path.getFileName().toString();
		} catch (IOException e) {
			printExecutionException(e);
			return;
		}

		// limit the output a bit
		if (stmt.length() > SOURCE_MAX_SIZE) {
			printExecutionError(CliStrings.MESSAGE_MAX_SIZE_EXCEEDED);
			return;
		}

		// currently ONLY support SqlKind.QUERY
		terminal.writer().println(CliStrings.messageInfo(CliStrings.MESSAGE_WILL_EXECUTE).toAnsi());
		terminal.writer().println("Bond only support SQL statements of type SELECT. Other statements would be ignored");
		terminal.writer().println(new AttributedString(stmt).toString());
		terminal.flush();

		final List statements = splitSemiColon(stmt);
		final List calls = new ArrayList<>();

		for (String statement : statements) {
			final Optional call = parseCommand(statement);

			if (!call.isPresent()) {
				// if we found invalid command, issue error message and return
				return;
			}

			if (call.get().command == SqlCommandParser.SqlCommand.SELECT) {
				calls.add(call.get());
			}
		}

		// prepare plan
		List> results =
			calls
				.stream()
				.map(call -> callBondCommand(call))
				.filter((result) -> result.isPresent())
				.map(result -> result.get())
				.collect(Collectors.toList());

		// submit plan
		final List resultDescList;
		try {
			resultDescList = executor.executeBondQuery(context, results, jobName);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return;
		}

		final long end = System.currentTimeMillis();

		if (executor.getOrCreateExecutionContext(context).getMergedEnvironment().getExecution().isNonInteractiveViewMode()) {
			resultDescList.forEach((desc) -> {
				printRows(desc, start, end);
				this.executor.cancelQuery(context, desc.getResultId(), desc.getStatement());
			});
		} else {
			resultDescList.forEach((desc) -> {
				final CliResultView view;
				if (desc.isMaterialized()) {
					view = new CliTableResultView(this, desc);
				} else {
					view = new CliChangelogResultView(this, desc);
				}

				// enter view
				try {
					view.open();

					// view left
					printInfo(CliStrings.MESSAGE_RESULT_QUIT);
				} catch (SqlExecutionException e) {
					printExecutionException(e);
				}
			});
		}
	}

	private Optional> callBondSelect(SqlCommandCall cmdCall) {
		final DynamicResult result;
		try {
			result = executor.applyBondQuery(context, cmdCall.operands[0]);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return Optional.empty();
		}
		return Optional.of(result);
	}

	private void callAlterDatabase(SqlCommandCall cmdCall) {
		try {
			executor.alterDatabase(context, cmdCall.operands[0]);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
		}
	}

	private void callAnalyze(SqlCommandCall cmdCall) {
		final long start = System.currentTimeMillis();

		try {
			executor.analyzeTable(context, cmdCall.operands[0]);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
		}

		final long end = System.currentTimeMillis();

		terminal.writer().println(String.format("Time token %.2f seconds.", (end - start) / 1000.0));
		terminal.writer().flush();
	}

	private void callImport(SqlCommandCall cmdCall) {
		final long start = System.currentTimeMillis();

		final String srcTableName = cmdCall.operands[0];
		final String descTAbleName = cmdCall.operands[1];

		terminal.writer().println("Import command would scan data from src and write to desc");
		terminal.writer().println("src and desc should have the same Schema or a error will be issued.");
		terminal.writer().flush();

		final String convertSqlString = String.format("INSERT INTO %s SELECT * FROM %s", descTAbleName, srcTableName);
		final String statsSqlString = executor.generateAnalyzeSQL(context, srcTableName);

		// prepare plan
		try {
			executor.applyBondUpdate(context, convertSqlString);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return;
		}

		final List> results = new ArrayList<>();
		try {
			results.add(executor.applyBondQuery(context, statsSqlString));
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return;
		}

		// submit plan
		final List resultDescList;
		try {
			resultDescList = executor.executeBondQuery(context, results, "import " + srcTableName + " to " + descTAbleName);
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return;
		}
		final long end = System.currentTimeMillis();

		Preconditions.checkArgument(resultDescList.size() == 1);

		final ResultDescriptor desc = resultDescList.get(0);
		final String resultId = desc.getResultId();
		final String statement = desc.getStatement();
		final List actualResults = new ArrayList<>();
		if (executor.getOrCreateExecutionContext(context).getMergedEnvironment().getExecution().isNonInteractiveViewMode()) {
			TypedResult> result = this.executor.retrieveResults(resultId, statement);
			if (TypedResult.ResultType.PAYLOAD == result.getType()) {
				actualResults.addAll(result.getPayload());
			}
		} else {
			do {
				final TypedResult result = executor.snapshotResult(this.getContext(), resultId, statement, 1);
				if (result.getType() == TypedResult.ResultType.PAYLOAD) {
					actualResults.clear();
					IntStream.rangeClosed(1, result.getPayload()).forEach((page) -> {
						for (Row row : executor.retrieveResultPage(resultId, statement, page)) {
							actualResults.add(row);
						}
					});
				} else if (result.getType() == TypedResult.ResultType.EOS) {
					// Job finished or cancelled
					break;
				}

				try {
					Thread.sleep(10);
				} catch (Exception e) {
					printExecutionException(e);
					return;
				}
			} while (true);
		}

		this.executor.cancelQuery(context, resultId, statement);

		Preconditions.checkArgument(actualResults.size() == 1);

		try {
			executor.applyTableStats(context, srcTableName, actualResults.get(0));
			executor.applyTableStats(context, descTAbleName, actualResults.get(0));
		} catch (SqlExecutionException e) {
			printExecutionException(e);
			return;
		}

		terminal.writer().println(String.format("Time token: %.2f seconds.", (end - start) / 1000.0));
		terminal.writer().flush();
	}

	// --------------------------------------------------------------------------------------------

	private void printExecutionException(Throwable t) {
		printExecutionException(null, t);
	}

	private void printExecutionException(String message, Throwable t) {
		final String finalMessage;
		if (message == null) {
			finalMessage = CliStrings.MESSAGE_SQL_EXECUTION_ERROR;
		} else {
			finalMessage = CliStrings.MESSAGE_SQL_EXECUTION_ERROR + ' ' + message;
		}
		printException(finalMessage, t);
	}

	private void printExecutionError(String message) {
		terminal.writer().println(CliStrings.messageError(CliStrings.MESSAGE_SQL_EXECUTION_ERROR, message).toAnsi());
		terminal.flush();
	}

	private void printException(String message, Throwable t) {
		LOG.warn(message, t);
		terminal.writer().println(CliStrings.messageError(message, t).toAnsi());
		terminal.flush();
	}

	private void printError(String message) {
		terminal.writer().println(CliStrings.messageError(message).toAnsi());
		terminal.flush();
	}

	private void printInfo(String message) {
		terminal.writer().println(CliStrings.messageInfo(message).toAnsi());
		terminal.flush();
	}

	/**
	 * Split a SemiColon-separated String, but ignore SemiColons in quotes.
	 * @param line
	 * @return
	 */
	private static List splitSemiColon(String line) {
		boolean inSingleQuotes = false;
		boolean inDoubleQuotes = false;
		boolean escape = false;

		// normalize
		line = line.replaceAll("--[^\r\n]*", ""); // remove single-line comments
		line = line.replaceAll("/\\*[\\w\\W]*?(?=\\*/)\\*/", ""); // remove double-line comments
		line = line.trim();

		List ret = new ArrayList<>();
		int beginIdx = 0;
		for (int idx = 0; idx < line.length(); idx++) {
			char c = line.charAt(idx);
			switch (c) {
				case ';':
					if (!inSingleQuotes && !inDoubleQuotes) {
						ret.add(line.substring(beginIdx, idx));
						beginIdx = idx + 1;
					}
					break;
				case '"':
					if (!escape) {
						inDoubleQuotes = !inDoubleQuotes;
					}
					break;
				case '\'':
					if (!escape) {
						inSingleQuotes = !inSingleQuotes;
					}
					break;
				default:
					break;
			}

			if (escape) {
				escape = false;
			} else if (c == '\\') {
				escape = true;
			}
		}

		if (beginIdx < line.length()) {
			ret.add(line.substring(beginIdx));
		}

		return ret;
	}

	/**
	 * Output Results in non-interactive way.
	 */
	void printRows(final ResultDescriptor desc, long start, long end) {
		final String resultId = desc.getResultId();
		final String statement = desc.getStatement();
		TypedResult> result = this.executor.retrieveResults(resultId, statement);
		if (TypedResult.ResultType.PAYLOAD == result.getType()) {
			final List rows = result.getPayload();
			terminal.writer().println("Fetched result is: ");
			rows.forEach((row) -> terminal.writer().println(row.toString()));
			terminal.writer().println(String.format("Time token: %.2f seconds, Fetched %d row(s).", (end - start) / 1000.0, rows.size()));
			terminal.writer().flush();
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy