From a64ed3b3a70b617679b434c27f52a9a0bb793b32 Mon Sep 17 00:00:00 2001 From: DataSentry Date: Sat, 10 Oct 2026 16:10:31 +0300 Subject: [PATCH 1/8] fix(parser): stop dropping columns named like constraint keywords The constraint check in _parse_column_line_robust used str.startswith() against a tuple of keywords. That matched any column whose name merely began with one of them, so unique_code, constraint_name, check_flag and excluded_at were all treated as table constraints and silently dropped. The Schema Designer then drew a table with fewer columns than the SQL declares and gave no warning. The keywords are now matched with a word boundary so PRIMARY KEY (a, b), UNIQUE (email) and CONSTRAINT ... CHECK (...) are still skipped while prefixed column names parse as columns. Adds two tests. One fails on the old code with unique_code and constraint_name missing from the column list. The other pins down that table-level constraints are still skipped. --- src/core/schema_parser.py | 15 ++++++--------- tests/test_schema_parser.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/core/schema_parser.py b/src/core/schema_parser.py index 432f061..470b0bd 100644 --- a/src/core/schema_parser.py +++ b/src/core/schema_parser.py @@ -14,6 +14,9 @@ logger = get_logger(__name__) +# A line that starts with one of these is a table constraint, not a column. +CONSTRAINT_LINE_RE = re.compile(r"(PRIMARY\s+KEY|FOREIGN\s+KEY|CONSTRAINT|UNIQUE|CHECK|EXCLUDE)\b") + class SchemaParser: """Parse CREATE TABLE and ALTER TABLE statements from SQL text. @@ -193,15 +196,9 @@ def _parse_column_line_robust(self, line: str) -> dict | None: # Skip pure constraint lines upper = line.upper().strip() - skip_patterns = ( - "PRIMARY KEY", - "FOREIGN KEY", - "CONSTRAINT", - "UNIQUE", - "CHECK", - "EXCLUDE", - ) - if upper.startswith(skip_patterns) and "(" in upper: + # Anchored on a word boundary so a column named unique_code or + # constraint_name is not mistaken for a constraint declaration. + if CONSTRAINT_LINE_RE.match(upper) and "(" in upper: return None # Try to extract column name and type diff --git a/tests/test_schema_parser.py b/tests/test_schema_parser.py index df4a0ab..9f8b5b8 100644 --- a/tests/test_schema_parser.py +++ b/tests/test_schema_parser.py @@ -190,6 +190,40 @@ def test_table_level_primary_key(self, parser): assert len(pks) == 2 assert {c["name"] for c in pks} == {"a", "b"} + def test_column_named_like_constraint_keyword(self, parser): + """A column whose name starts with a constraint keyword is a column. + + unique_code and constraint_name used to be dropped because the + constraint check compared raw prefixes. The designer then showed a + table with fewer columns than the SQL declares. + """ + sql = """ + CREATE TABLE t ( + id SERIAL PRIMARY KEY, + unique_code VARCHAR(10), + constraint_name VARCHAR(50), + check_flag BOOLEAN, + excluded_at TIMESTAMP + ); + """ + tables, _ = parser.parse_sql(sql) + names = [c["name"] for c in tables[0]["columns"]] + assert names == ["id", "unique_code", "constraint_name", "check_flag", "excluded_at"] + + def test_table_level_unique_constraint_still_skipped(self, parser): + """The keyword fix must not start parsing real constraint lines.""" + sql = """ + CREATE TABLE t ( + id SERIAL PRIMARY KEY, + email VARCHAR(200), + UNIQUE (email), + CONSTRAINT chk_len CHECK (length(email) > 3) + ); + """ + tables, _ = parser.parse_sql(sql) + names = [c["name"] for c in tables[0]["columns"]] + assert names == ["id", "email"] + def test_not_null(self, parser): sql = "CREATE TABLE t (name VARCHAR(100) NOT NULL);" tables, _ = parser.parse_sql(sql) From 9b0d3ee8e1e0191c2fa1b5d3e1ea00979ecd0446 Mon Sep 17 00:00:00 2001 From: Peter-L-SVK Date: Sat, 10 Oct 2026 23:59:21 +0200 Subject: [PATCH 2/8] test(conftest): allow test DB settings via environment variables The test database password was hardcoded as 'admin123' in tests/conftest.py. While this is a throw-away database for local and CI use, the value ended up in the repository history and could not be overridden without editing source. Now reads TEST_DB, TEST_USER, TEST_PASSWORD, TEST_HOST, and TEST_PORT from the environment, with the previous values as defaults so existing workflows (local + CI) keep working unchanged. Also added .env.example as documentation and .env to .gitignore, so contributors who point tests at a non-local PostgreSQL can keep the real credentials out of the repo. Reported-by: --- .env.example | 6 ++++++ .gitignore | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e1bba19 --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +# Copy to .env and customize if you need non-default test DB settings. +TEST_DB=sql_schema_studio_test +TEST_USER=postgres +TEST_PASSWORD=admin123 +TEST_HOST=localhost +TEST_PORT=5432 diff --git a/.gitignore b/.gitignore index 07ee637..e3fb418 100755 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,4 @@ Thumbs.db *.sqlite # Backups -*.bak \ No newline at end of file +*.bak.env From e850f6f013ce6d80dbd540377c8f162894eb7dd9 Mon Sep 17 00:00:00 2001 From: Peter-L-SVK Date: Sun, 11 Oct 2026 00:06:57 +0200 Subject: [PATCH 3/8] test(conftest): allow test DB settings via environment variables The test database password was hardcoded as 'admin123' in tests/conftest.py. While this is a throw-away database for local and CI use, the value ended up in the repository history and could not be overridden without editing source. Now reads TEST_DB, TEST_USER, TEST_PASSWORD, TEST_HOST, and TEST_PORT from the environment, with the previous values as defaults so existing workflows (local + CI) keep working unchanged. Also added .env.example as documentation and .env to .gitignore, so contributors who point tests at a non-local PostgreSQL can keep the real credentials out of the repo. Reported-by: [Sal-ami](https://github.com/Sal-ami) --- .gitignore | 3 ++- tests/conftest.py | 11 +++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index e3fb418..6d00196 100755 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ Thumbs.db *.sqlite # Backups -*.bak.env +*.bak +.env diff --git a/tests/conftest.py b/tests/conftest.py index f172707..5af6df3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,12 +19,11 @@ from src.core.db_connector import DatabaseConnector, ConnectionProfile # Test database settings — use a dedicated test database -TEST_DB = "sql_schema_studio_test" -TEST_USER = "postgres" -TEST_PASSWORD = "admin123" -TEST_HOST = "localhost" -TEST_PORT = 5432 - +TEST_DB = os.environ.get("TEST_DB", "sql_schema_studio_test") +TEST_USER = os.environ.get("TEST_USER", "postgres") +TEST_PASSWORD = os.environ.get("TEST_PASSWORD", "admin123") +TEST_HOST = os.environ.get("TEST_HOST", "localhost") +TEST_PORT = int(os.environ.get("TEST_PORT", "5432")) @pytest.fixture(scope="session") def test_db_setup(): From f11e2d92843ec86963395fc1eda6432e75db0bb1 Mon Sep 17 00:00:00 2001 From: Peter-L-SVK Date: Sun, 11 Oct 2026 00:12:30 +0200 Subject: [PATCH 4/8] test(conftest): allow test DB settings via environment variables The test database password was hardcoded as 'admin123' in tests/conftest.py. While this is a throw-away database for local and CI use, the value ended up in the repository history and could not be overridden without editing source. Now reads TEST_DB, TEST_USER, TEST_PASSWORD, TEST_HOST, and TEST_PORT from the environment, with the previous values as defaults so existing workflows (local + CI) keep working unchanged. Also added .env.example as documentation and .env to .gitignore, so contributors who point tests at a non-local PostgreSQL can keep the real credentials out of the repo. Reported-by: [Sal-ami](https://github.com/Sal-ami) --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cc172b1..bc56dd1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,7 +22,7 @@ jobs: env: POSTGRES_DB: postgres POSTGRES_USER: postgres - POSTGRES_PASSWORD: admin123 # Matches TEST_PASSWORD in conftest.py + POSTGRES_PASSWORD: ${{ env.example.TEST_DB_PASSWORD }} ports: - 5432:5432 # Ensure PostgreSQL is ready before steps start From d90e2c7d842ee25dc1d59bccc8736ce20fadea76 Mon Sep 17 00:00:00 2001 From: Peter-L-SVK Date: Sun, 11 Oct 2026 00:17:00 +0200 Subject: [PATCH 5/8] fix(ci): correct invalid env expression in postgres service --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bc56dd1..75b91e5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,7 +22,7 @@ jobs: env: POSTGRES_DB: postgres POSTGRES_USER: postgres - POSTGRES_PASSWORD: ${{ env.example.TEST_DB_PASSWORD }} + POSTGRES_PASSWORD: ${{ secrets.TEST_DB_PASSWORD }} ports: - 5432:5432 # Ensure PostgreSQL is ready before steps start From c4a3e40197943c3102c6beaebafb7610f22e17f7 Mon Sep 17 00:00:00 2001 From: Peter-L-SVK Date: Sun, 11 Oct 2026 00:20:19 +0200 Subject: [PATCH 6/8] fix(tests/conftest.py): add missing import --- tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/conftest.py b/tests/conftest.py index 5af6df3..cbd16f4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,6 +8,7 @@ """Shared test fixtures and configuration""" +import os import pytest import psycopg import sys From 43b2d35afb8612908105862089b7ca544a2c607d Mon Sep 17 00:00:00 2001 From: Peter-L-SVK Date: Sun, 11 Oct 2026 00:27:03 +0200 Subject: [PATCH 7/8] fix(ci): correct env setting --- .github/workflows/tests.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 75b91e5..88e61f7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,6 @@ jobs: matrix: python-version: ["3.12", "3.13"] - # PostgreSQL service container for database tests services: postgres: image: postgres:16 @@ -25,7 +24,6 @@ jobs: POSTGRES_PASSWORD: ${{ secrets.TEST_DB_PASSWORD }} ports: - 5432:5432 - # Ensure PostgreSQL is ready before steps start options: >- --health-cmd pg_isready --health-interval 10s @@ -66,9 +64,9 @@ jobs: run: | python -m pip install --upgrade pip pip install -e ".[dev]" - pip install flake8-pyproject keyrings.alt + pip install flake8-pyproject keyrings.alt - - name: Run flake8 (optional) + - name: Run flake8 run: | flake8 src/ --count --statistics || true @@ -83,3 +81,9 @@ jobs: - name: Run pytest run: | pytest tests/ -v --tb=short + env: + TEST_PASSWORD: ${{ secrets.TEST_DB_PASSWORD }} + TEST_USER: postgres + TEST_DB: sql_schema_studio_test + TEST_HOST: localhost + TEST_PORT: "5432" From c5cea2a41928f3d43897c96fc97af499146a0224 Mon Sep 17 00:00:00 2001 From: Peter-L-SVK Date: Sun, 11 Oct 2026 00:39:30 +0200 Subject: [PATCH 8/8] fix(tests/conftest.py): apply black formating --- tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/conftest.py b/tests/conftest.py index cbd16f4..3d33e7c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -26,6 +26,7 @@ TEST_HOST = os.environ.get("TEST_HOST", "localhost") TEST_PORT = int(os.environ.get("TEST_PORT", "5432")) + @pytest.fixture(scope="session") def test_db_setup(): """Create test database once per session, drop at end"""