diff --git a/README.md b/README.md index 5f0ecd1..0482ce7 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ The GridDB CLI provides command line interface tool to manage GridDB cluster ope Building and program execution are checked in the environment below. OS: CentOS 7.9(x64) - GridDB Server: V5.3 CE(Community Edition) - Java: Java™ SE Development Kit 11 (OpenJDK 11.0.19) + GridDB Server: V5.5 CE(Community Edition) + Java: OpenJDK 1.8.0 ## Quick start from CLI Source Code diff --git a/Specification_en.md b/Specification_en.md index b691c8f..34af5d6 100644 --- a/Specification_en.md +++ b/Specification_en.md @@ -1473,6 +1473,89 @@ Set whether to execute count query when SQL querying. [Memo] - If FALSE is specified, the response will be faster instead of displaying no hit count. The execution time is not affected by this setting. + +### Query result display format setting + +Set the result display format before executing SQL or TQL statements. + +- Sub-command + + | | + | -------------------------- | + | setresultformat \ | + +- Argument + + | Argument | Note | + | ---------- | ------------------------------------ | + | FORMAT | The result format is to be displayed. Can be set to `TABLE` or `CSV`. Default is `TABLE`.| + +- Example + + ```example + gs[public]> sql SELECT '1+1' AS expr, '2' AS ans; + 1 results. (4 ms) + gs[public]> get + +------+-----+ + | expr | ans | + +------+-----+ + | 1+1 | 2 | + +------+-----+ + The 1 results had been acquired. + gs[public]> + ``` + +\[Memo\] + +- Display format is always CSV when EXPLAIN is executed. + +### Set maximum width for display table + +Set the maximum width for column in query result if the format is TABLE. +When setting the max width to column, the overflow text will be displayed with three trailing dots `...`. + +- Sub-command + + | | + | --------------------------- | + | setresultmaxwidth \ | + +- Argument + + | Argument | Note | + | ----------------- | -------------------------------------------------------------------------------------------------------------------------- | + | MAX_COLUMN_WIDTH | The length of displayed text, including `...` when the text is longer than the setting width. Must be integer (value ≥ 1). Default is 31. | + +- Example + + ```example + gs[public]> sql SELECT * FROM product; + 3 results. (44 ms) + + gs[public]> get + +--------------------------+-----------------+---------+---------------------------------------------------------+ + | time | name | weight | note | + +--------------------------+-----------------+---------+---------------------------------------------------------+ + | 2023-07-04T07:46:27.415Z | apple | 9.99 | Envy | + | 2023-07-04T07:47:02.731Z | chia | 0.1 | Chia seed from Australia | + | 2023-07-04T07:50:33.437Z | water and light | 0.0 | Hats off to Geoff Adams, who made this mini-documentary | + +--------------------------+-----------------+---------+---------------------------------------------------------+ + The 3 results had been acquired. + + gs[public]> setresultmaxwidth 24 + gs[public]> sql SELECT * from product; + 3 results. (44 ms) + + gs[public]> get + +--------------------------+-----------------+---------+--------------------------+ + | time | name | weight | note | + +--------------------------+-----------------+---------+--------------------------+ + | 2023-07-04T07:46:27.415Z | apple | 9.99 | Envy | + | 2023-07-04T07:47:02.731Z | chia | 0.1 | Chia seed from Australia | + | 2023-07-04T07:50:33.437Z | water and light | 0.0 | Hats off to Geoff Ada... | + +--------------------------+-----------------+---------+--------------------------+ + The 3 results had been acquired. + ``` ## Database management diff --git a/Specification_ja.md b/Specification_ja.md index debb599..6f92710 100644 --- a/Specification_ja.md +++ b/Specification_ja.md @@ -1,3 +1,4 @@ + # クラスタ運用管理コマンド・インタプリタ(gs_sh) ## 概要 @@ -25,12 +26,41 @@ gs_shを利用するには、あらかじめ以下を実施ください。 - ユーザ作成 - ネットワーク設定(GridDBクラスタ定義ファイル、ノード定義ファイル) -  ※ 手順の詳細に関しては、[GridDB クイックスタートガイド](https://github.com/griddb/docs-ja/blob/master/manuals/GridDB_QuickStartGuide/toc.md)を参照してください。 +  ※ 手順の詳細に関しては、[GridDB クイックスタートガイド](https://github.com/griddb/docs-ja/blob/master/manuals/GridDB_QuickStartGuide/toc.md)の「GridDBのインストール」の章を参照してください。 - SSHによるリモート接続設定 - - gs_sh実行環境から各GridDBノード実行環境へ、OSユーザ「gsadm」でSSH接続するための設定 + - gs_sh実行環境から各GridDBノード実行環境へ、OSユーザ「gsadm」でSSH接続するために必要な設定
※ SSH接続の手順の詳細に関しては、各OSのマニュアルを参照してください。 + [メモ] + + 以下のOSでは、鍵交換アルゴリズムの追加が必要です。 + + RHEL9系の場合: + ``` example + $ sudo update-crypto-policies --set DEFAULT:SHA1 + $ sudo reboot   # OSの再起動 + ``` + + Ubuntu 20.04の場合: + ``` example + $ vi /etc/ssh/sshd_config # エディタで設定ファイルを編集 + ... + Kexalgorithms +diffie-hellman-group14-sha1 + + $ sudo systemctl reload sshd + ``` + + Ubuntu 22.04の場合: + ``` example + $ vi /etc/ssh/sshd_config # エディタで設定ファイルを編集 + ... + Kexalgorithms +diffie-hellman-group14-sha1 + HostKeyAlgorithms +ssh-rsa + + $ sudo systemctl reload sshd + ``` + ### gs_sh起動 gs_shには2種類の起動モードがあります。 @@ -99,10 +129,10 @@ GridDBノードのIPアドレスとポート番号を、ノード変数に定義 ``` example //4つのGridDBノードを定義 - gs> setnode node0 192.168.0.1 10000 - gs> setnode node1 192.168.0.2 10000 - gs> setnode node2 192.168.0.3 10000 - gs> setnode node3 192.168.0.4 10000 + gs> setnode node0 192.168.0.1 10040 + gs> setnode node1 192.168.0.2 10040 + gs> setnode node2 192.168.0.3 10040 + gs> setnode node3 192.168.0.4 10040 ``` 【メモ】 @@ -286,7 +316,7 @@ GridDBクラスタにアクセスするユーザおよびパスワードを定 ``` example //変数を定義 - gs> set GS_PORT 10000 + gs> set GS_PORT 10040 //変数の設定をクリア gs> set GS_PORT ``` @@ -320,10 +350,10 @@ GridDBクラスタにアクセスするユーザおよびパスワードを定 //定義した全変数を定表示 gs> show ノード変数: - node0=Node[192.168.0.1:10000,ssh=22] - node1=Node[192.168.0.2:10000,ssh=22] - node2=Node[192.168.0.3:10000,ssh=22] - node3=Node[192.168.0.4:10000,ssh=22] + node0=Node[192.168.0.1:10040,ssh=22] + node1=Node[192.168.0.2:10040,ssh=22] + node2=Node[192.168.0.3:10040,ssh=22] + node3=Node[192.168.0.4:10040,ssh=22] クラスタ変数: cluster0=Cluster[name=name,200.0.0.1:1000,nodes=(node0,node1,node2)] その他の変数: @@ -742,6 +772,7 @@ leaveclusterサブコマンドや障害などによってGridDBクラスタか 【メモ】 - 管理ユーザのみが実行可能なコマンドです。 - ノード変数を利用する際には、変数名の先頭に"$"をつけます。 +  @@ -1230,6 +1261,7 @@ SQL文の先頭が下記文字列のいずれかである場合、サブコマ | DDL文 | 何も表示されません。 | - SQLの詳細に関しては、[GridDB SQLリファレンス](https://github.com/griddb/docs-ja/blob/master/manuals/GridDB_SQL_Reference/toc.md)を参照してください。 +   ### 検索結果の取得 @@ -1251,6 +1283,147 @@ SQL文の先頭が下記文字列のいずれかである場合、サブコマ |----------|----------------------------------------------------------------------------------| | 取得件数 | 検索結果の取得件数を指定します。省略すると、全ての検索結果を取得して表示します。 | +標準出力の表示形式にはTABLE形式とCSV形式の2種類があります。 +V5.5から表示形式のデフォルトがTABLE形式になります。以前の形式に戻す場合は「表示形式がCSVの場合」を実施してください。 + +(A-1) 標準出力の表示形式を設定します。 + +- サブコマンド + + | | + |-| + | setresultformat \[TABLE\|CSV\] | + +- 引数 + + | 引数 | 説明 | + |----------|----------------------------------------------------------------------------------| + | TABLE\|CSV | 標準出力の表示形式をTABLEまたはCSVで設定します。デフォルトはTABLE形式で標準出力します。 | + +例) + +- 表示形式がTABLEの場合 + + ``` example + //表示形式をTABLEに設定 + gs[public]> setresultformat TABLE + + //検索を実行 + gs[public]> select * from tim1; + 検索を実行しました。 (1 ms) + + //結果を表示 + gs[public]> get + +--------------------------+-----------------------------+--------------------------------+ + | date | micro | nano | + +--------------------------+-----------------------------+--------------------------------+ + | 2022-12-31T00:00:00.123Z | 2022-12-31T00:00:00.123456Z | 2022-12-31T00:00:00.123456789Z | + +--------------------------+-----------------------------+--------------------------------+ + 1 件の取得が完了しました。 + ``` + +- 表示形式がCSVの場合 + + ``` example + //表示形式をCSVに設定 + gs[public]> setresultformat CSV + + //検索を実行 + gs[public]> select * from tim1; + 検索を実行しました。 (2 ms) + + //結果を表示 + gs[public]> get + date,micro,nano + 2022-12-31T00:00:00.123Z,2022-12-31T00:00:00.123456Z,2022-12-31T00:00:00.123456789Z + 1 件の取得が完了しました。 + ``` + +【メモ】 +- 一度設定するとgs_shの起動中はgetサブコマンドで、設定した表示形式が有効になります。 +- 何度でも表示形式の切り替えは可能です。 + +(A-2) 標準出力の文字列長を設定します。 + +- サブコマンド + + | | + |-| + | setresultmaxwidth \[文字列長\] | + +- 引数 + + | 引数 | 説明 | + |----------|----------------------------------------------------------------------------------| + | 文字列長 | 標準出力の文字列長を自然数で設定します。値はバイト数で設定され、半角英数字の場合は1バイト、日本語の場合は2バイトで設定されます。表示形式がTABLEの場合のみ適用されます。 | + +例) + + ``` example + //検索を実行 + gs[public]> select * from tim1; + 検索を実行しました。 (28 ms) + gs[public]> get + +--------------------------+-----------------------------+--------------------------------+ + | date | micro | nano | + +--------------------------+-----------------------------+--------------------------------+ + | 2022-12-31T00:00:00.123Z | 2022-12-31T00:00:00.123456Z | 2022-12-31T00:00:00.123456789Z | + +--------------------------+-----------------------------+--------------------------------+ + 1 件の取得が完了しました。 + + //文字列長を設定 + gs[public]> setresultmaxwidth 29 + + //検索を実行 + gs[public]> select * from tim1; + 検索を実行しました。 (2 ms) + + //結果を表示 + gs[public]> get + +--------------------------+-----------------------------+-------------------------------+ + | date | micro | nano | + +--------------------------+-----------------------------+-------------------------------+ + | 2022-12-31T00:00:00.123Z | 2022-12-31T00:00:00.123456Z | 2022-12-31T00:00:00.123456... | + +--------------------------+-----------------------------+-------------------------------+ + 1 件の取得が完了しました。 + + //文字列長を設定 + gs[public]> setresultmaxwidth 26 + + //検索を実行 + gs[public]> select * from tim1; + 検索を実行しました。 (2 ms) + + //結果を表示 + gs[public]> get + +--------------------------+----------------------------+----------------------------+ + | date | micro | nano | + +--------------------------+----------------------------+----------------------------+ + | 2022-12-31T00:00:00.123Z | 2022-12-31T00:00:00.123... | 2022-12-31T00:00:00.123... | + +--------------------------+----------------------------+----------------------------+ + 1 件の取得が完了しました。 + + //文字列長を設定 + gs[public]> setresultmaxwidth 22 + + //検索を実行 + gs[public]> select * from tim1; + 検索を実行しました。 (1 ms) + + //結果を表示 + gs[public]> get + +------------------------+------------------------+------------------------+ + | date | micro | nano | + +------------------------+------------------------+------------------------+ + | 2022-12-31T00:00:00... | 2022-12-31T00:00:00... | 2022-12-31T00:00:00... | + +------------------------+------------------------+------------------------+ + 1 件の取得が完了しました。 + ``` + +【メモ】 +- 一度設定するとgs_shの起動中はgetサブコマンドのTABLE形式の表示で、設定した文字列長が有効になります。 +- 何度でも文字列長の設定は可能です。 +- 取得した結果の値が設定した文字列長より長い場合、設定した文字列長で区切って末尾に「...」が付与されます。 (B) 取得した結果をCSV形式でファイルに保存します。 @@ -2816,6 +2989,9 @@ gs_shのバージョンを表示します。 【メモ】 - connectサブコマンド実行後に、settimezoneサブコマンドでタイムゾーン設定を変更した場合、変更後のタイムゾーン設定は再度、connectサブコマンドを実行するまで反映されません。タイムゾーン設定変更を行った後には再度、connectサブコマンドを実行してください。 + + + ### マルチキャストパケットを受信するインターフェースの設定 複数のネットワークインターフェースがあるときにクラスタのネットワーク構成をマルチキャスト方式にする場合、マルチキャストパケットを受信するインターフェースのIPアドレスを指定します。 @@ -2982,6 +3158,8 @@ historyサブコマンドで表示した履歴よりサブコマンドを再実 | connect | クラスタ変数 \[データベース名\] | GridDBクラスタに接続します。 | | | tql | コンテナ名 クエリ ; | 検索を実行し、検索結果を保持します。 | | | get | \[ 取得件数 \] | 検索結果を取得し、標準出力に表示します。 | | + | setresultformat | \[ TABLE\|CSV \] | 標準出力の表示形式を設定します。 | | + | setresultmaxwidth | \[文字列長\] | 標準出力の文字列長を設定します。 | | | getcsv | CSVファイル名 \[ 取得件数 \] | 検索結果を取得し、CSV形式でファイルに保存します。 | | | getnoprint | \[ 取得件数 \] | クエリの結果を取得しますが、標準出力に表示しません。 | | | tqlclose | | TQLをクローズし、保持する検索結果を破棄します。 | | diff --git a/build.gradle b/build.gradle index a9aeaff..98e00c8 100644 --- a/build.gradle +++ b/build.gradle @@ -15,8 +15,8 @@ sourceSets { } } -def gridstoreVersion = '5.3.0' -def gridstoreJdbcVersion = '5.3.0' +def gridstoreVersion = '5.5.0' +def gridstoreJdbcVersion = '5.5.0' repositories { mavenCentral() diff --git a/common/build.gradle b/common/build.gradle index 9b0b1b1..7ad9c78 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -12,8 +12,8 @@ sourceSets { } } -def gridstoreVersion = '5.3.0' -def gridstoreJdbcVersion = '5.3.0' +def gridstoreVersion = '5.5.0' +def gridstoreJdbcVersion = '5.5.0' dependencies { implementation 'commons-io:commons-io:2.4' diff --git a/common/src/com/toshiba/mwcloud/gs/tools/common/data/MetaContainerFileIO.java b/common/src/com/toshiba/mwcloud/gs/tools/common/data/MetaContainerFileIO.java index 8da5d85..6f1162c 100644 --- a/common/src/com/toshiba/mwcloud/gs/tools/common/data/MetaContainerFileIO.java +++ b/common/src/com/toshiba/mwcloud/gs/tools/common/data/MetaContainerFileIO.java @@ -231,7 +231,7 @@ public ToolContainerInfo readMetaInfo(String filePath) throws GridStoreCommandEx * * ・ひとつのメタ情報ファイルを継続して読み込みます。 * - * @param fileName メタ情報ファイル + * @param file メタ情報ファイル * @param containerName コンテナ名文字列 * @param dbName データベース名 (デフォルトDBの場合はnull) * @return コンテナ情報クラス @@ -677,7 +677,7 @@ private ToolContainerInfo readMetaFile(String containerName, String dbName) thro * * @param jp JsonParser * @param ci コンテナ情報オブジェクト - * @throws GSEIException + * @throws GridStoreCommandException */ private void readColumnSet(JsonParser jp, ToolContainerInfo ci) throws GridStoreCommandException { // 配列 @@ -775,7 +775,7 @@ private void readColumnSet(JsonParser jp, ToolContainerInfo ci) throws GridStore } ColumnInfo columnInfo = new ColumnInfo(columnName, columnType, nullable, null); - if(precision != null && columnType == GSType.TIMESTAMP) { + if (precision != null && columnType == GSType.TIMESTAMP) { ColumnInfo.Builder builder = new ColumnInfo.Builder(columnInfo); builder.setTimePrecision(precision); ColumnInfo swap = builder.toInfo(); @@ -791,7 +791,7 @@ private void readColumnSet(JsonParser jp, ToolContainerInfo ci) throws GridStore * * @param jp JsonParser * @param ci コンテナ情報オブジェクト - * @throws GSEIException + * @throws GridStoreCommandException */ public void readIndexSet(JsonParser jp, ToolContainerInfo ci) throws GridStoreCommandException { // 配列 @@ -971,7 +971,7 @@ public List readIndexcolumnNames(JsonParser jp) throws GridStoreCommandE * * @param jp JsonParser * @param ci コンテナ情報オブジェクト - * @throws GSEIException + * @throws GridStoreCommandException */ public void readTriggerInfoSet(JsonParser jp, ToolContainerInfo ci) throws GridStoreCommandException { // 配列 @@ -1101,7 +1101,7 @@ public void readTriggerInfoSet(JsonParser jp, ToolContainerInfo ci) throws GridS * * @param jp JsonParser * @param ci コンテナ情報オブジェクト - * @throws GSEIException + * @throws GridStoreCommandException */ public void readCompressionInfoSet(JsonParser jp, ToolContainerInfo ci) throws GridStoreCommandException { // 配列 @@ -1212,7 +1212,7 @@ public void readCompressionInfoSet(JsonParser jp, ToolContainerInfo ci) throws G * * @param jp JsonParser * @param ci コンテナ情報オブジェクト - * @throws GSEIException + * @throws GridStoreCommandException */ public void readRowKeySetProperties(JsonParser jp, ToolContainerInfo ci) throws GridStoreCommandException { // 配列 @@ -1250,7 +1250,7 @@ public void readRowKeySetProperties(JsonParser jp, ToolContainerInfo ci) throws * * @param jp JsonParser * @param ci コンテナ情報オブジェクト - * @throws GSEIException + * @throws GridStoreCommandException */ public void readTimeSeriesProperties(JsonParser jp, ToolContainerInfo ci) throws GridStoreCommandException { // 配列 @@ -1712,7 +1712,7 @@ private TimeIntervalInfo readTimeIntervalInfo(JsonParser jp, ToolContainerInfo c /** * コンテナ情報をJSON文字列に変換します。 * - * @param containerInfoList コンテナ情報クラスリスト + * @param cInfo コンテナ情報クラスリスト * @return 出力文字列データ */ private StringWriter buildJsonObjects(ToolContainerInfo cInfo) throws Exception { @@ -2032,11 +2032,9 @@ public static GSType convertStringToColumnType(String type) throws GridStoreComm return GSType.TIMESTAMP_ARRAY; } else if (type.equalsIgnoreCase(ToolConstants.COLUMN_TYPE_BOOL)) { return GSType.BOOL; - } else if(type.equalsIgnoreCase(ToolConstants.COLUMN_TYPE_TIMESTAMP_MILI)) { - return GSType.TIMESTAMP; - } else if(type.equalsIgnoreCase(ToolConstants.COLUMN_TYPE_TIMESTAMP_MICRO)) { - return GSType.TIMESTAMP; - } else if(type.equalsIgnoreCase(ToolConstants.COLUMN_TYPE_TIMESTAMP_NANO)) { + } else if (type.equalsIgnoreCase(ToolConstants.COLUMN_TYPE_TIMESTAMP_MILI) + || type.equalsIgnoreCase(ToolConstants.COLUMN_TYPE_TIMESTAMP_MICRO) + || type.equalsIgnoreCase(ToolConstants.COLUMN_TYPE_TIMESTAMP_NANO)) { return GSType.TIMESTAMP; } return GSType.valueOf(type.toUpperCase().trim()); @@ -2044,13 +2042,14 @@ public static GSType convertStringToColumnType(String type) throws GridStoreComm } catch (Exception e) { // "カラム種別の解析処理でエラーが発生しました" //throw new GridStoreCommandException(messageResource.getString("MESS_COMM_ERR_METAINFO_13")+ ": type=[" - throw new GridStoreCommandException("Error occurded in convert to type"+ ": type=[" + throw new GridStoreCommandException("Error occurred when converting to type"+ ": type=[" +type+"] msg=[" + e.getMessage()+"]", e); } } /** * Convert string to value of TimeUnit type + * * @param unit the unit of precision * @return TimeUnit value * @throws GridStoreCommandException @@ -2059,7 +2058,7 @@ public static TimeUnit convertStringToTimeUnit(String unit) throws GridStoreComm try { return TimeUnit.valueOf(unit.toUpperCase().trim()); } catch (Exception e) { - throw new GridStoreCommandException("Error occurded in converting to time unit" + throw new GridStoreCommandException("Error occurred when converting to time unit" + ": unit=[" + unit + "] msg=[" + e.getMessage() + "]", e); } } @@ -2069,20 +2068,21 @@ public static TimeUnit convertStringToTimeUnit(String unit) throws GridStoreComm * TIMESTAMP(3) -> MILLISECOND * TIMESTAMP(6) -> MICROSECOND * TIMESTAMP(9) -> NANOSECOND + * * @param preciseTimestampType the precise timestamp type string * @return TimeUnit * @throws GridStoreCommandException */ public static TimeUnit convertTimestampStringToTimeUnit(String preciseTimestampType) throws GridStoreCommandException { String type = preciseTimestampType.trim(); - if(type.equalsIgnoreCase(ToolConstants.COLUMN_TYPE_TIMESTAMP_MILI)) { + if (type.equalsIgnoreCase(ToolConstants.COLUMN_TYPE_TIMESTAMP_MILI)) { return TimeUnit.MILLISECOND; - } else if(type.equalsIgnoreCase(ToolConstants.COLUMN_TYPE_TIMESTAMP_MICRO)) { + } else if (type.equalsIgnoreCase(ToolConstants.COLUMN_TYPE_TIMESTAMP_MICRO)) { return TimeUnit.MICROSECOND; - } else if(type.equalsIgnoreCase(ToolConstants.COLUMN_TYPE_TIMESTAMP_NANO)) { + } else if (type.equalsIgnoreCase(ToolConstants.COLUMN_TYPE_TIMESTAMP_NANO)) { return TimeUnit.NANOSECOND; } else { - throw new GridStoreCommandException("Error occurded in convert to type" + throw new GridStoreCommandException("Error occurred when converting to type" + ": type=[" + type + "] msg=[Not a precise timestamp type]"); } } @@ -2092,8 +2092,9 @@ public static TimeUnit convertTimestampStringToTimeUnit(String preciseTimestampT * TimeUnit.MILLISECOND -> TIMESTAMP(3) * TimeUnit.MICROSECOND -> TIMESTAMP(6) * TimeUnit.NANOSECOND -> TIMESTAMP(9) - * @param timeUnit - * @return String of TIMESTAMP with number + * + * @param timeUnit The precision time unit of timestamp + * @return String of TIMESTAMP type with numbered suffix * @throws GridStoreCommandException */ public static String convertTimeunitToTimestampType(TimeUnit timeUnit) throws GridStoreCommandException { @@ -2105,7 +2106,7 @@ public static String convertTimeunitToTimestampType(TimeUnit timeUnit) throws Gr case NANOSECOND : return ToolConstants.COLUMN_TYPE_TIMESTAMP_NANO.toUpperCase(); default : - throw new GridStoreCommandException("Error occurded in convert time unit" + throw new GridStoreCommandException("Error occurred when converting time unit" + ": type=[" + timeUnit.name() + "] msg=[Not a time unit]"); } } @@ -2113,6 +2114,7 @@ public static String convertTimeunitToTimestampType(TimeUnit timeUnit) throws Gr /** * Check if a string is TIMESTAMP type has suffix * The valid string is "TIMESTAMP(3)", "TIMESTAMP(6)", and "TIMESTAMP(9)" + * * @param timestampTypeHasSuffix the TIMESTAMP type has suffix * @return true if the given string is timestamp type with number */ @@ -2124,9 +2126,10 @@ public static boolean isTimestampStringInSeconds(String timestampTypeHasSuffix) } /** - * Check if timeunit is MILLISECOND or MICROSECOND or NANOSECOND - * @param timeUnit - * @return true if time unit is MILLISECOND or MICROSECOND or NANOSECOND + * Check if time unit is MILLISECOND, MICROSECOND or NANOSECOND + * + * @param timeUnit the precision time unit of timestamp + * @return true if time unit is MILLISECOND, MICROSECOND or NANOSECOND */ public static boolean isTimestampUnit(TimeUnit timeUnit) { return TimeUnit.MILLISECOND == timeUnit @@ -2136,7 +2139,8 @@ public static boolean isTimestampUnit(TimeUnit timeUnit) { /** * Check if a column is precise timestamp - * @param columnInfo + * + * @param columnInfo GridStore column information * @return true if the given column is precise timestamp */ public static boolean isPreciseColumn(ColumnInfo columnInfo) { @@ -2146,7 +2150,8 @@ public static boolean isPreciseColumn(ColumnInfo columnInfo) { /** * Get the DateTimeFormatter base on time unit - * @param timePrecision + * + * @param timeUnit The precision time unit of timestamp * @return date time format of time unit */ public static DateTimeFormatter getDateTimeFormatter(TimeUnit timeUnit) { @@ -2202,7 +2207,7 @@ private String convertColumnType(GSType type) throws GridStoreCommandException { } catch (Exception e) { // "カラム種別の変換処理でエラーが発生しました" //throw new GridStoreCommandException(messageResource.getString("MESS_COMM_ERR_METAINFO_30")+ ": type=[" - throw new GridStoreCommandException("Error occurded in convert to type"+ ": type=[" + throw new GridStoreCommandException("Error occurred when converting to type"+ ": type=[" +type+"] msg=[" + e.getMessage()+"]", e); } } diff --git a/common/src/com/toshiba/mwcloud/gs/tools/common/data/ToolConstants.java b/common/src/com/toshiba/mwcloud/gs/tools/common/data/ToolConstants.java index d3b42bc..0859f11 100644 --- a/common/src/com/toshiba/mwcloud/gs/tools/common/data/ToolConstants.java +++ b/common/src/com/toshiba/mwcloud/gs/tools/common/data/ToolConstants.java @@ -17,7 +17,7 @@ public class ToolConstants { /** メタ情報ファイルフォーマットのバージョン */ - public static String META_FILE_VERSION = "5.3.0"; + public static String META_FILE_VERSION = "5.5.0"; /** ロウファイルのタイプ(CSV/バイナリ) */ diff --git a/src/com/toshiba/mwcloud/gs/tools/shell/GridStoreShellMessages.properties b/src/com/toshiba/mwcloud/gs/tools/shell/GridStoreShellMessages.properties index 82aa5f1..0d256c6 100644 --- a/src/com/toshiba/mwcloud/gs/tools/shell/GridStoreShellMessages.properties +++ b/src/com/toshiba/mwcloud/gs/tools/shell/GridStoreShellMessages.properties @@ -1,4 +1,4 @@ -version=gs_sh version 5.3.1 +version=gs_sh version 5.5.0 help.version=Show version help.help=Show this help diff --git a/src/com/toshiba/mwcloud/gs/tools/shell/GridStoreShellMessages_ja.properties b/src/com/toshiba/mwcloud/gs/tools/shell/GridStoreShellMessages_ja.properties index 35f7c61..24db5e6 100644 --- a/src/com/toshiba/mwcloud/gs/tools/shell/GridStoreShellMessages_ja.properties +++ b/src/com/toshiba/mwcloud/gs/tools/shell/GridStoreShellMessages_ja.properties @@ -1,4 +1,4 @@ -version=gs_sh version 5.3.1 +version=gs_sh version 5.5.0 help.version=Show version help.help=Show this help diff --git a/src/com/toshiba/mwcloud/gs/tools/shell/commands/DataCommandClass.java b/src/com/toshiba/mwcloud/gs/tools/shell/commands/DataCommandClass.java index 8556c89..3725491 100644 --- a/src/com/toshiba/mwcloud/gs/tools/shell/commands/DataCommandClass.java +++ b/src/com/toshiba/mwcloud/gs/tools/shell/commands/DataCommandClass.java @@ -104,6 +104,7 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.stream.Stream; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.script.ScriptContext; @@ -155,6 +156,15 @@ public enum AuthenticationMethods { private Connection m_jdbcCon; private Statement m_jdbcStmt; private ResultSet m_jdbcRS; + private String m_jdbcSQL = ""; + + /* Improve CLI result format */ + private static final Integer MAX_COLUMN_WIDTH_DEFAULT = 31; + private static final Integer MIN_COLUMN_WIDTH_LIMIT = 1; + private static final Integer MAX_COLUMN_WIDTH_LIMIT = 1_000_000; + private Integer m_resultMaxWidth = MAX_COLUMN_WIDTH_DEFAULT; + private enum ResultFormat { TABLE, CSV } + private ResultFormat m_resultFormat = ResultFormat.TABLE; private String m_dbName; private String m_connectedUser; @@ -2122,7 +2132,7 @@ private void showCollectionDetail(ContainerInfo contInfo, boolean isPartitioned) println("------------------------------------------------------------------------------"); int colCount = contInfo.getColumnCount(); List gsIndices = contInfo.getIndexInfoList(); - List rowKeyColumnList = contInfo.getRowKeyColumnList(); + List rowKeyColumnList = contInfo.getRowKeyColumnList(); for (int colNo = 0; colNo < colCount; ++colNo) { ColumnInfo colInfo = contInfo.getColumnInfo(colNo); @@ -2414,6 +2424,7 @@ public void tql(String containerName, String query) { println(getMessage("message.selectOnly", end - start)); } + } catch (GSException e) { queryObjClose(); throw new ShellException(getMessage("error.tql") + " : msg=[" + e.getMessage() + "]", e); @@ -2491,6 +2502,7 @@ public void sql(String sql) { } m_jdbcStmt = m_jdbcCon.createStatement(); + m_jdbcSQL = sql; String sqlCheckStr = sql.replaceAll("/\\*[^\\*]*\\*/", " "); sqlCheckStr = (sqlCheckStr.replaceAll("--.*(\r\n|\n)", " ")).trim(); @@ -2549,6 +2561,7 @@ public void sql(String sql) { m_jdbcStmt = null; } + } catch (Exception e) { queryObjClose(); throw new ShellException(getMessage("error.sql") + " : msg=[" + e.getMessage() + "]", e); @@ -2590,6 +2603,178 @@ private int getSqlResultCount(String sql) { return count; } + /* improve query result: display pretty format as table instead of CSV */ + private class ResultTable { + private final String CELL_PADDING; + private final Integer MAX_CELL_WIDTH; + private final Integer columnCount; + private final ArrayList columnHeader; + private final Integer[] cellWidthList; + private ArrayList> resultRows; + + /** + * Create a result table + * + * @param columnNames name of columns + * @param maxColumnWidth maximum width for all columns + */ + public ResultTable(ArrayList columnNames, Integer maxColumnWidth) { + this.columnHeader = columnNames; + this.columnCount = columnNames.size(); + this.cellWidthList = columnNames.stream() + .map(column -> Math.min(column.length(), maxColumnWidth)) + .toArray(Integer[]::new); + this.resultRows = new ArrayList>(); + this.MAX_CELL_WIDTH = maxColumnWidth; + this.CELL_PADDING = new String(new char[maxColumnWidth]) + .replace("\0", " "); + } + + /** + * Create a result table + * + * @param columnNames name of columns + */ + public ResultTable(ArrayList columnNames) { + this(columnNames, MAX_COLUMN_WIDTH_DEFAULT); + } + + /** + * Get the width of a given column + * + * @param cloumnIndex the position of column in table + * @return a interger value + */ + public Integer getColumnWidth(int cloumnIndex) { + return cellWidthList[cloumnIndex]; + } + + /** + * Set width to a column + * + * @param cloumnIndex index of column to set width + * @param width the value to set + */ + public void setColumnWidth(int cloumnIndex, Integer width) { + cellWidthList[cloumnIndex] = width; + } + + /** + * Add a new row into the ResultTable + * + * @param row the row that represent by list of cell values + */ + public void addRow(ArrayList row) { + if (row.size() != columnCount) { + throw new ShellException(getMessage("error.getrow") + " : msg=[" + getMessage("message.getrow.mismatch", row.size(), columnCount) + "]"); + } + resultRows.add(row); + /* update column width */ + for (int colNo = 0; colNo < columnCount; ++colNo) { + int cellWidth = getConsoleTextLength(row.get(colNo)); + /* limit cell width if it exceeds MAX_CELL_WIDTH */ + if (MAX_CELL_WIDTH > 0 && cellWidth > MAX_CELL_WIDTH) { + cellWidth = MAX_CELL_WIDTH; + } + /* expand cell width */ + if (cellWidth > cellWidthList[colNo]) { + cellWidthList[colNo] = cellWidth; + } + } + } + + /** + * Get width of text when it display on command line console + * + * @param text the text to measure + * @return width of text in integer value + */ + public int getConsoleTextLength(String text) { + return text.codePoints() + .map(c -> getCharacterDisplayWidth(c)) + .reduce(0, (sum, x) -> sum + x); + } + + /** + * Print border of table + */ + private void printBorder() { + System.out.println("+" + String.join("+", + Stream.of(this.cellWidthList) + .map(width -> new String(new char[width+2]) + .replace("\0", "-")) + .toArray(String[]::new)) + "+"); + } + + /** + * Print a row in table + * + * @param row the row to print + */ + private void printRow(ArrayList row) { + String line = "|"; + for (int i = 0; i < columnCount; ++i) { + String value = row.get(i); + int width = getConsoleTextLength(value); + /* fill padding to cell by spaces */ + String padding = ""; + if (width < cellWidthList[i]) { + padding = CELL_PADDING.substring(0, cellWidthList[i] - width); + } + String cell = value; + if (width > MAX_CELL_WIDTH) { + String cutInfo = "..."; + if (MAX_CELL_WIDTH < cutInfo.length()) { + cutInfo = cutInfo.substring(0, MAX_CELL_WIDTH); + } + /* in case of display width of all charactes is 1 */ + if(value.length() == width) { + cell = cell.substring(0, MAX_CELL_WIDTH - cutInfo.length()) + cutInfo; + } else { + /* calculate the display width of mixed CJK and latin characters, + display width of them are different */ + final int[] charCodes = value.codePoints().toArray(); + int displayWidth = 0; + int k = 0; + for(; k < charCodes.length; k++) { + int charWidth = getCharacterDisplayWidth(charCodes[k]); + if (displayWidth + charWidth + cutInfo.length() > MAX_CELL_WIDTH) { + break; + } + displayWidth += charWidth; + } + /* shownChars: the number of characters of display part */ + final int shownChars = k; + /* Extra Dots: + Assuming max-width=6, then 'aa米米a' has width = 7 (米=2), + will display 'aa....' (6 chars: 2 displayed + 1 extraDots + 3 cutInfo's dots) */ + String extraDots = ""; + if (displayWidth < MAX_CELL_WIDTH) { + extraDots = new String(new char[MAX_CELL_WIDTH - cutInfo.length() - displayWidth]) + .replace("\0", "."); + } + cell = cell.substring(0, shownChars) + extraDots + cutInfo; + } + } + line += " " + cell + padding + " |"; + } + System.out.println(line); + } + + /** + * Display the result table + */ + public void display() { + printBorder(); + printRow(columnHeader); + printBorder(); + resultRows.forEach(row -> { + printRow(row); + }); + printBorder(); + } + } + /** Acquire and display query result. */ private abstract class RowGetter { @@ -2771,6 +2956,11 @@ public int getRow(Integer count, boolean replaceNull) { entry.getStatement()); } } + + if (getResultFormat() == ResultFormat.TABLE) { + displayAsTable(); + } + return rowNo; } catch (GSException | IllegalArgumentException e) { @@ -2788,6 +2978,15 @@ public int getRow(Integer count, boolean replaceNull) { public int getRowSQL(Integer count, boolean replaceNull) { checkConnectedSQL(); checkQueriedSQL(); + + + String[] sqlTokens = simplifySQL(m_jdbcSQL); + boolean isExplain = "EXPLAIN".equals(sqlTokens[0]); + ResultFormat originFormat = m_resultFormat; + /* Change to result format to CSV if SQL is explain */ + if(isExplain) { + m_resultFormat = ResultFormat.CSV; + } int countVal = (count == null) ? Integer.MAX_VALUE : count; @@ -2822,6 +3021,15 @@ public int getRowSQL(Integer count, boolean replaceNull) { } printLine(1 + rowNo, line); } + + if (getResultFormat() == ResultFormat.TABLE) { + displayAsTable(); + } + + /* Restore orginal display mode after display SQL explain result */ + if(isExplain) { + m_resultFormat = originFormat; + } return rowNo; @@ -2829,6 +3037,9 @@ public int getRowSQL(Integer count, boolean replaceNull) { throw new ShellException(getMessage("error.getrow") + " : msg=[" + e.getMessage() + "]", e); } } + + protected void displayAsTable() {}; + } /** @@ -2845,16 +3056,27 @@ public void getRow(@GSNullable Integer count) { RowGetter rowgetter = new RowGetter() { + private ResultTable resultTable = null; @Override protected void printLine(int rowNumber, String... line) { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < line.length; ++i) { - if (i != 0) { - builder.append(','); + if (getResultFormat() == ResultFormat.CSV) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < line.length; ++i) { + if (i != 0) { + builder.append(','); + } + builder.append(line[i]); + } + println(builder.toString()); + } else { + if (this.resultTable == null) { + ArrayList columnNames = new ArrayList(Arrays.asList(line)); + this.resultTable = new ResultTable(columnNames, getResultMaxColumnWidth()); + } + else { + this.resultTable.addRow(new ArrayList(Arrays.asList(line))); } - builder.append(line[i]); } - println(builder.toString()); } @Override @@ -2869,6 +3091,14 @@ protected String formatDate(Date date) { } return zdt.format(dateTimeFormatter); } + + @Override + protected void displayAsTable() { + if (this.resultTable != null) { + this.resultTable.display(); + } + } + }; int gotCount = 0; @@ -5958,6 +6188,74 @@ public void setNotificationInterfaceAddress(@GSNullable String notificationInter } } + /** + * The main method for sub-command {@code setresultmaxwidth}.
+ * Set the maximum width for column in query result + * + * @param width The maximum width of column + * @throws ShellException when width is invalid. + */ + @GSCommand(name = "setresultmaxwidth") + public void setResultMaxWidth(@GSNullable Integer width) { + if (width != null) { + if (width < MIN_COLUMN_WIDTH_LIMIT || width > MAX_COLUMN_WIDTH_LIMIT) { + throw new ShellException(getMessage( + "error.illegalEnum", width, ": " + + MIN_COLUMN_WIDTH_LIMIT + " -> " + MAX_COLUMN_WIDTH_LIMIT)); + } + m_resultMaxWidth = width; + } else { + m_resultMaxWidth = MAX_COLUMN_WIDTH_DEFAULT; + } + } + + /** + * The main method for sub-command {@code setresultformat}.
+ * Set the format to display the query result + * + * @param format The format of query result: choose TABLE or CSV. + * @throws ShellException when format is invalid. + */ + @GSCommand(name = "setresultformat") + public void setResultFormat(@GSNullable String format) { + if (format != null) { + String resultFormat = format.toUpperCase(); + try { + m_resultFormat = ResultFormat.valueOf(resultFormat); + } catch(Exception e) { + throw new ShellException( + getMessage("error.illegalEnum", format, Arrays.toString(ResultFormat.values()))); + } + } else { + m_resultFormat = ResultFormat.TABLE; + } + } + + private int getResultMaxColumnWidth() { + return m_resultMaxWidth != null ? m_resultMaxWidth : MAX_COLUMN_WIDTH_DEFAULT; + } + + private ResultFormat getResultFormat() { + return m_resultFormat != null ? m_resultFormat : ResultFormat.TABLE; + } + + private static String[] simplifySQL(String sql) { + String[] sqlTokens = sql.replaceAll("['][^']+[']", "'_string_'") // simplify SQL string + .replaceAll("[\"][^\"]+[\"]", " _object_ ") // simplify SQL object identifier + .replaceAll("/\\*.*?\\*/", "") // remove comment + .replaceAll("--.*$", "") // remove comment + .toUpperCase() + .trim() + .split("\\s+"); + return sqlTokens; + } + + private static int getCharacterDisplayWidth(int charCode) { + return org.jline.utils.WCWidth.wcwidth(charCode); + } + + + private void checkNotificationInterfaceAddressVal(String notificationInterfaceAddress) { boolean result = false; if (isValidIpAddress(notificationInterfaceAddress)) { @@ -6013,4 +6311,5 @@ private static String formatValidIpAddress(String ipAddress) { } return outputIpAddress.substring(0, outputIpAddress.length() - 1); } + } diff --git a/src/com/toshiba/mwcloud/gs/tools/shell/commands/DataCommandClassMessages.properties b/src/com/toshiba/mwcloud/gs/tools/shell/commands/DataCommandClassMessages.properties index 3cc8a7c..d7768d7 100644 --- a/src/com/toshiba/mwcloud/gs/tools/shell/commands/DataCommandClassMessages.properties +++ b/src/com/toshiba/mwcloud/gs/tools/shell/commands/DataCommandClassMessages.properties @@ -332,6 +332,17 @@ setntfif.detail=\n\ Argument:\n\ \ Specify Notification Interface Address.\n\ \ When not specified, the value of "notificationInterfaceAddress" variable is cleared. +setresultformat.description=Set the format to display the query result. +setresultformat.parameter= +setresultformat.detail=\n\ +Argument:\n\ +\ The format of query result: choose TABLE or CSV. + +setresultmaxwidth.description=Set the maximum width for column in query result (when result format is TABLE). +setresultmaxwidth.parameter= +setresultmaxwidth.detail=\n\ +Argument:\n\ +\ The maximum width of column. message.connected=The connection attempt was successful(NoSQL). message.connectedSQL=The connection attempt was successful(NewSQL). @@ -344,6 +355,7 @@ message.gotCountProgress=The {0} results had been acquired. message.insertcount=The {0} records had been inserted. message.deletecount=The {0} records had been deleted. message.updatecount=The {0} records had been updated. +message.getrow.mismatch=Number of cells ({0}) in row does not match to number of columns ({1}). error.userPasswordNull=D20301: The username and password is not set. error.notConnected=D20302: This connection has already been closed(NoSQL). @@ -471,3 +483,4 @@ error.missingArgument=D20405: The required argument is not set. The help command error.notificationInterfaceAddressInvalid=D20436: "{0}" is invalid IPv4 address format. error.userOrRoleNotFound=D20437: The user name or role name "{0}" does not exists. +error.illegalEnum=D20407: This argument {0} is incorrect. An acceptable argument is {1}. diff --git a/src/com/toshiba/mwcloud/gs/tools/shell/commands/DataCommandClassMessages_ja.properties b/src/com/toshiba/mwcloud/gs/tools/shell/commands/DataCommandClassMessages_ja.properties index c7da280..dc803ba 100644 --- a/src/com/toshiba/mwcloud/gs/tools/shell/commands/DataCommandClassMessages_ja.properties +++ b/src/com/toshiba/mwcloud/gs/tools/shell/commands/DataCommandClassMessages_ja.properties @@ -321,7 +321,17 @@ setntfif.detail=\n\ \ \u4F7F\u7528\u53EF\u80FD\u306A\u5024\u306FIPv4\u30A2\u30C9\u30EC\u30B9\u5F62\u5F0F\u306B\u5F93\u3044\u307E\u3059\u3002\n\ \ \u6307\u5B9A\u3055\u308C\u3066\u3044\u306A\u3044\u5834\u5408\u3001\u300CnotificationInterfaceAddress\u300D\u5909\u6570\u306E\u5024\u304C\u524A\u9664\u3055\u308C\u307E\u3059\u3002 +setresultformat.description=\u30af\u30a8\u30ea\u306e\u5b9f\u884c\u7d50\u679c\u3092\u8868\u793a\u3055\u305b\u308b\u30d5\u30a9\u30fc\u30de\u30c3\u30c8\u306e\u8a2d\u5b9a. +setresultformat.parameter= +setresultformat.detail=\n\ +\u5f15\u6570:\n\ +\ \u30af\u30a8\u30ea\u306e\u5b9f\u884c\u7d50\u679c\u306e\u30d5\u30a9\u30fc\u30de\u30c3\u30c8\uff1aTABLE\u53c8\u306fCSV. +setresultmaxwidth.description=\uff08\u30af\u30a8\u30ea\u306e\u5b9f\u884c\u7d50\u679c\u306e\u30d5\u30a9\u30fc\u30de\u30c3\u30c8\u306e\u5834\u5408\uff09\u30ab\u30e9\u30e0\u306e\u6700\u5927\u5e45\u306e\u8a2d\u5b9a. +setresultmaxwidth.parameter= +setresultmaxwidth.detail=\n\ +\u5f15\u6570:\n\ +\ \u30ab\u30e9\u30e0\u306e\u6700\u5927\u5e45. message.connected=\u63a5\u7d9a\u306b\u6210\u529f\u3057\u307e\u3057\u305f(NoSQL)\u3002 message.connectedSQL=\u63a5\u7d9a\u306b\u6210\u529f\u3057\u307e\u3057\u305f(NewSQL)\u3002 @@ -461,3 +471,4 @@ error.missingArgument=D20405: \u5fc5\u9808\u5f15\u6570\u304c\u4e0d\u8db3\u3057\u error.notificationInterfaceAddressInvalid=D20436: "{0}"\u306F\u7121\u52B9\u306AIPv4\u30A2\u30C9\u30EC\u30B9\u306E\u5F62\u5F0F\u3067\u3059\u3002 error.userOrRoleNotFound=D20437: \u30E6\u30FC\u30B6\u540D\u307E\u305F\u306F\u30ED\u30FC\u30EB\u540D"{0}"\u306F\u5B58\u5728\u3057\u307E\u305B\u3093\u3002 +error.illegalEnum=D20407: {0} \u306f\u5f15\u6570\u3068\u3057\u3066\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093\u3002{1}\u306e\u307f\u304c\u6307\u5b9a\u3067\u304d\u307e\u3059\u3002