From 985d902410db19ae252d7c55b0d5e3f5faa43c0d Mon Sep 17 00:00:00 2001 From: Valentin Kuznetsov Date: Wed, 16 Sep 2026 17:30:19 +0300 Subject: [PATCH] feat: honor omit_unused_structs (#74) Port Go's filterUnusedStructs so unused table and enum models are omitted from models.py. Bump CI/goldens to sqlc 1.30.0. --- .github/workflows/ci.yml | 2 +- README.md | 6 + examples/src/authors/models.py | 2 +- examples/src/authors/query.py | 2 +- examples/src/booktest/models.py | 2 +- examples/src/booktest/query.py | 2 +- examples/src/jets/models.py | 2 +- examples/src/jets/query-building.py | 2 +- examples/src/ondeck/city.py | 2 +- examples/src/ondeck/models.py | 2 +- examples/src/ondeck/venue.py | 2 +- internal/config.go | 1 + .../emit_pydantic_models/db/models.py | 2 +- .../testdata/emit_pydantic_models/db/query.py | 2 +- .../testdata/emit_pydantic_models/sqlc.yaml | 2 +- .../testdata/emit_str_enum/db/models.py | 2 +- .../testdata/emit_str_enum/db/query.py | 2 +- .../endtoend/testdata/emit_str_enum/sqlc.yaml | 2 +- .../testdata/exec_result/python/models.py | 2 +- .../testdata/exec_result/python/query.py | 2 +- .../endtoend/testdata/exec_result/sqlc.yaml | 2 +- .../testdata/exec_rows/python/models.py | 2 +- .../testdata/exec_rows/python/query.py | 2 +- .../endtoend/testdata/exec_rows/sqlc.yaml | 2 +- .../python/models.py | 2 +- .../python/query.py | 2 +- .../inflection_exclude_table_names/sqlc.yaml | 2 +- .../testdata/omit_unused_structs/db/models.py | 66 +++++++ .../testdata/omit_unused_structs/db/query.py | 168 ++++++++++++++++++ .../testdata/omit_unused_structs/query.sql | 20 +++ .../testdata/omit_unused_structs/schema.sql | 69 +++++++ .../testdata/omit_unused_structs/sqlc.yaml | 19 ++ .../python/models.py | 2 +- .../query_parameter_limit_two/python/query.py | 2 +- .../query_parameter_limit_two/sqlc.yaml | 2 +- .../python/models.py | 2 +- .../python/query.py | 2 +- .../query_parameter_limit_undefined/sqlc.yaml | 2 +- .../python/models.py | 2 +- .../python/query.py | 2 +- .../query_parameter_limit_zero/sqlc.yaml | 2 +- .../query_parameter_no_limit/sqlc.yaml | 2 +- internal/gen.go | 120 ++++++++++++- 43 files changed, 497 insertions(+), 42 deletions(-) create mode 100644 internal/endtoend/testdata/omit_unused_structs/db/models.py create mode 100644 internal/endtoend/testdata/omit_unused_structs/db/query.py create mode 100644 internal/endtoend/testdata/omit_unused_structs/query.sql create mode 100644 internal/endtoend/testdata/omit_unused_structs/schema.sql create mode 100644 internal/endtoend/testdata/omit_unused_structs/sqlc.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0788422..56df2c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: go-version: '1.23.5' - uses: sqlc-dev/setup-sqlc@v4 with: - sqlc-version: '1.28.0' + sqlc-version: '1.30.0' - run: make - run: make test - run: sqlc diff diff --git a/README.md b/README.md index c9f2531..4446c1c 100644 --- a/README.md +++ b/README.md @@ -76,3 +76,9 @@ class Status(str, enum.Enum): OPEN = "op!en" CLOSED = "clo@sed" ``` + +### Omit unused table and enum structs + +Option: `omit_unused_structs` + +If `true`, sqlc won't generate table and enum structs that aren't used in queries for a given package. diff --git a/examples/src/authors/models.py b/examples/src/authors/models.py index 96553a5..007ea19 100644 --- a/examples/src/authors/models.py +++ b/examples/src/authors/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 import dataclasses from typing import Optional diff --git a/examples/src/authors/query.py b/examples/src/authors/query.py index 019f877..48513ed 100644 --- a/examples/src/authors/query.py +++ b/examples/src/authors/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 # source: query.sql from typing import AsyncIterator, Iterator, Optional diff --git a/examples/src/booktest/models.py b/examples/src/booktest/models.py index d7ee131..a882d02 100644 --- a/examples/src/booktest/models.py +++ b/examples/src/booktest/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 import dataclasses import datetime import enum diff --git a/examples/src/booktest/query.py b/examples/src/booktest/query.py index bc71f22..6e71192 100644 --- a/examples/src/booktest/query.py +++ b/examples/src/booktest/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 # source: query.sql import dataclasses import datetime diff --git a/examples/src/jets/models.py b/examples/src/jets/models.py index 0d4eb5d..7d9a81e 100644 --- a/examples/src/jets/models.py +++ b/examples/src/jets/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 import dataclasses diff --git a/examples/src/jets/query-building.py b/examples/src/jets/query-building.py index 7651116..51d69a5 100644 --- a/examples/src/jets/query-building.py +++ b/examples/src/jets/query-building.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 # source: query-building.sql from typing import AsyncIterator, Optional diff --git a/examples/src/ondeck/city.py b/examples/src/ondeck/city.py index 5af93e9..951d95b 100644 --- a/examples/src/ondeck/city.py +++ b/examples/src/ondeck/city.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 # source: city.sql from typing import AsyncIterator, Optional diff --git a/examples/src/ondeck/models.py b/examples/src/ondeck/models.py index 1161408..bc05e8a 100644 --- a/examples/src/ondeck/models.py +++ b/examples/src/ondeck/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 import dataclasses import datetime import enum diff --git a/examples/src/ondeck/venue.py b/examples/src/ondeck/venue.py index 6159bf6..87f7992 100644 --- a/examples/src/ondeck/venue.py +++ b/examples/src/ondeck/venue.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 # source: venue.sql import dataclasses from typing import AsyncIterator, List, Optional diff --git a/internal/config.go b/internal/config.go index 1a8a565..cde8561 100644 --- a/internal/config.go +++ b/internal/config.go @@ -8,6 +8,7 @@ type Config struct { Out string `json:"out"` EmitPydanticModels bool `json:"emit_pydantic_models"` EmitStrEnum bool `json:"emit_str_enum"` + OmitUnusedStructs bool `json:"omit_unused_structs,omitempty"` QueryParameterLimit *int32 `json:"query_parameter_limit"` InflectionExcludeTableNames []string `json:"inflection_exclude_table_names"` } diff --git a/internal/endtoend/testdata/emit_pydantic_models/db/models.py b/internal/endtoend/testdata/emit_pydantic_models/db/models.py index 7676e5c..2300f77 100644 --- a/internal/endtoend/testdata/emit_pydantic_models/db/models.py +++ b/internal/endtoend/testdata/emit_pydantic_models/db/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 import pydantic from typing import Optional diff --git a/internal/endtoend/testdata/emit_pydantic_models/db/query.py b/internal/endtoend/testdata/emit_pydantic_models/db/query.py index 6f5b76f..946674d 100644 --- a/internal/endtoend/testdata/emit_pydantic_models/db/query.py +++ b/internal/endtoend/testdata/emit_pydantic_models/db/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 # source: query.sql from typing import AsyncIterator, Iterator, Optional diff --git a/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml b/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml index beae200..26ae5ff 100644 --- a/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml +++ b/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "35db40d0d3e1f8e62575e9954f2f83f29b65bb01ac245703899c672fd5dec208" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_str_enum/db/models.py b/internal/endtoend/testdata/emit_str_enum/db/models.py index 5fdf754..5fd5508 100644 --- a/internal/endtoend/testdata/emit_str_enum/db/models.py +++ b/internal/endtoend/testdata/emit_str_enum/db/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 import dataclasses import enum from typing import Optional diff --git a/internal/endtoend/testdata/emit_str_enum/db/query.py b/internal/endtoend/testdata/emit_str_enum/db/query.py index 8082889..c02a9ec 100644 --- a/internal/endtoend/testdata/emit_str_enum/db/query.py +++ b/internal/endtoend/testdata/emit_str_enum/db/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 # source: query.sql from typing import AsyncIterator, Iterator, Optional diff --git a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml index 04e3feb..d77f3e2 100644 --- a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml +++ b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "35db40d0d3e1f8e62575e9954f2f83f29b65bb01ac245703899c672fd5dec208" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/exec_result/python/models.py b/internal/endtoend/testdata/exec_result/python/models.py index 034fb2d..ced3715 100644 --- a/internal/endtoend/testdata/exec_result/python/models.py +++ b/internal/endtoend/testdata/exec_result/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 import dataclasses diff --git a/internal/endtoend/testdata/exec_result/python/query.py b/internal/endtoend/testdata/exec_result/python/query.py index b68ce39..c063868 100644 --- a/internal/endtoend/testdata/exec_result/python/query.py +++ b/internal/endtoend/testdata/exec_result/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 # source: query.sql import sqlalchemy import sqlalchemy.ext.asyncio diff --git a/internal/endtoend/testdata/exec_result/sqlc.yaml b/internal/endtoend/testdata/exec_result/sqlc.yaml index ddffc83..dd048e9 100644 --- a/internal/endtoend/testdata/exec_result/sqlc.yaml +++ b/internal/endtoend/testdata/exec_result/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "35db40d0d3e1f8e62575e9954f2f83f29b65bb01ac245703899c672fd5dec208" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/exec_rows/python/models.py b/internal/endtoend/testdata/exec_rows/python/models.py index 034fb2d..ced3715 100644 --- a/internal/endtoend/testdata/exec_rows/python/models.py +++ b/internal/endtoend/testdata/exec_rows/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 import dataclasses diff --git a/internal/endtoend/testdata/exec_rows/python/query.py b/internal/endtoend/testdata/exec_rows/python/query.py index 7a9b2a6..c5a936d 100644 --- a/internal/endtoend/testdata/exec_rows/python/query.py +++ b/internal/endtoend/testdata/exec_rows/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 # source: query.sql import sqlalchemy import sqlalchemy.ext.asyncio diff --git a/internal/endtoend/testdata/exec_rows/sqlc.yaml b/internal/endtoend/testdata/exec_rows/sqlc.yaml index ddffc83..dd048e9 100644 --- a/internal/endtoend/testdata/exec_rows/sqlc.yaml +++ b/internal/endtoend/testdata/exec_rows/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "35db40d0d3e1f8e62575e9954f2f83f29b65bb01ac245703899c672fd5dec208" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/inflection_exclude_table_names/python/models.py b/internal/endtoend/testdata/inflection_exclude_table_names/python/models.py index 8ba8803..0614ac0 100644 --- a/internal/endtoend/testdata/inflection_exclude_table_names/python/models.py +++ b/internal/endtoend/testdata/inflection_exclude_table_names/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 import dataclasses diff --git a/internal/endtoend/testdata/inflection_exclude_table_names/python/query.py b/internal/endtoend/testdata/inflection_exclude_table_names/python/query.py index 1e1e161..8b9eb26 100644 --- a/internal/endtoend/testdata/inflection_exclude_table_names/python/query.py +++ b/internal/endtoend/testdata/inflection_exclude_table_names/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 # source: query.sql from typing import Optional diff --git a/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml b/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml index efbb150..1071b41 100644 --- a/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml +++ b/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "35db40d0d3e1f8e62575e9954f2f83f29b65bb01ac245703899c672fd5dec208" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/omit_unused_structs/db/models.py b/internal/endtoend/testdata/omit_unused_structs/db/models.py new file mode 100644 index 0000000..3d8397c --- /dev/null +++ b/internal/endtoend/testdata/omit_unused_structs/db/models.py @@ -0,0 +1,66 @@ +# Code generated by sqlc. DO NOT EDIT. +# versions: +# sqlc v1.30.0 +import dataclasses +import enum +from typing import List, Optional + + +class ArrayEnum(enum.StrEnum): + O = "o" + P = "p" + + +class QueryParamEnumTableEnum(enum.StrEnum): + G = "g" + H = "h" + + +class QueryParamStructEnumTableEnum(enum.StrEnum): + I = "i" + J = "j" + + +class QueryReturnEnumTableEnum(enum.StrEnum): + K = "k" + L = "l" + + +class QueryReturnFullTableEnum(enum.StrEnum): + E = "e" + F = "f" + + +class QueryReturnStructEnumTableEnum(enum.StrEnum): + K = "k" + L = "l" + + +class QuerySqlcEmbedEnum(enum.StrEnum): + M = "m" + N = "n" + + +@dataclasses.dataclass() +class ArrayEnumTable: + id: int + value: Optional[List[ArrayEnum]] + + +@dataclasses.dataclass() +class QueryParamEnumTable: + id: int + other: QueryParamEnumTableEnum + value: Optional[QueryParamEnumTableEnum] + + +@dataclasses.dataclass() +class QueryReturnFullTable: + id: int + value: Optional[QueryReturnFullTableEnum] + + +@dataclasses.dataclass() +class QuerySqlcEmbedTable: + id: int + value: Optional[QuerySqlcEmbedEnum] diff --git a/internal/endtoend/testdata/omit_unused_structs/db/query.py b/internal/endtoend/testdata/omit_unused_structs/db/query.py new file mode 100644 index 0000000..e8ab609 --- /dev/null +++ b/internal/endtoend/testdata/omit_unused_structs/db/query.py @@ -0,0 +1,168 @@ +# Code generated by sqlc. DO NOT EDIT. +# versions: +# sqlc v1.30.0 +# source: query.sql +import dataclasses +from typing import Any, AsyncIterator, Iterator, Optional + +import sqlalchemy +import sqlalchemy.ext.asyncio + +from db import models + + +QUERY_ENUM_ARRAY_TABLE = """-- name: query_enum_array_table \\:many +SELECT id, value FROM array_enum_table +""" + + +QUERY_PARAM_ENUM_TABLE = """-- name: query_param_enum_table \\:one +SELECT id, other, value FROM query_param_enum_table WHERE value = :p1 +""" + + +QUERY_PARAM_STRUCT_ENUM_TABLE = """-- name: query_param_struct_enum_table \\:one +SELECT id FROM query_param_struct_enum_table WHERE id = :p1 AND value = :p2 +""" + + +QUERY_RETURN_ENUM_TABLE = """-- name: query_return_enum_table \\:one +SELECT value FROM query_return_enum_table WHERE id = :p1 +""" + + +QUERY_RETURN_FULL_TABLE = """-- name: query_return_full_table \\:many +SELECT id, value FROM query_return_full_table +""" + + +QUERY_RETURN_STRUCT_ENUM_TABLE = """-- name: query_return_struct_enum_table \\:one +SELECT value, another FROM query_return_struct_enum_table WHERE id = :p1 +""" + + +@dataclasses.dataclass() +class query_return_struct_enum_tableRow: + value: Optional[models.QueryReturnStructEnumTableEnum] + another: Optional[int] + + +QUERY_SQLC_EMBED_TABLE = """-- name: query_sqlc_embed_table \\:one +SELECT query_sqlc_embed_table.id, query_sqlc_embed_table.value FROM query_sqlc_embed_table WHERE id = :p1 +""" + + +class Querier: + def __init__(self, conn: sqlalchemy.engine.Connection): + self._conn = conn + + def query_enum_array_table(self) -> Iterator[models.ArrayEnumTable]: + result = self._conn.execute(sqlalchemy.text(QUERY_ENUM_ARRAY_TABLE)) + for row in result: + yield models.ArrayEnumTable( + id=row[0], + value=row[1], + ) + + def query_param_enum_table(self, *, value: Optional[models.QueryParamEnumTableEnum]) -> Optional[models.QueryParamEnumTable]: + row = self._conn.execute(sqlalchemy.text(QUERY_PARAM_ENUM_TABLE), {"p1": value}).first() + if row is None: + return None + return models.QueryParamEnumTable( + id=row[0], + other=row[1], + value=row[2], + ) + + def query_param_struct_enum_table(self, *, id: int, value: Optional[models.QueryParamStructEnumTableEnum]) -> Optional[int]: + row = self._conn.execute(sqlalchemy.text(QUERY_PARAM_STRUCT_ENUM_TABLE), {"p1": id, "p2": value}).first() + if row is None: + return None + return row[0] + + def query_return_enum_table(self, *, id: int) -> Optional[Optional[models.QueryReturnEnumTableEnum]]: + row = self._conn.execute(sqlalchemy.text(QUERY_RETURN_ENUM_TABLE), {"p1": id}).first() + if row is None: + return None + return row[0] + + def query_return_full_table(self) -> Iterator[models.QueryReturnFullTable]: + result = self._conn.execute(sqlalchemy.text(QUERY_RETURN_FULL_TABLE)) + for row in result: + yield models.QueryReturnFullTable( + id=row[0], + value=row[1], + ) + + def query_return_struct_enum_table(self, *, id: int) -> Optional[query_return_struct_enum_tableRow]: + row = self._conn.execute(sqlalchemy.text(QUERY_RETURN_STRUCT_ENUM_TABLE), {"p1": id}).first() + if row is None: + return None + return query_return_struct_enum_tableRow( + value=row[0], + another=row[1], + ) + + def query_sqlc_embed_table(self, *, id: int) -> Optional[Optional[Any]]: + row = self._conn.execute(sqlalchemy.text(QUERY_SQLC_EMBED_TABLE), {"p1": id}).first() + if row is None: + return None + return row[0] + + +class AsyncQuerier: + def __init__(self, conn: sqlalchemy.ext.asyncio.AsyncConnection): + self._conn = conn + + async def query_enum_array_table(self) -> AsyncIterator[models.ArrayEnumTable]: + result = await self._conn.stream(sqlalchemy.text(QUERY_ENUM_ARRAY_TABLE)) + async for row in result: + yield models.ArrayEnumTable( + id=row[0], + value=row[1], + ) + + async def query_param_enum_table(self, *, value: Optional[models.QueryParamEnumTableEnum]) -> Optional[models.QueryParamEnumTable]: + row = (await self._conn.execute(sqlalchemy.text(QUERY_PARAM_ENUM_TABLE), {"p1": value})).first() + if row is None: + return None + return models.QueryParamEnumTable( + id=row[0], + other=row[1], + value=row[2], + ) + + async def query_param_struct_enum_table(self, *, id: int, value: Optional[models.QueryParamStructEnumTableEnum]) -> Optional[int]: + row = (await self._conn.execute(sqlalchemy.text(QUERY_PARAM_STRUCT_ENUM_TABLE), {"p1": id, "p2": value})).first() + if row is None: + return None + return row[0] + + async def query_return_enum_table(self, *, id: int) -> Optional[Optional[models.QueryReturnEnumTableEnum]]: + row = (await self._conn.execute(sqlalchemy.text(QUERY_RETURN_ENUM_TABLE), {"p1": id})).first() + if row is None: + return None + return row[0] + + async def query_return_full_table(self) -> AsyncIterator[models.QueryReturnFullTable]: + result = await self._conn.stream(sqlalchemy.text(QUERY_RETURN_FULL_TABLE)) + async for row in result: + yield models.QueryReturnFullTable( + id=row[0], + value=row[1], + ) + + async def query_return_struct_enum_table(self, *, id: int) -> Optional[query_return_struct_enum_tableRow]: + row = (await self._conn.execute(sqlalchemy.text(QUERY_RETURN_STRUCT_ENUM_TABLE), {"p1": id})).first() + if row is None: + return None + return query_return_struct_enum_tableRow( + value=row[0], + another=row[1], + ) + + async def query_sqlc_embed_table(self, *, id: int) -> Optional[Optional[Any]]: + row = (await self._conn.execute(sqlalchemy.text(QUERY_SQLC_EMBED_TABLE), {"p1": id})).first() + if row is None: + return None + return row[0] diff --git a/internal/endtoend/testdata/omit_unused_structs/query.sql b/internal/endtoend/testdata/omit_unused_structs/query.sql new file mode 100644 index 0000000..60104c8 --- /dev/null +++ b/internal/endtoend/testdata/omit_unused_structs/query.sql @@ -0,0 +1,20 @@ +-- name: query_return_full_table :many +SELECT * FROM query_return_full_table; + +-- name: query_param_enum_table :one +SELECT * FROM query_param_enum_table WHERE value = $1; + +-- name: query_param_struct_enum_table :one +SELECT id FROM query_param_struct_enum_table WHERE id = $1 AND value = $2; + +-- name: query_return_enum_table :one +SELECT value FROM query_return_enum_table WHERE id = $1; + +-- name: query_return_struct_enum_table :one +SELECT value, another FROM query_return_struct_enum_table WHERE id = $1; + +-- name: query_sqlc_embed_table :one +SELECT sqlc.embed(query_sqlc_embed_table) FROM query_sqlc_embed_table WHERE id = $1; + +-- name: query_enum_array_table :many +SELECT * FROM array_enum_table; diff --git a/internal/endtoend/testdata/omit_unused_structs/schema.sql b/internal/endtoend/testdata/omit_unused_structs/schema.sql new file mode 100644 index 0000000..4ce5921 --- /dev/null +++ b/internal/endtoend/testdata/omit_unused_structs/schema.sql @@ -0,0 +1,69 @@ +CREATE TYPE unused_enum AS ENUM ( + 'a', 'b' +); + +CREATE TYPE unused_table_enum AS ENUM ( + 'c', 'd' +); +CREATE TABLE unused_table ( + id INTEGER PRIMARY KEY, + value unused_table_enum +); + +CREATE TYPE query_return_full_table_enum AS ENUM ( + 'e', 'f' +); +CREATE TABLE query_return_full_table ( + id INTEGER PRIMARY KEY, + value query_return_full_table_enum +); + +CREATE TYPE query_param_enum_table_enum AS ENUM ( + 'g', 'h' +); +CREATE TABLE query_param_enum_table ( + id INTEGER PRIMARY KEY, + other query_param_enum_table_enum NOT NULL, + value query_param_enum_table_enum +); + +CREATE TYPE query_param_struct_enum_table_enum AS ENUM ( + 'i', 'j' +); +CREATE TABLE query_param_struct_enum_table ( + id INTEGER PRIMARY KEY, + value query_param_struct_enum_table_enum +); + +CREATE TYPE query_return_enum_table_enum AS ENUM ( + 'k', 'l' +); +CREATE TABLE query_return_enum_table ( + id INTEGER PRIMARY KEY, + value query_return_enum_table_enum +); + +CREATE TYPE query_return_struct_enum_table_enum AS ENUM ( + 'k', 'l' +); +CREATE TABLE query_return_struct_enum_table ( + id INTEGER PRIMARY KEY, + value query_return_struct_enum_table_enum, + another INTEGER +); + +CREATE TYPE query_sqlc_embed_enum AS ENUM ( + 'm', 'n' +); +CREATE TABLE query_sqlc_embed_table ( + id INTEGER PRIMARY KEY, + value query_sqlc_embed_enum +); + +CREATE TYPE array_enum AS ENUM ( + 'o', 'p' +); +CREATE TABLE array_enum_table ( + id INTEGER PRIMARY KEY, + value array_enum[] +); diff --git a/internal/endtoend/testdata/omit_unused_structs/sqlc.yaml b/internal/endtoend/testdata/omit_unused_structs/sqlc.yaml new file mode 100644 index 0000000..a24614c --- /dev/null +++ b/internal/endtoend/testdata/omit_unused_structs/sqlc.yaml @@ -0,0 +1,19 @@ +version: "2" +plugins: + - name: py + wasm: + url: file://../../../../bin/sqlc-gen-python.wasm + sha256: "35db40d0d3e1f8e62575e9954f2f83f29b65bb01ac245703899c672fd5dec208" +sql: + - schema: schema.sql + queries: query.sql + engine: postgresql + codegen: + - plugin: py + out: db + options: + package: db + emit_sync_querier: true + emit_async_querier: true + emit_str_enum: true + omit_unused_structs: true diff --git a/internal/endtoend/testdata/query_parameter_limit_two/python/models.py b/internal/endtoend/testdata/query_parameter_limit_two/python/models.py index 059675d..2ddf019 100644 --- a/internal/endtoend/testdata/query_parameter_limit_two/python/models.py +++ b/internal/endtoend/testdata/query_parameter_limit_two/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 import dataclasses diff --git a/internal/endtoend/testdata/query_parameter_limit_two/python/query.py b/internal/endtoend/testdata/query_parameter_limit_two/python/query.py index e8b723e..5a97c59 100644 --- a/internal/endtoend/testdata/query_parameter_limit_two/python/query.py +++ b/internal/endtoend/testdata/query_parameter_limit_two/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 # source: query.sql import sqlalchemy import sqlalchemy.ext.asyncio diff --git a/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml index 336bca7..b46e924 100644 --- a/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "35db40d0d3e1f8e62575e9954f2f83f29b65bb01ac245703899c672fd5dec208" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_undefined/python/models.py b/internal/endtoend/testdata/query_parameter_limit_undefined/python/models.py index 30e80db..77bdfe5 100644 --- a/internal/endtoend/testdata/query_parameter_limit_undefined/python/models.py +++ b/internal/endtoend/testdata/query_parameter_limit_undefined/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 import dataclasses diff --git a/internal/endtoend/testdata/query_parameter_limit_undefined/python/query.py b/internal/endtoend/testdata/query_parameter_limit_undefined/python/query.py index 5a1fbbc..6380dce 100644 --- a/internal/endtoend/testdata/query_parameter_limit_undefined/python/query.py +++ b/internal/endtoend/testdata/query_parameter_limit_undefined/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 # source: query.sql import sqlalchemy import sqlalchemy.ext.asyncio diff --git a/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml index c20cd57..be60136 100644 --- a/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "35db40d0d3e1f8e62575e9954f2f83f29b65bb01ac245703899c672fd5dec208" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_zero/python/models.py b/internal/endtoend/testdata/query_parameter_limit_zero/python/models.py index 059675d..2ddf019 100644 --- a/internal/endtoend/testdata/query_parameter_limit_zero/python/models.py +++ b/internal/endtoend/testdata/query_parameter_limit_zero/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 import dataclasses diff --git a/internal/endtoend/testdata/query_parameter_limit_zero/python/query.py b/internal/endtoend/testdata/query_parameter_limit_zero/python/query.py index 47bd6a9..5edcd9c 100644 --- a/internal/endtoend/testdata/query_parameter_limit_zero/python/query.py +++ b/internal/endtoend/testdata/query_parameter_limit_zero/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.30.0 # source: query.sql import dataclasses diff --git a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml index 6e2cdeb..ade2630 100644 --- a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "35db40d0d3e1f8e62575e9954f2f83f29b65bb01ac245703899c672fd5dec208" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml b/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml index c432e4f..768875a 100644 --- a/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml +++ b/internal/endtoend/testdata/query_parameter_no_limit/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "d6846ffad948181e611e883cedd2d2be66e091edc1273a0abc6c9da18399e0ca" + sha256: "35db40d0d3e1f8e62575e9954f2f83f29b65bb01ac245703899c672fd5dec208" sql: - schema: schema.sql queries: query.sql diff --git a/internal/gen.go b/internal/gen.go index 6e50fae..982426a 100644 --- a/internal/gen.go +++ b/internal/gen.go @@ -49,10 +49,25 @@ func (t pyType) Annotation() *pyast.Node { return ann } +// typeNames returns InnerType plus the models.-stripped form. +// Optional/List are flags on pyType, not wrappers in the name. +func (t pyType) typeNames() []string { + if t.InnerType == "" { + return nil + } + names := []string{t.InnerType} + if bare := strings.TrimPrefix(t.InnerType, "models."); bare != t.InnerType { + names = append(names, bare) + } + return names +} + type Field struct { Name string Type pyType Comment string + // Embed is the table model behind sqlc.embed; used only by omit_unused_structs. + Embed *Struct } type Struct struct { @@ -67,6 +82,8 @@ type QueryValue struct { Name string Struct *Struct Typ pyType + // Embed is the table model behind a single-column sqlc.embed return. + Embed *Struct } func (v QueryValue) Annotation() *pyast.Node { @@ -321,7 +338,24 @@ type pyColumn struct { *plugin.Column } -func columnsToStruct(req *plugin.GenerateRequest, name string, columns []pyColumn) *Struct { +func findEmbedModel(structs []Struct, embed *plugin.Identifier, defaultSchema string) *Struct { + if embed == nil { + return nil + } + embedSchema := defaultSchema + if embed.Schema != "" { + embedSchema = embed.Schema + } + for i := range structs { + s := &structs[i] + if embed.Name == s.Table.Name && embedSchema == s.Table.Schema { + return s + } + } + return nil +} + +func columnsToStruct(req *plugin.GenerateRequest, name string, columns []pyColumn, structs []Struct) *Struct { gs := Struct{ Name: name, } @@ -343,8 +377,9 @@ func columnsToStruct(req *plugin.GenerateRequest, name string, columns []pyColum fieldName = fmt.Sprintf("%s_%d", fieldName, suffix) } gs.Fields = append(gs.Fields, Field{ - Name: fieldName, - Type: makePyType(req, c.Column), + Name: fieldName, + Type: makePyType(req, c.Column), + Embed: findEmbedModel(structs, c.EmbedTable, req.Catalog.DefaultSchema), }) seen[colName]++ } @@ -406,7 +441,7 @@ func buildQueries(conf Config, req *plugin.GenerateRequest, structs []Struct) ([ gq.Args = []QueryValue{{ Emit: true, Name: "arg", - Struct: columnsToStruct(req, query.Name+"Params", cols), + Struct: columnsToStruct(req, query.Name+"Params", cols, structs), }} } else { args := make([]QueryValue, 0, len(query.Params)) @@ -422,8 +457,9 @@ func buildQueries(conf Config, req *plugin.GenerateRequest, structs []Struct) ([ if len(query.Columns) == 1 { c := query.Columns[0] gq.Ret = QueryValue{ - Name: columnName(c, 0), - Typ: makePyType(req, c), + Name: columnName(c, 0), + Typ: makePyType(req, c), + Embed: findEmbedModel(structs, c.EmbedTable, req.Catalog.DefaultSchema), } } else if len(query.Columns) > 1 { var gs *Struct @@ -461,7 +497,7 @@ func buildQueries(conf Config, req *plugin.GenerateRequest, structs []Struct) ([ Column: c, }) } - gs = columnsToStruct(req, query.Name+"Row", columns) + gs = columnsToStruct(req, query.Name+"Row", columns, structs) emit = true } gq.Ret = QueryValue{ @@ -1081,6 +1117,72 @@ func HashComment(s string) string { return "# " + strings.ReplaceAll(s, "\n", "\n# ") } +func filterUnusedStructs(enums []Enum, models []Struct, queries []Query) ([]Enum, []Struct) { + keepTypes := make(map[string]struct{}) + + keep := func(t string) { + keepTypes[t] = struct{}{} + if bare := strings.TrimPrefix(t, "models."); bare != t { + keepTypes[bare] = struct{}{} + } + } + + keepPyType := func(t pyType) { + for _, name := range t.typeNames() { + keep(name) + } + } + + var keepStruct func(*Struct) + keepStruct = func(s *Struct) { + if s == nil { + return + } + keep(s.Name) + for _, f := range s.Fields { + keepPyType(f.Type) + keepStruct(f.Embed) + } + } + + keepValue := func(v QueryValue) { + if v.isEmpty() { + return + } + if v.IsStruct() { + keepStruct(v.Struct) + } else { + keepPyType(v.Typ) + } + keepStruct(v.Embed) + } + + for _, query := range queries { + for _, arg := range query.Args { + keepValue(arg) + } + keepValue(query.Ret) + } + + keepEnums := make([]Enum, 0, len(enums)) + for _, enum := range enums { + _, keepBare := keepTypes[enum.Name] + _, keepQualified := keepTypes["models."+enum.Name] + if keepBare || keepQualified { + keepEnums = append(keepEnums, enum) + } + } + + keepStructs := make([]Struct, 0, len(models)) + for _, st := range models { + if _, ok := keepTypes[st.Name]; ok { + keepStructs = append(keepStructs, st) + } + } + + return keepEnums, keepStructs +} + func Generate(_ context.Context, req *plugin.GenerateRequest) (*plugin.GenerateResponse, error) { var conf Config if len(req.PluginOptions) > 0 { @@ -1096,6 +1198,10 @@ func Generate(_ context.Context, req *plugin.GenerateRequest) (*plugin.GenerateR return nil, err } + if conf.OmitUnusedStructs { + enums, models = filterUnusedStructs(enums, models, queries) + } + i := &importer{ Models: models, Queries: queries,