Skip to content

Commit

Permalink
[SQLLINE-386] Support saved connections
Browse files Browse the repository at this point in the history
1. Path to file with connections could be specified in connectionConfig property
2. Connections in a file have names, these names could be used as connection nicknames
3. Connections are allowed for both: input args and !connect command
4. !rereadconfconnections to reset connections loaded from file or use another file
5. !showconfconnections to read current connection file content
  • Loading branch information
snuyanzin committed Dec 10, 2020
1 parent dbd4931 commit a7d3a95
Show file tree
Hide file tree
Showing 10 changed files with 397 additions and 21 deletions.
60 changes: 60 additions & 0 deletions src/docbkx/manual.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1463,6 +1463,7 @@ sqlline> !help
!reconnect Reconnect to the database
!record Record all output to the specified file
!rehash Fetch table and column names for command completion
!rereadconfconnections Reread file with saved connections
!resize Reset max height/width based on terminal height/width
!rollback Roll back the current transaction (if autocommit is off)
!reset Reset a sqlline variable
Expand All @@ -1471,6 +1472,7 @@ sqlline> !help
!scan Scan for installed JDBC drivers
!schemas List all the schemas in the database
!script Start saving a script to a file
!showconfconnections Show connections from the file
!set Set a sqlline variable, or show its value
!sql Execute a SQL command
!tables List all the tables in the database
Expand Down Expand Up @@ -2981,6 +2983,64 @@ Script closed. Enter "run /tmp/mysession.script" to replay it.
</refentry>
</section>

<section id="sect_command_rereadconfconnections">
<title>rereadconfconnections</title>
<refentry id="command_rereadconfconnections">
<refmeta>
<refentrytitle>rereadconfconnections</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>

<refnamediv>
<refname>rereadconfconnections</refname>
<refpurpose>
Reread file with saved connections
</refpurpose>
</refnamediv>

<refsynopsisdiv>
<cmdsynopsis>
<command>!rereadconfconnections</command>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
Reread file with saved connections
</para>
</refsect1>
</refentry>
</section>

<section id="sect_command_showconfconnections">
<title>showconfconnections</title>
<refentry id="command_showconfconnections">
<refmeta>
<refentrytitle>showconfconnections</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>

<refnamediv>
<refname>showconfconnections</refname>
<refpurpose>
Show connections from the file
</refpurpose>
</refnamediv>

<refsynopsisdiv>
<cmdsynopsis>
<command>!showconfconnections</command>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
Show connections from the file
</para>
</refsect1>
</refentry>
</section>

<section id="sect_command_sql">
<title>sql</title>
<refentry id="command_sql">
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/sqlline/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ public Collection<CommandHandler> getCommandHandlers(SqlLine sqlLine) {
new ReflectiveCommandHandler(sqlLine,
new StringsCompleter(getConnectionUrlExamples()), "connect",
"open"),
new ReflectiveCommandHandler(sqlLine, empty, "showconfconnections"),
new ReflectiveCommandHandler(sqlLine, new FileNameCompleter(),
"rereadconfconnections"),
new ReflectiveCommandHandler(sqlLine, empty, "nickname"),
new ReflectiveCommandHandler(sqlLine, tableCompleter, "describe"),
new ReflectiveCommandHandler(sqlLine, tableCompleter, "indexes"),
Expand Down
1 change: 1 addition & 0 deletions src/main/java/sqlline/BuiltInProperty.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public enum BuiltInProperty implements SqlLineProperty {
CONNECT_INTERACTION_MODE("connectInteractionMode", Type.STRING,
new Application().getDefaultInteractiveMode(), true, false,
new HashSet<>(new Application().getConnectInteractiveModes())),
CONNECTION_CONFIG("connectionConfig", Type.FILE_PATH, ""),
CSV_DELIMITER("csvDelimiter", Type.STRING, ","),

CSV_QUOTE_CHARACTER("csvQuoteCharacter", Type.CHAR, '\''),
Expand Down
99 changes: 92 additions & 7 deletions src/main/java/sqlline/Commands.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.*;
import java.util.*;
Expand Down Expand Up @@ -159,9 +161,11 @@ public class Commands {
};

private final SqlLine sqlLine;
private final ConnectionConfigParser conConfParser;

Commands(SqlLine sqlLine) {
this.sqlLine = sqlLine;
this.conConfParser = new ConnectionConfigParser(sqlLine);
}

public void metadata(String line, DispatchCallback callback) {
Expand Down Expand Up @@ -1274,7 +1278,7 @@ public void properties(String line, DispatchCallback callback)

public void connect(String line, DispatchCallback callback) {
String example =
"Usage: connect [-p property value]* <url> [username] [password] [driver]"
"Usage: connect [-p property value]* (-(c|cn) <connectionName> | <url>) [username] [password] [driver]"
+ SqlLine.getSeparator();
String[] parts = sqlLine.split(line);
if (parts == null) {
Expand All @@ -1298,11 +1302,35 @@ public void connect(String line, DispatchCallback callback) {
}
}

String url = parts.length < offset + 1 ? null : parts[offset];
Properties props = null;
String url = null;
String nickname = null;
boolean nickNameFromConfig =
parts.length >= offset && "-cn".equals(parts[offset]);
if (nickNameFromConfig
|| parts.length >= offset && "-c".equals(parts[offset])) {
if (parts.length == offset + 1) {
sqlLine.error(example);
return;
}
props = conConfParser.getConnectionProperties(parts[offset + 1]);
if (props == null) {
sqlLine.error(parts[offset + 1] + " not found in connection config");
return;
}
nickname = nickNameFromConfig ? parts[offset + 1] : nickname;
offset++;
} else {
url = parts.length < offset + 1 ? null : parts[offset];
}
if (props == null) {
props = new Properties();
}

String user = parts.length < offset + 2 ? null : parts[offset + 1];
String pass = parts.length < offset + 3 ? null : parts[offset + 2];
String driver = parts.length < offset + 4 ? null : parts[offset + 3];
Properties props = new Properties();

if (url != null) {
props.setProperty(ConnectionProperties.URL.getSqllineName(), url);
}
Expand All @@ -1323,6 +1351,11 @@ public void connect(String line, DispatchCallback callback) {
}
}
connect(props, callback);
final DatabaseConnection databaseConnection =
sqlLine.getDatabaseConnection();
if (nickNameFromConfig && databaseConnection != null) {
databaseConnection.setNickname(nickname);
}
}

public void nickname(String line, DispatchCallback callback) {
Expand Down Expand Up @@ -1485,6 +1518,52 @@ public void rehash(String line, DispatchCallback callback) {
}
}

public void showconfconnections(String line, DispatchCallback callback) {
final String connectionConfig = sqlLine.getOpts().getConnectionConfig();
if (connectionConfig == null || connectionConfig.isEmpty()) {
sqlLine.error("Configuration file is not specified");
return;
}
Path path = Paths.get(connectionConfig);
if (Files.exists(path) && !Files.isDirectory(path)) {
try (InputStream in = new FileInputStream(path.toFile())) {
less(in);
} catch (IOException e) {
callback.setToFailure();
sqlLine.error(e);
}
} else {
sqlLine.error("Configuration file '"
+ path + "' does not exist or is a directory");
}
}

public void rereadconfconnections(String line, DispatchCallback callback) {
String example = "Usage: rereadconfconnections [new_config]"
+ SqlLine.getSeparator();
try {
String[] parts = sqlLine.split(line);
if (parts == null || parts.length > 2) {
callback.setToFailure();
sqlLine.error(example);
return;
}
if (parts.length == 2) {
sqlLine.getOpts().setConnectionConfig(parts[1]);
} else {
resetconfconnections();
}
callback.setToSuccess();
} catch (Exception e) {
callback.setToFailure();
sqlLine.error(e);
}
}

void resetconfconnections() {
conConfParser.resetConnectionProperties();
}

void resize() {
final Terminal terminal = sqlLine.getTerminal();
if (terminal == null
Expand Down Expand Up @@ -1905,6 +1984,14 @@ public void manual(String line, DispatchCallback callback)
return;
}

if (less(in)) {
callback.setToSuccess();
} else {
callback.setToFailure();
}
}

private boolean less(InputStream in) throws IOException {
// Workaround for windows because of
// https://github.com/jline/jline3/issues/304
if (System.getProperty("os.name")
Expand All @@ -1916,13 +2003,11 @@ public void manual(String line, DispatchCallback callback)
in, sqlLine.getOutputStream(), sqlLine.getErrorStream(),
null, new String[]{"-I", "--syntax=none"});
} catch (Exception e) {
callback.setToFailure();
sqlLine.error(e);
return;
return false;
}
}

callback.setToSuccess();
return true;
}

private void sillyLess(InputStream in) throws IOException {
Expand Down
100 changes: 100 additions & 0 deletions src/main/java/sqlline/ConnectionConfigParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde licenses this file to you under the Modified BSD License
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at:
//
// http://opensource.org/licenses/BSD-3-Clause
*/

package sqlline;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

class ConnectionConfigParser {
private static final String SEPARATOR = ": ";
private static final char COMMENT_START = '#';
private final SqlLine sqlLine;
private final Map<String, Properties> connections = new HashMap<>();

ConnectionConfigParser(SqlLine sqlLine) {
this.sqlLine = sqlLine;
}

Properties getConnectionProperties(String connectionName) {
if (connections.isEmpty()) {
final String connectionConfig = sqlLine.getOpts().getConnectionConfig();
if (connectionConfig == null || connectionConfig.isEmpty()) {
sqlLine.error("Connection config is not set");
return null;
}
readFromFile(Paths.get(connectionConfig));
}
return connections.get(connectionName);
}

void resetConnectionProperties() {
connections.clear();
}

private void readFromFile(Path path) {
if (!Files.exists(path) || Files.isDirectory(path)) {
sqlLine.error(sqlLine.loc("no-file", path.toAbsolutePath()));
return;
}
int minOffset = Integer.MAX_VALUE;
int offset;
String connectionName = null;
try (BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream(path.toFile()), StandardCharsets.UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
if (line.isEmpty()) {
continue;
}
String name;
String value;
offset = 0;
while (Character.isWhitespace(line.charAt(offset))) {
offset++;
}
if (line.charAt(offset) == COMMENT_START
|| line.charAt(offset) == ':' || !line.contains(SEPARATOR)) {
// skip commented line, line started with ':',
// line not containing SEPARATOR at all
continue;
}
final int sepIndex = line.indexOf(SEPARATOR);
name = line.substring(0, sepIndex).trim();
if (minOffset >= offset) {
connectionName = name;
connections.put(connectionName, new Properties());
minOffset = offset;
} else {
value = line.substring(sepIndex + SEPARATOR.length()).trim();
if (!value.isEmpty() && value.charAt(0) == COMMENT_START) {
value = "";
}
connections.get(connectionName).setProperty(name, value);
}
}
} catch (Exception e) {
sqlLine.error(e);
}
}
}

// ConnectionConfigParser.java
Loading

0 comments on commit a7d3a95

Please sign in to comment.