diff --git a/README.md b/README.md
index 3bde08d..ea08d8a 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,410 @@
-# postgres-dynamic
-Postgres Dynamic Query
+# postgres-dynamic - Python-PostgreSQL Dynamic Query Builder
+
+Postgres dynamic is a simple query builder developed for internal usage. It currently supports select, insert, update, and delete statements.
+The purpose of this library is for better managament and maintenance of the code used in our environment.
+
+## Parameter Format:
+- connection_string: dict
+ ```
+ connection_string = {
+ 'PG_HOST': 'YOUR_CONNETION_HOST_ADDRESS',
+ 'PG_PORT': 'YOUR_CONNECTION_PORT',
+ 'PG_DATABASE': 'YOUR_CONNECTION_DATABASE_NAME',
+ 'PG_USER': 'YOUR_CONNECTION_USERNAME',
+ 'PG_PASSWORD': 'YOUR_CONNECTION_PASSWORD',
+ }
+ ```
+- connection_object: Callable
+ ```
+ connection_object = psycopg2.connect(host,port,database,user,password) #object created from psycopg2.connect()
+ ```
+- where: List(dict)
+ ```
+ where = [
+ {
+ 'column_name': 'some_column_name',
+ 'value': 'some_value', # can accept str, int, list, or tuple
+ 'operator': 'some_operator', # can be omitted (accepted operators are =, >, <, >=, <=, IN),
+ 'conjunction': 'some_conjunction', # can be omitted, used when you need to specify more than one conditions and will link with next index value (accepted conjunctions are AND, OR)
+ },
+ ],
+ ```
+
+- main_table: Union[dict, str]
+
+ For **select** query
+ ```
+ main_table = {
+ 'table': 'some_table_name',
+ 'alias': 'some_alias_for_table',
+ },
+ ```
+
+ For **transaction** query
+ ```
+ main_table = 'some_table_name'
+ ```
+
+- join_table: List(dict)
+ ```
+ join_table = [
+ {
+ 'table': 'some_table_name',
+ 'alias': 'some_alias_for_table',
+ 'join_method': 'join_method', # accepted join methods are (INNER, LEFT, RIGHT, FULL)
+ 'on': 'matching_column_on_both_table',
+ },
+ ]
+ ```
+
+- column_name: List(str)
+ ```
+ column_name = ['some_column_name', 'some_column_name', 'some_column_name',]
+ ```
+
+- column_and_value: dict
+ ```
+ column_and_value = {
+ 'some_column_name': 'some_value', # for multiple values just provide more key:value pair
+ }
+ ```
+
+- order: dict
+ ```
+ order = {
+ 'some_column_name': 'ASC', # accepted order values are (ASC, DESC), for multiple order conditions just provide more key:value pair
+ }
+ ```
+
+## Usage & Code Samples
+
+Example DB
+
+`table:` **employees**
+| id | first_name | last_name
+| --- | --- | ---
+| 1 | Alex | Garcia
+| 2 | Joe | Black
+| 3 | John | Doe
+| 4 | Barry | Allen
+| 5 | Charlie | Cox
+
+`table:` **salaries**
+| employee_id | salary
+| --- | ---
+| 1 | 120,000
+| 2 | 135,000
+| 3 | 150,000
+| 4 | 180,000
+| 5 | 120,000
+
+- SELECT
+
+ - Single Select
+ Single select always return a single value from the query, based on `fetchone` in psycopg2 and returning a dictionary with `{column_name: value}` of the tables.
+
+ Show more...
+
+ Parameters:
+ ```
+ connection_string #required
+ main_table #required
+ where #required
+ join_table #optional (if omitted it won't join to any table)
+ column_name #optional (if omitted it will select all columns on the provided table)
+ ```
+
+ Code samples:
+ ```
+ # without joining table
+
+ from postgres_dynamic import PGDGet
+ import asyncio
+
+ query_result = PGDGet.get_one(
+ connection_string={
+ 'PG_HOST': 'localhost',
+ 'PG_PORT': 5432,
+ 'PG_DATABASE': 'postgres',
+ 'PG_USER': 'postgres',
+ 'PG_PASSWORD': 'password'
+ },
+ main_table={'table': 'employees'},
+ where=[
+ {'column_name': 'id', 'value': '1'},
+ ],
+ column_name=['first_name']
+ )
+
+ result = asyncio.run(query_result)
+ print(result)
+
+ # {'first_name': 'Alex'}
+ ```
+
+ ```
+ # with join table salaries
+
+ query_result = PGDGet.get_one(
+ connection_string={
+ 'PG_HOST': 'localhost',
+ 'PG_PORT': 5432,
+ 'PG_DATABASE': 'postgres',
+ 'PG_USER': 'postgres',
+ 'PG_PASSWORD': 'password'
+ },
+ main_table={'table': 'employees', 'alias': 'emp'},
+ join_table=[
+ {'table': 'salaries', 'alias': 'sal', 'join_method': 'INNER', 'on': 'emp.id = sal.employee_id'}
+ ],
+ where=[
+ {'column_name': 'id', 'value': '1'},
+ ],
+ )
+
+ result = asyncio.run(query_result)
+ print(result)
+
+ # {'id': '1', 'first_name': 'Alex', 'last_name': 'Garcia', 'employee_id': '1', 'salary': 120000}
+ ```
+
+
+ - Multi Select
+ Multi select always return a dict with key `data`, based on `fetchall` in psycopg2 and returning a list of dictionary with `{column_name: value}` of the tables.
+ Parameters:
+
+ Show more...
+
+ ```
+ connection_string #required
+ main_table #required
+ where #optional (if omitted no condition will be passed)
+ join_table #optional (if omitted it won't join to any table)
+ column_name #optional (if omitted it will select all columns on the provided table)
+ order #optional (if omitted it won't sort the query)
+ limit #optional (if a limit count is given, no more than that many rows will be returned but possibly fewer, if the query itself yields fewer rows)
+ offset #optional (it used to skip that many rows before beginning to return rows)
+
+ notes:
+ - If both OFFSET and LIMIT appear, then OFFSET rows are skipped before starting to count the LIMIT rows that are returned
+ - When using LIMIT, it is important to use an ORDER BY clause that constrains the result rows into a unique order. Otherwise you will get an unpredictable subset of the query's rows.
+ - For paging, you can specify 0 or 1 for the starting point of the first page
+ ```
+
+ Code samples:
+ ```
+ from postgres_dynamic import PGDGet
+ import asyncio
+
+ query_result = PGDGet.get_all(
+ connection_string={
+ 'PG_HOST': 'localhost',
+ 'PG_PORT': 5432,
+ 'PG_DATABASE': 'postgres',
+ 'PG_USER': 'postgres',
+ 'PG_PASSWORD': 'password'
+ },
+ main_table={'table': 'employees'},
+ limit=3,
+ offset=2
+ )
+
+ result = asyncio.run(query_result)
+ print(result)
+
+ # {'data': [{'id': '4', 'first_name': 'Barry', 'last_name': 'Allen'}, {'id': '5', 'first_name': 'Charlie', 'last_name': 'Cox'}]}
+ ```
+
+
+
+
+- INSERT
+
+ - Insert Statement
+ Insert will not return anyting, and will not saved changes to the database unless you specify `commit=True` in the parameters.
+
+ Show more...
+
+ Parameters:
+ ```
+ connection_object #required
+ main_table #required
+ column_and_value #required
+ commit #optional (if omitted, default value will be False which will not saving any changes to database)
+ ```
+
+ Code samples:
+ ```
+ # with auto commit
+
+ from postgres_dynamic import PGDTransaction
+ import asyncio
+
+ connection_object = psycopg2.connect(database='postgres', host='localhost', port=5432, user='postgres', password='password')
+ query_result = PGDTransaction.insert(
+ connection_object=connection_object,
+ main_table='employees',
+ column_and_value={'id': 6, 'first_name': 'Harrison', 'last_name': 'Ford'},
+ commit=True
+ )
+
+ result = asyncio.run(query_result)
+ print(result)
+
+ # None
+ # will insert a new employee to the employees table
+ ```
+
+ ```
+ # without auto commit
+
+ connection_object = psycopg2.connect(database='postgres', host='localhost', port=5432, user='postgres', password='password')
+ query_result = PGDTransaction.insert(
+ connection_object=connection_object,
+ main_table='salaries',
+ column_and_value={'employee_id': 6, 'salary': 250000},
+ )
+
+ result = asyncio.run(query_result)
+ print(result)
+
+ # None
+ # will insert a new salary to the salaries table
+
+ # save changes to the database
+ connection_object.commit()
+
+ ```
+
+
+
+- UPDATE
+
+ - Update Statement
+ Update will not return anyting, and will not saved changes to the database unless you specify `commit=True` in the parameters.
+
+ Show more...
+
+ Parameters:
+ ```
+ connection_object #required
+ main_table #required
+ column_and_value #required
+ where #required
+ commit #optional (if omitted, default value will be False which will not saving any changes to database)
+ ```
+
+ Code samples:
+ ```
+ # with auto commit
+
+ from postgres_dynamic import PGDTransaction
+ import asyncio
+
+ connection_object = psycopg2.connect(database='postgres', host='localhost', port=5432, user='postgres', password='password')
+ query_result = PGDTransaction.update(
+ connection_object=connection_object,
+ main_table='employees',
+ column_and_value={'first_name': 'Tyler', 'last_name': 'Oakley'},
+ where=[
+ {'column_name': 'id', 'value': '6'},
+ ],
+ commit=True
+ )
+
+ result = asyncio.run(query_result)
+ print(result)
+
+ # None
+ # will update employee first_name and last_name with id 6
+ ```
+
+ ```
+ # without auto commit
+
+ connection_object = psycopg2.connect(database='postgres', host='localhost', port=5432, user='postgres', password='password')
+ query_result = PGDTransaction.update(
+ connection_object=connection_object,
+ main_table='salaries',
+ column_and_value={'salary': 450000},
+ where=[
+ {'column_name': 'employee_id', 'value': '6'},
+ ],
+ )
+
+ result = asyncio.run(query_result)
+ print(result)
+
+ # None
+ # will update the salary with employee_id 6
+
+ # save changes to the database
+ connection_object.commit()
+
+ ```
+
+
+
+
+- DELETE
+
+ - Delete Statement
+ Delete will not return anyting, and will not saved changes to the database unless you specify `commit=True` in the parameters.
+
+ Show more...
+
+ Parameters:
+ ```
+ connection_object #required
+ main_table #required
+ where #required
+ commit #optional (if omitted, default value will be False which will not saving any changes to database)
+ ```
+
+ Code samples:
+ ```
+ # with auto commit
+
+ from postgres_dynamic import PGDTransaction
+ import asyncio
+
+ connection_object = psycopg2.connect(database='postgres', host='localhost', port=5432, user='postgres', password='password')
+ query_result = PGDTransaction.delete(
+ connection_object=connection_object,
+ main_table='salaries',
+ where=[
+ {'column_name': 'employee_id', 'value': '6'},
+ ],
+ commit=True
+ )
+
+ result = asyncio.run(query_result)
+ print(result)
+
+ # None
+ # will delete salary data with employee_id 6
+ ```
+
+ ```
+ # without auto commit
+
+ connection_object = psycopg2.connect(database='postgres', host='localhost', port=5432, user='postgres', password='password')
+ query_result = PGDTransaction.delete(
+ connection_object=connection_object,
+ main_table='employees',
+ where=[
+ {'column_name': 'id', 'value': '6'},
+ ],
+ )
+
+ result = asyncio.run(query_result)
+ print(result)
+
+ # None
+ # will delete the employee with id 6
+
+ # save changes to the database
+ connection_object.commit()
+ ```
+
+
\ No newline at end of file
diff --git a/src/postgres_dynamic/pgd_connection.py b/src/postgres_dynamic/pgd_connection.py
index b10b2cf..fec3991 100644
--- a/src/postgres_dynamic/pgd_connection.py
+++ b/src/postgres_dynamic/pgd_connection.py
@@ -5,11 +5,11 @@ def __init__(self, connection_string: dict, query: str, values: tuple) -> None:
self.query = query
self.values = values
self.connection = psycopg2.connect(
- host = connection_string['pg_host'],
- port = connection_string['pg_port'],
- database = connection_string['pg_database'],
- user = connection_string['pg_user'],
- password = connection_string['pg_password']
+ host = connection_string['PG_HOST'],
+ port = connection_string['PG_PORT'],
+ database = connection_string['PG_DATABASE'],
+ user = connection_string['PG_USER'],
+ password = connection_string['PG_PASSWORD']
)
self.cursor = self.connection.cursor()
diff --git a/src/postgres_dynamic/pgd_get.py b/src/postgres_dynamic/pgd_get.py
index 1571ee6..284bcee 100644
--- a/src/postgres_dynamic/pgd_get.py
+++ b/src/postgres_dynamic/pgd_get.py
@@ -1,6 +1,7 @@
from typing import Awaitable, Optional
from datetime import datetime, timedelta
from postgres_dynamic.pgd_connection import PGDConnection
+
class PGDGet:
@classmethod
async def get_one(cls, connection_string: dict, main_table: dict, where: list, join_table:list = [], column_name: list = None) -> Awaitable[dict]:
@@ -10,14 +11,17 @@ async def get_one(cls, connection_string: dict, main_table: dict, where: list, j
query = """
SELECT {column_name} FROM {main_table} {main_table_alias} {join_table} WHERE {where_query}
"""
+
where_query=''
for w in where:
where_query += f' {w["column_name"]} {w["operator"]} %s' if w.get('operator') else f' {w["column_name"]} = %s'
where_query += f' {w["conjunction"]}' if w.get('conjunction') else ' '
+
join_query = ''
for i in join_table:
join_query += ' INNER JOIN' if i['join_method'] == 'INNER' else ' LEFT JOIN' if i['join_method'] == 'LEFT' else ' RIGHT JOIN'
join_query += f' {i["table"]} {i["alias"]} ON {i["on"]}'
+
query = query.format(column_name=column_name, main_table=main_table['table'], main_table_alias=main_table['alias'] if main_table.get('alias') else '', join_table=join_query, where_query=where_query)
where_values = tuple(wv['value'] for wv in where)
values = where_values
@@ -32,22 +36,30 @@ async def get_one(cls, connection_string: dict, main_table: dict, where: list, j
raise e
@classmethod
- async def get_all(cls, connection_string: dict, main_table: dict, where: list, join_table:list = [], order: dict = {}, column_name: list = None, limit: Optional[int] = None, offset: Optional[int] = None) -> Awaitable[dict]:
+ async def get_all(cls, connection_string: dict, main_table: dict, where: list = [], join_table:list = [], order: dict = {}, column_name: list = None, limit: Optional[int] = None, offset: Optional[int] = None) -> Awaitable[dict]:
try:
result = []
column_name = ','.join(column_name) if column_name else '*'
query = """
- SELECT {column_name} FROM {main_table} {main_table_alias} {join_table} WHERE {where_query} {order_query} LIMIT %s OFFSET %s
+ SELECT {column_name} FROM {main_table} {main_table_alias} {join_table} {where_query} {order_query} LIMIT %s OFFSET %s
"""
- where_query=''
- for w in where:
- where_query += f' {w["column_name"]} {w["operator"]} %s' if w.get('operator') else f' {w["column_name"]} = %s'
- where_query += f' {w["conjunction"]}' if w.get('conjunction') else ' '
- order_query = 'ORDER BY ' + ''.join([key + f' {order[key]}' for key in order.keys()]) if len(order) == 1 else ' , '.join([key + f' {order[key]}' for key in order.keys()])
+ where_query = ''
+ if where:
+ where_query += 'WHERE '
+ for w in where:
+ where_query += f' {w["column_name"]} {w["operator"]} %s' if w.get('operator') else f' {w["column_name"]} = %s'
+ where_query += f' {w["conjunction"]}' if w.get('conjunction') else ' '
+
+ order_query = ''
+ if order:
+ order_query = 'ORDER BY '
+ order_query += ''.join([f'{k} {v}' for k,v in order.items()]) if len(order) == 1 else ', '.join([f'{k} {v}' for k,v in order.items()])
+
join_query = ''
for i in join_table:
join_query += ' INNER JOIN' if i['join_method'] == 'INNER' else ' LEFT JOIN' if i['join_method'] == 'LEFT' else ' RIGHT JOIN'
join_query += f' {i["table"]} {i["alias"]} ON {i["on"]}'
+
query = query.format(column_name=column_name, main_table=main_table['table'], main_table_alias=main_table['alias'] if main_table.get('alias') else '', join_table=join_query, where_query=where_query, order_query=order_query)
where_values = tuple(wv['value'] for wv in where)
offset_value = (limit*(offset-1)) if offset else None
@@ -64,20 +76,25 @@ async def get_all(cls, connection_string: dict, main_table: dict, where: list, j
raise e
@classmethod
- async def get_count(cls, connection_string: dict, main_table: dict, where: list, join_table:list = []) -> Awaitable[dict]:
+ async def get_count(cls, connection_string: dict, main_table: dict, where: list = [], join_table:list = []) -> Awaitable[dict]:
try:
result = []
query = """
- SELECT COUNT(*) FROM {main_table} {main_table_alias} {join_table} WHERE {where_query}
+ SELECT COUNT(*) FROM {main_table} {main_table_alias} {join_table} {where_query}
"""
- where_query=''
- for w in where:
- where_query += f' {w["column_name"]} {w["operator"]} %s' if w.get('operator') else f' {w["column_name"]} = %s'
- where_query += f' {w["conjunction"]}' if w.get('conjunction') else ' '
+
+ where_query = ''
+ if where:
+ where_query += 'WHERE '
+ for w in where:
+ where_query += f' {w["column_name"]} {w["operator"]} %s' if w.get('operator') else f' {w["column_name"]} = %s'
+ where_query += f' {w["conjunction"]}' if w.get('conjunction') else ' '
+
join_query = ''
for i in join_table:
join_query += ' INNER JOIN' if i['join_method'] == 'INNER' else ' LEFT JOIN' if i['join_method'] == 'LEFT' else ' RIGHT JOIN'
join_query += f' {i["table"]} {i["alias"]} ON {i["on"]}'
+
query = query.format(main_table=main_table['table'], main_table_alias=main_table['alias'] if main_table.get('alias') else '', join_table=join_query, where_query=where_query)
where_values = tuple(wv['value'] if isinstance(wv['value'], str) else tuple(wv['value']) for wv in where )
values = where_values