Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Tests

on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.9']
db-type: ['pgsql', 'mysql']

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Make scripts executable
run: |
chmod +x test_in_docker.sh
chmod +x test.sh
if [ -f "tests/db/${{ matrix.db-type }}/init_db.sh" ]; then
chmod +x "tests/db/${{ matrix.db-type }}/init_db.sh"
fi

- name: Run tests in Docker
run: |
./test_in_docker.sh \
--python-version "${{ matrix.python-version }}" \
--db-type "${{ matrix.db-type }}"
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM python:3.8-bullseye

# Install required Python packages
RUN pip install foliantcontrib.utils requests psycopg2-binary pyodbc

WORKDIR /app
ENTRYPOINT ["./test.sh"]
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ preprocessors:
password: !env DBDOC_PASS
doc: True
scheme: True
strict: False
trusted_connection: False
filters:
...
doc_template: dbdoc.j2
Expand Down Expand Up @@ -141,6 +143,12 @@ preprocessors:
`scheme`
: If `true` — the platuml code for database scheme will be generated. Default: `true`

`strict`
: If `true` — the build will fail if connection to database cannot be established. If `false` — the preprocessor will skip the tag with warning. Default: `false`

`trusted_connection`
: Specific option for MS SQL Server. If true - will use Windows Authentication (Trusted Connection) instead of username/password. Default: false. Requires proper ODBC driver configuration.

`filters`
: SQL-like operators for filtering the results. More info in the **Filters** section.

Expand Down Expand Up @@ -309,6 +317,47 @@ If you wish to create your own template, the default ones may be a good starting
* [Default **SQL Server doc** template.](https://github.com/foliant-docs/foliantcontrib.dbdoc/blob/master/foliant/preprocessors/dbdoc/mssql/templates/doc.j2)
* [Default **SQL Server scheme** template.](https://github.com/foliant-docs/foliantcontrib.dbdoc/blob/master/foliant/preprocessors/dbdoc/mssql/templates/doc.j2)

## Tests

For run tests, use:
```bash
./test_in_docker.sh --python-version "3.9" --db-type "mysql"
```

**Options:**
`--python-version <python-version>` – Specifies Python version for test environment. Available_: 3.8, 3.9, 3.10 etc.

`--db-type <db-type>` – Chooses database type for testing. Available_: mysql, pgsql.

**Usage Examples**

```bash
# Basic usage with defaults
./test_in_docker.sh

# Specific Python and database
./test_in_docker.sh --python-version "3.10" --db-type "pgsql"

# Only change database type
./test_in_docker.sh --db-type "mysql"

# Only change Python version
./test_in_docker.sh --python-version "3.9"
```

**What It Does:**
1. Starts Docker container with specified Python version.
2. Initializes chosen database type with test data.
3. Runs test suite.
4. Cleans up resources after completion.
5. Returns exit code based on test results.

**Notes**
- Requires Docker installed;
- Test data is automatically loaded from `test_data/` directory;
- Results are displayed in console with color formatting;
- Exit code 0 = success, 1 = test failures.

## Troubleshooting

If you get errors during build, especially errors concerning connection to the database, you have to make sure that you are supplying the right parameters.
Expand Down Expand Up @@ -379,3 +428,28 @@ con = pyodbc.connect(
"UID=Usernam;PWD=Password_0"
)
```

**Microsoft SQL Server Authentication Issues**

When using MS SQL Server, you have two authentication options:

1. SQL Server Authentication (username/password):

```yaml
trusted_connection: false
user: your_username
password: your_password
```

2. Windows Authentication (Trusted Connection):

```yaml
trusted_connection: true
# no user/password needed
```

For Windows Authentication to work:

- Make sure your ODBC driver supports Trusted Connections.
- The account running Foliant must have proper database permissions.
- On Linux/Mac, you may need to configure Kerberos for cross-platform authentication.
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 0.1.9
- Add: strict option.
- Add: tests.

# 0.1.8
- DBMS python connectors are only imported on use.

Expand Down
48 changes: 31 additions & 17 deletions foliant/preprocessors/dbdoc/mssql/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from copy import deepcopy
from logging import getLogger

Expand All @@ -9,6 +10,8 @@
from .queries import TriggersQuery
from .queries import ViewsQuery
from foliant.preprocessors.dbdoc.base.main import DBRendererBase
from foliant.utils import output


logger = getLogger('unbound.dbdoc.mssql')

Expand All @@ -29,7 +32,8 @@ class MSSQLRenderer(DBRendererBase):
'functions',
'triggers',
'views'
]
],
'strict': False
}
module_name = __name__

Expand All @@ -46,24 +50,34 @@ def connect(self):
'and make sure that MS SQL Server is installed on the machine'
)

if self.options['trusted_connection']:
connection_string = (
f"DRIVER={self.options['driver']};"
f"SERVER={self.options['host']},{self.options['port']};"
f"DATABASE={self.options['dbname']};Trusted_Connection=yes"
)
try:
if self.options['trusted_connection']:
connection_string = (
f"DRIVER={self.options['driver']};"
f"SERVER={self.options['host']},{self.options['port']};"
f"DATABASE={self.options['dbname']};Trusted_Connection=yes"
)

else:
connection_string = (
f"DRIVER={self.options['driver']};"
f"SERVER={self.options['host']},{self.options['port']};"
f"DATABASE={self.options['dbname']};"
f"UID={self.options['user']};PWD={self.options['password']}"
else:
connection_string = (
f"DRIVER={self.options['driver']};"
f"SERVER={self.options['host']},{self.options['port']};"
f"DATABASE={self.options['dbname']};"
f"UID={self.options['user']};PWD={self.options['password']}"
)
logger.debug(
f"Trying to connect: {connection_string}"
)
logger.debug(
f"Trying to connect: {connection_string}"
)
self.con = pyodbc.connect(connection_string)
self.con = pyodbc.connect(connection_string)
logger.debug("Successfully connected to the database")
except pyodbc.Error as e:
msg = f"\MS SQL database database connection error: {e}"
if self.options['strict']:
logger.error(msg)
output(f'ERROR: {msg}. Exit.')
os._exit(1)
else:
logger.debug(f'{msg}. Skipping.')

def collect_datasets(self) -> dict:

Expand Down
40 changes: 26 additions & 14 deletions foliant/preprocessors/dbdoc/mysql/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from copy import deepcopy
from logging import getLogger

Expand All @@ -9,6 +10,8 @@
from .queries import TriggersQuery
from .queries import ViewsQuery
from foliant.preprocessors.dbdoc.base.main import DBRendererBase
from foliant.utils import output


logger = getLogger('unbound.dbdoc.mysql')

Expand All @@ -27,7 +30,8 @@ class MySQLRenderer(DBRendererBase):
'functions',
'triggers',
'views'
]
],
'strict': False
}
module_name = __name__

Expand All @@ -45,18 +49,27 @@ def connect(self):
'and make sure that MySQL Client is installed on the machine'
)

logger.debug(
f"Trying to connect: host={self.options['host']} port={self.options['port']}"
f" dbname={self.options['dbname']}, user={self.options['user']} "
f"password={self.options['password']}."
)
self.con = _mysql.connect(
host=self.options['host'],
port=self.options['port'],
user=self.options['user'],
passwd=self.options['password'],
db=self.options['dbname']
)
try:
logger.debug(
f"Trying to connect: host={self.options['host']} port={self.options['port']}"
f" dbname={self.options['dbname']}, user={self.options['user']} "
f"password={self.options['password']}."
)
self.con = _mysql.connect(
host=self.options['host'],
port=self.options['port'],
user=self.options['user'],
password=self.options['password'],
database=self.options['dbname']
)
except _mysql.Error as e:
msg = f"\nMySQL database error: {e}"
if self.options['strict']:
logger.error(msg)
output(f'ERROR: {msg}. Exit.')
os._exit(1)
else:
logger.debug(f'{msg}. Skipping.')

def collect_datasets(self) -> dict:

Expand Down Expand Up @@ -94,7 +107,6 @@ def collect_datasets(self) -> dict:
q_triggers = TriggersQuery(self.con, filters)
logger.debug(f'Triggers query:\n\n {q_triggers.sql}')
result['triggers'] = q_triggers.run()

return result

def collect_tables(self,
Expand Down
28 changes: 20 additions & 8 deletions foliant/preprocessors/dbdoc/oracle/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from copy import deepcopy
from logging import getLogger

Expand All @@ -9,6 +10,7 @@
from .queries import ViewsQuery
from ..base.main import LibraryNotInstalledError
from foliant.preprocessors.dbdoc.base.main import DBRendererBase
from foliant.utils import output


logger = getLogger('unbound.dbdoc.oracle')
Expand All @@ -28,7 +30,8 @@ class OracleRenderer(DBRendererBase):
'functions',
'triggers',
'views'
]
],
'strict': False
}
module_name = __name__

Expand All @@ -50,13 +53,22 @@ def connect(self):
f" dbname={self.options['dbname']}, user={self.options['user']} "
f"password={self.options['password']}."
)
self.con = cx_Oracle.connect(
f"{self.options['user']}/{self.options['password']}@"
f"{self.options['host']}:{self.options['port']}/"
f"{self.options['dbname']}",
encoding='UTF-8',
nencoding='UTF-8'
)
try:
self.con = cx_Oracle.connect(
f"{self.options['user']}/{self.options['password']}@"
f"{self.options['host']}:{self.options['port']}/"
f"{self.options['dbname']}",
encoding='UTF-8',
nencoding='UTF-8'
)
except cx_Oracle.Error as e:
msg = f"\nOracle database connection error: {e}"
if self.options['strict']:
logger.error(msg)
output(f'ERROR: {msg}. Exit.')
os._exit(1)
else:
logger.debug(f"{msg}. Skipping.")

def collect_datasets(self) -> dict:

Expand Down
2 changes: 1 addition & 1 deletion foliant/preprocessors/dbdoc/oracle/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ class ColumnsQuery(QueryBase):
col.DATA_LENGTH,
col.DATA_PRECISION,
com.COMMENTS
FROM user_tab_columns col
FROM all_tab_columns col
JOIN all_tables tab
ON col.TABLE_NAME = tab.TABLE_NAME
LEFT JOIN user_col_comments com
Expand Down
Loading