Repository: nao1215/sqluv
Pull request #57: feat: display ddl information
Author: login: nao1215
Created: 2025-03-30T06:16:58Z | Merged: 2025-03-30T06:20:55Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
	- Introduced new keyboard shortcuts in the sidebar: use **Enter** to display table schema details and **Space** to toggle column visibility.
	- Enhanced the table search experience with improved shortcut functionality.
	- Expanded support to include multiple databases: MySQL, PostgreSQL, SQLite3, and SQL Server.
  
- **Documentation**
	- Updated user instructions detailing the project architecture and the new interactive key bindings.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/.github/copilot-instructions.ja.md b/.github/copilot-instructions.ja.md
index 970fd1a..14c9c08 100644
--- a/.github/copilot-instructions.ja.md
+++ b/.github/copilot-instructions.ja.md
@@ -20,6 +20,12 @@
 - tui：TUIを管理する
 - usecase：tuiから呼び出されるユースケースインターフェースを管理する
 
+# sqluvのアーキテクチャ
+
+クリーンアーキテクチャを採用している。TUIは、usecaseパッケージ（インターフェース）に依存する。usecaseのインターフェースは、interactorパッケージで実装されている。interactorは、domain/modelパッケージおよびdomain/repositoryパッケージ（インターフェース）に依存する。domain/repositoryパッケージは、infrastructure/memoryパッケージおよびinfrastructure/persistenceパッケージで実装されている。infrastructure/memoryパッケージおよびinfrastructure/persistenceパッケージは、domain/modelパッケージに依存する。
+
+domain/modelパッケージは、ドメインオブジェクトを管理する。殆どの構造体をValue Objectとして実装し、初期化関数を用意している。
+
 # Text User Interfaceの概要
 
 `github.com/rivo/tview`のライブラリを用いて、TUIを提供している。TUIは、以下のコンポーネントで構成されている。
@@ -62,8 +68,19 @@
 | Ctrl + x	|選択したテキストをカット |
 | Ctrl + s	|結果をファイルに保存 |
 | Ctrl + t	|テーマを変更 |
+| /       |サイドバーでテーブル名を検索（サイドバーにフォーカスがある場合）|
+| ESC       |サイドバーでテーブル名検索をクリア（サイドバーにフォーカスがある場合） |
+| Space     | テーブルのカラム一覧を表示／非表示（サイドバーにフォーカスがある場合） |
+| Enter     | テーブルのDDL情報を表示（サイドバーにフォーカスがある場合） |
 | F1        |サイドバーにフォーカス |
 | F2        |クエリ入力エリアにフォーカス |
 | F3        |クエリ結果テーブルにフォーカス |
 | TAB	|次のフィールドに移動 |
 | Shift + TAB	|前のフィールドに移動 |
+
+# サポートしているRDBMS
+
+- MySQL
+- PostgreSQL
+- SQLite3
+- SQL Server
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index bb619c0..eae93cb 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -42,7 +42,7 @@ The above components make up the TUI that appears **after connecting to an RDBMS
 
 # Architecture of sqluv
 
-The project adopts Clean Architecture. The TUI depends on the `usecase` package (interfaces). The interfaces in the `usecase` package are implemented in the `interactor` package. The `interactor` depends on the `domain/model` package and the `domain/repository` package. The `domain/repository` package is implemented in the `infrastructure/memory` package and the `infrastructure/persistence` package. The `infrastructure/memory` and `infrastructure/persistence` packages depend on the `domain/model` package.
+The project adopts Clean Architecture. The TUI depends on the `usecase` package (interfaces). The interfaces in the `usecase` package are implemented in the `interactor` package. The `interactor` depends on the `domain/model` package and the `domain/repository` package (interfaces). The `domain/repository` package is implemented in the `infrastructure/memory` package and the `infrastructure/persistence` package. The `infrastructure/memory` and `infrastructure/persistence` packages depend on the `domain/model` package.
 
 The `domain/model` package manages domain objects. Most structs are implemented as Value Objects, and initialization functions are provided.
 
@@ -93,8 +93,19 @@ The `infrastructure` package implements code that varies depending on RDBMS type
 | Ctrl + x | Cut the selected text |
 | Ctrl + s | Save the result to a file |
 | Ctrl + t | Change the theme |
+| /        | Search the table name (when the focus is on the sidebar)|
+| ESC      | Clear the search field (when the focus is on the sidebar)|
+| Space    | Show/Hide Columns (when the focus is on the sidebar)|
+| Enter    | Show the table DDL (when the focus is on the sidebar)|
 | F1       | Focus on the sidebar |
 | F2       | Focus on the query text area |
 | F3       | Focus on the query result table |
 | TAB | Move to the next field |
 | Shift + TAB | Move to the previous field |
+
+# **Supported RDBMSs**
+
+- MySQL
+- PostgreSQL
+- SQLite3
+- SQL Server
diff --git a/README.md b/README.md
index d1ecf1b..b1d244d 100644
--- a/README.md
+++ b/README.md
@@ -71,6 +71,10 @@ To search for a table name, press the `/` key at the sidebar. The sqluv will dis
 
 ![sql_query](doc/image/search_tables.png)
 
+To show/hide columns, press the `Space` key at the sidebar. If you press the `Space` key at the sidebar, the sqluv display the table ddl.
+
+![ddl](doc/image/ddl_info.png)
+
 ## SQL query history
 
 If you execute a SQL query, the history will be saved in the `~/.config/sqluv/history.db`. So, you can look up the history by pressing the history button.
@@ -120,7 +124,9 @@ You can save the result to a file by pressing the `Ctrl + s` key. The sqluv will
 | Ctrl + s | Save the result to a file |
 | Ctrl + t | Change the theme |
 | /        | Search the table name (when the focus is on the sidebar)|
-| ESC      | Clear the search field |
+| ESC      | Clear the search field (when the focus is on the sidebar)|
+| Space    | Show/Hide Columns (when the focus is on the sidebar)|
+| Enter    | Show the table DDL (when the focus is on the sidebar)|
 | F1       | Focus on the sidebar |
 | F2       | Focus on the query text area |
 | F3       | Focus on the query result table |
diff --git a/doc/image/ddl_info.png b/doc/image/ddl_info.png
new file mode 100644
index 0000000..bf345ef
Binary files /dev/null and b/doc/image/ddl_info.png differ
diff --git a/domain/repository/persistence.go b/domain/repository/persistence.go
index 598ee15..937466b 100644
--- a/domain/repository/persistence.go
+++ b/domain/repository/persistence.go
@@ -23,4 +23,10 @@ type (
 	TablesInRemoteGetter interface {
 		GetTables(ctx context.Context) ([]*model.Table, error)
 	}
+
+	// TableDDLGetter is an interface for retrieving a table's DDL information,
+	// including columns, data types, precision, nullability, default values, primary key status, etc.
+	TableDDLGetter interface {
+		GetTableDDL(ctx context.Context, tableName string) ([]*model.Table, error)
+	}
 )
diff --git a/infrastructure/mock/persistence.go b/infrastructure/mock/persistence.go
index b995a8d..1090f24 100644
--- a/infrastructure/mock/persistence.go
+++ b/infrastructure/mock/persistence.go
@@ -205,3 +205,66 @@ func (c *MockTablesInRemoteGetterGetTablesCall) DoAndReturn(f func(context.Conte
 	c.Call = c.Call.DoAndReturn(f)
 	return c
 }
+
+// MockTableDDLGetter is a mock of TableDDLGetter interface.
+type MockTableDDLGetter struct {
+	ctrl     *gomock.Controller
+	recorder *MockTableDDLGetterMockRecorder
+	isgomock struct{}
+}
+
+// MockTableDDLGetterMockRecorder is the mock recorder for MockTableDDLGetter.
+type MockTableDDLGetterMockRecorder struct {
+	mock *MockTableDDLGetter
+}
+
+// NewMockTableDDLGetter creates a new mock instance.
+func NewMockTableDDLGetter(ctrl *gomock.Controller) *MockTableDDLGetter {
+	mock := &MockTableDDLGetter{ctrl: ctrl}
+	mock.recorder = &MockTableDDLGetterMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockTableDDLGetter) EXPECT() *MockTableDDLGetterMockRecorder {
+	return m.recorder
+}
+
+// GetTableDDL mocks base method.
+func (m *MockTableDDLGetter) GetTableDDL(ctx context.Context, tableName string) ([]*model.Table, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetTableDDL", ctx, tableName)
+	ret0, _ := ret[0].([]*model.Table)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetTableDDL indicates an expected call of GetTableDDL.
+func (mr *MockTableDDLGetterMockRecorder) GetTableDDL(ctx, tableName any) *MockTableDDLGetterGetTableDDLCall {
+	mr.mock.ctrl.T.Helper()
+	call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTableDDL", reflect.TypeOf((*MockTableDDLGetter)(nil).GetTableDDL), ctx, tableName)
+	return &MockTableDDLGetterGetTableDDLCall{Call: call}
+}
+
+// MockTableDDLGetterGetTableDDLCall wrap *gomock.Call
+type MockTableDDLGetterGetTableDDLCall struct {
+	*gomock.Call
+}
+
+// Return rewrite *gomock.Call.Return
+func (c *MockTableDDLGetterGetTableDDLCall) Return(arg0 []*model.Table, arg1 error) *MockTableDDLGetterGetTableDDLCall {
+	c.Call = c.Call.Return(arg0, arg1)
+	return c
+}
+
+// Do rewrite *gomock.Call.Do
+func (c *MockTableDDLGetterGetTableDDLCall) Do(f func(context.Context, string) ([]*model.Table, error)) *MockTableDDLGetterGetTableDDLCall {
+	c.Call = c.Call.Do(f)
+	return c
+}
+
+// DoAndReturn rewrite *gomock.Call.DoAndReturn
+func (c *MockTableDDLGetterGetTableDDLCall) DoAndReturn(f func(context.Context, string) ([]*model.Table, error)) *MockTableDDLGetterGetTableDDLCall {
+	c.Call = c.Call.DoAndReturn(f)
+	return c
+}
diff --git a/infrastructure/persistence/dbms.go b/infrastructure/persistence/dbms.go
index 7c510d2..fa02cb0 100644
--- a/infrastructure/persistence/dbms.go
+++ b/infrastructure/persistence/dbms.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"database/sql"
 	"fmt"
+	"strconv"
 
 	"github.com/nao1215/sqluv/config"
 	"github.com/nao1215/sqluv/domain/model"
@@ -188,3 +189,108 @@ func (e *statementExecutor) ExecuteStatement(ctx context.Context, sql *model.SQL
 	}
 	return result.RowsAffected()
 }
+
+type ddlGetter struct {
+	db       *sql.DB
+	dbmsType config.DBMSType
+	database string
+}
+
+// NewTableDDLGetter returns a new TableDDLGetter.
+func NewTableDDLGetter(
+	db *sql.DB,
+	database string,
+	dbmsType config.DBMSType,
+) repository.TableDDLGetter {
+	return &ddlGetter{
+		db:       db,
+		database: database,
+		dbmsType: dbmsType,
+	}
+}
+
+// GetTableDDL retrieves the DDL info of the given table as a Table struct.
+func (d *ddlGetter) GetTableDDL(ctx context.Context, tableName string) ([]*model.Table, error) {
+	var query string
+	var rows *sql.Rows
+	var err error
+
+	switch d.dbmsType {
+	case config.MySQL:
+		query = `
+            SELECT COLUMN_NAME, DATA_TYPE, IFNULL(CHARACTER_MAXIMUM_LENGTH, 0),
+                   IS_NULLABLE, IFNULL(COLUMN_DEFAULT, ''), COLUMN_KEY
+            FROM information_schema.columns
+            WHERE table_schema=? AND table_name=?`
+		rows, err = d.db.QueryContext(ctx, query, d.database, tableName)
+	case config.PostgreSQL:
+		query = `
+            SELECT column_name, data_type, COALESCE(character_maximum_length, 0),
+                   is_nullable, COALESCE(column_default, ''), ''
+            FROM information_schema.columns
+            WHERE table_schema='public' AND table_name=$1::text`
+		rows, err = d.db.QueryContext(ctx, query, tableName)
+	case config.SQLite3:
+		// PRAGMA table_info returns: cid, name, type, notnull, dflt_value, pk
+		query = "PRAGMA table_info(" + tableName + ")"
+		rows, err = d.db.QueryContext(ctx, query)
+	case config.SQLServer:
+		query = `
+            SELECT COLUMN_NAME, DATA_TYPE, ISNULL(CHARACTER_MAXIMUM_LENGTH, 0),
+                   IS_NULLABLE, ISNULL(COLUMN_DEFAULT, ''), '' as column_key
+            FROM INFORMATION_SCHEMA.COLUMNS 
+            WHERE TABLE_CATALOG=? AND TABLE_NAME=?`
+		rows, err = d.db.QueryContext(ctx, query, d.database, tableName)
+	default:
+		return nil, fmt.Errorf("unsupported DBMS type: %v", d.dbmsType)
+	}
+
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+
+	// Define header for DDL info using the model.Header (CSV/TSV table header)
+	header := model.NewHeader([]string{"Column Name", "Type", "Precision", "Nullable", "DefaultValue", "PrimaryKey"})
+	var records []model.Record
+
+	for rows.Next() {
+		switch d.dbmsType {
+		case config.SQLite3:
+			var cid int
+			var colName, colType string
+			var notnull, pk int
+			var dflt sql.NullString
+			if err := rows.Scan(&cid, &colName, &colType, &notnull, &dflt, &pk); err != nil {
+				return nil, err
+			}
+			dfltValue := ""
+			if dflt.Valid {
+				dfltValue = dflt.String
+			}
+			nullable := "YES"
+			if notnull != 0 {
+				nullable = "NO"
+			}
+			key := ""
+			if pk > 0 {
+				key = "PRI"
+			}
+			records = append(records, model.NewRecord([]string{
+				colName, colType, "0", nullable, dfltValue, key,
+			}))
+		default:
+			var colName, dataType, nullable, dfltValue, columnKey string
+			var precision int
+			if err := rows.Scan(&colName, &dataType, &precision, &nullable, &dfltValue, &columnKey); err != nil {
+				return nil, err
+			}
+			records = append(records, model.NewRecord([]string{
+				colName, dataType, strconv.Itoa(precision), nullable, dfltValue, columnKey,
+			}))
+		}
+	}
+
+	ddlTable := model.NewTable(tableName, header, records)
+	return []*model.Table{ddlTable}, nil
+}
diff --git a/infrastructure/persistence/wire.go b/infrastructure/persistence/wire.go
index 4c6f519..c6c0da1 100644
--- a/infrastructure/persistence/wire.go
+++ b/infrastructure/persistence/wire.go
@@ -17,4 +17,5 @@ var Set = wire.NewSet(
 	NewHistoryCreator,
 	NewHistoryLister,
 	NewS3Client,
+	NewTableDDLGetter,
 )
diff --git a/interactor/mock/persistence.go b/interactor/mock/persistence.go
index 31b54f5..8c6f842 100644
--- a/interactor/mock/persistence.go
+++ b/interactor/mock/persistence.go
@@ -143,3 +143,66 @@ func (c *MockTablesGetterGetTablesCall) DoAndReturn(f func(context.Context) ([]*
 	c.Call = c.Call.DoAndReturn(f)
 	return c
 }
+
+// MockTableDDLGetter is a mock of TableDDLGetter interface.
+type MockTableDDLGetter struct {
+	ctrl     *gomock.Controller
+	recorder *MockTableDDLGetterMockRecorder
+	isgomock struct{}
+}
+
+// MockTableDDLGetterMockRecorder is the mock recorder for MockTableDDLGetter.
+type MockTableDDLGetterMockRecorder struct {
+	mock *MockTableDDLGetter
+}
+
+// NewMockTableDDLGetter creates a new mock instance.
+func NewMockTableDDLGetter(ctrl *gomock.Controller) *MockTableDDLGetter {
+	mock := &MockTableDDLGetter{ctrl: ctrl}
+	mock.recorder = &MockTableDDLGetterMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockTableDDLGetter) EXPECT() *MockTableDDLGetterMockRecorder {
+	return m.recorder
+}
+
+// GetTableDDL mocks base method.
+func (m *MockTableDDLGetter) GetTableDDL(ctx context.Context, tableName string) ([]*model.Table, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetTableDDL", ctx, tableName)
+	ret0, _ := ret[0].([]*model.Table)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetTableDDL indicates an expected call of GetTableDDL.
+func (mr *MockTableDDLGetterMockRecorder) GetTableDDL(ctx, tableName any) *MockTableDDLGetterGetTableDDLCall {
+	mr.mock.ctrl.T.Helper()
+	call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTableDDL", reflect.TypeOf((*MockTableDDLGetter)(nil).GetTableDDL), ctx, tableName)
+	return &MockTableDDLGetterGetTableDDLCall{Call: call}
+}
+
+// MockTableDDLGetterGetTableDDLCall wrap *gomock.Call
+type MockTableDDLGetterGetTableDDLCall struct {
+	*gomock.Call
+}
+
+// Return rewrite *gomock.Call.Return
+func (c *MockTableDDLGetterGetTableDDLCall) Return(arg0 []*model.Table, arg1 error) *MockTableDDLGetterGetTableDDLCall {
+	c.Call = c.Call.Return(arg0, arg1)
+	return c
+}
+
+// Do rewrite *gomock.Call.Do
+func (c *MockTableDDLGetterGetTableDDLCall) Do(f func(context.Context, string) ([]*model.Table, error)) *MockTableDDLGetterGetTableDDLCall {
+	c.Call = c.Call.Do(f)
+	return c
+}
+
+// DoAndReturn rewrite *gomock.Call.DoAndReturn
+func (c *MockTableDDLGetterGetTableDDLCall) DoAndReturn(f func(context.Context, string) ([]*model.Table, error)) *MockTableDDLGetterGetTableDDLCall {
+	c.Call = c.Call.DoAndReturn(f)
+	return c
+}
diff --git a/interactor/persistence.go b/interactor/persistence.go
index 63307d5..e667c62 100644
--- a/interactor/persistence.go
+++ b/interactor/persistence.go
@@ -63,3 +63,24 @@ func NewTablesGetter(
 func (m *tablesGetter) GetTables(ctx context.Context) ([]*model.Table, error) {
 	return m.TablesInRemoteGetter.GetTables(ctx)
 }
+
+// _ interface implementation check
+var _ usecase.TableDDLGetter = (*tableDDLGetter)(nil)
+
+type tableDDLGetter struct {
+	repository.TableDDLGetter
+}
+
+// NewTableDDLGetter creates a new TableDDLGetter.
+func NewTableDDLGetter(
+	tdg repository.TableDDLGetter,
+) usecase.TableDDLGetter {
+	return &tableDDLGetter{
+		TableDDLGetter: tdg,
+	}
+}
+
+// GetTableDDL gets DDL information of a table in MySQL database.
+func (m *tableDDLGetter) GetTableDDL(ctx context.Context, tableName string) ([]*model.Table, error) {
+	return m.TableDDLGetter.GetTableDDL(ctx, tableName)
+}
diff --git a/interactor/wire.go b/interactor/wire.go
index 366bc35..134bde2 100644
--- a/interactor/wire.go
+++ b/interactor/wire.go
@@ -14,4 +14,5 @@ var Set = wire.NewSet(
 	NewHistoryTableCreator,
 	NewHistoryCreator,
 	NewHistoryLister,
+	NewTableDDLGetter,
 )
diff --git a/tui/footer.go b/tui/footer.go
index d642b77..4b8f5ac 100644
--- a/tui/footer.go
+++ b/tui/footer.go
@@ -106,6 +106,8 @@ func (f *footer) setDefaulShortcut() {
 func (f *footer) setSidebarShortcut() {
 	f.clearShortcuts()
 	f.addShortcut("/", "Search")
+	f.addShortcut("Enter", "Show DDL")
+	f.addShortcut("Space", "Show/Hide Columns")
 	f.addShortcut("ESC", "Clear search")
 	f.update()
 }
diff --git a/tui/tui.go b/tui/tui.go
index cb56a88..be69d7c 100644
--- a/tui/tui.go
+++ b/tui/tui.go
@@ -35,6 +35,7 @@ type (
 	dbmsUsecases struct {
 		queryExecutor usecase.QueryExecutor
 		tablesGetter  usecase.TablesGetter
+		ddlGetter     usecase.TableDDLGetter
 		fileWriter    usecase.FileWriter
 
 		closeDB       func() // Added field for database cleanup function
@@ -161,10 +162,12 @@ func (t *TUI) handleDBConnection(conn *config.DBConnection) error {
 	queryExecutor := persistence.NewQueryExecutor(db)
 	statementExecutor := persistence.NewStatementExecutor(db)
 	tablesGetter := persistence.NewTablesGetter(db, conn.Database, conn.Type)
+	tableDDLGetter := persistence.NewTableDDLGetter(db, conn.Database, conn.Type)
 
 	t.dbmsUsecases = &dbmsUsecases{
 		queryExecutor: interactor.NewQueryExecutor(queryExecutor, statementExecutor),
 		tablesGetter:  interactor.NewTablesGetter(tablesGetter),
+		ddlGetter:     interactor.NewTableDDLGetter(tableDDLGetter),
 	}
 
 	// Store the database connection for later use
@@ -462,6 +465,24 @@ func (t *TUI) keyBindings(event *tcell.EventKey) *tcell.EventKey {
 			t.app.SetFocus(t.home.sidebar)
 		}
 		return nil
+	case event.Key() == tcell.KeyEnter:
+		if t.home.sidebar.HasFocus() {
+			node := t.home.sidebar.GetCurrentNode()
+			if node != nil {
+				if table, ok := node.GetReference().(*model.Table); ok {
+					ddlTables, err := t.dbmsUsecases.ddlGetter.GetTableDDL(context.Background(), table.Name())
+					if err != nil {
+						t.showError(err)
+						return nil
+					}
+					if len(ddlTables) > 0 {
+						// Display the first returned DDL table.
+						t.home.resultTable.update(ddlTables[0], t.home.rowStatistics, 0)
+					}
+				}
+			}
+			return nil
+		}
 	}
 	return event
 }
diff --git a/usecase/persistence.go b/usecase/persistence.go
index 60dc233..e4105a6 100644
--- a/usecase/persistence.go
+++ b/usecase/persistence.go
@@ -24,6 +24,12 @@ type (
 	TablesGetter interface {
 		GetTables(ctx context.Context) ([]*model.Table, error)
 	}
+
+	// TableDDLGetter is an interface for retrieving a table's DDL information,
+	// including columns, data types, precision, nullability, default values, primary key status, etc.
+	TableDDLGetter interface {
+		GetTableDDL(ctx context.Context, tableName string) ([]*model.Table, error)
+	}
 )
 
 // NewExecuteQueryOutput creates a new ExecuteSQLOutput.


Reviews:
user: login: github-actions[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-30T06:18:18Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>interactor/persistence.go (1)</summary><blockquote>

`83-86`: **Clarify database type in documentation**

The comment states that this method gets DDL information from a "MySQL database," but the project supports multiple RDBMS types as indicated in the documentation.


Consider updating the comment to be more generic:

```diff
-// GetTableDDL gets DDL information of a table in MySQL database.
+// GetTableDDL gets DDL information of a table in the connected database.
```

</blockquote></details>
<details>
<summary>.github/copilot-instructions.ja.md (1)</summary><blockquote>

`78-79`: **Fix hard tabs in table rows**

The markdown table is using hard tabs instead of spaces, which can cause inconsistent rendering across different editors.

```diff
-| TAB	|次のフィールドに移動 |
-| Shift + TAB	|前のフィールドに移動 |
+| TAB       |次のフィールドに移動 |
+| Shift + TAB       |前のフィールドに移動 |
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

78-78: Hard tabs
Column: 6

(MD010, no-hard-tabs)

---

79-79: Hard tabs
Column: 14

(MD010, no-hard-tabs)

</details>

</details>

</blockquote></details>
<details>
<summary>tui/tui.go (1)</summary><blockquote>

`468-485`: **Enhancement suggestion for DDL display handling**

The Enter key handling is correctly implemented, but could be improved to handle edge cases better.

Consider these improvements:
1. Add an informative message when no DDL data is returned (empty slice but no error)
2. Consider showing a status message indicating DDL information is being displayed
3. If multiple DDL tables are returned, provide a way to navigate between them

```diff
 case event.Key() == tcell.KeyEnter:
     if t.home.sidebar.HasFocus() {
         node := t.home.sidebar.GetCurrentNode()
         if node != nil {
             if table, ok := node.GetReference().(*model.Table); ok {
                 ddlTables, err := t.dbmsUsecases.ddlGetter.GetTableDDL(context.Background(), table.Name())
                 if err != nil {
                     t.showError(err)
                     return nil
                 }
                 if len(ddlTables) > 0 {
+                    // Update status or add an indicator that DDL is being displayed
+                    t.home.rowStatistics.setStatusMessage("Displaying DDL for " + table.Name())
                     // Display the first returned DDL table.
                     t.home.resultTable.update(ddlTables[0], t.home.rowStatistics, 0)
+                } else {
+                    t.showError(fmt.Errorf("no DDL information available for table %s", table.Name()))
                 }
             }
         }
         return nil
     }
```

</blockquote></details>
<details>
<summary>infrastructure/persistence/dbms.go (1)</summary><blockquote>

`235-235`: **Consider using query parameters for SQLite**

Direct string concatenation of table names in SQL queries can potentially lead to SQL injection issues in some contexts.

While less critical for SQLite's PRAGMA (which doesn't support parameterization directly), consider using a prepared statement approach for consistency with other DBMS types:

```diff
-        query = "PRAGMA table_info(" + tableName + ")"
+        query = fmt.Sprintf("PRAGMA table_info(%q)", tableName)
         rows, err = d.db.QueryContext(ctx, query)
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 92e2c8771ba0fcbc75779a927622a59479059112 and 33b06e3e76c05455019ff04471ecc2ccfd3103d0.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `doc/image/ddl_info.png` is excluded by `!**/*.png`

</details>

<details>
<summary>📒 Files selected for processing (13)</summary>

* `.github/copilot-instructions.ja.md` (2 hunks)
* `.github/copilot-instructions.md` (2 hunks)
* `README.md` (2 hunks)
* `domain/repository/persistence.go` (1 hunks)
* `infrastructure/mock/persistence.go` (1 hunks)
* `infrastructure/persistence/dbms.go` (2 hunks)
* `infrastructure/persistence/wire.go` (1 hunks)
* `interactor/mock/persistence.go` (1 hunks)
* `interactor/persistence.go` (1 hunks)
* `interactor/wire.go` (1 hunks)
* `tui/footer.go` (1 hunks)
* `tui/tui.go` (3 hunks)
* `usecase/persistence.go` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (4)</summary>

<details>
<summary>infrastructure/persistence/wire.go (2)</summary><blockquote>

<details>
<summary>interactor/persistence.go (1)</summary>

* `NewTableDDLGetter` (75-81)

</details>
<details>
<summary>infrastructure/persistence/dbms.go (1)</summary>

* `NewTableDDLGetter` (200-210)

</details>

</blockquote></details>
<details>
<summary>interactor/wire.go (2)</summary><blockquote>

<details>
<summary>infrastructure/persistence/dbms.go (1)</summary>

* `NewTableDDLGetter` (200-210)

</details>
<details>
<summary>interactor/persistence.go (1)</summary>

* `NewTableDDLGetter` (75-81)

</details>

</blockquote></details>
<details>
<summary>interactor/mock/persistence.go (1)</summary><blockquote>

<details>
<summary>infrastructure/mock/persistence.go (4)</summary>

* `MockTableDDLGetterMockRecorder` (217-219)
* `MockTableDDLGetter` (210-214)
* `NewMockTableDDLGetter` (222-226)
* `MockTableDDLGetterGetTableDDLCall` (250-252)

</details>

</blockquote></details>
<details>
<summary>infrastructure/mock/persistence.go (1)</summary><blockquote>

<details>
<summary>interactor/mock/persistence.go (4)</summary>

* `MockTableDDLGetterMockRecorder` (155-157)
* `MockTableDDLGetter` (148-152)
* `NewMockTableDDLGetter` (160-164)
* `MockTableDDLGetterGetTableDDLCall` (188-190)

</details>

</blockquote></details>

</details><details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

<details>
<summary>.github/copilot-instructions.ja.md</summary>

78-78: Hard tabs
Column: 6

(MD010, no-hard-tabs)

---

79-79: Hard tabs
Column: 14

(MD010, no-hard-tabs)

</details>

</details>
<details>
<summary>🪛 golangci-lint (1.64.8)</summary>

<details>
<summary>infrastructure/persistence/dbms.go</summary>

225-225: rows.Err must be checked

(rowserrcheck)

---

232-232: rows.Err must be checked

(rowserrcheck)

---

236-236: rows.Err must be checked

(rowserrcheck)

---

243-243: rows.Err must be checked

(rowserrcheck)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (21)</summary><blockquote>

<details>
<summary>tui/footer.go (1)</summary>

`109-110`: **Good addition of keyboard shortcuts for DDL display!**

These new shortcuts enhance the UI by providing clear access to DDL information and column visibility controls, aligning with the PR objective to display DDL information.

</details>
<details>
<summary>interactor/wire.go (1)</summary>

`17-17`: **Appropriate addition to dependency injection setup.**

The `NewTableDDLGetter` provider is correctly added to the interactor's wire Set, enabling injection of the DDL getter functionality throughout the application.

</details>
<details>
<summary>infrastructure/persistence/wire.go (1)</summary>

`20-20`: **Correct integration in persistence layer.**

The `NewTableDDLGetter` provider is properly added to the persistence layer's wire Set, making the concrete implementation available for injection.

</details>
<details>
<summary>domain/repository/persistence.go (1)</summary>

`27-31`: **Well-defined interface for DDL retrieval.**

The `TableDDLGetter` interface is clearly documented and follows the established pattern of other repository interfaces. The method signature is appropriate for retrieving table DDL information.

</details>
<details>
<summary>.github/copilot-instructions.md (3)</summary>

`45-45`: **Clear architecture explanation added**

The update clarifies that the `domain/repository` package contains interfaces, which better reflects the clean architecture pattern being used in the project.

---

`96-99`: **Good documentation of new sidebar key bindings**

These key binding additions enhance the user experience by providing clear instructions for interacting with the sidebar component. The documentation properly describes the functionality of each key.

---

`106-111`: **Well-organized RDBMS support documentation**

Adding a dedicated section for supported RDBMSs improves the documentation structure and makes it easier for users to understand the project's capabilities.

</details>
<details>
<summary>README.md (2)</summary>

`76-76`: **Good addition of DDL information screenshot**

Including a screenshot that demonstrates the DDL display feature helps users understand what to expect when using this functionality.

---

`127-129`: **Clear key binding documentation**

The updated key bindings section clearly describes when and how to use each key, which improves the user experience.

</details>
<details>
<summary>usecase/persistence.go (1)</summary>

`28-32`: **Well-designed interface for table DDL retrieval**

The new `TableDDLGetter` interface is clearly documented with descriptive comments that explain its purpose. The method signature follows project conventions and maintains consistency with other interfaces in the file.

</details>
<details>
<summary>interactor/persistence.go (2)</summary>

`67-72`: **Good interface implementation check and struct definition**

The implementation check ensures type safety at compile time, which is a good practice. The struct follows the project's established patterns for interface implementations.

---

`74-81`: **Constructor follows established pattern**

The `NewTableDDLGetter` constructor follows the same pattern as other constructors in the file, maintaining consistency in the codebase.

</details>
<details>
<summary>.github/copilot-instructions.ja.md (3)</summary>

`23-27`: **Good architecture documentation addition!**

The new section explaining Clean Architecture implementation in the project is clear and helpful. This will assist contributors in understanding the dependency flow between packages.

---

`71-74`: **Great addition of sidebar key bindings!**

The documentation of keyboard shortcuts for the sidebar functionality is well-detailed and clearly indicates which actions can be performed when the sidebar has focus.

---

`81-86`: **Good addition of supported RDBMS section!**

Clearly documenting the supported database systems helps users understand the capabilities of the application.

</details>
<details>
<summary>tui/tui.go (2)</summary>

`38-38`: **Clean addition of DDL getter interface**

Adding the DDL getter field to the dbmsUsecases struct is a good approach for extending functionality while maintaining clean architecture principles.

---

`165-170`: **Proper initialization of DDL getter**

The initialization of the tableDDLGetter and its assignment to the dbmsUsecases is correctly implemented, following the established pattern for other similar services.

</details>
<details>
<summary>infrastructure/persistence/dbms.go (2)</summary>

`193-210`: **Well-structured DDL getter implementation**

The ddlGetter struct and constructor follow the established pattern in the codebase and properly implement the repository interface.

---

`218-246`: **Comprehensive DBMS type handling for DDL queries**

The implementation correctly handles different query syntax for various database systems (MySQL, PostgreSQL, SQLite3, SQL Server).

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 golangci-lint (1.64.8)</summary>

225-225: rows.Err must be checked

(rowserrcheck)

---

232-232: rows.Err must be checked

(rowserrcheck)

---

236-236: rows.Err must be checked

(rowserrcheck)

---

243-243: rows.Err must be checked

(rowserrcheck)

</details>

</details>

</details>
<details>
<summary>interactor/mock/persistence.go (1)</summary>

`147-208`: **Appropriate mock implementation for TableDDLGetter**

The generated mock code follows the standard structure for GoMock, with proper method signatures and return types. This will help in unit testing the table DDL functionality.

</details>
<details>
<summary>infrastructure/mock/persistence.go (1)</summary>

`209-270`: **The mock implementation for TableDDLGetter looks good.**

The added `MockTableDDLGetter` and its associated types follow the standard gomock pattern and are consistent with other mocks in this file. This implementation will allow for effective testing of components that depend on the TableDDLGetter interface, which is essential for the new DDL display functionality.

The mock correctly implements:
- The interface structure with controller and recorder
- The EXPECT method for setting expectations
- The GetTableDDL method with proper parameter and return types
- The necessary helper methods (Return, Do, DoAndReturn) for configuring mock behavior

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-30T06:21:46Z

Review comments:
diff_hunk: @@ -188,3 +189,108 @@ func (e *statementExecutor) ExecuteStatement(ctx context.Context, sql *model.SQL
 	}
 	return result.RowsAffected()
 }
+
+type ddlGetter struct {
+	db       *sql.DB
+	dbmsType config.DBMSType
+	database string
+}
+
+// NewTableDDLGetter returns a new TableDDLGetter.
+func NewTableDDLGetter(
+	db *sql.DB,
+	database string,
+	dbmsType config.DBMSType,
+) repository.TableDDLGetter {
+	return &ddlGetter{
+		db:       db,
+		database: database,
+		dbmsType: dbmsType,
+	}
+}
+
+// GetTableDDL retrieves the DDL info of the given table as a Table struct.
+func (d *ddlGetter) GetTableDDL(ctx context.Context, tableName string) ([]*model.Table, error) {
+	var query string
+	var rows *sql.Rows
+	var err error
+
+	switch d.dbmsType {
+	case config.MySQL:
+		query = `
+            SELECT COLUMN_NAME, DATA_TYPE, IFNULL(CHARACTER_MAXIMUM_LENGTH, 0),
+                   IS_NULLABLE, IFNULL(COLUMN_DEFAULT, ''), COLUMN_KEY
+            FROM information_schema.columns
+            WHERE table_schema=? AND table_name=?`
+		rows, err = d.db.QueryContext(ctx, query, d.database, tableName)
path: infrastructure/persistence/dbms.go
user: login: github-actions[bot]
body: 🚫 **[golangci]** <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>rows.Err must be checked (rowserrcheck)
<!-- __reviewdog__:ChBmNzI1ZTFmOTkyMTdkMTExEghnb2xhbmdjaQ== -->

created_at: 2025-03-30T06:18:18Z
author_association: NONE
diff_hunk: @@ -188,3 +189,108 @@ func (e *statementExecutor) ExecuteStatement(ctx context.Context, sql *model.SQL
 	}
 	return result.RowsAffected()
 }
+
+type ddlGetter struct {
+	db       *sql.DB
+	dbmsType config.DBMSType
+	database string
+}
+
+// NewTableDDLGetter returns a new TableDDLGetter.
+func NewTableDDLGetter(
+	db *sql.DB,
+	database string,
+	dbmsType config.DBMSType,
+) repository.TableDDLGetter {
+	return &ddlGetter{
+		db:       db,
+		database: database,
+		dbmsType: dbmsType,
+	}
+}
+
+// GetTableDDL retrieves the DDL info of the given table as a Table struct.
+func (d *ddlGetter) GetTableDDL(ctx context.Context, tableName string) ([]*model.Table, error) {
+	var query string
+	var rows *sql.Rows
+	var err error
+
+	switch d.dbmsType {
+	case config.MySQL:
+		query = `
+            SELECT COLUMN_NAME, DATA_TYPE, IFNULL(CHARACTER_MAXIMUM_LENGTH, 0),
+                   IS_NULLABLE, IFNULL(COLUMN_DEFAULT, ''), COLUMN_KEY
+            FROM information_schema.columns
+            WHERE table_schema=? AND table_name=?`
+		rows, err = d.db.QueryContext(ctx, query, d.database, tableName)
+	case config.PostgreSQL:
+		query = `
+            SELECT column_name, data_type, COALESCE(character_maximum_length, 0),
+                   is_nullable, COALESCE(column_default, ''), ''
+            FROM information_schema.columns
+            WHERE table_schema='public' AND table_name=$1::text`
+		rows, err = d.db.QueryContext(ctx, query, tableName)
path: infrastructure/persistence/dbms.go
user: login: github-actions[bot]
body: 🚫 **[golangci]** <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>rows.Err must be checked (rowserrcheck)
<!-- __reviewdog__:ChBmMWRjNzRmOGI0OWQxZWFmEghnb2xhbmdjaQ== -->

created_at: 2025-03-30T06:18:18Z
author_association: NONE
diff_hunk: @@ -188,3 +189,108 @@ func (e *statementExecutor) ExecuteStatement(ctx context.Context, sql *model.SQL
 	}
 	return result.RowsAffected()
 }
+
+type ddlGetter struct {
+	db       *sql.DB
+	dbmsType config.DBMSType
+	database string
+}
+
+// NewTableDDLGetter returns a new TableDDLGetter.
+func NewTableDDLGetter(
+	db *sql.DB,
+	database string,
+	dbmsType config.DBMSType,
+) repository.TableDDLGetter {
+	return &ddlGetter{
+		db:       db,
+		database: database,
+		dbmsType: dbmsType,
+	}
+}
+
+// GetTableDDL retrieves the DDL info of the given table as a Table struct.
+func (d *ddlGetter) GetTableDDL(ctx context.Context, tableName string) ([]*model.Table, error) {
+	var query string
+	var rows *sql.Rows
+	var err error
+
+	switch d.dbmsType {
+	case config.MySQL:
+		query = `
+            SELECT COLUMN_NAME, DATA_TYPE, IFNULL(CHARACTER_MAXIMUM_LENGTH, 0),
+                   IS_NULLABLE, IFNULL(COLUMN_DEFAULT, ''), COLUMN_KEY
+            FROM information_schema.columns
+            WHERE table_schema=? AND table_name=?`
+		rows, err = d.db.QueryContext(ctx, query, d.database, tableName)
+	case config.PostgreSQL:
+		query = `
+            SELECT column_name, data_type, COALESCE(character_maximum_length, 0),
+                   is_nullable, COALESCE(column_default, ''), ''
+            FROM information_schema.columns
+            WHERE table_schema='public' AND table_name=$1::text`
+		rows, err = d.db.QueryContext(ctx, query, tableName)
+	case config.SQLite3:
+		// PRAGMA table_info returns: cid, name, type, notnull, dflt_value, pk
+		query = "PRAGMA table_info(" + tableName + ")"
+		rows, err = d.db.QueryContext(ctx, query)
path: infrastructure/persistence/dbms.go
user: login: github-actions[bot]
body: 🚫 **[golangci]** <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>rows.Err must be checked (rowserrcheck)
<!-- __reviewdog__:ChBmMDE0NTkyNjM3MzQxNmY5Eghnb2xhbmdjaQ== -->

created_at: 2025-03-30T06:18:18Z
author_association: NONE
diff_hunk: @@ -188,3 +189,108 @@ func (e *statementExecutor) ExecuteStatement(ctx context.Context, sql *model.SQL
 	}
 	return result.RowsAffected()
 }
+
+type ddlGetter struct {
+	db       *sql.DB
+	dbmsType config.DBMSType
+	database string
+}
+
+// NewTableDDLGetter returns a new TableDDLGetter.
+func NewTableDDLGetter(
+	db *sql.DB,
+	database string,
+	dbmsType config.DBMSType,
+) repository.TableDDLGetter {
+	return &ddlGetter{
+		db:       db,
+		database: database,
+		dbmsType: dbmsType,
+	}
+}
+
+// GetTableDDL retrieves the DDL info of the given table as a Table struct.
+func (d *ddlGetter) GetTableDDL(ctx context.Context, tableName string) ([]*model.Table, error) {
+	var query string
+	var rows *sql.Rows
+	var err error
+
+	switch d.dbmsType {
+	case config.MySQL:
+		query = `
+            SELECT COLUMN_NAME, DATA_TYPE, IFNULL(CHARACTER_MAXIMUM_LENGTH, 0),
+                   IS_NULLABLE, IFNULL(COLUMN_DEFAULT, ''), COLUMN_KEY
+            FROM information_schema.columns
+            WHERE table_schema=? AND table_name=?`
+		rows, err = d.db.QueryContext(ctx, query, d.database, tableName)
+	case config.PostgreSQL:
+		query = `
+            SELECT column_name, data_type, COALESCE(character_maximum_length, 0),
+                   is_nullable, COALESCE(column_default, ''), ''
+            FROM information_schema.columns
+            WHERE table_schema='public' AND table_name=$1::text`
+		rows, err = d.db.QueryContext(ctx, query, tableName)
+	case config.SQLite3:
+		// PRAGMA table_info returns: cid, name, type, notnull, dflt_value, pk
+		query = "PRAGMA table_info(" + tableName + ")"
+		rows, err = d.db.QueryContext(ctx, query)
+	case config.SQLServer:
+		query = `
+            SELECT COLUMN_NAME, DATA_TYPE, ISNULL(CHARACTER_MAXIMUM_LENGTH, 0),
+                   IS_NULLABLE, ISNULL(COLUMN_DEFAULT, ''), '' as column_key
+            FROM INFORMATION_SCHEMA.COLUMNS 
+            WHERE TABLE_CATALOG=? AND TABLE_NAME=?`
+		rows, err = d.db.QueryContext(ctx, query, d.database, tableName)
path: infrastructure/persistence/dbms.go
user: login: github-actions[bot]
body: 🚫 **[golangci]** <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>rows.Err must be checked (rowserrcheck)
<!-- __reviewdog__:ChBiMGYwMWU4NzQ2NGFhZjRiEghnb2xhbmdjaQ== -->

created_at: 2025-03-30T06:18:18Z
author_association: NONE
diff_hunk: @@ -71,6 +71,10 @@ To search for a table name, press the `/` key at the sidebar. The sqluv will dis
 
 ![sql_query](doc/image/search_tables.png)
 
+To show/hide columns, press the `Space` key at the sidebar. If you press the `Space` key at the sidebar, the sqluv display the table ddl.
path: README.md
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Inconsistency in key binding description**

The description suggests that pressing `Space` key at the sidebar will display the table DDL, but according to the key bindings section (lines 127-129), the `Enter` key is used to show the table DDL, while `Space` is for showing/hiding columns.


Apply this fix to correct the inconsistency:

```diff
-To show/hide columns, press the `Space` key at the sidebar. If you press the `Space` key at the sidebar, the sqluv display the table ddl.
+To show/hide columns, press the `Space` key at the sidebar. To display the table DDL, press the `Enter` key at the sidebar.
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
To show/hide columns, press the `Space` key at the sidebar. To display the table DDL, press the `Enter` key at the sidebar.
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-30T06:21:45Z
author_association: NONE
diff_hunk: @@ -188,3 +189,108 @@ func (e *statementExecutor) ExecuteStatement(ctx context.Context, sql *model.SQL
 	}
 	return result.RowsAffected()
 }
+
+type ddlGetter struct {
+	db       *sql.DB
+	dbmsType config.DBMSType
+	database string
+}
+
+// NewTableDDLGetter returns a new TableDDLGetter.
+func NewTableDDLGetter(
+	db *sql.DB,
+	database string,
+	dbmsType config.DBMSType,
+) repository.TableDDLGetter {
+	return &ddlGetter{
+		db:       db,
+		database: database,
+		dbmsType: dbmsType,
+	}
+}
+
+// GetTableDDL retrieves the DDL info of the given table as a Table struct.
+func (d *ddlGetter) GetTableDDL(ctx context.Context, tableName string) ([]*model.Table, error) {
+	var query string
+	var rows *sql.Rows
+	var err error
+
+	switch d.dbmsType {
+	case config.MySQL:
+		query = `
+            SELECT COLUMN_NAME, DATA_TYPE, IFNULL(CHARACTER_MAXIMUM_LENGTH, 0),
+                   IS_NULLABLE, IFNULL(COLUMN_DEFAULT, ''), COLUMN_KEY
+            FROM information_schema.columns
+            WHERE table_schema=? AND table_name=?`
+		rows, err = d.db.QueryContext(ctx, query, d.database, tableName)
+	case config.PostgreSQL:
+		query = `
+            SELECT column_name, data_type, COALESCE(character_maximum_length, 0),
+                   is_nullable, COALESCE(column_default, ''), ''
+            FROM information_schema.columns
+            WHERE table_schema='public' AND table_name=$1::text`
+		rows, err = d.db.QueryContext(ctx, query, tableName)
+	case config.SQLite3:
+		// PRAGMA table_info returns: cid, name, type, notnull, dflt_value, pk
+		query = "PRAGMA table_info(" + tableName + ")"
+		rows, err = d.db.QueryContext(ctx, query)
+	case config.SQLServer:
+		query = `
+            SELECT COLUMN_NAME, DATA_TYPE, ISNULL(CHARACTER_MAXIMUM_LENGTH, 0),
+                   IS_NULLABLE, ISNULL(COLUMN_DEFAULT, ''), '' as column_key
+            FROM INFORMATION_SCHEMA.COLUMNS 
+            WHERE TABLE_CATALOG=? AND TABLE_NAME=?`
+		rows, err = d.db.QueryContext(ctx, query, d.database, tableName)
+	default:
+		return nil, fmt.Errorf("unsupported DBMS type: %v", d.dbmsType)
+	}
+
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+
+	// Define header for DDL info using the model.Header (CSV/TSV table header)
+	header := model.NewHeader([]string{"Column Name", "Type", "Precision", "Nullable", "DefaultValue", "PrimaryKey"})
+	var records []model.Record
+
+	for rows.Next() {
+		switch d.dbmsType {
+		case config.SQLite3:
+			var cid int
+			var colName, colType string
+			var notnull, pk int
+			var dflt sql.NullString
+			if err := rows.Scan(&cid, &colName, &colType, &notnull, &dflt, &pk); err != nil {
+				return nil, err
+			}
+			dfltValue := ""
+			if dflt.Valid {
+				dfltValue = dflt.String
+			}
+			nullable := "YES"
+			if notnull != 0 {
+				nullable = "NO"
+			}
+			key := ""
+			if pk > 0 {
+				key = "PRI"
+			}
+			records = append(records, model.NewRecord([]string{
+				colName, colType, "0", nullable, dfltValue, key,
+			}))
+		default:
+			var colName, dataType, nullable, dfltValue, columnKey string
+			var precision int
+			if err := rows.Scan(&colName, &dataType, &precision, &nullable, &dfltValue, &columnKey); err != nil {
+				return nil, err
+			}
+			records = append(records, model.NewRecord([]string{
+				colName, dataType, strconv.Itoa(precision), nullable, dfltValue, columnKey,
+			}))
+		}
+	}
+
+	ddlTable := model.NewTable(tableName, header, records)
+	return []*model.Table{ddlTable}, nil
+}
path: infrastructure/persistence/dbms.go
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Add missing rows.Err() check after iteration**

The code should check for errors that might occur during row iteration using rows.Err().

```diff
 for rows.Next() {
     // existing code...
 }
+if err = rows.Err(); err != nil {
+    return nil, err
+}

 ddlTable := model.NewTable(tableName, header, records)
 return []*model.Table{ddlTable}, nil
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	// Define header for DDL info using the model.Header (CSV/TSV table header)
	header := model.NewHeader([]string{"Column Name", "Type", "Precision", "Nullable", "DefaultValue", "PrimaryKey"})
	var records []model.Record

	for rows.Next() {
		switch d.dbmsType {
		case config.SQLite3:
			var cid int
			var colName, colType string
			var notnull, pk int
			var dflt sql.NullString
			if err := rows.Scan(&cid, &colName, &colType, &notnull, &dflt, &pk); err != nil {
				return nil, err
			}
			dfltValue := ""
			if dflt.Valid {
				dfltValue = dflt.String
			}
			nullable := "YES"
			if notnull != 0 {
				nullable = "NO"
			}
			key := ""
			if pk > 0 {
				key = "PRI"
			}
			records = append(records, model.NewRecord([]string{
				colName, colType, "0", nullable, dfltValue, key,
			}))
		default:
			var colName, dataType, nullable, dfltValue, columnKey string
			var precision int
			if err := rows.Scan(&colName, &dataType, &precision, &nullable, &dfltValue, &columnKey); err != nil {
				return nil, err
			}
			records = append(records, model.NewRecord([]string{
				colName, dataType, strconv.Itoa(precision), nullable, dfltValue, columnKey,
			}))
		}
	}
	if err = rows.Err(); err != nil {
		return nil, err
	}

	ddlTable := model.NewTable(tableName, header, records)
	return []*model.Table{ddlTable}, nil
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-30T06:21:46Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-30T06:17:06Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
The changes add a new documentation section outlining the project's Clean Architecture dependencies and update key binding instructions. New interfaces and implementations for retrieving table DDL information have been introduced across the repository. This functionality is integrated into the TUI where key bindings allow users to query and display schema details. Mocks, dependency injection providers, and persistence methods have been updated accordingly to support multiple DBMS types (MySQL, PostgreSQL, SQLite3, SQL Server).

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `.github/copilot-instructions*.md`, `README.md` | Added new sections documenting the Clean Architecture setup, supported RDBMSs, and updated TUI key binding instructions (e.g., for toggling columns and showing DDL). |
| `domain/repository/persistence.go` | Introduced the new `TableDDLGetter` interface with the `GetTableDDL` method for retrieving table DDL information. |
| `infrastructure/mock/persistence.go`, `interactor/mock/persistence.go` | Added mock implementations for `TableDDLGetter`, including recorder types and helper methods to simulate DDL retrieval in tests. |
| `infrastructure/persistence/dbms.go`, `infrastructure/persistence/wire.go` | Created the `ddlGetter` type with a constructor (`NewTableDDLGetter`) and methods to fetch DDL based on DBMS type; registered this provider via Wire DI. |
| `interactor/persistence.go`, `interactor/wire.go` | Added the `tableDDLGetter` struct implementing the usecase interface with a corresponding constructor and DI registration. |
| `tui/footer.go`, `tui/tui.go` | Integrated new key bindings in the TUI for showing/hiding columns and displaying table DDL; updated the control flow to fetch and display DDL on key events. |
| `usecase/persistence.go` | Added the new `TableDDLGetter` interface to the usecase layer for DDL retrieval. |

## Sequence Diagram(s)
```mermaid
sequenceDiagram
    participant U as User
    participant T as TUI
    participant I as Interactor (tableDDLGetter)
    participant R as Repository (ddlGetter)

    U->>T: Press "Enter" on sidebar
    T->>I: Call GetTableDDL(ctx, tableName)
    I->>R: Execute GetTableDDL(ctx, tableName)
    R-->>I: Return DDL info or error
    I-->>T: Return result
    T->>U: Display DDL information / error message
```

## Possibly related PRs
- **nao1215/sqluv#47**: Introduces similar documentation and architectural enhancements focusing on Clean Architecture and key binding updates.
- **nao1215/sqluv#49**: Focuses on modifying keyboard shortcuts in the TUI’s footer, directly impacting user interactions.
- **nao1215/sqluv#53**: Adds new key bindings for sidebar interactions, specifically for displaying table DDL and managing search functionality.

## Poem
> I hopped through lines of intricate code,  
> New key dances in my nightly mode,  
> DDL secrets now bloom on every key,  
> Clean architecture makes my spirit free,  
> In SQL fields, my rabbit heart glows—hip, hip, hooray! 🥕🐇

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=nao1215/sqluv&utm_content=57):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-30T06:17:29Z
body: ## Code Metrics Report
|                         | [main](https://github.com/nao1215/sqluv/tree/main) ([92e2c87](https://github.com/nao1215/sqluv/commit/92e2c8771ba0fcbc75779a927622a59479059112)) | [#57](https://github.com/nao1215/sqluv/pull/57) ([9567b76](https://github.com/nao1215/sqluv/commit/9567b76eb0ce1304ad9a484d96f59d4d505ede53)) |  +/-  |
|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------:|----------------------------------------------------------------------------------------------------------------------------------------------:|------:|
| **Coverage**            |                                                                                                                                            34.7% |                                                                                                                                         33.2% | -1.6% |
| **Test Execution Time** |                                                                                                                                               2s |                                                                                                                                            3s |   +1s |

<details>

<summary>Details</summary>

``` diff
  |                     | main (92e2c87) | #57 (9567b76) |  +/-  |
  |---------------------|----------------|---------------|-------|
- | Coverage            |          34.7% |         33.2% | -1.6% |
  |   Files             |             27 |            27 |     0 |
  |   Lines             |            966 |          1012 |   +46 |
  |   Covered           |            336 |           336 |     0 |
- | Test Execution Time |             2s |            3s |   +1s |
```

</details>


### Code coverage of files in pull request scope (0.0% → 0.0%)

|                                                                            Files                                                                            | Coverage | +/-  |
|-------------------------------------------------------------------------------------------------------------------------------------------------------------|---------:|-----:|
| [infrastructure/persistence/dbms.go](https://github.com/nao1215/sqluv/blob/33b06e3e76c05455019ff04471ecc2ccfd3103d0/infrastructure%2Fpersistence%2Fdbms.go) | 0.0%     | 0.0% |
| [interactor/persistence.go](https://github.com/nao1215/sqluv/blob/33b06e3e76c05455019ff04471ecc2ccfd3103d0/interactor%2Fpersistence.go)                     | 0.0%     | 0.0% |
| [usecase/persistence.go](https://github.com/nao1215/sqluv/blob/33b06e3e76c05455019ff04471ecc2ccfd3103d0/usecase%2Fpersistence.go)                           | 0.0%     | 0.0% |

---
Reported by [octocov](https://github.com/k1LoW/octocov)
<!-- octocov -->
author_association: NONE