diff --git a/src/docbkx/manual.xml b/src/docbkx/manual.xml index 1061dd9b..33747833 100644 --- a/src/docbkx/manual.xml +++ b/src/docbkx/manual.xml @@ -3507,6 +3507,13 @@ java.sql.SQLException: ORA-00942: table or view does not exist Defaults to 0, which is interpreted as fetching all rows. + + scriptengine + + A script engine to use to evaluate scripts for + instance from promptScript. Defaults to nashorn. + + showcompletiondesc diff --git a/src/main/java/sqlline/Application.java b/src/main/java/sqlline/Application.java index 052a9211..08ba4985 100644 --- a/src/main/java/sqlline/Application.java +++ b/src/main/java/sqlline/Application.java @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -26,6 +27,8 @@ import java.util.ServiceLoader; import java.util.Set; import java.util.TreeSet; +import javax.script.ScriptEngineFactory; +import javax.script.ScriptEngineManager; import org.jline.builtins.Completers.FileNameCompleter; import org.jline.reader.Completer; @@ -373,6 +376,38 @@ public String getDefaultInteractiveMode() { public Map getName2TableOutputFormatStyle() { return BuiltInTableOutputFormatStyles.BY_NAME; } + + /** + * Override this method to modify available script engine names. + * + *

If method is not overridden, current set of engine names will + * contain first non intersected values (ordered by abc). + * + * @return Set of available script engine names + */ + public Set getAvailableScriptEngineNames() { + final Set result = new HashSet<>(); + final Map> fName2Aliases = new HashMap<>(); + final List factories = + new ScriptEngineManager().getEngineFactories(); + for (ScriptEngineFactory factory: factories) { + fName2Aliases.put(factory.getEngineName(), + new HashSet<>(factory.getNames())); + } + for (Map.Entry> fEntry: fName2Aliases.entrySet()) { + Set aliases = new TreeSet<>(fEntry.getValue()); + for (Map.Entry> fEntry2: fName2Aliases.entrySet()) { + if (fEntry.getKey().equals(fEntry2.getKey())) { + continue; + } + aliases.removeAll(fEntry2.getValue()); + } + if (!aliases.isEmpty()) { + result.add(aliases.iterator().next()); + } + } + return result; + } } // End Application.java diff --git a/src/main/java/sqlline/BuiltInProperty.java b/src/main/java/sqlline/BuiltInProperty.java index c2a2c146..4f524f20 100644 --- a/src/main/java/sqlline/BuiltInProperty.java +++ b/src/main/java/sqlline/BuiltInProperty.java @@ -87,6 +87,8 @@ public enum BuiltInProperty implements SqlLineProperty { RIGHT_PROMPT("rightPrompt", Type.STRING, ""), ROW_LIMIT("rowLimit", Type.INTEGER, 0), SHOW_ELAPSED_TIME("showElapsedTime", Type.BOOLEAN, true), + SCRIPT_ENGINE("scriptEngine", Type.STRING, "nashorn", true, false, + new Application().getAvailableScriptEngineNames()), SHOW_COMPLETION_DESCR("showCompletionDesc", Type.BOOLEAN, true), SHOW_HEADER("showHeader", Type.BOOLEAN, true), SHOW_LINE_NUMBERS("showLineNumbers", Type.BOOLEAN, false), diff --git a/src/main/java/sqlline/PromptHandler.java b/src/main/java/sqlline/PromptHandler.java index 479bf8a9..098e5f1d 100644 --- a/src/main/java/sqlline/PromptHandler.java +++ b/src/main/java/sqlline/PromptHandler.java @@ -61,11 +61,26 @@ public class PromptHandler { protected final SqlLine sqlLine; - static final Supplier SCRIPT_ENGINE_SUPPLIER = - new MemoizingSupplier<>(() -> { - final ScriptEngineManager engineManager = new ScriptEngineManager(); - return engineManager.getEngineByName("nashorn"); - }); + final Supplier scriptEngineSupplier = + getEngineSupplier(); + + MemoizingSupplier getEngineSupplier() { + return new MemoizingSupplier<>(() -> { + final ScriptEngineManager engineManager = new ScriptEngineManager(); + String engineName = sqlLine.getOpts().get(BuiltInProperty.SCRIPT_ENGINE); + ScriptEngine scriptEngine = engineManager.getEngineByName(engineName); + if (scriptEngine == null) { + if (engineManager.getEngineFactories().isEmpty()) { + sqlLine.error(sqlLine.loc("not-supported-script-engine-no-available", + engineName)); + } else { + sqlLine.error(sqlLine.loc("not-supported-script-engine", + engineName, BuiltInProperty.SCRIPT_ENGINE.getAvailableValues())); + } + } + return scriptEngine; + }); + } public PromptHandler(SqlLine sqlLine) { this.sqlLine = sqlLine; @@ -112,16 +127,20 @@ public AttributedString getPrompt() { private String getPromptFromScript(SqlLine sqlLine, String promptScript) { try { - final ScriptEngine engine = SCRIPT_ENGINE_SUPPLIER.get(); - final Bindings bindings = new SimpleBindings(); - final ConnectionMetadata meta = sqlLine.getConnectionMetadata(); - bindings.put("connectionIndex", meta.getIndex()); - bindings.put("databaseProductName", meta.getDatabaseProductName()); - bindings.put("userName", meta.getUserName()); - bindings.put("url", meta.getUrl()); - bindings.put("currentSchema", meta.getCurrentSchema()); - final Object o = engine.eval(promptScript, bindings); - return String.valueOf(o); + final ScriptEngine engine = scriptEngineSupplier.get(); + if (engine == null) { + return ">"; + } else { + final Bindings bindings = new SimpleBindings(); + final ConnectionMetadata meta = sqlLine.getConnectionMetadata(); + bindings.put("connectionIndex", meta.getIndex()); + bindings.put("databaseProductName", meta.getDatabaseProductName()); + bindings.put("userName", meta.getUserName()); + bindings.put("url", meta.getUrl()); + bindings.put("currentSchema", meta.getCurrentSchema()); + final Object o = engine.eval(promptScript, bindings); + return String.valueOf(o); + } } catch (ScriptException e) { e.printStackTrace(); return ">"; diff --git a/src/main/java/sqlline/SqlLineOpts.java b/src/main/java/sqlline/SqlLineOpts.java index 7ab5d183..510d3d1f 100644 --- a/src/main/java/sqlline/SqlLineOpts.java +++ b/src/main/java/sqlline/SqlLineOpts.java @@ -23,6 +23,8 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import java.util.stream.Collectors; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; import org.jline.builtins.Completers; import org.jline.keymap.KeyMap; @@ -75,6 +77,7 @@ import static sqlline.BuiltInProperty.PROPERTIES_FILE; import static sqlline.BuiltInProperty.RIGHT_PROMPT; import static sqlline.BuiltInProperty.ROW_LIMIT; +import static sqlline.BuiltInProperty.SCRIPT_ENGINE; import static sqlline.BuiltInProperty.SHOW_COMPLETION_DESCR; import static sqlline.BuiltInProperty.SHOW_ELAPSED_TIME; import static sqlline.BuiltInProperty.SHOW_HEADER; @@ -130,6 +133,7 @@ public class SqlLineOpts implements Completer { put(NUMBER_FORMAT, SqlLineOpts.this::setNumberFormat); put(OUTPUT_FORMAT, SqlLineOpts.this::setOutputFormat); put(PROPERTIES_FILE, SqlLineOpts.this::setPropertiesFile); + put(SCRIPT_ENGINE, SqlLineOpts.this::setScriptEngine); put(SHOW_COMPLETION_DESCR, SqlLineOpts.this::setShowCompletionDesc); put(TABLE_STYLE, SqlLineOpts.this::setTableStyle); @@ -523,7 +527,8 @@ public void set(SqlLineProperty key, Object value) { ? (String) value : String.valueOf(value); valueToSet = DEFAULT.equalsIgnoreCase(strValue) ? key.defaultValue() : value; - if (!key.getAvailableValues().isEmpty() + if (!DEFAULT.equalsIgnoreCase(strValue) + && !key.getAvailableValues().isEmpty() && !key.getAvailableValues().contains(valueToSet.toString())) { sqlLine.error( sqlLine.loc("unknown-value", @@ -1083,6 +1088,24 @@ public Pattern getCompiledConfirmPattern() { public String getHistoryFlags() { return get(HISTORY_FLAGS); } + + public String getScriptEngine() { + return get(SCRIPT_ENGINE); + } + + public void setScriptEngine(String engineName) { + if (DEFAULT.equalsIgnoreCase(engineName)) { + set(SCRIPT_ENGINE, SCRIPT_ENGINE.defaultValue()); + return; + } + final ScriptEngineManager engineManager = new ScriptEngineManager(); + ScriptEngine scriptEngine = engineManager.getEngineByName(engineName); + if (scriptEngine == null && engineManager.getEngineFactories().isEmpty()) { + sqlLine.error(sqlLine.loc("not-supported-script-engine-no-available", + engineName)); + } + set(SCRIPT_ENGINE, engineName); + } } // End SqlLineOpts.java diff --git a/src/main/resources/sqlline/SqlLine.properties b/src/main/resources/sqlline/SqlLine.properties index 0cc90799..ef94eae0 100644 --- a/src/main/resources/sqlline/SqlLine.properties +++ b/src/main/resources/sqlline/SqlLine.properties @@ -37,6 +37,8 @@ column: Column new-size-after-resize: New size: height = {0}, width = {1} empty-value-not-supported: Empty value for type {0} not supported no-file: File {0} does not exist or is a directory +not-supported-script-engine: Not found script engine "{0}", available values: {1} +not-supported-script-engine-no-available: Not found script engine "{0}", no available script engines jdbc-level: JDBC level compliant: Compliant @@ -151,6 +153,7 @@ variables:\ \nrightPrompt pattern Format right prompt\ \nrowLimit integer Maximum number of rows returned from a query; zero\ \n means no limit\ +\nscriptEngine String Script engine name\ \nshowCompletionDesc true/false Display help for completions\ \nshowElapsedTime true/false Display execution time when verbose\ \nshowHeader true/false Show column names in query results\ @@ -340,6 +343,7 @@ cmd-usage: Usage: java sqlline.SqlLine \n \ \ --autoCommit=[true/false] enable/disable automatic transaction commit\n \ \ --readOnly=[true/false] enable/disable readonly connection\n \ \ --verbose=[true/false] show verbose error messages and debug info\n \ +\ --scriptEngine=[string] script engine name\n \ \ --showCompletionDesc=[true/false] display help for completions\n \ \ --showLineNumbers=[true/false] show line numbers while multiline queries\n \ \ --showTime=[true/false] display execution time when verbose\n \ diff --git a/src/main/resources/sqlline/manual.txt b/src/main/resources/sqlline/manual.txt index 04625df5..810394fa 100644 --- a/src/main/resources/sqlline/manual.txt +++ b/src/main/resources/sqlline/manual.txt @@ -104,6 +104,8 @@ schemas schemas — List all the schemas in the database script script — Save executed commands to a file +scriptengine +scriptengine - script engine name set set — Set a preference showconfconnections @@ -258,6 +260,8 @@ scan scan — Scan class path for JDBC drivers script script — Save executed commands to a file +scriptengine +scriptengine - script engine name set set — Set a preference showconfconnections @@ -502,6 +506,8 @@ scan scan — Scan class path for JDBC drivers script script — Save executed commands to a file +scriptengine +scriptengine - script engine name set set — Set a preference showconfconnections @@ -1223,6 +1229,7 @@ propertiesFile path File from which SQLLine reads properties on rightPrompt pattern Format right prompt rowLimit integer Maximum number of rows returned from a query; zero means no limit +scriptEngine string Script engine name showCompletionDesc true/false Display help for completions showElapsedTime true/false Display execution time when verbose showHeader true/false Show column names in query results @@ -2411,6 +2418,10 @@ rowlimit The maximum number of rows to fetch per query. Defaults to 0, which is interpreted as fetching all rows. +scriptengine + +A script engine to use to evaluate scripts for instance from promptScript. Defaults to nashorn. + showheader If true, display the names of the columns when displaying results. Defaults to true. diff --git a/src/test/java/sqlline/PromptTest.java b/src/test/java/sqlline/PromptTest.java index 6f2ebd12..12f35975 100644 --- a/src/test/java/sqlline/PromptTest.java +++ b/src/test/java/sqlline/PromptTest.java @@ -15,6 +15,7 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; +import javax.script.ScriptEngineManager; import org.jline.utils.AttributedString; import org.jline.utils.AttributedStringBuilder; @@ -221,8 +222,9 @@ public void testPromptWithSchema() { * The `promptscript` property is broken on JDK 15 and higher. */ @Test public void testPromptScript() { - Assumptions.assumeTrue(getJavaMajorVersion() < 15, - "promptscript fails on JDK 15 and higher; " + Assumptions.assumeTrue( + !new ScriptEngineManager().getEngineFactories().isEmpty(), + "promptscript fails if there is no script engines; " + "see "); sqlLine.getOpts().set(BuiltInProperty.PROMPT_SCRIPT, "'hel' + 'lo'");