From 0344639618ece682264426bcefb4ad9eced4ad14 Mon Sep 17 00:00:00 2001 From: Hishida Masato Date: Wed, 10 Apr 2024 10:57:53 +0900 Subject: [PATCH] feat: variable prompt --- .../tsurugidb/console/cli/repl/ReplCvKey.java | 22 ++ .../console/cli/repl/ReplEngine.java | 6 + .../console/cli/repl/ReplScript.java | 63 +++- .../tsurugidb/console/core/ScriptRunner.java | 25 +- .../console/core/config/ScriptCvKey.java | 22 +- .../console/core/config/ScriptPrompt.java | 336 ++++++++++++++++++ .../core/executor/engine/BasicEngine.java | 6 + .../console/core/executor/engine/Engine.java | 11 + .../core/executor/report/ScriptReporter.java | 103 +----- .../report/TransactionOptionReportUtil.java | 161 +++++++++ .../core/executor/sql/BasicSqlProcessor.java | 31 +- .../core/executor/sql/SqlProcessor.java | 8 + .../core/executor/sql/TransactionWrapper.java | 51 +++ .../console/core/config/ScriptPromptTest.java | 158 ++++++++ .../core/executor/engine/BasicEngineTest.java | 6 + .../executor/report/ScriptReporterTest.java | 92 ----- .../TransactionOptionReportUtilTest.java | 103 ++++++ .../executor/sql/BasicSqlProcessorTest.java | 37 +- modules/tgsql/docs/prompt_ja.md | 86 +++++ 19 files changed, 1087 insertions(+), 240 deletions(-) create mode 100755 modules/tgsql/core/src/main/java/com/tsurugidb/console/core/config/ScriptPrompt.java create mode 100755 modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/report/TransactionOptionReportUtil.java create mode 100755 modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/sql/TransactionWrapper.java create mode 100755 modules/tgsql/core/src/test/java/com/tsurugidb/console/core/config/ScriptPromptTest.java create mode 100755 modules/tgsql/core/src/test/java/com/tsurugidb/console/core/executor/report/TransactionOptionReportUtilTest.java create mode 100755 modules/tgsql/docs/prompt_ja.md diff --git a/modules/tgsql/cli/src/main/java/com/tsurugidb/console/cli/repl/ReplCvKey.java b/modules/tgsql/cli/src/main/java/com/tsurugidb/console/cli/repl/ReplCvKey.java index 1b257bc..fb10a53 100755 --- a/modules/tgsql/cli/src/main/java/com/tsurugidb/console/cli/repl/ReplCvKey.java +++ b/modules/tgsql/cli/src/main/java/com/tsurugidb/console/cli/repl/ReplCvKey.java @@ -2,12 +2,34 @@ import com.tsurugidb.console.core.config.ScriptCvKey; import com.tsurugidb.console.core.config.ScriptCvKey.ScriptCvKeyColor; +import com.tsurugidb.console.core.config.ScriptCvKey.ScriptCvKeyPrompt; /** * client variable key. */ public final class ReplCvKey { + /** console.prompt1.default . */ + public static final ScriptCvKeyPrompt PROMPT1_DEFAULT = new ScriptCvKeyPrompt("console.prompt1.default"); //$NON-NLS-1$ + /** console.prompt1.tx . */ + public static final ScriptCvKeyPrompt PROMPT1_TRANSACTION = new ScriptCvKeyPrompt("console.prompt1.tx"); //$NON-NLS-1$ + /** console.prompt1.occ . */ + public static final ScriptCvKeyPrompt PROMPT1_OCC = new ScriptCvKeyPrompt("console.prompt1.occ"); //$NON-NLS-1$ + /** console.prompt1.ltx . */ + public static final ScriptCvKeyPrompt PROMPT1_LTX = new ScriptCvKeyPrompt("console.prompt1.ltx"); //$NON-NLS-1$ + /** console.prompt1.rtx . */ + public static final ScriptCvKeyPrompt PROMPT1_RTX = new ScriptCvKeyPrompt("console.prompt1.rtx"); //$NON-NLS-1$ + /** console.prompt2.default . */ + public static final ScriptCvKeyPrompt PROMPT2_DEFAULT = new ScriptCvKeyPrompt("console.prompt2.default"); //$NON-NLS-1$ + /** console.prompt2.tx . */ + public static final ScriptCvKeyPrompt PROMPT2_TRANSACTION = new ScriptCvKeyPrompt("console.prompt2.tx"); //$NON-NLS-1$ + /** console.prompt2.occ . */ + public static final ScriptCvKeyPrompt PROMPT2_OCC = new ScriptCvKeyPrompt("console.prompt2.occ"); //$NON-NLS-1$ + /** console.prompt2.ltx . */ + public static final ScriptCvKeyPrompt PROMPT2_LTX = new ScriptCvKeyPrompt("console.prompt2.ltx"); //$NON-NLS-1$ + /** console.prompt2.rtx . */ + public static final ScriptCvKeyPrompt PROMPT2_RTX = new ScriptCvKeyPrompt("console.prompt2.rtx"); //$NON-NLS-1$ + /** console.info.color . */ public static final ScriptCvKeyColor CONSOLE_INFO_COLOR = new ScriptCvKeyColor("console.info.color"); //$NON-NLS-1$ /** console.implicit.color . */ diff --git a/modules/tgsql/cli/src/main/java/com/tsurugidb/console/cli/repl/ReplEngine.java b/modules/tgsql/cli/src/main/java/com/tsurugidb/console/cli/repl/ReplEngine.java index 5295b6b..375b94b 100755 --- a/modules/tgsql/cli/src/main/java/com/tsurugidb/console/cli/repl/ReplEngine.java +++ b/modules/tgsql/cli/src/main/java/com/tsurugidb/console/cli/repl/ReplEngine.java @@ -7,6 +7,7 @@ import com.tsurugidb.console.core.executor.engine.Engine; import com.tsurugidb.console.core.executor.engine.EngineException; import com.tsurugidb.console.core.executor.report.ScriptReporter; +import com.tsurugidb.console.core.executor.sql.TransactionWrapper; import com.tsurugidb.console.core.model.CallStatement; import com.tsurugidb.console.core.model.CommitStatement; import com.tsurugidb.console.core.model.ErroneousStatement; @@ -50,6 +51,11 @@ public void connect() throws ServerException, IOException, InterruptedException delegate.connect(); } + @Override + public TransactionWrapper getTransaction() { + return delegate.getTransaction(); + } + @Override public boolean executeErroneousStatement(ErroneousStatement statement) throws EngineException, ServerException, IOException, InterruptedException { return delegate.executeErroneousStatement(statement); diff --git a/modules/tgsql/cli/src/main/java/com/tsurugidb/console/cli/repl/ReplScript.java b/modules/tgsql/cli/src/main/java/com/tsurugidb/console/cli/repl/ReplScript.java index db9fcf3..15bed89 100755 --- a/modules/tgsql/cli/src/main/java/com/tsurugidb/console/cli/repl/ReplScript.java +++ b/modules/tgsql/cli/src/main/java/com/tsurugidb/console/cli/repl/ReplScript.java @@ -3,6 +3,7 @@ import java.util.List; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.jline.reader.EndOfFileException; import org.jline.reader.LineReader; @@ -11,8 +12,12 @@ import org.slf4j.LoggerFactory; import com.tsurugidb.console.cli.repl.jline.ReplJLineParser.ParsedStatement; +import com.tsurugidb.console.core.ScriptRunner.StatementSupplier; +import com.tsurugidb.console.core.config.ScriptConfig; +import com.tsurugidb.console.core.config.ScriptCvKey.ScriptCvKeyPrompt; +import com.tsurugidb.console.core.config.ScriptPrompt; import com.tsurugidb.console.core.exception.ScriptInterruptedException; -import com.tsurugidb.console.core.executor.IoSupplier; +import com.tsurugidb.console.core.executor.sql.TransactionWrapper; import com.tsurugidb.console.core.model.Region; import com.tsurugidb.console.core.model.SimpleStatement; import com.tsurugidb.console.core.model.Statement; @@ -21,7 +26,7 @@ /** * Tsurugi SQL console repl script. */ -public class ReplScript implements IoSupplier> { +public class ReplScript implements StatementSupplier { private static final Logger LOG = LoggerFactory.getLogger(ReplScript.class); private static final String PROMPT1 = "tgsql> "; //$NON-NLS-1$ @@ -36,15 +41,17 @@ public class ReplScript implements IoSupplier> { */ public ReplScript(@Nonnull LineReader lineReader) { this.lineReader = lineReader; - - lineReader.setVariable(LineReader.SECONDARY_PROMPT_PATTERN, PROMPT2); } @Override - public List get() { + public List get(ScriptConfig config, @Nullable TransactionWrapper transaction) { + String prompt2 = getPrompt(config, ReplCvKey.PROMPT2_DEFAULT, ReplCvKey.PROMPT2_TRANSACTION, ReplCvKey.PROMPT2_OCC, ReplCvKey.PROMPT2_LTX, ReplCvKey.PROMPT2_RTX, PROMPT2, transaction); + lineReader.setVariable(LineReader.SECONDARY_PROMPT_PATTERN, prompt2); + String text; try { - text = lineReader.readLine(PROMPT1); + String prompt1 = getPrompt(config, ReplCvKey.PROMPT1_DEFAULT, ReplCvKey.PROMPT1_TRANSACTION, ReplCvKey.PROMPT1_OCC, ReplCvKey.PROMPT1_LTX, ReplCvKey.PROMPT1_RTX, PROMPT1, transaction); + text = lineReader.readLine(prompt1); } catch (UserInterruptException e) { throw new ScriptInterruptedException(e); } catch (EndOfFileException e) { @@ -63,4 +70,48 @@ public List get() { } throw new AssertionError(line); } + + private String getPrompt(ScriptConfig config, ScriptCvKeyPrompt keyDefault, ScriptCvKeyPrompt keyTx, ScriptCvKeyPrompt keyOcc, ScriptCvKeyPrompt keyLtx, ScriptCvKeyPrompt keyRtx, + String defaultPrompt, @Nullable TransactionWrapper transaction) { + var variableMap = config.getClientVariableMap(); + + ScriptPrompt prompt = null; + ScriptCvKeyPrompt key = null; + if (transaction != null) { + switch (transaction.getOption().getType()) { + case SHORT: + key = keyOcc; + break; + case LONG: + key = keyLtx; + break; + case READ_ONLY: + key = keyRtx; + break; + default: + break; + } + if (key != null) { + prompt = variableMap.get(key); + } + if (prompt == null) { + key = keyTx; + prompt = variableMap.get(key); + } + } + if (prompt == null) { + key = keyDefault; + prompt = variableMap.get(key); + if (prompt == null) { + return defaultPrompt; + } + } + + try { + return prompt.getPrompt(config, transaction); + } catch (Exception e) { + LOG.debug("ReplScript.getPrompt error (key={}, prompt={})", key, prompt, e); //$NON-NLS-1$ + return defaultPrompt; + } + } } diff --git a/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/ScriptRunner.java b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/ScriptRunner.java index 2dcc81f..0136f67 100644 --- a/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/ScriptRunner.java +++ b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/ScriptRunner.java @@ -14,6 +14,7 @@ import java.util.function.Function; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,6 +33,7 @@ import com.tsurugidb.console.core.executor.result.BasicResultProcessor; import com.tsurugidb.console.core.executor.result.ResultProcessor; import com.tsurugidb.console.core.executor.sql.BasicSqlProcessor; +import com.tsurugidb.console.core.executor.sql.TransactionWrapper; import com.tsurugidb.console.core.model.Statement; import com.tsurugidb.console.core.model.Statement.Kind; import com.tsurugidb.console.core.parser.SqlParser; @@ -224,7 +226,7 @@ private static IoSupplier toReaderSupplier(String script) thro * @throws InterruptedException if interrupted while establishing connection */ public static void repl(// - @Nonnull IoSupplier> script, // + @Nonnull StatementSupplier script, // @Nonnull ScriptConfig config, // @Nonnull Function engineWrapper, // @Nonnull ResultProcessor resultProcessor, // @@ -239,6 +241,22 @@ public static void repl(// } } + /** + * statement supplier. + */ + @FunctionalInterface + public interface StatementSupplier { + /** + * get statement. + * + * @param config script configuration + * @param transaction transaction + * @return list of statement + * @throws IOException if I/O error was occurred + */ + List get(ScriptConfig config, @Nullable TransactionWrapper transaction) throws IOException; + } + /** * Executes REPL. * @@ -249,7 +267,7 @@ public static void repl(// * @throws InterruptedException if interrupted while establishing connection */ public static void repl(// - @Nonnull IoSupplier> script, // + @Nonnull StatementSupplier script, // @Nonnull Engine engine) throws ServerException, IOException, InterruptedException { Objects.requireNonNull(script); Objects.requireNonNull(engine); @@ -259,7 +277,8 @@ public static void repl(// LOG.info("start repl"); loop: while (true) { try { - List statementList = script.get(); + var transaction = engine.getTransaction(); + List statementList = script.get(engine.getConfig(), transaction); if (statementList == null) { LOG.trace("EOF"); break; diff --git a/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/config/ScriptCvKey.java b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/config/ScriptCvKey.java index 5c82375..9ed9136 100755 --- a/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/config/ScriptCvKey.java +++ b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/config/ScriptCvKey.java @@ -25,7 +25,7 @@ public abstract class ScriptCvKey { public static final ScriptCvKeyInt SELECT_MAX_LINES = new ScriptCvKeyInt("select.maxlines"); //$NON-NLS-1$ /** sql.timing . */ public static final ScriptCvKeyBoolean SQL_TIMING = new ScriptCvKeyBoolean("sql.timing"); //$NON-NLS-1$ - /** sql.timing . */ + /** auto-commit.when-transaction-started-implicitly . */ public static final ScriptCvKeyBoolean AUTO_COMMIT_TX_STARTED_IMPLICITLY = new ScriptCvKeyBoolean("auto-commit.when-transaction-started-implicitly"); //$NON-NLS-1$ /** dot.verbose . */ @@ -147,6 +147,26 @@ public ScriptColor convertValue(@Nonnull String s) { } } + /** + * client variable key for Prompt. + */ + public static class ScriptCvKeyPrompt extends ScriptCvKey { + + /** + * Creates a new instance. + * + * @param name variable name + */ + public ScriptCvKeyPrompt(String name) { + super(name); + } + + @Override + public ScriptPrompt convertValue(String s) { + return ScriptPrompt.create(s); + } + } + private static final Map> KEY_MAP = new ConcurrentHashMap<>(); static { registerKey(ScriptCvKey.class); diff --git a/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/config/ScriptPrompt.java b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/config/ScriptPrompt.java new file mode 100755 index 0000000..1b80b05 --- /dev/null +++ b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/config/ScriptPrompt.java @@ -0,0 +1,336 @@ +package com.tsurugidb.console.core.config; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.tsurugidb.console.core.executor.report.TransactionOptionReportUtil; +import com.tsurugidb.console.core.executor.sql.TransactionWrapper; + +/** + * prompt. + */ +public class ScriptPrompt { + private static final Logger LOG = LoggerFactory.getLogger(ScriptPrompt.class); + + /** + * Creates a new instance. + * + * @param format prompt format + * @return prompt + */ + public static @Nullable ScriptPrompt create(String format) { + if (format.isEmpty()) { + return null; + } + + var pattern = Pattern.compile("(" + Pattern.quote("{{") + ")|(" + Pattern.quote("}}") + ")|\\{([^{}]*)\\}"); + var matcher = pattern.matcher(format); + + var elementList = new ArrayList(); + + int index = 0; + var sb = new StringBuilder(); + while (matcher.find()) { + int end; + end = appendSb(format, index, matcher, 1, sb, "{"); + if (end >= 0) { + index = end; + continue; + } + end = appendSb(format, index, matcher, 2, sb, "}"); + if (end >= 0) { + index = end; + continue; + } + index = addList(format, index, matcher, 3, sb, elementList); + } + addSbToList(format, index, format.length(), sb, elementList); + + if (elementList.isEmpty()) { + elementList.add(ScriptPromptElement.text(format)); + } + + return new ScriptPrompt(format, elementList); + } + + private static int appendSb(String format, int index, Matcher matcher, int group, StringBuilder sb, String text) { + if (matcher.group(group) == null) { + return -1; + } + + int start = matcher.start(group); + int end = matcher.end(group); + if (index < start) { + String s = format.substring(index, start); + sb.append(s); + } + + sb.append(text); + + return end; + } + + private static int addList(String format, int index, Matcher matcher, int group, StringBuilder sb, List elementList) { + int start = matcher.start(group) - 1; + int end = matcher.end(group) + 1; + addSbToList(format, index, start, sb, elementList); + + String text = matcher.group(group); + var element = parseElement(text); + elementList.add(element); + + return end; + } + + private static void addSbToList(String format, int index, int start, StringBuilder sb, List elementList) { + if (index < start) { + String s = format.substring(index, start); + sb.append(s); + } + + if (sb.length() != 0) { + String s = escape(sb); + elementList.add(ScriptPromptElement.text(s)); + sb.setLength(0); + } + } + + private static String escape(StringBuilder sb) { + return sb.toString().replace("\\t", "\t").replace("\\r", "\r").replace("\\n", "\n"); + } + + private static ScriptPromptElement parseElement(String text) { + String target, field; + { + int n = text.indexOf('.'); + if (n >= 0) { + target = text.substring(0, n).trim().toLowerCase(Locale.ENGLISH); + field = text.substring(n + 1).trim().toLowerCase(Locale.ENGLISH).replaceAll("[_-]", ""); + } else { + target = text; + field = ""; + } + } + + switch (target) { + case "endpoint": + return ScriptPrompt::getEndpoint; + case "tx": + switch (field) { + case "id": + return ScriptPrompt::getTransactionId; + case "option": + return ScriptPrompt::getTxOption; + case "type": + return ScriptPrompt::getTxType; + case "label": + return ScriptPrompt::getTxLabel; + case "includeddl": + case "includedefinition": + case "includedefinitions": + return ScriptPrompt::getTxIncludeDdl; + case "wp": + case "writepreserve": + return ScriptPrompt::getTxWritePreserve; + case "ira": + case "inclusivereadarea": + return ScriptPrompt::getTxInclusiveReadArea; + case "era": + case "exclusivereadarea": + return ScriptPrompt::getTxExclusiveReadArea; + case "priority": + return ScriptPrompt::getTxPriority; + default: + break; + } + break; + default: + break; + } + + LOG.trace("unsupported prompt.parseElement. text=[{}]", text); + String s = "{" + target + "." + field + "}"; + return ScriptPromptElement.text(s); + } + + /** + * prompt element. + */ + @FunctionalInterface + protected interface ScriptPromptElement { + + /** + * Creates a text prompt. + * + * @param text text + * @return prompt element for text + */ + static ScriptPromptElement text(String text) { + return (config, transaction) -> text; + } + + /** + * get prompt. + * + * @param config script configuration + * @param transaction transaction + * @return prompt text + */ + String get(ScriptConfig config, TransactionWrapper transaction); + } + + static String getEndpoint(ScriptConfig config, TransactionWrapper transaction) { + return config.getEndpoint(); + } + + static String getTransactionId(ScriptConfig config, TransactionWrapper transaction) { + if (transaction == null) { + return ""; + } + + var tx = transaction.getTransaction(); + return tx.getTransactionId(); + } + + static String getTxOption(ScriptConfig config, TransactionWrapper transaction) { + if (transaction == null) { + return ""; + } + + var option = transaction.getOption(); + var util = TransactionOptionReportUtil.getInstance(); + return util.toString(option); + } + + static String getTxType(ScriptConfig config, TransactionWrapper transaction) { + if (transaction == null) { + return ""; + } + + var option = transaction.getOption(); + var type = option.getType(); + var util = TransactionOptionReportUtil.getInstance(); + return util.toString(type); + } + + static String getTxLabel(ScriptConfig config, TransactionWrapper transaction) { + if (transaction == null) { + return ""; + } + + var option = transaction.getOption(); + String label = option.getLabel(); + if (label == null) { + return ""; + } + return label; + } + + static String getTxIncludeDdl(ScriptConfig config, TransactionWrapper transaction) { + if (transaction == null) { + return ""; + } + + var option = transaction.getOption(); + boolean includeDdl = option.getModifiesDefinitions(); + return Boolean.toString(includeDdl); + } + + static String getTxWritePreserve(ScriptConfig config, TransactionWrapper transaction) { + if (transaction == null) { + return ""; + } + + var option = transaction.getOption(); + var list = option.getWritePreservesList(); + var util = TransactionOptionReportUtil.getInstance(); + return util.toStringWp(list); + } + + static String getTxInclusiveReadArea(ScriptConfig config, TransactionWrapper transaction) { + if (transaction == null) { + return ""; + } + + var option = transaction.getOption(); + var list = option.getInclusiveReadAreasList(); + var util = TransactionOptionReportUtil.getInstance(); + return util.toStringRa(list); + } + + static String getTxExclusiveReadArea(ScriptConfig config, TransactionWrapper transaction) { + if (transaction == null) { + return ""; + } + + var option = transaction.getOption(); + var list = option.getExclusiveReadAreasList(); + var util = TransactionOptionReportUtil.getInstance(); + return util.toStringRa(list); + } + + static String getTxPriority(ScriptConfig config, TransactionWrapper transaction) { + if (transaction == null) { + return ""; + } + + var option = transaction.getOption(); + var priority = option.getPriority(); + var util = TransactionOptionReportUtil.getInstance(); + return util.toString(priority); + } + + private final String format; + private final List elementList; + + /** + * Creates a new instance. + * + * @param format prompt format + * @param elementList list of prompt element + */ + public ScriptPrompt(String format, List elementList) { + this.format = format; + this.elementList = elementList; + } + + /** + * get prompt. + * + * @param config script configuration + * @param transaction transaction + * @return prompt + */ + public String getPrompt(ScriptConfig config, TransactionWrapper transaction) { + if (elementList.size() == 1) { + var element = elementList.get(0); + return element.get(config, transaction); + } + + var sb = new StringBuilder(); + for (var element : elementList) { + String s; + try { + s = element.get(config, transaction); + } catch (Exception e) { + LOG.debug("PromptElement.get error", e); + s = ""; + } + sb.append(s); + } + return sb.toString(); + } + + @Override + public String toString() { + return this.format; + } +} diff --git a/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/engine/BasicEngine.java b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/engine/BasicEngine.java index ba736e2..2870d05 100644 --- a/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/engine/BasicEngine.java +++ b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/engine/BasicEngine.java @@ -25,6 +25,7 @@ import com.tsurugidb.console.core.executor.report.ScriptReporter; import com.tsurugidb.console.core.executor.result.ResultProcessor; import com.tsurugidb.console.core.executor.sql.SqlProcessor; +import com.tsurugidb.console.core.executor.sql.TransactionWrapper; import com.tsurugidb.console.core.model.CallStatement; import com.tsurugidb.console.core.model.CommitStatement; import com.tsurugidb.console.core.model.ErroneousStatement; @@ -100,6 +101,11 @@ public boolean disconnect() throws ServerException, IOException, InterruptedExce return sqlProcessor.disconnect(); } + @Override + public TransactionWrapper getTransaction() { + return sqlProcessor.getTransaction(); + } + @Override public boolean executeEmptyStatement(@Nonnull Statement statement) throws EngineException, ServerException, IOException, InterruptedException { Objects.requireNonNull(statement); diff --git a/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/engine/Engine.java b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/engine/Engine.java index b6c3bcc..16a936c 100644 --- a/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/engine/Engine.java +++ b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/engine/Engine.java @@ -3,9 +3,11 @@ import java.io.IOException; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import com.tsurugidb.console.core.config.ScriptConfig; import com.tsurugidb.console.core.executor.report.ScriptReporter; +import com.tsurugidb.console.core.executor.sql.TransactionWrapper; import com.tsurugidb.console.core.model.Statement; import com.tsurugidb.tsubakuro.exception.ServerException; @@ -56,6 +58,15 @@ default boolean disconnect() throws ServerException, IOException, InterruptedExc return false; // do override } + /** + * Returns the running transaction. + * + * @return the running transaction, or {@code null} if there is no active transactions + */ + default @Nullable TransactionWrapper getTransaction() { + throw new UnsupportedOperationException("do override"); + } + /** * Executes a statement. * diff --git a/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/report/ScriptReporter.java b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/report/ScriptReporter.java index 9cb69b2..dbc5ccd 100755 --- a/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/report/ScriptReporter.java +++ b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/report/ScriptReporter.java @@ -15,11 +15,7 @@ import com.tsurugidb.sql.proto.SqlCommon; import com.tsurugidb.sql.proto.SqlRequest.CommitStatus; -import com.tsurugidb.sql.proto.SqlRequest.ReadArea; import com.tsurugidb.sql.proto.SqlRequest.TransactionOption; -import com.tsurugidb.sql.proto.SqlRequest.TransactionPriority; -import com.tsurugidb.sql.proto.SqlRequest.TransactionType; -import com.tsurugidb.sql.proto.SqlRequest.WritePreserve; import com.tsurugidb.tsubakuro.exception.ServerException; import com.tsurugidb.tsubakuro.explain.PlanGraph; import com.tsurugidb.tsubakuro.sql.CounterType; @@ -103,8 +99,9 @@ public void reportDisconnect(boolean disconnected) { * @param option transaction option */ public void reportStartTransactionImplicitly(TransactionOption option) { + var util = TransactionOptionReportUtil.getInstance(); String message = MessageFormat.format("start transaction implicitly. option={0}", // - toString(option)); + util.toString(option)); reportStartTransactionImplicitly(message, option); } @@ -124,104 +121,12 @@ protected void reportStartTransactionImplicitly(String message, TransactionOptio * @param option transaction option */ public void reportTransactionStarted(TransactionOption option) { + var util = TransactionOptionReportUtil.getInstance(); String message = MessageFormat.format("transaction started. option={0}", // - toString(option)); + util.toString(option)); reportTransactionStarted(message, option); } - protected String toString(TransactionOption option) { - var sb = new StringBuilder(64); - - { - sb.append("[\n type: "); - sb.append(toString(option.getType())); - } - { - var label = option.getLabel(); - if (label != null && !label.isEmpty()) { - sb.append("\n label: \""); - sb.append(label); - sb.append("\""); - } - } - { - var list = option.getWritePreservesList(); - if (!list.isEmpty()) { - sb.append("\n write_preserve: "); - sb.append(toStringWp(list)); - } - } - { - var includeDdl = option.getModifiesDefinitions(); - if (includeDdl) { - sb.append("\n include_ddl: "); - sb.append(includeDdl); - } - } - { - var list = option.getInclusiveReadAreasList(); - if (!list.isEmpty()) { - sb.append("\n read_area_include: "); - sb.append(toStringRa(list)); - } - } - { - var list = option.getExclusiveReadAreasList(); - if (!list.isEmpty()) { - sb.append("\n read_area_exclude: "); - sb.append(toStringRa(list)); - } - } - { - var priority = option.getPriority(); - if (priority != null && priority != TransactionPriority.TRANSACTION_PRIORITY_UNSPECIFIED) { - sb.append("\n priority: "); - sb.append(toString(priority)); - } - } - - sb.append("\n]"); - return sb.toString(); - } - - protected String toString(TransactionType type) { - switch (type) { - case SHORT: - return "OCC"; - case LONG: - return "LTX"; - case READ_ONLY: - return "RTX"; - case TRANSACTION_TYPE_UNSPECIFIED: - return "DEFAULT"; - default: - return type.toString(); - } - } - - protected String toStringWp(List list) { - return list.stream().map(s -> "\"" + s.getTableName() + "\"").collect(Collectors.joining(", ")); - } - - protected String toStringRa(List list) { - return list.stream().map(s -> "\"" + s.getTableName() + "\"").collect(Collectors.joining(", ")); - } - - protected String toString(TransactionPriority priority) { - switch (priority) { - case WAIT: - return "prior deferrable"; - case INTERRUPT: - return "prior immediate"; - case WAIT_EXCLUDE: - return "excluding deferrable"; - case INTERRUPT_EXCLUDE: - return "excluding immediate"; - default: - return priority.toString(); - } - } - /** * output message for transaction started. * diff --git a/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/report/TransactionOptionReportUtil.java b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/report/TransactionOptionReportUtil.java new file mode 100755 index 0000000..185b968 --- /dev/null +++ b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/report/TransactionOptionReportUtil.java @@ -0,0 +1,161 @@ +package com.tsurugidb.console.core.executor.report; + +import java.util.List; +import java.util.stream.Collectors; + +import com.tsurugidb.sql.proto.SqlRequest.ReadArea; +import com.tsurugidb.sql.proto.SqlRequest.TransactionOption; +import com.tsurugidb.sql.proto.SqlRequest.TransactionPriority; +import com.tsurugidb.sql.proto.SqlRequest.TransactionType; +import com.tsurugidb.sql.proto.SqlRequest.WritePreserve; + +/** + * transaction option report utility. + */ +public class TransactionOptionReportUtil { + + private static TransactionOptionReportUtil defaultInstance = new TransactionOptionReportUtil(); + + /** + * get default instance. + * + * @return transaction option report utility + */ + public static TransactionOptionReportUtil getInstance() { + return defaultInstance; + } + + /** + * set default instance. + * + * @param instance transaction option report utility + */ + public static void setDefaultInstance(TransactionOptionReportUtil instance) { + defaultInstance = instance; + } + + /** + * convert text. + * + * @param option transaction option + * @return text + */ + public String toString(TransactionOption option) { + var sb = new StringBuilder(64); + + { + sb.append("[\n type: "); + sb.append(toString(option.getType())); + } + { + var label = option.getLabel(); + if (label != null && !label.isEmpty()) { + sb.append("\n label: \""); + sb.append(label); + sb.append("\""); + } + } + { + var list = option.getWritePreservesList(); + if (!list.isEmpty()) { + sb.append("\n write_preserve: "); + sb.append(toStringWp(list)); + } + } + { + var includeDdl = option.getModifiesDefinitions(); + if (includeDdl) { + sb.append("\n include_ddl: "); + sb.append(includeDdl); + } + } + { + var list = option.getInclusiveReadAreasList(); + if (!list.isEmpty()) { + sb.append("\n read_area_include: "); + sb.append(toStringRa(list)); + } + } + { + var list = option.getExclusiveReadAreasList(); + if (!list.isEmpty()) { + sb.append("\n read_area_exclude: "); + sb.append(toStringRa(list)); + } + } + { + var priority = option.getPriority(); + if (priority != null && priority != TransactionPriority.TRANSACTION_PRIORITY_UNSPECIFIED) { + sb.append("\n priority: "); + sb.append(toString(priority)); + } + } + + sb.append("\n]"); + return sb.toString(); + } + + /** + * convert text. + * + * @param type transaction type + * @return text + */ + public String toString(TransactionType type) { + switch (type) { + case SHORT: + return "OCC"; + case LONG: + return "LTX"; + case READ_ONLY: + return "RTX"; + case TRANSACTION_TYPE_UNSPECIFIED: + return "DEFAULT"; + default: + return type.toString(); + } + } + + /** + * convert text. + * + * @param list write preserve + * @return text + */ + public String toStringWp(List list) { + return list.stream().map(s -> "\"" + s.getTableName() + "\"").collect(Collectors.joining(", ")); + } + + /** + * convert text. + * + * @param list read area + * @return text + */ + public String toStringRa(List list) { + return list.stream().map(s -> "\"" + s.getTableName() + "\"").collect(Collectors.joining(", ")); + } + + /** + * convert text. + * + * @param priority transaction priority + * @return text + */ + public String toString(TransactionPriority priority) { + switch (priority) { + case TRANSACTION_PRIORITY_UNSPECIFIED: + return "unspecified"; + case WAIT: + return "prior deferrable"; + case INTERRUPT: + return "prior immediate"; + case WAIT_EXCLUDE: + return "excluding deferrable"; + case INTERRUPT_EXCLUDE: + return "excluding immediate"; + default: + return priority.toString().toLowerCase(); + } + } +} diff --git a/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/sql/BasicSqlProcessor.java b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/sql/BasicSqlProcessor.java index e83f0aa..4a62769 100644 --- a/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/sql/BasicSqlProcessor.java +++ b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/sql/BasicSqlProcessor.java @@ -21,7 +21,6 @@ import com.tsurugidb.tsubakuro.sql.SqlServiceException; import com.tsurugidb.tsubakuro.sql.StatementMetadata; import com.tsurugidb.tsubakuro.sql.TableMetadata; -import com.tsurugidb.tsubakuro.sql.Transaction; import com.tsurugidb.tsubakuro.sql.exception.TargetNotFoundException; /** @@ -33,7 +32,7 @@ public class BasicSqlProcessor implements SqlProcessor { private String sessionEndpoint; private Session session; private SqlClient sqlClient; - private Transaction transaction; + private TransactionWrapper transaction; /** * Creates a new instance. @@ -132,15 +131,17 @@ public void startTransaction(@Nonnull SqlRequest.TransactionOption option) throw desireInactive(); LOG.debug("start transaction: {}", option); var client = getSqlClient(); - transaction = client.createTransaction(option).await(); + var tx = client.createTransaction(option).await(); + this.transaction = new TransactionWrapper(tx, option); } @Override public void commitTransaction(@Nullable SqlRequest.CommitStatus status) throws ServerException, IOException, InterruptedException { LOG.debug("start commit: {}", status); //$NON-NLS-1$ desireActive(); - try (var t = transaction) { + try (var tx = transaction) { transaction = null; + var t = tx.getTransaction(); if (status == null) { t.commit().await(); } else { @@ -153,8 +154,9 @@ public void commitTransaction(@Nullable SqlRequest.CommitStatus status) throws S public void rollbackTransaction() throws ServerException, IOException, InterruptedException { LOG.debug("start rollback"); //$NON-NLS-1$ if (isTransactionActive()) { - try (var t = transaction) { + try (var tx = transaction) { transaction = null; + var t = tx.getTransaction(); t.rollback().await(); } } else { @@ -169,13 +171,14 @@ public void rollbackTransaction() throws ServerException, IOException, Interrupt desireActive(); var client = getSqlClient(); try (var prepared = client.prepare(statement).await()) { + var t = transaction.getTransaction(); if (prepared.hasResultRecords()) { LOG.debug("start query: '{}'", statement); - var result = transaction.executeQuery(prepared).await(); + var result = t.executeQuery(prepared).await(); return new PreparedStatementResult(result); } LOG.debug("start execute: '{}'", statement); - var result = transaction.executeStatement(prepared).await(); + var result = t.executeStatement(prepared).await(); return new PreparedStatementResult(result); } } @@ -211,7 +214,8 @@ public String getTransactionId() { if (!isTransactionActive()) { return null; } - return transaction.getTransactionId(); + var t = transaction.getTransaction(); + return t.getTransactionId(); } @Override @@ -219,15 +223,12 @@ public SqlServiceException getTransactionException() throws ServerException, IOE if (!isTransactionActive()) { return null; } - return transaction.getSqlServiceException().await(); + var t = transaction.getTransaction(); + return t.getSqlServiceException().await(); } - /** - * Returns the running transaction. - * - * @return the running transaction, or {@code null} if there is no active transactions - */ - public @Nullable Transaction getTransaction() { + @Override + public @Nullable TransactionWrapper getTransaction() { return transaction; } diff --git a/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/sql/SqlProcessor.java b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/sql/SqlProcessor.java index fd89890..da27a60 100644 --- a/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/sql/SqlProcessor.java +++ b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/sql/SqlProcessor.java @@ -85,6 +85,14 @@ public interface SqlProcessor extends ServerResource { */ boolean isTransactionActive(); + /** + * Returns the running transaction. + * + * @return the running transaction, or {@code null} if there is no active transactions + */ + @Nullable + TransactionWrapper getTransaction(); + /** * Provides transaction id that is unique to for the duration of the database server's lifetime. * diff --git a/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/sql/TransactionWrapper.java b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/sql/TransactionWrapper.java new file mode 100755 index 0000000..0f8243c --- /dev/null +++ b/modules/tgsql/core/src/main/java/com/tsurugidb/console/core/executor/sql/TransactionWrapper.java @@ -0,0 +1,51 @@ +package com.tsurugidb.console.core.executor.sql; + +import java.io.IOException; + +import com.tsurugidb.sql.proto.SqlRequest; +import com.tsurugidb.sql.proto.SqlRequest.TransactionOption; +import com.tsurugidb.tsubakuro.exception.ServerException; +import com.tsurugidb.tsubakuro.sql.Transaction; + +/** + * Transaction with transaction option. + */ +public class TransactionWrapper implements AutoCloseable { + + private final Transaction transaction; + private final TransactionOption option; + + /** + * Creates a new instance. + * + * @param transaction transaction + * @param option transaction option + */ + public TransactionWrapper(Transaction transaction, SqlRequest.TransactionOption option) { + this.transaction = transaction; + this.option = option; + } + + /** + * get transaction. + * + * @return transaction + */ + public Transaction getTransaction() { + return this.transaction; + } + + /** + * get transaction option. + * + * @return transaction option + */ + public SqlRequest.TransactionOption getOption() { + return this.option; + } + + @Override + public void close() throws ServerException, IOException, InterruptedException { + transaction.close(); + } +} diff --git a/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/config/ScriptPromptTest.java b/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/config/ScriptPromptTest.java new file mode 100755 index 0000000..206b16b --- /dev/null +++ b/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/config/ScriptPromptTest.java @@ -0,0 +1,158 @@ +package com.tsurugidb.console.core.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.tsurugidb.console.core.executor.sql.TransactionWrapper; +import com.tsurugidb.sql.proto.SqlRequest.ReadArea; +import com.tsurugidb.sql.proto.SqlRequest.TransactionOption; +import com.tsurugidb.sql.proto.SqlRequest.TransactionType; +import com.tsurugidb.sql.proto.SqlRequest.WritePreserve; +import com.tsurugidb.tsubakuro.sql.Transaction; + +class ScriptPromptTest { + + @Test + void empty() { + var prompt = ScriptPrompt.create(""); + assertNull(prompt); + } + + @Test + void constant() { + var prompt = ScriptPrompt.create("tgsql> "); + String actual = prompt.getPrompt(null, null); + assertEquals("tgsql> ", actual); + } + + @Test + void endpoint() { + var prompt = ScriptPrompt.create("{endpoint}> "); + { + var config = new ScriptConfig(); + config.setEndpoint("tcp://localhost:12345"); + String actual = prompt.getPrompt(config, null); + assertEquals("tcp://localhost:12345> ", actual); + } + { + var config = new ScriptConfig(); + String actual = prompt.getPrompt(config, null); + assertEquals("null> ", actual); + } + } + + @Test + void transactionId() { + var prompt = ScriptPrompt.create("{tx.id}> "); + var tx = new Transaction() { + @Override + public String getTransactionId() { + return "TID-12345"; + } + }; + var transaction = new TransactionWrapper(tx, null); + String actual = prompt.getPrompt(null, transaction); + assertEquals("TID-12345> ", actual); + } + + @Test + void txType() { + var prompt = ScriptPrompt.create("type={tx.type}> "); + var option = TransactionOption.newBuilder().setType(TransactionType.SHORT).build(); + var transaction = new TransactionWrapper(null, option); + String actual = prompt.getPrompt(null, transaction); + assertEquals("type=OCC> ", actual); + } + + @Test + void txLabel() { + var prompt = ScriptPrompt.create("label=[{tx.label}]> "); + var option = TransactionOption.newBuilder().setLabel("abc").build(); + var transaction = new TransactionWrapper(null, option); + String actual = prompt.getPrompt(null, transaction); + assertEquals("label=[abc]> ", actual); + } + + @ParameterizedTest + @ValueSource(strings = { "tx.include-ddl", "tx.include_ddl", "tx.includeDdl" }) + void txIncludeDdl(String property) { + var prompt = ScriptPrompt.create("include_ddl={" + property + "}> "); + var option = TransactionOption.newBuilder().setModifiesDefinitions(true).build(); + var transaction = new TransactionWrapper(null, option); + String actual = prompt.getPrompt(null, transaction); + assertEquals("include_ddl=true> ", actual); + } + + @ParameterizedTest + @ValueSource(strings = { "tx.wp", "tx.write-preserve" }) + void txWritePreserve(String property) { + var prompt = ScriptPrompt.create("{tx.type}(wp=[{" + property + "}])> "); + var option = TransactionOption.newBuilder().setType(TransactionType.LONG) // + .addWritePreserves(WritePreserve.newBuilder().setTableName("test1").build()) // + .addWritePreserves(WritePreserve.newBuilder().setTableName("test2").build()) // + .build(); + var transaction = new TransactionWrapper(null, option); + String actual = prompt.getPrompt(null, transaction); + assertEquals("LTX(wp=[\"test1\", \"test2\"])> ", actual); + } + + @ParameterizedTest + @ValueSource(strings = { "tx.ira", "tx.inclusive-read-area" }) + void txInclusiveReadArea(String property) { + var prompt = ScriptPrompt.create("{tx.type}(ra=[{" + property + "}])> "); + var option = TransactionOption.newBuilder().setType(TransactionType.LONG) // + .addInclusiveReadAreas(ReadArea.newBuilder().setTableName("test1").build()) // + .addInclusiveReadAreas(ReadArea.newBuilder().setTableName("test2").build()) // + .build(); + var transaction = new TransactionWrapper(null, option); + String actual = prompt.getPrompt(null, transaction); + assertEquals("LTX(ra=[\"test1\", \"test2\"])> ", actual); + } + + @ParameterizedTest + @ValueSource(strings = { "tx.era", "tx.exclusive-read-area" }) + void txExclusiveReadArea(String property) { + var prompt = ScriptPrompt.create("{tx.type}(ra=[{" + property + "}])> "); + var option = TransactionOption.newBuilder().setType(TransactionType.LONG) // + .addExclusiveReadAreas(ReadArea.newBuilder().setTableName("test1").build()) // + .addExclusiveReadAreas(ReadArea.newBuilder().setTableName("test2").build()) // + .build(); + var transaction = new TransactionWrapper(null, option); + String actual = prompt.getPrompt(null, transaction); + assertEquals("LTX(ra=[\"test1\", \"test2\"])> ", actual); + } + + @Test + void txPriority() { + var prompt = ScriptPrompt.create("{tx.priority}> "); + var option = TransactionOption.newBuilder().build(); + var transaction = new TransactionWrapper(null, option); + String actual = prompt.getPrompt(null, transaction); + assertEquals("unspecified> ", actual); + } + + @Test + void brace1() { + var prompt = ScriptPrompt.create("{{abc}}"); + String actual = prompt.getPrompt(null, null); + assertEquals("{abc}", actual); + } + + @Test + void brace2() { + var prompt = ScriptPrompt.create("tid={{{tx.id}}}> "); + var tx = new com.tsurugidb.tsubakuro.sql.Transaction() { + @Override + public String getTransactionId() { + return "TID-12345"; + } + }; + var transaction = new TransactionWrapper(tx, null); + String actual = prompt.getPrompt(null, transaction); + assertEquals("tid={TID-12345}> ", actual); + } +} diff --git a/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/executor/engine/BasicEngineTest.java b/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/executor/engine/BasicEngineTest.java index 904e873..b842309 100644 --- a/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/executor/engine/BasicEngineTest.java +++ b/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/executor/engine/BasicEngineTest.java @@ -25,6 +25,7 @@ import com.tsurugidb.console.core.executor.result.ResultProcessor; import com.tsurugidb.console.core.executor.sql.PreparedStatementResult; import com.tsurugidb.console.core.executor.sql.SqlProcessor; +import com.tsurugidb.console.core.executor.sql.TransactionWrapper; import com.tsurugidb.console.core.model.Region; import com.tsurugidb.console.core.model.Statement; import com.tsurugidb.console.core.parser.SqlParser; @@ -86,6 +87,11 @@ public boolean isSessionActive() { throw new UnsupportedOperationException(); } + @Override + public TransactionWrapper getTransaction() { + throw new UnsupportedOperationException(); + } + @Override public boolean isTransactionActive() { return active; diff --git a/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/executor/report/ScriptReporterTest.java b/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/executor/report/ScriptReporterTest.java index 59b7577..a3edfd2 100755 --- a/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/executor/report/ScriptReporterTest.java +++ b/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/executor/report/ScriptReporterTest.java @@ -4,11 +4,6 @@ import org.junit.jupiter.api.Test; -import com.tsurugidb.sql.proto.SqlRequest.ReadArea; -import com.tsurugidb.sql.proto.SqlRequest.TransactionOption; -import com.tsurugidb.sql.proto.SqlRequest.TransactionPriority; -import com.tsurugidb.sql.proto.SqlRequest.TransactionType; -import com.tsurugidb.sql.proto.SqlRequest.WritePreserve; import com.tsurugidb.tsubakuro.sql.CounterType; class ScriptReporterTest { @@ -36,93 +31,6 @@ public void implicit(String message) { } }; - @Test - void toStringOcc() { - { - var option = TransactionOption.newBuilder().setType(TransactionType.SHORT).build(); - var actual = REPORTER.toString(option); - var expected = "[" // - + "\n type: OCC" // - + "\n]"; - assertEquals(expected, actual); - } - { - var option = TransactionOption.newBuilder().setType(TransactionType.SHORT).setLabel("test").build(); - var actual = REPORTER.toString(option); - var expected = "[" // - + "\n type: OCC" // - + "\n label: \"test\"" // - + "\n]"; - assertEquals(expected, actual); - } - } - - @Test - void toStringLtx() { - { - var option = TransactionOption.newBuilder().setType(TransactionType.LONG).setModifiesDefinitions(true).build(); - var actual = REPORTER.toString(option); - var expected = "[" // - + "\n type: LTX" // - + "\n include_ddl: true" // - + "\n]"; - assertEquals(expected, actual); - } - { - var option = TransactionOption.newBuilder().setType(TransactionType.LONG).addWritePreserves(WritePreserve.newBuilder().setTableName("test")).build(); - var actual = REPORTER.toString(option); - var expected = "[" // - + "\n type: LTX" // - + "\n write_preserve: \"test\"" // - + "\n]"; - assertEquals(expected, actual); - } - { - var option = TransactionOption.newBuilder().setType(TransactionType.LONG) // - .addWritePreserves(WritePreserve.newBuilder().setTableName("test1")) // - .addWritePreserves(WritePreserve.newBuilder().setTableName("test2")) // - .build(); - var actual = REPORTER.toString(option); - var expected = "[" // - + "\n type: LTX" // - + "\n write_preserve: \"test1\", \"test2\"" // - + "\n]"; - assertEquals(expected, actual); - } - { - var option = TransactionOption.newBuilder().setType(TransactionType.LONG) // - .addWritePreserves(WritePreserve.newBuilder().setTableName("test1")) // - .addWritePreserves(WritePreserve.newBuilder().setTableName("test2")) // - .addInclusiveReadAreas(ReadArea.newBuilder().setTableName("in1")) // - .addInclusiveReadAreas(ReadArea.newBuilder().setTableName("in2")) // - .addExclusiveReadAreas(ReadArea.newBuilder().setTableName("ex1")) // - .addExclusiveReadAreas(ReadArea.newBuilder().setTableName("ex2")) // - .setPriority(TransactionPriority.WAIT) // - .build(); - var actual = REPORTER.toString(option); - var expected = "[" // - + "\n type: LTX" // - + "\n write_preserve: \"test1\", \"test2\"" // - + "\n read_area_include: \"in1\", \"in2\"" // - + "\n read_area_exclude: \"ex1\", \"ex2\"" // - + "\n priority: prior deferrable" // - + "\n]"; - assertEquals(expected, actual); - } - } - - @Test - void toStringRtx() { - { - var option = TransactionOption.newBuilder().setType(TransactionType.READ_ONLY).build(); - var actual = REPORTER.toString(option); - var expected = "[" // - + "\n type: RTX" // - + "\n]"; - assertEquals(expected, actual); - } - } - @Test void getStatementResultMessage() { { diff --git a/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/executor/report/TransactionOptionReportUtilTest.java b/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/executor/report/TransactionOptionReportUtilTest.java new file mode 100755 index 0000000..2d5dcfa --- /dev/null +++ b/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/executor/report/TransactionOptionReportUtilTest.java @@ -0,0 +1,103 @@ +package com.tsurugidb.console.core.executor.report; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import com.tsurugidb.sql.proto.SqlRequest.ReadArea; +import com.tsurugidb.sql.proto.SqlRequest.TransactionOption; +import com.tsurugidb.sql.proto.SqlRequest.TransactionPriority; +import com.tsurugidb.sql.proto.SqlRequest.TransactionType; +import com.tsurugidb.sql.proto.SqlRequest.WritePreserve; + +class TransactionOptionReportUtilTest { + + private static final TransactionOptionReportUtil TARGET = TransactionOptionReportUtil.getInstance(); + + @Test + void toStringOcc() { + { + var option = TransactionOption.newBuilder().setType(TransactionType.SHORT).build(); + var actual = TARGET.toString(option); + var expected = "[" // + + "\n type: OCC" // + + "\n]"; + assertEquals(expected, actual); + } + { + var option = TransactionOption.newBuilder().setType(TransactionType.SHORT).setLabel("test").build(); + var actual = TARGET.toString(option); + var expected = "[" // + + "\n type: OCC" // + + "\n label: \"test\"" // + + "\n]"; + assertEquals(expected, actual); + } + } + + @Test + void toStringLtx() { + { + var option = TransactionOption.newBuilder().setType(TransactionType.LONG).setModifiesDefinitions(true).build(); + var actual = TARGET.toString(option); + var expected = "[" // + + "\n type: LTX" // + + "\n include_ddl: true" // + + "\n]"; + assertEquals(expected, actual); + } + { + var option = TransactionOption.newBuilder().setType(TransactionType.LONG).addWritePreserves(WritePreserve.newBuilder().setTableName("test")).build(); + var actual = TARGET.toString(option); + var expected = "[" // + + "\n type: LTX" // + + "\n write_preserve: \"test\"" // + + "\n]"; + assertEquals(expected, actual); + } + { + var option = TransactionOption.newBuilder().setType(TransactionType.LONG) // + .addWritePreserves(WritePreserve.newBuilder().setTableName("test1")) // + .addWritePreserves(WritePreserve.newBuilder().setTableName("test2")) // + .build(); + var actual = TARGET.toString(option); + var expected = "[" // + + "\n type: LTX" // + + "\n write_preserve: \"test1\", \"test2\"" // + + "\n]"; + assertEquals(expected, actual); + } + { + var option = TransactionOption.newBuilder().setType(TransactionType.LONG) // + .addWritePreserves(WritePreserve.newBuilder().setTableName("test1")) // + .addWritePreserves(WritePreserve.newBuilder().setTableName("test2")) // + .addInclusiveReadAreas(ReadArea.newBuilder().setTableName("in1")) // + .addInclusiveReadAreas(ReadArea.newBuilder().setTableName("in2")) // + .addExclusiveReadAreas(ReadArea.newBuilder().setTableName("ex1")) // + .addExclusiveReadAreas(ReadArea.newBuilder().setTableName("ex2")) // + .setPriority(TransactionPriority.WAIT) // + .build(); + var actual = TARGET.toString(option); + var expected = "[" // + + "\n type: LTX" // + + "\n write_preserve: \"test1\", \"test2\"" // + + "\n read_area_include: \"in1\", \"in2\"" // + + "\n read_area_exclude: \"ex1\", \"ex2\"" // + + "\n priority: prior deferrable" // + + "\n]"; + assertEquals(expected, actual); + } + } + + @Test + void toStringRtx() { + { + var option = TransactionOption.newBuilder().setType(TransactionType.READ_ONLY).build(); + var actual = TARGET.toString(option); + var expected = "[" // + + "\n type: RTX" // + + "\n]"; + assertEquals(expected, actual); + } + } +} diff --git a/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/executor/sql/BasicSqlProcessorTest.java b/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/executor/sql/BasicSqlProcessorTest.java index 2af04d7..d812f4a 100644 --- a/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/executor/sql/BasicSqlProcessorTest.java +++ b/modules/tgsql/core/src/test/java/com/tsurugidb/console/core/executor/sql/BasicSqlProcessorTest.java @@ -47,7 +47,7 @@ public FutureResponse createTransaction(SqlRequest.TransactionOptio assertFalse(sql.isTransactionActive()); sql.startTransaction(SqlRequest.TransactionOption.getDefaultInstance()); assertTrue(sql.isTransactionActive()); - assertSame(tx, sql.getTransaction()); + assertSame(tx, sql.getTransaction().getTransaction()); } } @@ -64,8 +64,7 @@ public FutureResponse createTransaction(SqlRequest.TransactionOptio }; try (var sql = new BasicSqlProcessor(client)) { sql.startTransaction(SqlRequest.TransactionOption.getDefaultInstance()); - assertThrows(IllegalStateException.class, - () -> sql.startTransaction(SqlRequest.TransactionOption.getDefaultInstance())); + assertThrows(IllegalStateException.class, () -> sql.startTransaction(SqlRequest.TransactionOption.getDefaultInstance())); } } @@ -152,9 +151,7 @@ void execute_wo_result() throws Exception { PreparedStatement ps = createPreparedStatement(false); Transaction tx = new Transaction() { @Override - public FutureResponse executeStatement( - PreparedStatement statement, - Collection parameters) throws IOException { + public FutureResponse executeStatement(PreparedStatement statement, Collection parameters) throws IOException { if (!reached.compareAndSet(false, true)) { throw new AssertionError(); } @@ -167,10 +164,9 @@ public FutureResponse executeStatement( public FutureResponse createTransaction(SqlRequest.TransactionOption option) throws IOException { return FutureResponse.returns(tx); } + @Override - public FutureResponse prepare( - String source, - Collection placeholders) throws IOException { + public FutureResponse prepare(String source, Collection placeholders) throws IOException { return FutureResponse.returns(ps); } }; @@ -187,14 +183,11 @@ public FutureResponse prepare( @Test void execute_w_result() throws Exception { var reached = new AtomicBoolean(); - ResultSet r = Relation.of() - .getResultSet(new ResultSetMetadataAdapter(SqlResponse.ResultSetMetadata.getDefaultInstance())); + ResultSet r = Relation.of().getResultSet(new ResultSetMetadataAdapter(SqlResponse.ResultSetMetadata.getDefaultInstance())); PreparedStatement ps = createPreparedStatement(true); Transaction tx = new Transaction() { @Override - public FutureResponse executeQuery( - PreparedStatement statement, - Collection parameters) throws IOException { + public FutureResponse executeQuery(PreparedStatement statement, Collection parameters) throws IOException { if (!reached.compareAndSet(false, true)) { throw new AssertionError(); } @@ -207,10 +200,9 @@ public FutureResponse executeQuery( public FutureResponse createTransaction(SqlRequest.TransactionOption option) throws IOException { return FutureResponse.returns(tx); } + @Override - public FutureResponse prepare( - String source, - Collection placeholders) throws IOException { + public FutureResponse prepare(String source, Collection placeholders) throws IOException { return FutureResponse.returns(ps); } }; @@ -246,19 +238,16 @@ public FutureResponse explain(String source) throws IOExcepti } @Override - public FutureResponse prepare( - String source, - Collection placeholders) throws IOException { + public FutureResponse prepare(String source, Collection placeholders) throws IOException { assertNull(preparedSource); preparedSource = source; return FutureResponse.returns(preparedStatement); } @Override - public FutureResponse explain( - PreparedStatement statement, - Collection parameters) throws IOException { - assertSame(preparedStatement, statement);; + public FutureResponse explain(PreparedStatement statement, Collection parameters) throws IOException { + assertSame(preparedStatement, statement); + ; return explain(preparedSource); } }; diff --git a/modules/tgsql/docs/prompt_ja.md b/modules/tgsql/docs/prompt_ja.md new file mode 100755 index 0000000..80a49cc --- /dev/null +++ b/modules/tgsql/docs/prompt_ja.md @@ -0,0 +1,86 @@ +# prompt for tgsql + +Tsurugi SQLコンソール(tgsql)のプロンプトはクライアント変数によって変更することが出来る。 + + + +## デフォルトのプロンプト + +最初に表示されるプロンプトをprompt1、複数行にまたがって入力するときのプロンプトをprompt2と呼ぶ。 + +promt1, prompt2のデフォルトは以下の通り。 + +``` +tgsql> ...prompt1 + | ...prompt2 +``` + + + +## プロンプトのクライアント変数 + +以下のクライアント変数にプロンプトの書式を設定する。 + +| クライアント変数名 | 説明 | +| ----------------------- | ---------------------------------------------- | +| console.prompt1.default | デフォルトのプロンプト | +| console.prompt1.tx | トランザクション実行中のデフォルトのプロンプト | +| console.prompt1.occ | OCC実行中のプロンプト | +| console.prompt1.ltx | LTX実行中のプロンプト | +| console.prompt1.rtx | RTX実行中のプロンプト | + +トランザクションが実行されていないときやトランザクション実行中のプロンプトが設定されていない場合は、console.prompt1.defaultの設定が使われる。 + +トランザクション実行中でconsole.prompt1.occ/ltx/rtxが設定されていない場合は、console.prompt1.txの設定が使われる。 + +空文字列を設定するとデフォルトに戻る。(設定が削除される) + +prompt2もprompt1と同様に設定可能。 + + + +## プロンプトの書式 + +プロンプトのクライアント変数には専用の書式を設定する。 + +波括弧で囲ったプロパティーは実行中の値に置換される。 + +トランザクションに関するプロパティーは、トランザクション実行中のプロンプトのみで使用できる。 + +| プロパティー名 | 説明 | 出力例 | +| ------------------------------ | -------------------------- | --------------------- | +| endpoint | エンドポイント | tcp://localhost:12345 | +| tx.id | トランザクションID | TID-0000000000000001 | +| tx.option | トランザクションオプション | | +| tx.type | トランザクション種別 | OCC | +| tx.label | トランザクションのラベル | | +| tx.include-ddl | DDL実行オプション | true | +| tx.write-preserve, tx.wp | write preserve | "test1", "test2" | +| tx.inclusive-read-area, tx.ira | inclusive read area | "test1", "test2" | +| tx.exclusive-read-area, tx.era | exclusive read area | "test1", "test2" | +| tx.priority | priority | | + + + +## 例 + +``` +tgsql> \set console.prompt1.default "example> " +console.prompt1.default=example> +example> \set console.prompt1.default "" +console.prompt1.default=null +tgsql> +``` + +``` +tgsql> \set console.prompt1.tx "{tx.type}({tx.label})> " +console.prompt1.tx={tx.type}({tx.label})> +tgsql> begin as tx1; +transaction started. option=[ + type: OCC + label: "tx1" +] +Time: 2.731 ms +OCC(tx1)> +``` +