Skip to content
Open
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
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,14 @@ valk-guard-example/
Workflow install target is configured as:

```text
github.com/valkdb/valk-guard/cmd/valk-guard@latest
github.com/valkdb/valk-guard/cmd/valk-guard.0.0-20260304065917-b9d9468a4ea3@latest
```

Rationale:

- This repository is a demo showcase; tracking latest keeps examples aligned with current built-in behavior.
- If you need strict reproducibility, pin a fixed tag in workflow `VALK_GUARD_INSTALL_REF`.
- This repository is a demo showcase, but CI is pinned to a known-good valk-guard build to keep output shape stable.
- Pinning avoids format drift that can break downstream tooling steps (for example: `Convert to reviewdog format`).
- When upgrading, bump `VALK_GUARD_INSTALL_REF` intentionally and verify the full workflow output.

## Creating Demo PRs (one rule at a time)

Expand Down Expand Up @@ -105,6 +106,15 @@ Separate snippets for schema-aware rules are in:
- `docs/schema-aware-demos/VG105.md`
- `docs/schema-aware-demos/VG106.md`

## Complex Query Patterns Demo

For parser/extractor showcase coverage of advanced query shapes (CTE, `UNION ALL`, `LEFT JOIN`) across ORM and non-ORM paths with intentional findings:

- `demo/complex-patterns/README.md`
- `demo/complex-patterns/sql/complex_queries.sql`
- `demo/complex-patterns/python/orm_complex.py`
- `demo/complex-patterns/python/raw_complex.py`

## License

[Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0)
27 changes: 27 additions & 0 deletions demo/complex-patterns/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Complex Query Patterns (Intentional Violations)

This demo intentionally introduces issues in higher-complexity query shapes across both ORM and non-ORM paths.

Patterns covered:

- CTE (`WITH ...`)
- `UNION ALL`
- `LEFT JOIN`

Expected rule families:

- `VG004` (unbounded selects / missing limit)
- `VG106` (unknown filter column on joined table)

Files:

- `sql/complex_queries.sql`
- `python/orm_complex.py`
- `python/raw_complex.py`

Local verification on this branch currently yields:

- 8 findings of `VG004`
- 2 findings of `VG106`

Use this folder as a stress-case fixture for parser/extractor behavior and complex-query rule coverage.
36 changes: 36 additions & 0 deletions demo/complex-patterns/python/orm_complex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Complex SQLAlchemy ORM query patterns with intentional violations."""

from sqlalchemy import select, text
from sqlalchemy.orm import Session

from models import Order, User


def orm_cte_unbounded(session: Session):
active_users = (
select(User.id.label("id"), User.email.label("email"))
.where(User.active.is_(True))
.cte("active_users")
Comment on lines +11 to +13
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [valk-guard] reported by reviewdog 🐶
VG004: SELECT without LIMIT may return unbounded rows; add LIMIT or FETCH FIRST | Origin: SQLAlchemy query builder | Query: SELECT "User"."id", "User"."email" FROM "User" WHERE 1=1

)

# Intentionally no LIMIT on outer query -> VG004 expected.
stmt = select(active_users.c.id, active_users.c.email).order_by(active_users.c.id)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [valk-guard] reported by reviewdog 🐶
VG004: SELECT without LIMIT may return unbounded rows; add LIMIT or FETCH FIRST | Origin: SQLAlchemy query builder | Query: SELECT "active_users"."c"."id", "active_users"."c"."email" FROM "active_users"

return session.execute(stmt).all()


def orm_union_all_unbounded(session: Session):
q_active = session.query(User.id, User.email).filter(User.active.is_(True))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [valk-guard] reported by reviewdog 🐶
VG004: SELECT without LIMIT may return unbounded rows; add LIMIT or FETCH FIRST | Origin: SQLAlchemy query builder | Query: SELECT "User"."id", "User"."email" FROM "User" WHERE 1=1

q_inactive = session.query(User.id, User.email).filter(User.active.is_(False))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [valk-guard] reported by reviewdog 🐶
VG004: SELECT without LIMIT may return unbounded rows; add LIMIT or FETCH FIRST | Origin: SQLAlchemy query builder | Query: SELECT "User"."id", "User"."email" FROM "User" WHERE 1=1

# Intentionally no LIMIT on union result -> VG004 expected.
return q_active.union_all(q_inactive).all()


def orm_left_join_unknown_filter(session: Session):
return (
session.query(User.id, User.email, Order.status)
.outerjoin(Order, User.id == Order.user_id)
.filter(text("orders.ghost_status = 'pending'"))
.order_by(User.id)
.limit(100)
.all()
)
52 changes: 52 additions & 0 deletions demo/complex-patterns/python/raw_complex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Complex raw SQL execution patterns with intentional violations."""

from sqlalchemy import text
from sqlalchemy.orm import Session


def raw_cte_select_star_unbounded(session: Session):
return session.execute(
text(
"""
WITH active_users AS (
SELECT *
FROM users
WHERE users.active = true
)
SELECT active_users.id, active_users.email
FROM active_users
ORDER BY active_users.id
"""
)
).all()
Comment on lines +8 to +21
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [valk-guard] reported by reviewdog 🐶
VG004: SELECT without LIMIT may return unbounded rows; add LIMIT or FETCH FIRST | Query: WITH active_users AS (



def raw_union_all_unbounded(session: Session):
return session.execute(
text(
"""
SELECT users.id, users.email
FROM users
WHERE users.active = true
UNION ALL
SELECT users.id, users.email
FROM users
WHERE users.active = false
"""
)
).all()
Comment on lines +25 to +37
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [valk-guard] reported by reviewdog 🐶
VG004: SELECT without LIMIT may return unbounded rows; add LIMIT or FETCH FIRST | Query: SELECT users.id, users.email



def raw_left_join_unknown_filter(session: Session):
return session.execute(
text(
"""
SELECT users.id, users.email, orders.status
FROM users
LEFT JOIN orders ON users.id = orders.user_id
WHERE orders.ghost_status = 'pending'
ORDER BY users.id
LIMIT 100
"""
)
).all()
Comment on lines +41 to +52
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [valk-guard] reported by reviewdog 🐶
VG106: filter predicate column "ghost_status" not found in table "orders" schema; check predicate/group/order columns in schema/model mappings | Query: SELECT users.id, users.email, orders.status

26 changes: 26 additions & 0 deletions demo/complex-patterns/sql/complex_queries.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-- CTE: SELECT * + unbounded outer query (intentional violations).
WITH active_users AS (
SELECT *
FROM users
WHERE users.active = true
)
SELECT active_users.id, active_users.email
FROM active_users
ORDER BY active_users.id;
Comment on lines +2 to +9
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [valk-guard] reported by reviewdog 🐶
VG004: SELECT without LIMIT may return unbounded rows; add LIMIT or FETCH FIRST | Query: WITH active_users AS (


-- UNION ALL: unbounded final result (intentional violation).
SELECT users.id, users.email
FROM users
WHERE users.active = true
UNION ALL
SELECT users.id, users.email
FROM users
WHERE users.active = false;
Comment on lines +12 to +18
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [valk-guard] reported by reviewdog 🐶
VG004: SELECT without LIMIT may return unbounded rows; add LIMIT or FETCH FIRST | Query: SELECT users.id, users.email


-- LEFT JOIN: unknown filter column on joined table (intentional violation).
SELECT users.id, users.email, orders.status
FROM users
LEFT JOIN orders ON users.id = orders.user_id
WHERE orders.ghost_status = 'pending'
ORDER BY users.id
LIMIT 100;
Comment on lines +21 to +26
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [valk-guard] reported by reviewdog 🐶
VG106: filter predicate column "ghost_status" not found in table "orders" schema; check predicate/group/order columns in schema/model mappings | Query: SELECT users.id, users.email, orders.status

11 changes: 11 additions & 0 deletions demo/complex-patterns/sql/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CREATE TABLE users (
id INTEGER PRIMARY KEY,
email TEXT NOT NULL,
active BOOLEAN NOT NULL
);

CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
status TEXT NOT NULL
);