Skip to content

Commit f7f411b

Browse files
committed
refactor: refactor project flow and structure
1 parent d512484 commit f7f411b

13 files changed

Lines changed: 506 additions & 87 deletions

File tree

internal/cli/root.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/balaji01-4d/pgxcli/internal/config"
99
"github.com/balaji01-4d/pgxcli/internal/database"
1010
"github.com/balaji01-4d/pgxcli/internal/logger"
11+
"github.com/balaji01-4d/pgxcli/internal/repl"
1112

1213
"github.com/fatih/color"
1314
"github.com/spf13/cobra"
@@ -79,23 +80,23 @@ var rootCmd = &cobra.Command{
7980
ctx := context.Background()
8081

8182
postgres := database.New(neverPrompt, forcePrompt, ctx, cfg)
82-
defer postgres.Close()
83+
defer postgres.Close(ctx)
8384

8485
if strings.Contains(finalDB, "://") {
85-
err := postgres.ConnectURI(finalDB)
86+
err := postgres.ConnectURI(ctx, finalDB)
8687
if err != nil {
8788
printErr(os.Stderr, "%v\n", err)
8889
os.Exit(1)
8990
}
9091
} else if strings.Contains(finalDB, "=") {
91-
err := postgres.ConnectDSN(finalDB)
92+
err := postgres.ConnectDSN(ctx, finalDB)
9293
if err != nil {
9394
printErr(os.Stderr, "%v\n", err)
9495
os.Exit(1)
9596
}
9697
} else {
9798
logger.Log.Info("Connecting to database", "host", host, "port", port, "database", finalDB, "user", finalUser)
98-
err := postgres.Connect(host, finalUser, "", finalDB, "", port)
99+
err := postgres.Connect(ctx, host, finalUser, "", finalDB, "", port)
99100
if err != nil {
100101
logger.Log.Error("Connection failed", "error", err, "host", host, "database", finalDB)
101102
printErr(os.Stderr, "%v\n", err)
@@ -106,8 +107,10 @@ var rootCmd = &cobra.Command{
106107
printErr(os.Stderr, "Not connected to any database\n")
107108
os.Exit(1)
108109
}
109-
postgres.RunCli()
110-
postgres.Close()
110+
111+
repl := repl.New(postgres, cfg)
112+
repl.Run(ctx)
113+
repl.Close()
111114
},
112115
}
113116

internal/config/config.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,25 @@ import (
77
"github.com/BurntSushi/toml"
88
)
99

10-
const filename = "config.toml"
10+
const (
11+
Default = "default"
12+
filename = "config.toml"
13+
)
1114

1215
type Config struct {
13-
Prompt string `toml:"prompt"`
16+
Main main `toml:"main"`
17+
}
18+
19+
type main struct {
20+
Prompt string `toml:"prompt"`
21+
HistoryFile string `toml:"history_file"`
1422
}
1523

1624
var DefaultConfig = Config{
17-
Prompt: `\u@\h:\d> `,
25+
Main: main{
26+
Prompt: `\u@\h:\d> `,
27+
HistoryFile: "default",
28+
},
1829
}
1930

2031
func GetConfigDir() (string, error) {

internal/config/config.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
[main]
12
# Postgres prompt
23
# \t - Current date and time
34
# \u - Username
@@ -7,3 +8,7 @@
78
# \p - Database port
89
# \n - Newline
910
prompt = '\u@\h:\d> '
11+
12+
13+
# history
14+
history_file = default

internal/config/config_test.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
package config_test
1+
package config
22

33
import (
44
"os"
55
path "path/filepath"
66
"testing"
77

8-
"github.com/balaji01-4d/pgxcli/internal/config"
9-
108
"github.com/stretchr/testify/assert"
119
)
1210

@@ -18,9 +16,9 @@ func TestLoadConfig_ValidConfig(t *testing.T) {
1816
err := os.WriteFile(configPath, []byte(configContent), 0644)
1917
assert.NoError(t, err)
2018

21-
cfg, err := config.LoadConfig(configPath)
19+
cfg, err := LoadConfig(configPath)
2220
assert.NoError(t, err)
23-
assert.Equal(t, "\\u@\\h:\\d> ", cfg.Prompt)
21+
assert.Equal(t, "\\u@\\h:\\d> ", cfg.Main.Prompt)
2422
}
2523

2624
func TestLoadConfig_InvalidConfig(t *testing.T) {
@@ -31,26 +29,29 @@ func TestLoadConfig_InvalidConfig(t *testing.T) {
3129
err := os.WriteFile(configPath, []byte(invalidContent), 0644)
3230
assert.NoError(t, err)
3331

34-
_, err = config.LoadConfig(configPath)
32+
_, err = LoadConfig(configPath)
3533
assert.Error(t, err)
3634
}
3735

3836
func TestLoadConfig_MissingFile(t *testing.T) {
39-
_, err := config.LoadConfig("non_existent_config.toml")
37+
_, err := LoadConfig("non_existent_config.toml")
4038
assert.Error(t, err)
4139
}
4240

4341
func TestSaveConfig(t *testing.T) {
4442
tempDir := t.TempDir()
4543
configPath := path.Join(tempDir, "config.toml")
4644

47-
cfg := config.Config{
48-
Prompt: "\\u@\\h:\\d> ",
45+
cfg := Config{
46+
Main: main{
47+
Prompt: "\\u@\\h:\\d> ",
48+
HistoryFile: "default",
49+
},
4950
}
5051

51-
err := config.SaveConfig(configPath, cfg)
52+
err := SaveConfig(configPath, cfg)
5253
assert.NoError(t, err)
53-
loadedCfg, err := config.LoadConfig(configPath)
54+
loadedCfg, err := LoadConfig(configPath)
5455
assert.NoError(t, err)
55-
assert.Equal(t, cfg.Prompt, loadedCfg.Prompt)
56+
assert.Equal(t, cfg.Main.Prompt, loadedCfg.Main.Prompt)
5657
}

internal/database/client.go

Lines changed: 95 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,22 @@ import (
44
"context"
55
"fmt"
66
"os"
7+
osUser "os/user"
78
"strings"
89
"time"
910

1011
"github.com/balaji01-4d/pgxcli/internal/config"
1112
"github.com/balaji01-4d/pgxcli/internal/logger"
12-
13-
osUser "os/user"
13+
"github.com/balaji01-4d/pgxspecial"
1414

1515
"github.com/jackc/pgx/v5"
1616
)
1717

18-
const (
19-
DefaultPrompt = `\u@\h:\d> `
20-
MaxLenPrompt = 30
21-
)
22-
2318
type Client struct {
2419
CurrentDB string
2520
Executor *Executor
2621
ForcePasswordPrompt bool
2722
NeverPasswordPrompt bool
28-
ctx context.Context
29-
Config config.Config
3023

3124
now time.Time
3225
}
@@ -35,14 +28,12 @@ func New(neverPasswordPrompt, forcePasswordPrompt bool, ctx context.Context, cfg
3528
postgres := &Client{
3629
NeverPasswordPrompt: neverPasswordPrompt,
3730
ForcePasswordPrompt: forcePasswordPrompt,
38-
ctx: ctx,
39-
Config: cfg,
4031
now: time.Now(),
4132
}
4233
return postgres
4334
}
4435

45-
func (p *Client) Connect(host, user, password, database, dsn string, port uint16) error {
36+
func (c *Client) Connect(ctx context.Context, host, user, password, database, dsn string, port uint16) error {
4637
if user == "" {
4738
currentUser, err := osUser.Current()
4839
if err != nil {
@@ -55,11 +46,11 @@ func (p *Client) Connect(host, user, password, database, dsn string, port uint16
5546
database = user
5647
}
5748

58-
if p.NeverPasswordPrompt && password == "" {
49+
if c.NeverPasswordPrompt && password == "" {
5950
password = os.Getenv("PGPASSWORD")
6051
}
6152

62-
if p.ForcePasswordPrompt && password == "" {
53+
if c.ForcePasswordPrompt && password == "" {
6354
fmt.Print("Password: ")
6455
var pwd string
6556
fmt.Scanln(&pwd)
@@ -76,70 +67,129 @@ func (p *Client) Connect(host, user, password, database, dsn string, port uint16
7667
port = parsedDsn.Port
7768
}
7869

79-
exec, err := NewExecutor(host, database, user, password, port, dsn, p.ctx)
70+
exec, err := NewExecutor(host, database, user, password, port, dsn, ctx)
8071
if err != nil {
8172
return err
8273
}
83-
p.Executor = exec
84-
p.CurrentDB = database
74+
c.Executor = exec
75+
c.CurrentDB = database
8576
logger.Log.Info("Database connection established", "database", database, "user", user)
8677

8778
return nil
8879
}
8980

90-
func (p *Client) ConnectDSN(dsn string) error {
91-
return p.Connect("", "", "", "", dsn, 0)
81+
func (c *Client) ConnectDSN(ctx context.Context, dsn string) error {
82+
return c.Connect(ctx, "", "", "", "", dsn, 0)
9283
}
9384

94-
func (p *Client) ConnectURI(uri string) error {
85+
func (c *Client) ConnectURI(ctx context.Context, uri string) error {
9586
parsedURI, err := pgx.ParseConfig(uri)
9687
if err != nil {
9788
return fmt.Errorf("failed to parse URI: %w", err)
9889
}
99-
return p.Connect(parsedURI.Host, parsedURI.User, parsedURI.Password, parsedURI.Database, "", parsedURI.Port)
90+
return c.Connect(ctx, parsedURI.Host, parsedURI.User, parsedURI.Password, parsedURI.Database, "", parsedURI.Port)
10091
}
10192

102-
func (p *Client) Close() {
103-
if p.Executor != nil {
104-
p.Executor.Close(p.ctx)
105-
}
93+
func (c *Client) ExecuteSpecial(ctx context.Context,
94+
command string) (pgxspecial.SpecialCommandResult, bool, error) {
95+
return pgxspecial.ExecuteSpecialCommand(ctx, c.Executor.Conn, command)
10696
}
10797

108-
func (p *Client) IsConnected() bool {
109-
return p.Executor != nil && p.Executor.IsConnected()
98+
func (c *Client) ExecuteQuery(ctx context.Context, query string) (Result, error) {
99+
return c.Executor.Execute(ctx, query)
110100
}
111101

112-
func (p *Client) GetConnectionInfo() {
113-
logger.Log.Debug("Connection information",
114-
"connection string", p.Executor.Conn.Config().ConnString(),
115-
"host", p.Executor.Host,
116-
"Port", p.Executor.Port,
117-
"Database", p.Executor.Database,
118-
"User", p.Executor.User,
119-
"URI", p.Executor.URI,
120-
)
102+
func (c *Client) IsConnected() bool {
103+
return c.Executor != nil && c.Executor.IsConnected()
121104
}
122105

123-
func (p *Client) ChangeDatabase(dbName string) error {
124-
if !p.IsConnected() {
106+
func (c *Client) ChangeDatabase(ctx context.Context, dbName string) error {
107+
if !c.IsConnected() {
125108
return fmt.Errorf("not connected to any database")
126109
}
127110

128111
exec, err := NewExecutor(
129-
p.Executor.Host,
112+
c.Executor.Host,
130113
dbName,
131-
p.Executor.User,
132-
p.Executor.Password,
133-
p.Executor.Port,
114+
c.Executor.User,
115+
c.Executor.Password,
116+
c.Executor.Port,
134117
"",
135-
p.ctx,
118+
ctx,
136119
)
137120
if err != nil {
138121
return err
139122
}
140-
p.Executor = exec
141-
p.CurrentDB = dbName
123+
c.Executor = exec
124+
c.CurrentDB = dbName
142125
logger.Log.Info("Database changed", "database", dbName)
143126

144127
return nil
145128
}
129+
130+
func (c *Client) ParsePrompt(str string) string {
131+
str = strings.ReplaceAll(str, "\\t", c.now.Format("02/06/2006 15:04:05"))
132+
if c.Executor.User != "" {
133+
str = strings.ReplaceAll(str, "\\u", c.Executor.User)
134+
} else {
135+
str = strings.ReplaceAll(str, "\\u", "(nil)")
136+
}
137+
138+
if c.Executor.Host != "" {
139+
str = strings.ReplaceAll(str, "\\H", c.Executor.Host)
140+
str = strings.ReplaceAll(str, "\\h", func() string {
141+
return strings.Split(c.Executor.Host, ".")[0]
142+
}())
143+
} else {
144+
str = strings.ReplaceAll(str, "\\H", "(nil)")
145+
str = strings.ReplaceAll(str, "\\h", "(nil)")
146+
}
147+
148+
if c.CurrentDB != "" {
149+
str = strings.ReplaceAll(str, "\\d", c.CurrentDB)
150+
} else {
151+
str = strings.ReplaceAll(str, "\\d", "(nil)")
152+
}
153+
if c.Executor.Port != 0 {
154+
str = strings.ReplaceAll(str, "\\p", fmt.Sprintf("%d", c.Executor.Port))
155+
} else {
156+
str = strings.ReplaceAll(str, "\\p", "5432")
157+
}
158+
159+
str = strings.ReplaceAll(str, "\\n", "\n")
160+
161+
return str
162+
}
163+
164+
func (c *Client) GetUser() string {
165+
return c.Executor.User
166+
}
167+
168+
func (c *Client) GetDatabase() string {
169+
return c.Executor.Database
170+
}
171+
172+
func (c *Client) GetPort() uint16 {
173+
return c.Executor.Port
174+
}
175+
176+
func (c *Client) GetHost() string {
177+
return c.Executor.Host
178+
}
179+
180+
func (c *Client) GetConnectionInfo() {
181+
logger.Log.Debug("Connection information",
182+
"connection string", c.Executor.Conn.Config().ConnString(),
183+
"host", c.Executor.Host,
184+
"Port", c.Executor.Port,
185+
"Database", c.Executor.Database,
186+
"User", c.Executor.User,
187+
"URI", c.Executor.URI,
188+
)
189+
}
190+
191+
func (c *Client) Close(ctx context.Context) {
192+
if c.Executor != nil {
193+
c.Executor.Close(ctx)
194+
}
195+
}

internal/database/exec_result.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ type ExecResult struct {
1313
Duration time.Duration
1414
}
1515

16-
func (e *ExecResult) GetType() string {
17-
return "EXEC"
18-
}
16+
func (e *ExecResult) isResult() {}
1917

2018
func (e *ExecResult) Render() string {
2119
return fmt.Sprintf(

internal/database/executor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
)
1414

1515
type Result interface {
16-
GetType() string
16+
isResult()
1717
}
1818

1919
// executor struct to execute queries

0 commit comments

Comments
 (0)