Skip to content

Commit 0232cba

Browse files
Support for Oracle Database (#67)
1 parent 87bc3e0 commit 0232cba

File tree

7 files changed

+81
-7
lines changed

7 files changed

+81
-7
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
## 🚀 Features
66

7-
- **Multi-Database Support**: Works with MySQL, PostgreSQL, MariaDB, SQLite, MongoDB, MSSQL and Firebase.
7+
- **Multi-Database Support**: Works with MySQL, PostgreSQL, MariaDB, SQLite, MongoDB, MSSQL, OracleDB, and Firebase.
88
- **Quick Data Inspection**: View all tables or a specific table with a simple command.
99
- **User-Friendly CLI**: Easy-to-use command-line interface powered by Click.
1010
- **Secure Local Storage**: Securely store database connection details with encryption on your local machine.

peepdb/cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def cli():
2929
peepDB: A quick database table viewer.
3030
3131
This tool allows you to quickly inspect database tables without writing SQL queries.
32-
It supports MySQL, PostgreSQL, MariaDB, SQLite, MongoDB, Firebase, and MSSQL.
32+
It supports MySQL, PostgreSQL, MariaDB, SQLite, MongoDB, Firebase, MSSQL, and OracleDB.
3333
3434
Usage examples:
3535
@@ -53,7 +53,7 @@ def cli():
5353
@cli.command()
5454
@click.argument('connection_name')
5555
@click.option('--db-type',
56-
type=click.Choice(['mysql', 'postgres', 'mariadb', 'sqlite', 'mongodb', 'firebase', 'mssql']),
56+
type=click.Choice(['mysql', 'postgres', 'mariadb', 'sqlite', 'mongodb', 'firebase', 'mssql', 'oracle']),
5757
required=True,
5858
help='Database type')
5959
@click.option('--host', required=True, help='Database host or file path for SQLite')

peepdb/core.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
MongoDBDatabase,
1212
SQLiteDatabase,
1313
FirebaseDatabase,
14-
MSSQLDatabase
14+
MSSQLDatabase,
15+
OracleDatabase
1516
)
1617

1718
logger = logging.getLogger(__name__)
@@ -45,6 +46,9 @@ def connect_to_database(db_type: str, host: str, user: str, password: str, datab
4546
return FirebaseDatabase(host, **kwargs)
4647
elif db_type == 'mssql':
4748
return MSSQLDatabase(host, user, password, database, **kwargs)
49+
elif db_type == 'oracle':
50+
kwargs.pop('trusted', None)
51+
return OracleDatabase(host, user, password, database, **kwargs)
4852
else:
4953
raise ValueError("Unsupported database type")
5054

peepdb/db/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .sqlite import SQLiteDatabase
77
from .firebase import FirebaseDatabase
88
from .mssql import MSSQLDatabase
9+
from .oracle import OracleDatabase
910

1011
__all__ = [
1112
'BaseDatabase',
@@ -15,5 +16,6 @@
1516
'MongoDBDatabase',
1617
'SQLiteDatabase',
1718
'FirebaseDatabase',
18-
'MSSQLDatabase'
19+
'MSSQLDatabase',
20+
'OracleDatabase'
1921
]

peepdb/db/oracle.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import oracledb
2+
from typing import List, Dict, Any
3+
from .base import BaseDatabase
4+
import logging
5+
6+
7+
class OracleDatabase(BaseDatabase):
8+
def connect(self) -> None:
9+
try:
10+
self.connection = oracledb.connect(
11+
host=self.host,
12+
user=self.user,
13+
password=self.password,
14+
service_name=self.database,
15+
port=self.port or 1521,
16+
**self.extra_params,
17+
)
18+
self.cursor = self.connection.cursor()
19+
self.logger.info(f"Connected to Oracle database: {self.database}")
20+
21+
except oracledb.Error as e:
22+
self.logger.error(f"Error connecting to Oracle database: {e}")
23+
raise
24+
25+
def disconnect(self) -> None:
26+
if self.cursor:
27+
self.cursor.close()
28+
if self.connection:
29+
self.connection.close()
30+
self.logger.info(f"Disconnected from Oracle database: {self.database}")
31+
32+
def fetch_tables(self) -> List[str]:
33+
self.cursor.execute("SELECT table_name FROM user_tables")
34+
return [table[0] for table in self.cursor.fetchall()]
35+
36+
def fetch_data(
37+
self, table: str, page: int = 1, page_size: int = 100
38+
) -> Dict[str, Any]:
39+
offset = (page - 1) * page_size
40+
self.cursor.execute(f"SELECT COUNT(*) as total FROM {table}")
41+
total_rows = self.cursor.fetchone()[0]
42+
43+
self.cursor.execute(
44+
f"SELECT * FROM {table} OFFSET {offset} ROWS FETCH NEXT {page_size} ROWS ONLY"
45+
)
46+
rows = self.cursor.fetchall()
47+
48+
column_names = [description[0] for description in self.cursor.description]
49+
50+
formatted_rows = [dict(zip(column_names, row)) for row in rows]
51+
52+
return {
53+
"data": formatted_rows,
54+
"page": page,
55+
"total_pages": (total_rows + page_size - 1) // page_size,
56+
"total_rows": total_rows,
57+
}

peepdb/tests/test_peepdb.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
from decimal import Decimal
44
from peepdb.core import peep_db
55
from peepdb.config import save_connection, get_connection, list_connections, remove_connection, remove_all_connections
6-
from peepdb.db import MySQLDatabase, PostgreSQLDatabase, MariaDBDatabase, MongoDBDatabase, FirebaseDatabase
6+
from peepdb.db import MySQLDatabase, PostgreSQLDatabase, MariaDBDatabase, MongoDBDatabase, FirebaseDatabase, OracleDatabase
77

88

99
@patch('peepdb.core.FirebaseDatabase')
1010
@patch('peepdb.core.MongoDBDatabase')
1111
@patch('peepdb.core.MySQLDatabase')
1212
@patch('peepdb.core.PostgreSQLDatabase')
1313
@patch('peepdb.core.MariaDBDatabase')
14-
def test_peep_db(mock_mariadb, mock_postgresql, mock_mysql, mock_mongodb, mock_firebase_db):
14+
@patch('peepdb.core.OracleDatabase')
15+
def test_peep_db(mock_mariadb, mock_postgresql, mock_mysql, mock_mongodb, mock_firebase_db, mock_oracle_db):
1516
# Create a mock database object
1617
mock_db = Mock()
1718
mock_db.fetch_tables.return_value = ['table1', 'table2']
@@ -31,6 +32,7 @@ def test_peep_db(mock_mariadb, mock_postgresql, mock_mysql, mock_mongodb, mock_f
3132
mock_mariadb.return_value = mock_db
3233
mock_mongodb.return_value = mock_db
3334
mock_firebase_db.return_value = mock_db
35+
mock_oracle_db.return_value = mock_db
3436

3537
# Expected result for non-scientific, JSON format
3638
expected_result = {
@@ -81,6 +83,10 @@ def test_peep_db(mock_mariadb, mock_postgresql, mock_mysql, mock_mongodb, mock_f
8183
result = peep_db('mongodb', 'host', 'user', 'password', 'database', format='json', scientific=False)
8284
assert result == expected_result
8385

86+
# Test OracleDB without scientific notation
87+
result = peep_db('oracle', 'host', 'user', 'password', 'database', format='json', scientific=False)
88+
assert result == expected_result
89+
8490
# Test Firebase without scientific notation
8591
result = peep_db('firebase', 'path/to/serviceAccountKey.json', '', '', '', format='json', scientific=False)
8692
assert result == expected_result
@@ -105,6 +111,10 @@ def test_peep_db(mock_mariadb, mock_postgresql, mock_mysql, mock_mongodb, mock_f
105111
result = peep_db('mongodb', 'host', 'user', 'password', 'database', format='json', scientific=True)
106112
assert result == expected_result
107113

114+
# Test OracleDB with scientific notation
115+
result = peep_db('oracle', 'host', 'user', 'password', 'database', format='json', scientific=True)
116+
assert result == expected_result
117+
108118
# Test fetching a specific table/collection for Firebase
109119
result = peep_db('firebase', 'path/to/serviceAccountKey.json', '', '', '', table='table1', format='json', scientific=False)
110120
assert result == {'table1': expected_result['table1']}

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ mariadb==1.1.10
3535
more-itertools==10.5.0
3636
msgpack==1.1.0
3737
mysql-connector-python==9.0.0
38+
oracledb==2.5.1
3839
packaging==24.1
3940
-e git+https://github.com/evangelosmeklis/peepDB.git@4db25b049fce0058fa21e8f95b4b8840a9f3261e#egg=peepdb
4041
pluggy==1.5.0

0 commit comments

Comments
 (0)