Skip to content

Commit

Permalink
feat: add oracle driver to support oracle databases (#201)
Browse files Browse the repository at this point in the history
* feat(client): add oracle drive to support oracle databases

110

* feat(connection): build the connection string to oracle

* feat(client): add support for oracle from the client perspective

* refactor(oracle): add some changes to align with the oracle standard

* build(compose): add a oracle contianer to the compose file

* build(makefile): add a target command to connect with oracle

* feat(oracle): add parsing to build a url to connect with an oracle database

* docs(readme): add some docs on how to use dblab to connect with an oracle database

* feat(config): add support for the oracle driver to the config file feature

feat(config): add missing files

* docs(readme): add an example to describe how to add the parameters to connect with oracle through the config file

* feat(form): add oracle support to the form mini tui app
  • Loading branch information
danvergara authored Jun 5, 2024
1 parent be31a85 commit bc13da4
Show file tree
Hide file tree
Showing 19 changed files with 462 additions and 49 deletions.
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ run: build
run-mysql: build
./dblab --host localhost --user myuser --db mydb --pass 5@klkbN#ABC --ssl enable --port 3306 --driver mysql

.PHONY: run-oracle
## run-oracle: Runs the application making a connection to the Oreacle database
run-oracle: build
./dblab --host localhost --user system --db FREEPDB1 --pass password --port 1521 --driver oracle --limit 50

.PHONY: run-mysql-socket
## run-mysql-socket: Runs the application with a connection to mysql through a socket file. In this example the socke file is located in /var/lib/mysql/mysql.sock.
run-mysql-socket: build
Expand Down
51 changes: 35 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ dblab ![integration tests](https://github.com/danvergara/dblab/actions/workflows
<img style="float: right;" src="assets/gopher-dblab.png" alt="dblab logo"/ width=200>
</p>

__Interactive client for PostgreSQL, MySQL and SQLite3.__
__Interactive client for PostgreSQL, MySQL, SQLite3 and Oracle.__

<img src="screenshots/dblab-cover.png" />

Expand Down Expand Up @@ -37,7 +37,7 @@ dblab is a fast and lightweight interactive terminal based UI application for Po
written in Go and works on OSX, Linux and Windows machines. Main idea behind using Go for backend development
is to utilize ability of the compiler to produce zero-dependency binaries for
multiple platforms. dblab was created as an attempt to build very simple and portable
application to work with local or remote PostgreSQL/MySQL/SQLite3 databases.
application to work with local or remote PostgreSQL/MySQL/SQLite3/Oracle databases.

## Features

Expand Down Expand Up @@ -91,20 +91,27 @@ Available Commands:
version The version of the project
Flags:
--cfg-name string Database config name section
--config get the connection data from a config file (default is $HOME/.dblab.yaml or the current directory)
--db string Database name
--driver string Database driver
-h, --help help for dblab
--host string Server host name or IP
--limit int Size of the result set from the table content query (should be greater than zero, otherwise the app will error out) (default 100)
--pass string Password for user
--port string Server port
--schema string Database schema (postgres only)
--socket string Path to a Unix socket file
--ssl string SSL mode
-u, --url string Database connection string
--user string Database user
--cfg-name string Database config name section
--config Get the connection data from a config file (default locations are: current directory, $HOME/.dblab.yaml or $XDG_CONFIG_HOME/.dblab.yaml)
--db string Database name
--driver string Database driver
-h, --help help for dblab
--host string Server host name or IP
--limit uint Size of the result set from the table content query (should be greater than zero, otherwise the app will error out) (default 100)
--pass string Password for user
--port string Server port
--schema string Database schema (postgres only)
--socket string Path to a Unix socket file
--ssl string SSL mode
--ssl-verify string [enable|disable] or [true|false] enable ssl verify for the server
--sslcert string This parameter specifies the file name of the client SSL certificate, replacing the default ~/.postgresql/postgresql.crt
--sslkey string This parameter specifies the location for the secret key used for the client certificate. It can either specify a file name that will be used instead of the default ~/.postgresql/postgresql.key, or it can specify a key obtained from an external “engine”
--sslpassword string This parameter specifies the password for the secret key specified in sslkey
--sslrootcert string This parameter specifies the name of a file containing SSL certificate authority (CA) certificate(s) The default is ~/.postgresql/root.crt
--trace-file string File name for trace log
-u, --url string Database connection string
--user string Database user
--wallet string Path for auto-login oracle wallet
Use "dblab [command] --help" for more information about a command.
```
Expand All @@ -118,6 +125,7 @@ You can start the app passing no flags or parameters, you'll be asked for connec
```sh
$ dblab --host localhost --user myuser --db users --pass password --ssl disable --port 5432 --driver postgres --limit 50
$ dblab --db path/to/file.sqlite3 --driver sqlite
$ dblab --host localhost --user system --db FREEPDB1 --pass password --port 1521 --driver oracle --limit 50
```

Connection URL scheme is also supported:
Expand All @@ -126,6 +134,7 @@ Connection URL scheme is also supported:
$ dblab --url postgres://user:password@host:port/database?sslmode=[mode]
$ dblab --url mysql://user:password@tcp(host:port)/db
$ dblab --url file:test.db?cache=shared&mode=memory
$ dblab --url oracle://user:password@localhost:1521/db
```

if you're using PostgreSQL, you have the option to define the schema you want to work with, the default value is `public`.
Expand Down Expand Up @@ -204,6 +213,16 @@ database:
driver: "postgres"
ssl: "require"
sslrootcert: "~/.postgresql/root.crt."
- name: "oracle"
host: "localhost"
port: 1521
db: "FREEPDB1 "
password: "password"
user: "system"
driver: "oracle"
ssl: "enable"
wallet: "path/to/wallet"
ssl-verify: true
# should be greater than 0, otherwise the app will error out
limit: 50
```
Expand Down
25 changes: 20 additions & 5 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package cmd

import (
"github.com/danvergara/gocui"
"github.com/spf13/cobra"

"github.com/danvergara/dblab/pkg/app"
"github.com/danvergara/dblab/pkg/command"
"github.com/danvergara/dblab/pkg/config"
"github.com/danvergara/dblab/pkg/connection"
"github.com/danvergara/dblab/pkg/form"
"github.com/danvergara/gocui"
"github.com/spf13/cobra"
)

// Define all the global flags.
Expand All @@ -29,6 +30,10 @@ var (
sslkey string
sslpassword string
sslrootcert string
// oracle specific.
traceFile string
sslVerify string
wallet string
)

// NewRootCmd returns the root command.
Expand Down Expand Up @@ -63,6 +68,9 @@ func NewRootCmd() *cobra.Command {
SSLKey: sslkey,
SSLPassword: sslpassword,
SSLRootcert: sslrootcert,
SSLVerify: sslVerify,
TraceFile: traceFile,
Wallet: wallet,
}

if form.IsEmpty(opts) {
Expand Down Expand Up @@ -111,7 +119,8 @@ func init() {
// will be global for your application.

// config file flag.
rootCmd.PersistentFlags().BoolVarP(&cfg, "config", "", false, "Get the connection data from a config file (default locations are: current directory, $HOME/.dblab.yaml or $XDG_CONFIG_HOME/.dblab.yaml)")
rootCmd.PersistentFlags().
BoolVarP(&cfg, "config", "", false, "Get the connection data from a config file (default locations are: current directory, $HOME/.dblab.yaml or $XDG_CONFIG_HOME/.dblab.yaml)")
// cfg-name is used to indicate the name of the config section to be used to establish a
// connection with desired database.
// default: if empty, the first item of the databases options is gonna be selected.
Expand All @@ -127,7 +136,8 @@ func init() {
rootCmd.Flags().StringVarP(&db, "db", "", "", "Database name")
rootCmd.Flags().StringVarP(&schema, "schema", "", "", "Database schema (postgres only)")
rootCmd.Flags().StringVarP(&ssl, "ssl", "", "", "SSL mode")
rootCmd.Flags().UintVarP(&limit, "limit", "", 100, "Size of the result set from the table content query (should be greater than zero, otherwise the app will error out)")
rootCmd.Flags().
UintVarP(&limit, "limit", "", 100, "Size of the result set from the table content query (should be greater than zero, otherwise the app will error out)")
rootCmd.Flags().StringVarP(&socket, "socket", "", "", "Path to a Unix socket file")
rootCmd.Flags().StringVarP(
&sslcert,
Expand All @@ -143,12 +153,17 @@ func init() {
"",
"This parameter specifies the location for the secret key used for the client certificate. It can either specify a file name that will be used instead of the default ~/.postgresql/postgresql.key, or it can specify a key obtained from an external “engine”",
)
rootCmd.Flags().StringVarP(&sslpassword, "sslpassword", "", "", "This parameter specifies the password for the secret key specified in sslkey")
rootCmd.Flags().
StringVarP(&sslpassword, "sslpassword", "", "", "This parameter specifies the password for the secret key specified in sslkey")
rootCmd.Flags().StringVarP(
&sslrootcert,
"sslrootcert",
"",
"",
"This parameter specifies the name of a file containing SSL certificate authority (CA) certificate(s) The default is ~/.postgresql/root.crt",
)
rootCmd.Flags().
StringVarP(&sslVerify, "ssl-verify", "", "", "[enable|disable] or [true|false] enable ssl verify for the server")
rootCmd.Flags().StringVarP(&traceFile, "trace-file", "", "", "File name for trace log")
rootCmd.Flags().StringVarP(&wallet, "wallet", "", "", "Path for auto-login oracle wallet")
}
9 changes: 9 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ services:
networks:
- dblab

oracle:
image: container-registry.oracle.com/database/free:latest
environment:
ORACLE_PWD: password
ports:
- '1521:1521'
networks:
- dblab

dblab:
build:
context: .
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/lib/pq v1.10.6
github.com/muesli/termenv v0.12.0
github.com/olekukonko/tablewriter v0.0.5
github.com/sijms/go-ora/v2 v2.8.19
github.com/spf13/cobra v1.4.0
github.com/stretchr/testify v1.7.2
golang.org/x/text v0.3.7
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,8 @@ github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9Nz
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sijms/go-ora/v2 v2.8.19 h1:7LoKZatDYGi18mkpQTR/gQvG9yOdtc7hPAex96Bqisc=
github.com/sijms/go-ora/v2 v2.8.19/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
Expand Down
36 changes: 30 additions & 6 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
_ "github.com/sijms/go-ora/v2"
_ "modernc.org/sqlite"

"github.com/danvergara/dblab/pkg/command"
Expand Down Expand Up @@ -67,6 +68,8 @@ func New(opts command.Options) (*Client, error) {
c.databaseQuerier = newMySQL()
case drivers.SQLite:
c.databaseQuerier = newSQLite()
case drivers.Oracle:
c.databaseQuerier = newOracle()
default:
return nil, fmt.Errorf("%s driver not supported", c.driver)
}
Expand Down Expand Up @@ -320,10 +323,28 @@ func (c *Client) Driver() string {
func (c *Client) tableContent(tableName string) ([][]string, []string, error) {
var query string

if c.driver == "postgres" || c.driver == "postgresql" {
query = fmt.Sprintf("SELECT * FROM %q LIMIT %d OFFSET %d;", tableName, c.paginationManager.Limit(), c.paginationManager.Offset())
} else {
query = fmt.Sprintf("SELECT * FROM %s LIMIT %d OFFSET %d;", tableName, c.paginationManager.Limit(), c.paginationManager.Offset())
switch c.driver {
case drivers.Postgres, drivers.PostgreSQL:
query = fmt.Sprintf(
"SELECT * FROM %q LIMIT %d OFFSET %d;",
tableName,
c.paginationManager.Limit(),
c.paginationManager.Offset(),
)
case drivers.Oracle:
query = fmt.Sprintf(
"SELECT * FROM %s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY",
tableName,
c.paginationManager.Offset(),
c.paginationManager.Limit(),
)
default:
query = fmt.Sprintf(
"SELECT * FROM %s LIMIT %d OFFSET %d;",
tableName,
c.paginationManager.Limit(),
c.paginationManager.Offset(),
)
}

return c.Query(query)
Expand All @@ -336,9 +357,12 @@ func (c *Client) tableCount(tableName string) (int, error) {
count int
)

if c.driver == "postgres" || c.driver == "postgresql" {
switch c.driver {
case drivers.Postgres, drivers.PostgreSQL:
query = fmt.Sprintf("SELECT COUNT(*) FROM %q;", tableName)
} else {
case drivers.Oracle:
query = fmt.Sprintf("SELECT COUNT(*) FROM %s", tableName)
default:
query = fmt.Sprintf("SELECT COUNT(*) FROM %s;", tableName)
}

Expand Down
75 changes: 75 additions & 0 deletions pkg/client/oracle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package client

import (
"strings"

sq "github.com/Masterminds/squirrel"
_ "github.com/sijms/go-ora/v2"
)

// oracle struct is in charge of perform all the oracle related queries.
type oracle struct{}

// a validation to see if oracle is implementing databaseQuerier.
var _ databaseQuerier = (*oracle)(nil)

// returns a pointer to a oracle struct, it receives an schema as a parameter.
func newOracle() *oracle {
o := oracle{}

return &o
}

// ShowTables returns a query to retrieve all the tables.
func (p *oracle) ShowTables() (string, []interface{}, error) {
query := "SELECT table_name FROM user_tables"
return query, nil, nil
}

// TableStructure returns a query string to get all the relevant information of a given table.
func (p *oracle) TableStructure(tableName string) (string, []interface{}, error) {
query := sq.Select("*").
From("user_tab_columns").
Where(sq.Eq{"table_name": strings.ToUpper(tableName)}).
OrderBy("column_id").
PlaceholderFormat(sq.Colon)

sql, args, err := query.ToSql()
if err != nil {
return "", nil, err
}

return sql, args, nil
}

// Constraints returns all the constraints of a given table.
func (p *oracle) Constraints(tableName string) (string, []interface{}, error) {
query := sq.Select(
`constraint_name`,
`constraint_type`,
).
From("user_constraints").
Where(sq.Eq{"table_name": tableName}).
PlaceholderFormat(sq.Colon)

sql, args, err := query.ToSql()
if err != nil {
return "", nil, err
}

return sql, args, err
}

// Indexes returns the indexes of a table.
func (p *oracle) Indexes(tableName string) (string, []interface{}, error) {
sql, args, err := sq.Select("*").
From("all_indexes").
Where(sq.Eq{"table_name": tableName}).
PlaceholderFormat(sq.Colon).
ToSql()
if err != nil {
return "", nil, err
}

return sql, args, err
}
4 changes: 4 additions & 0 deletions pkg/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ type Options struct {
SSLKey string
SSLPassword string
SSLRootcert string
// oracle specific.
TraceFile string
SSLVerify string
Wallet string
}

// SetDefault returns a Options struct and fills the empty
Expand Down
11 changes: 11 additions & 0 deletions pkg/config/.dblab.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,15 @@ database:
driver: "postgres"
ssl: "require"
sslrootcert: "~/.postgresql/root.crt."
- name: "oracle"
host: "localhost"
port: 1521
db: "FREEPDB1 "
password: "password"
user: "system"
driver: "oracle"
ssl: "enable"
wallet: "path/to/wallet"
ssl-verify: true
trace: "trace.log"
limit: 50
Loading

0 comments on commit bc13da4

Please sign in to comment.