From 32c348ea7cba69283c476ff9c57effb5060579b1 Mon Sep 17 00:00:00 2001 From: Kellen Miller Date: Sat, 23 Aug 2025 14:19:44 -0400 Subject: [PATCH 01/10] Add support for `:copyfrom` via executemany() --- .gitignore | 2 + examples/src/authors/models.py | 2 +- examples/src/authors/query.py | 17 +- examples/src/authors/query.sql | 3 + 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 +- examples/src/tests/test_authors.py | 26 +++ .../testdata/copyfrom/python/models.py | 24 +++ .../testdata/copyfrom/python/query.py | 158 ++++++++++++++++++ internal/endtoend/testdata/copyfrom/query.sql | 17 ++ .../endtoend/testdata/copyfrom/schema.sql | 15 ++ internal/endtoend/testdata/copyfrom/sqlc.yaml | 17 ++ .../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 +- .../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 | 128 ++++++++++++-- internal/imports.go | 7 + 44 files changed, 435 insertions(+), 45 deletions(-) create mode 100644 internal/endtoend/testdata/copyfrom/python/models.py create mode 100644 internal/endtoend/testdata/copyfrom/python/query.py create mode 100644 internal/endtoend/testdata/copyfrom/query.sql create mode 100644 internal/endtoend/testdata/copyfrom/schema.sql create mode 100644 internal/endtoend/testdata/copyfrom/sqlc.yaml diff --git a/.gitignore b/.gitignore index 60b4f3d..d66bae9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ bin .direnv .devenv* devenv.local.nix + +.idea/ diff --git a/examples/src/authors/models.py b/examples/src/authors/models.py index 96553a5..b3b9554 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.29.0 import dataclasses from typing import Optional diff --git a/examples/src/authors/query.py b/examples/src/authors/query.py index 019f877..d10cc65 100644 --- a/examples/src/authors/query.py +++ b/examples/src/authors/query.py @@ -1,8 +1,8 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.28.0 +# sqlc v1.29.0 # source: query.sql -from typing import AsyncIterator, Iterator, Optional +from typing import Any, AsyncIterator, Iterator, List, Optional import sqlalchemy import sqlalchemy.ext.asyncio @@ -20,6 +20,11 @@ """ +CREATE_AUTHORS_BATCH = """-- name: create_authors_batch \\:copyfrom +INSERT INTO authors (name, bio) VALUES (:p1, :p2) +""" + + DELETE_AUTHOR = """-- name: delete_author \\:exec DELETE FROM authors WHERE id = :p1 @@ -52,6 +57,10 @@ def create_author(self, *, name: str, bio: Optional[str]) -> Optional[models.Aut bio=row[2], ) + def create_authors_batch(self, arg_list: List[Any]) -> int: + result = self._conn.executemany(sqlalchemy.text(CREATE_AUTHORS_BATCH), arg_list) + return result.rowcount + def delete_author(self, *, id: int) -> None: self._conn.execute(sqlalchemy.text(DELETE_AUTHOR), {"p1": id}) @@ -89,6 +98,10 @@ async def create_author(self, *, name: str, bio: Optional[str]) -> Optional[mode bio=row[2], ) + async def create_authors_batch(self, arg_list: List[Any]) -> int: + result = await self._conn.executemany(sqlalchemy.text(CREATE_AUTHORS_BATCH), arg_list) + return result.rowcount + async def delete_author(self, *, id: int) -> None: await self._conn.execute(sqlalchemy.text(DELETE_AUTHOR), {"p1": id}) diff --git a/examples/src/authors/query.sql b/examples/src/authors/query.sql index 75e38b2..e5e75cf 100644 --- a/examples/src/authors/query.sql +++ b/examples/src/authors/query.sql @@ -17,3 +17,6 @@ RETURNING *; -- name: DeleteAuthor :exec DELETE FROM authors WHERE id = $1; + +-- name: CreateAuthorsBatch :copyfrom +INSERT INTO authors (name, bio) VALUES ($1, $2); diff --git a/examples/src/booktest/models.py b/examples/src/booktest/models.py index d7ee131..dcfbc20 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.29.0 import dataclasses import datetime import enum diff --git a/examples/src/booktest/query.py b/examples/src/booktest/query.py index bc71f22..12d3717 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.29.0 # source: query.sql import dataclasses import datetime diff --git a/examples/src/jets/models.py b/examples/src/jets/models.py index 0d4eb5d..fc5464b 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.29.0 import dataclasses diff --git a/examples/src/jets/query-building.py b/examples/src/jets/query-building.py index 7651116..adcdcdb 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.29.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..2f2da93 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.29.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..a32fea2 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.29.0 import dataclasses import datetime import enum diff --git a/examples/src/ondeck/venue.py b/examples/src/ondeck/venue.py index 6159bf6..1911cb3 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.29.0 # source: venue.sql import dataclasses from typing import AsyncIterator, List, Optional diff --git a/examples/src/tests/test_authors.py b/examples/src/tests/test_authors.py index c3031cd..d679a62 100644 --- a/examples/src/tests/test_authors.py +++ b/examples/src/tests/test_authors.py @@ -29,6 +29,18 @@ def test_authors(db: sqlalchemy.engine.Connection): assert len(author_list) == 1 assert author_list[0] == new_author + # Test batch insert with copyfrom + batch_authors = [ + {"p1": "Dennis Ritchie", "p2": "Creator of C Programming Language"}, + {"p1": "Ken Thompson", "p2": "Creator of Unix and Go Programming Language"}, + {"p1": "Rob Pike", "p2": "Co-creator of Go Programming Language"}, + ] + rows_affected = querier.create_authors_batch(batch_authors) + assert rows_affected == 3 + + all_authors = list(querier.list_authors()) + assert len(all_authors) == 4 # 1 existing + 3 batch inserted + @pytest.mark.asyncio async def test_authors_async(async_db: sqlalchemy.ext.asyncio.AsyncConnection): @@ -54,3 +66,17 @@ async def test_authors_async(async_db: sqlalchemy.ext.asyncio.AsyncConnection): author_list.append(author) assert len(author_list) == 1 assert author_list[0] == new_author + + # Test batch insert with copyfrom + batch_authors = [ + {"p1": "Dennis Ritchie", "p2": "Creator of C Programming Language"}, + {"p1": "Ken Thompson", "p2": "Creator of Unix and Go Programming Language"}, + {"p1": "Rob Pike", "p2": "Co-creator of Go Programming Language"}, + ] + rows_affected = await querier.create_authors_batch(batch_authors) + assert rows_affected == 3 + + all_authors = [] + async for author in querier.list_authors(): + all_authors.append(author) + assert len(all_authors) == 4 # 1 existing + 3 batch inserted diff --git a/internal/endtoend/testdata/copyfrom/python/models.py b/internal/endtoend/testdata/copyfrom/python/models.py new file mode 100644 index 0000000..e728373 --- /dev/null +++ b/internal/endtoend/testdata/copyfrom/python/models.py @@ -0,0 +1,24 @@ +# Code generated by sqlc. DO NOT EDIT. +# versions: +# sqlc v1.29.0 +import dataclasses +import datetime +from typing import Optional + + +@dataclasses.dataclass() +class Author: + id: int + name: str + bio: str + + +@dataclasses.dataclass() +class User: + id: int + email: str + name: str + bio: Optional[str] + age: Optional[int] + active: Optional[bool] + created_at: datetime.datetime diff --git a/internal/endtoend/testdata/copyfrom/python/query.py b/internal/endtoend/testdata/copyfrom/python/query.py new file mode 100644 index 0000000..f17f820 --- /dev/null +++ b/internal/endtoend/testdata/copyfrom/python/query.py @@ -0,0 +1,158 @@ +# Code generated by sqlc. DO NOT EDIT. +# versions: +# sqlc v1.29.0 +# source: query.sql +import dataclasses +from typing import Any, List, Optional + +import sqlalchemy +import sqlalchemy.ext.asyncio + +from copyfrom import models + + +CREATE_AUTHOR = """-- name: create_author \\:one +INSERT INTO authors (name, bio) VALUES (:p1, :p2) RETURNING id, name, bio +""" + + +CREATE_AUTHORS = """-- name: create_authors \\:copyfrom +INSERT INTO authors (name, bio) VALUES (:p1, :p2) +""" + + +CREATE_AUTHORS_NAMED = """-- name: create_authors_named \\:copyfrom +INSERT INTO authors (name, bio) VALUES (:p1, :p2) +""" + + +CREATE_USER = """-- name: create_user \\:one +INSERT INTO users (email, name) VALUES (:p1, :p2) RETURNING id, email, name, bio, age, active, created_at +""" + + +CREATE_USERS_BATCH = """-- name: create_users_batch \\:copyfrom +INSERT INTO users (email, name) VALUES (:p1, :p2) +""" + + +CREATE_USERS_WITH_DETAILS = """-- name: create_users_with_details \\:copyfrom +INSERT INTO users (email, name, bio, age, active) VALUES (:p1, :p2, :p3, :p4, :p5) +""" + + +@dataclasses.dataclass() +class CreateUsersWithDetailsParams: + email: str + name: str + bio: Optional[str] + age: Optional[int] + active: Optional[bool] + + +class Querier: + def __init__(self, conn: sqlalchemy.engine.Connection): + self._conn = conn + + def create_author(self, *, name: str, bio: str) -> Optional[models.Author]: + row = self._conn.execute(sqlalchemy.text(CREATE_AUTHOR), {"p1": name, "p2": bio}).first() + if row is None: + return None + return models.Author( + id=row[0], + name=row[1], + bio=row[2], + ) + + def create_authors(self, arg_list: List[Any]) -> int: + result = self._conn.executemany(sqlalchemy.text(CREATE_AUTHORS), arg_list) + return result.rowcount + + def create_authors_named(self, arg_list: List[Any]) -> int: + result = self._conn.executemany(sqlalchemy.text(CREATE_AUTHORS_NAMED), arg_list) + return result.rowcount + + def create_user(self, *, email: str, name: str) -> Optional[models.User]: + row = self._conn.execute(sqlalchemy.text(CREATE_USER), {"p1": email, "p2": name}).first() + if row is None: + return None + return models.User( + id=row[0], + email=row[1], + name=row[2], + bio=row[3], + age=row[4], + active=row[5], + created_at=row[6], + ) + + def create_users_batch(self, arg_list: List[Any]) -> int: + result = self._conn.executemany(sqlalchemy.text(CREATE_USERS_BATCH), arg_list) + return result.rowcount + + def create_users_with_details(self, arg_list: List[CreateUsersWithDetailsParams]) -> int: + data = list() + for item in arg_list: + data.append({ + "p1": item.email, + "p2": item.name, + "p3": item.bio, + "p4": item.age, + "p5": item.active, + }) + result = self._conn.executemany(sqlalchemy.text(CREATE_USERS_WITH_DETAILS), data) + return result.rowcount + + +class AsyncQuerier: + def __init__(self, conn: sqlalchemy.ext.asyncio.AsyncConnection): + self._conn = conn + + async def create_author(self, *, name: str, bio: str) -> Optional[models.Author]: + row = (await self._conn.execute(sqlalchemy.text(CREATE_AUTHOR), {"p1": name, "p2": bio})).first() + if row is None: + return None + return models.Author( + id=row[0], + name=row[1], + bio=row[2], + ) + + async def create_authors(self, arg_list: List[Any]) -> int: + result = await self._conn.executemany(sqlalchemy.text(CREATE_AUTHORS), arg_list) + return result.rowcount + + async def create_authors_named(self, arg_list: List[Any]) -> int: + result = await self._conn.executemany(sqlalchemy.text(CREATE_AUTHORS_NAMED), arg_list) + return result.rowcount + + async def create_user(self, *, email: str, name: str) -> Optional[models.User]: + row = (await self._conn.execute(sqlalchemy.text(CREATE_USER), {"p1": email, "p2": name})).first() + if row is None: + return None + return models.User( + id=row[0], + email=row[1], + name=row[2], + bio=row[3], + age=row[4], + active=row[5], + created_at=row[6], + ) + + async def create_users_batch(self, arg_list: List[Any]) -> int: + result = await self._conn.executemany(sqlalchemy.text(CREATE_USERS_BATCH), arg_list) + return result.rowcount + + async def create_users_with_details(self, arg_list: List[CreateUsersWithDetailsParams]) -> int: + data = list() + for item in arg_list: + data.append({ + "p1": item.email, + "p2": item.name, + "p3": item.bio, + "p4": item.age, + "p5": item.active, + }) + result = await self._conn.executemany(sqlalchemy.text(CREATE_USERS_WITH_DETAILS), data) + return result.rowcount diff --git a/internal/endtoend/testdata/copyfrom/query.sql b/internal/endtoend/testdata/copyfrom/query.sql new file mode 100644 index 0000000..576b14c --- /dev/null +++ b/internal/endtoend/testdata/copyfrom/query.sql @@ -0,0 +1,17 @@ +-- name: CreateAuthors :copyfrom +INSERT INTO authors (name, bio) VALUES ($1, $2); + +-- name: CreateAuthor :one +INSERT INTO authors (name, bio) VALUES ($1, $2) RETURNING *; + +-- name: CreateAuthorsNamed :copyfrom +INSERT INTO authors (name, bio) VALUES (@name, @bio); + +-- name: CreateUser :one +INSERT INTO users (email, name) VALUES (@email, @name) RETURNING *; + +-- name: CreateUsersBatch :copyfrom +INSERT INTO users (email, name) VALUES (@email, @name); + +-- name: CreateUsersWithDetails :copyfrom +INSERT INTO users (email, name, bio, age, active) VALUES ($1, $2, $3, $4, $5); \ No newline at end of file diff --git a/internal/endtoend/testdata/copyfrom/schema.sql b/internal/endtoend/testdata/copyfrom/schema.sql new file mode 100644 index 0000000..3ce88a6 --- /dev/null +++ b/internal/endtoend/testdata/copyfrom/schema.sql @@ -0,0 +1,15 @@ +CREATE TABLE authors ( + id SERIAL PRIMARY KEY, + name text NOT NULL, + bio text NOT NULL +); + +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + email text NOT NULL, + name text NOT NULL, + bio text, + age int, + active boolean DEFAULT true, + created_at timestamp NOT NULL DEFAULT NOW() +); \ No newline at end of file diff --git a/internal/endtoend/testdata/copyfrom/sqlc.yaml b/internal/endtoend/testdata/copyfrom/sqlc.yaml new file mode 100644 index 0000000..9d0c56e --- /dev/null +++ b/internal/endtoend/testdata/copyfrom/sqlc.yaml @@ -0,0 +1,17 @@ +version: "2" +plugins: + - name: py + wasm: + url: file://../../../../bin/sqlc-gen-python.wasm + sha256: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" +sql: + - schema: "schema.sql" + queries: "query.sql" + engine: postgresql + codegen: + - out: python + plugin: py + options: + package: copyfrom + emit_sync_querier: true + emit_async_querier: true \ No newline at end of file diff --git a/internal/endtoend/testdata/emit_pydantic_models/db/models.py b/internal/endtoend/testdata/emit_pydantic_models/db/models.py index 7676e5c..61ad3eb 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.29.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..cc36118 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.29.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..456ccf2 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" 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..aa43ab1 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.29.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..5ea0264 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.29.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..62296ae 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" 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..6d3e9f5 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.29.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..c9c6e21 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.29.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..0e7eb1a 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" 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..6d3e9f5 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.29.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..a678f3d 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.29.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..0e7eb1a 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" 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..fc76620 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.29.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..1fc92fd 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.29.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..47daf09 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" sql: - schema: schema.sql queries: query.sql 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..89c0f8d 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.29.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..0d9bd97 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.29.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..e5f79f7 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" 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..dc09dab 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.29.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..49b7bd1 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.29.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..d4db347 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" 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..89c0f8d 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.29.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..38e0efb 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.29.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..332f2b9 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" 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..5c20b23 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" sql: - schema: schema.sql queries: query.sql diff --git a/internal/gen.go b/internal/gen.go index 6e50fae..716d629 100644 --- a/internal/gen.go +++ b/internal/gen.go @@ -2,7 +2,7 @@ package python import ( "context" - json "encoding/json" + "encoding/json" "errors" "fmt" "log" @@ -10,7 +10,6 @@ import ( "sort" "strings" - "github.com/sqlc-dev/plugin-sdk-go/metadata" "github.com/sqlc-dev/plugin-sdk-go/plugin" "github.com/sqlc-dev/plugin-sdk-go/sdk" @@ -135,6 +134,33 @@ type Query struct { } func (q Query) AddArgs(args *pyast.Arguments) { + switch q.Cmd { + case ":copyfrom": + q.addCopyFromArgs(args) + default: + q.addRegularArgs(args) + } +} + +// addCopyFromArgs adds arguments for :copyfrom commands +func (q Query) addCopyFromArgs(args *pyast.Arguments) { + // Check if we have a struct parameter + if len(q.Args) == 1 && q.Args[0].IsStruct() { + args.Args = append(args.Args, &pyast.Arg{ + Arg: q.Args[0].Name + "_list", + Annotation: subscriptNode("List", q.Args[0].Annotation()), + }) + } else { + // Fall back to List[Any] for individual parameters + args.Args = append(args.Args, &pyast.Arg{ + Arg: "arg_list", + Annotation: subscriptNode("List", poet.Name("Any")), + }) + } +} + +// addRegularArgs adds arguments for regular (non-copyfrom) commands +func (q Query) addRegularArgs(args *pyast.Arguments) { // A single struct arg does not need to be passed as a keyword argument if len(q.Args) == 1 && q.Args[0].IsStruct() { args.Args = append(args.Args, &pyast.Arg{ @@ -143,6 +169,8 @@ func (q Query) AddArgs(args *pyast.Arguments) { }) return } + + // Multiple args or non-struct args are passed as keyword arguments for _, a := range q.Args { args.KwOnlyArgs = append(args.KwOnlyArgs, &pyast.Arg{ Arg: a.Name, @@ -180,6 +208,79 @@ func (q Query) ArgDictNode() *pyast.Node { } } +// BuildCopyFromBody generates the method body for :copyfrom commands. +func (q Query) BuildCopyFromBody(isAsync bool) []*pyast.Node { + var body []*pyast.Node + + dataVar := "arg_list" + if len(q.Args) == 1 && q.Args[0].IsStruct() { + argName := q.Args[0].Name + "_list" + dataVar = "data" + body = append(body, q.buildStructToDictList(argName, dataVar)...) + } + + sqlText := poet.Node(&pyast.Call{ + Func: poet.Attribute(poet.Name("sqlalchemy"), "text"), + Args: []*pyast.Node{poet.Name(q.ConstantName)}, + }) + + execCall := poet.Node(&pyast.Call{ + Func: poet.Attribute(poet.Name("self._conn"), "executemany"), + Args: []*pyast.Node{ + sqlText, + poet.Name(dataVar), + }, + }) + + if isAsync { + execCall = poet.Await(execCall) + } + + body = append(body, + assignNode("result", execCall), + poet.Return(poet.Attribute(poet.Name("result"), "rowcount")), + ) + + return body +} + +// buildStructToDictList converts a list of parameter structs to a list of dicts for SQLAlchemy +func (q Query) buildStructToDictList(sourceVar, targetVar string) []*pyast.Node { + var body []*pyast.Node + + body = append(body, assignNode(targetVar, poet.Node(&pyast.Call{ + Func: poet.Name("list"), + Args: []*pyast.Node{}, + }))) + + loopVar := "item" + dict := &pyast.Dict{} + for i, field := range q.Args[0].Struct.Fields { + paramName := fmt.Sprintf("p%v", i+1) + dict.Keys = append(dict.Keys, poet.Constant(paramName)) + dict.Values = append(dict.Values, poet.Attribute(poet.Name(loopVar), field.Name)) + } + + body = append(body, poet.Node(&pyast.For{ + Target: poet.Name(loopVar), + Iter: poet.Name(sourceVar), + Body: []*pyast.Node{ + poet.Node(&pyast.Call{ + Func: poet.Attribute(poet.Name(targetVar), "append"), + Args: []*pyast.Node{ + { + Node: &pyast.Node_Dict{ + Dict: dict, + }, + }, + }, + }), + }, + })) + + return body +} + func makePyType(req *plugin.GenerateRequest, col *plugin.Column) pyType { typ := pyInnerType(req, col) return pyType{ @@ -372,9 +473,6 @@ func buildQueries(conf Config, req *plugin.GenerateRequest, structs []Struct) ([ if query.Cmd == "" { continue } - if query.Cmd == metadata.CmdCopyFrom { - return nil, errors.New("Support for CopyFrom in Python is not implemented") - } methodName := methodName(query.Name) @@ -403,11 +501,13 @@ func buildQueries(conf Config, req *plugin.GenerateRequest, structs []Struct) ([ Column: p.Column, }) } - gq.Args = []QueryValue{{ - Emit: true, - Name: "arg", - Struct: columnsToStruct(req, query.Name+"Params", cols), - }} + gq.Args = []QueryValue{ + { + Emit: true, + Name: "arg", + Struct: columnsToStruct(req, query.Name+"Params", cols), + }, + } } else { args := make([]QueryValue, 0, len(query.Params)) for _, p := range query.Params { @@ -959,6 +1059,10 @@ func buildQueryTree(ctx *pyTmplCtx, i *importer, source string) *pyast.Node { poet.Return(exec), ) f.Returns = typeRefNode("sqlalchemy", "engine", "Result") + case ":copyfrom": + // For copyfrom, use executemany for batch inserts + f.Body = append(f.Body, q.BuildCopyFromBody(false)...) + f.Returns = poet.Name("int") default: panic("unknown cmd " + q.Cmd) } @@ -1052,6 +1156,10 @@ func buildQueryTree(ctx *pyTmplCtx, i *importer, source string) *pyast.Node { poet.Return(poet.Await(exec)), ) f.Returns = typeRefNode("sqlalchemy", "engine", "Result") + case ":copyfrom": + // For async copyfrom, use executemany for batch inserts + f.Body = append(f.Body, q.BuildCopyFromBody(true)...) + f.Returns = poet.Name("int") default: panic("unknown cmd " + q.Cmd) } diff --git a/internal/imports.go b/internal/imports.go index b88c58c..b066b6f 100644 --- a/internal/imports.go +++ b/internal/imports.go @@ -161,6 +161,13 @@ func (i *importer) queryImportSpecs(fileName string) (map[string]importSpec, map std["typing.AsyncIterator"] = importSpec{Module: "typing", Name: "AsyncIterator"} } } + if q.Cmd == ":copyfrom" { + std["typing.List"] = importSpec{Module: "typing", Name: "List"} + // Add Any if non-struct args + if !(len(q.Args) == 1 && q.Args[0].IsStruct()) { + std["typing.Any"] = importSpec{Module: "typing", Name: "Any"} + } + } queryValueModelImports(q.Ret) for _, qv := range q.Args { queryValueModelImports(qv) From f265a4f0441ba9af04ce5c937069b5d1b9cfd947 Mon Sep 17 00:00:00 2001 From: Kellen Miller Date: Sat, 6 Sep 2025 11:06:13 -0400 Subject: [PATCH 02/10] Switch `executemany()` to `execute()` for batch inserts --- README.md | 46 +++++++++++++++++++ examples/src/authors/query.py | 4 +- .../testdata/copyfrom/python/query.py | 16 +++---- internal/endtoend/testdata/copyfrom/sqlc.yaml | 2 +- .../testdata/emit_pydantic_models/sqlc.yaml | 2 +- .../endtoend/testdata/emit_str_enum/sqlc.yaml | 2 +- .../endtoend/testdata/exec_result/sqlc.yaml | 2 +- .../endtoend/testdata/exec_rows/sqlc.yaml | 2 +- .../inflection_exclude_table_names/sqlc.yaml | 2 +- .../query_parameter_limit_two/sqlc.yaml | 2 +- .../query_parameter_limit_undefined/sqlc.yaml | 2 +- .../query_parameter_limit_zero/sqlc.yaml | 2 +- .../query_parameter_no_limit/sqlc.yaml | 2 +- internal/gen.go | 2 +- 14 files changed, 67 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index c9f2531..d6d59f0 100644 --- a/README.md +++ b/README.md @@ -76,3 +76,49 @@ class Status(str, enum.Enum): OPEN = "op!en" CLOSED = "clo@sed" ``` + +### Bulk Inserts with `:copyfrom` + +Use the `:copyfrom` command to generate batch insert methods that leverage SQLAlchemy’s executemany behavior via `Connection.execute()` with a list of parameter mappings. + +SQL (example): + +```sql +-- name: CreateUsersBatch :copyfrom +INSERT INTO users (email, name) VALUES ($1, $2); +``` + +Generated methods: + +```py +def create_users_batch(self, arg_list: List[Any]) -> int +async def create_users_batch(self, arg_list: List[Any]) -> int +``` + +Call with a list of dicts using positional parameter keys `p1..pN` (the generator converts `$1`/`@name` to `:pN`): + +```py +rows = [ + {"p1": "a@example.com", "p2": "Alice"}, + {"p1": "b@example.com", "p2": "Bob"}, +] +count = queries.create_users_batch(rows) # returns affected rowcount (int) +``` + +When a typed params struct is emitted (e.g., many parameters or config thresholds), the method accepts `List[Params]`. The generator converts items to dicts internally: + +```py +@dataclasses.dataclass() +class CreateUsersWithDetailsParams: + email: str + name: str + bio: Optional[str] + age: Optional[int] + active: Optional[bool] + +count = queries.create_users_with_details([ + CreateUsersWithDetailsParams("a@example.com", "Alice", None, None, True), +]) +``` + +Implementation note: sync and async use `conn.execute(sqlalchemy.text(SQL), list_of_dicts)` and `await async_conn.execute(...)` respectively; SQLAlchemy performs efficient batch inserts under the hood. diff --git a/examples/src/authors/query.py b/examples/src/authors/query.py index d10cc65..4463c85 100644 --- a/examples/src/authors/query.py +++ b/examples/src/authors/query.py @@ -58,7 +58,7 @@ def create_author(self, *, name: str, bio: Optional[str]) -> Optional[models.Aut ) def create_authors_batch(self, arg_list: List[Any]) -> int: - result = self._conn.executemany(sqlalchemy.text(CREATE_AUTHORS_BATCH), arg_list) + result = self._conn.execute(sqlalchemy.text(CREATE_AUTHORS_BATCH), arg_list) return result.rowcount def delete_author(self, *, id: int) -> None: @@ -99,7 +99,7 @@ async def create_author(self, *, name: str, bio: Optional[str]) -> Optional[mode ) async def create_authors_batch(self, arg_list: List[Any]) -> int: - result = await self._conn.executemany(sqlalchemy.text(CREATE_AUTHORS_BATCH), arg_list) + result = await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS_BATCH), arg_list) return result.rowcount async def delete_author(self, *, id: int) -> None: diff --git a/internal/endtoend/testdata/copyfrom/python/query.py b/internal/endtoend/testdata/copyfrom/python/query.py index f17f820..1c339ec 100644 --- a/internal/endtoend/testdata/copyfrom/python/query.py +++ b/internal/endtoend/testdata/copyfrom/python/query.py @@ -65,11 +65,11 @@ def create_author(self, *, name: str, bio: str) -> Optional[models.Author]: ) def create_authors(self, arg_list: List[Any]) -> int: - result = self._conn.executemany(sqlalchemy.text(CREATE_AUTHORS), arg_list) + result = self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), arg_list) return result.rowcount def create_authors_named(self, arg_list: List[Any]) -> int: - result = self._conn.executemany(sqlalchemy.text(CREATE_AUTHORS_NAMED), arg_list) + result = self._conn.execute(sqlalchemy.text(CREATE_AUTHORS_NAMED), arg_list) return result.rowcount def create_user(self, *, email: str, name: str) -> Optional[models.User]: @@ -87,7 +87,7 @@ def create_user(self, *, email: str, name: str) -> Optional[models.User]: ) def create_users_batch(self, arg_list: List[Any]) -> int: - result = self._conn.executemany(sqlalchemy.text(CREATE_USERS_BATCH), arg_list) + result = self._conn.execute(sqlalchemy.text(CREATE_USERS_BATCH), arg_list) return result.rowcount def create_users_with_details(self, arg_list: List[CreateUsersWithDetailsParams]) -> int: @@ -100,7 +100,7 @@ def create_users_with_details(self, arg_list: List[CreateUsersWithDetailsParams] "p4": item.age, "p5": item.active, }) - result = self._conn.executemany(sqlalchemy.text(CREATE_USERS_WITH_DETAILS), data) + result = self._conn.execute(sqlalchemy.text(CREATE_USERS_WITH_DETAILS), data) return result.rowcount @@ -119,11 +119,11 @@ async def create_author(self, *, name: str, bio: str) -> Optional[models.Author] ) async def create_authors(self, arg_list: List[Any]) -> int: - result = await self._conn.executemany(sqlalchemy.text(CREATE_AUTHORS), arg_list) + result = await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS), arg_list) return result.rowcount async def create_authors_named(self, arg_list: List[Any]) -> int: - result = await self._conn.executemany(sqlalchemy.text(CREATE_AUTHORS_NAMED), arg_list) + result = await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS_NAMED), arg_list) return result.rowcount async def create_user(self, *, email: str, name: str) -> Optional[models.User]: @@ -141,7 +141,7 @@ async def create_user(self, *, email: str, name: str) -> Optional[models.User]: ) async def create_users_batch(self, arg_list: List[Any]) -> int: - result = await self._conn.executemany(sqlalchemy.text(CREATE_USERS_BATCH), arg_list) + result = await self._conn.execute(sqlalchemy.text(CREATE_USERS_BATCH), arg_list) return result.rowcount async def create_users_with_details(self, arg_list: List[CreateUsersWithDetailsParams]) -> int: @@ -154,5 +154,5 @@ async def create_users_with_details(self, arg_list: List[CreateUsersWithDetailsP "p4": item.age, "p5": item.active, }) - result = await self._conn.executemany(sqlalchemy.text(CREATE_USERS_WITH_DETAILS), data) + result = await self._conn.execute(sqlalchemy.text(CREATE_USERS_WITH_DETAILS), data) return result.rowcount diff --git a/internal/endtoend/testdata/copyfrom/sqlc.yaml b/internal/endtoend/testdata/copyfrom/sqlc.yaml index 9d0c56e..8c05798 100644 --- a/internal/endtoend/testdata/copyfrom/sqlc.yaml +++ b/internal/endtoend/testdata/copyfrom/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" + sha256: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" sql: - schema: "schema.sql" queries: "query.sql" diff --git a/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml b/internal/endtoend/testdata/emit_pydantic_models/sqlc.yaml index 456ccf2..5c7749c 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" + sha256: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/emit_str_enum/sqlc.yaml b/internal/endtoend/testdata/emit_str_enum/sqlc.yaml index 62296ae..a46eb47 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" + sha256: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/exec_result/sqlc.yaml b/internal/endtoend/testdata/exec_result/sqlc.yaml index 0e7eb1a..5b24cbd 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" + sha256: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/exec_rows/sqlc.yaml b/internal/endtoend/testdata/exec_rows/sqlc.yaml index 0e7eb1a..5b24cbd 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" + sha256: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml b/internal/endtoend/testdata/inflection_exclude_table_names/sqlc.yaml index 47daf09..50aa9af 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" + sha256: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_two/sqlc.yaml index e5f79f7..618915f 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" + sha256: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_undefined/sqlc.yaml index d4db347..b2c2bdf 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" + sha256: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml b/internal/endtoend/testdata/query_parameter_limit_zero/sqlc.yaml index 332f2b9..918faeb 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" + sha256: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" 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 5c20b23..bfb9b11 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: "9aedc973afcc3c089934aaf509843a761e21ac92a4ce34d7a4ba3acebfb49bf0" + sha256: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" sql: - schema: schema.sql queries: query.sql diff --git a/internal/gen.go b/internal/gen.go index 716d629..3f18dda 100644 --- a/internal/gen.go +++ b/internal/gen.go @@ -225,7 +225,7 @@ func (q Query) BuildCopyFromBody(isAsync bool) []*pyast.Node { }) execCall := poet.Node(&pyast.Call{ - Func: poet.Attribute(poet.Name("self._conn"), "executemany"), + Func: poet.Attribute(poet.Name("self._conn"), "execute"), Args: []*pyast.Node{ sqlText, poet.Name(dataVar), From 0722c80f7f2e76623027844cb56c166f098853aa Mon Sep 17 00:00:00 2001 From: Kellen Miller Date: Sat, 6 Sep 2025 11:18:35 -0400 Subject: [PATCH 03/10] Remove outdated comments about `executemany` usage for `:copyfrom` --- internal/gen.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/gen.go b/internal/gen.go index 3f18dda..d76c603 100644 --- a/internal/gen.go +++ b/internal/gen.go @@ -1060,7 +1060,6 @@ func buildQueryTree(ctx *pyTmplCtx, i *importer, source string) *pyast.Node { ) f.Returns = typeRefNode("sqlalchemy", "engine", "Result") case ":copyfrom": - // For copyfrom, use executemany for batch inserts f.Body = append(f.Body, q.BuildCopyFromBody(false)...) f.Returns = poet.Name("int") default: @@ -1157,7 +1156,6 @@ func buildQueryTree(ctx *pyTmplCtx, i *importer, source string) *pyast.Node { ) f.Returns = typeRefNode("sqlalchemy", "engine", "Result") case ":copyfrom": - // For async copyfrom, use executemany for batch inserts f.Body = append(f.Body, q.BuildCopyFromBody(true)...) f.Returns = poet.Name("int") default: From 5b022f32e80ba34c0848ae621f9255b9d9b08c2b Mon Sep 17 00:00:00 2001 From: Kellen Miller Date: Tue, 2 Dec 2025 20:33:37 -0500 Subject: [PATCH 04/10] Add support for `:copyfrom` with shuffled parameter ordering and update examples --- .../testdata/copyfrom/python/models.py | 2 +- .../testdata/copyfrom/python/query.py | 42 ++++++++++++++++++- internal/endtoend/testdata/copyfrom/query.sql | 5 ++- internal/endtoend/testdata/copyfrom/sqlc.yaml | 2 +- .../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 +- .../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 | 20 +++++---- 30 files changed, 85 insertions(+), 36 deletions(-) diff --git a/internal/endtoend/testdata/copyfrom/python/models.py b/internal/endtoend/testdata/copyfrom/python/models.py index e728373..5bb8f99 100644 --- a/internal/endtoend/testdata/copyfrom/python/models.py +++ b/internal/endtoend/testdata/copyfrom/python/models.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 import dataclasses import datetime from typing import Optional diff --git a/internal/endtoend/testdata/copyfrom/python/query.py b/internal/endtoend/testdata/copyfrom/python/query.py index 1c339ec..1e6307d 100644 --- a/internal/endtoend/testdata/copyfrom/python/query.py +++ b/internal/endtoend/testdata/copyfrom/python/query.py @@ -1,6 +1,6 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.30.0 # source: query.sql import dataclasses from typing import Any, List, Optional @@ -36,6 +36,20 @@ """ +CREATE_USERS_SHUFFLED = """-- name: create_users_shuffled \\:copyfrom +INSERT INTO users (email, name, bio, age, active) VALUES (:p2, :p1, :p3, :p4, :p5) +""" + + +@dataclasses.dataclass() +class CreateUsersShuffledParams: + name: str + email: str + bio: Optional[str] + age: Optional[int] + active: Optional[bool] + + CREATE_USERS_WITH_DETAILS = """-- name: create_users_with_details \\:copyfrom INSERT INTO users (email, name, bio, age, active) VALUES (:p1, :p2, :p3, :p4, :p5) """ @@ -90,6 +104,19 @@ def create_users_batch(self, arg_list: List[Any]) -> int: result = self._conn.execute(sqlalchemy.text(CREATE_USERS_BATCH), arg_list) return result.rowcount + def create_users_shuffled(self, arg_list: List[CreateUsersShuffledParams]) -> int: + data = list() + for item in arg_list: + data.append({ + "p1": item.name, + "p2": item.email, + "p3": item.bio, + "p4": item.age, + "p5": item.active, + }) + result = self._conn.execute(sqlalchemy.text(CREATE_USERS_SHUFFLED), data) + return result.rowcount + def create_users_with_details(self, arg_list: List[CreateUsersWithDetailsParams]) -> int: data = list() for item in arg_list: @@ -144,6 +171,19 @@ async def create_users_batch(self, arg_list: List[Any]) -> int: result = await self._conn.execute(sqlalchemy.text(CREATE_USERS_BATCH), arg_list) return result.rowcount + async def create_users_shuffled(self, arg_list: List[CreateUsersShuffledParams]) -> int: + data = list() + for item in arg_list: + data.append({ + "p1": item.name, + "p2": item.email, + "p3": item.bio, + "p4": item.age, + "p5": item.active, + }) + result = await self._conn.execute(sqlalchemy.text(CREATE_USERS_SHUFFLED), data) + return result.rowcount + async def create_users_with_details(self, arg_list: List[CreateUsersWithDetailsParams]) -> int: data = list() for item in arg_list: diff --git a/internal/endtoend/testdata/copyfrom/query.sql b/internal/endtoend/testdata/copyfrom/query.sql index 576b14c..3afd4af 100644 --- a/internal/endtoend/testdata/copyfrom/query.sql +++ b/internal/endtoend/testdata/copyfrom/query.sql @@ -14,4 +14,7 @@ INSERT INTO users (email, name) VALUES (@email, @name) RETURNING *; INSERT INTO users (email, name) VALUES (@email, @name); -- name: CreateUsersWithDetails :copyfrom -INSERT INTO users (email, name, bio, age, active) VALUES ($1, $2, $3, $4, $5); \ No newline at end of file +INSERT INTO users (email, name, bio, age, active) VALUES ($1, $2, $3, $4, $5); + +-- name: CreateUsersShuffled :copyfrom +INSERT INTO users (email, name, bio, age, active) VALUES ($2, $1, $3, $4, $5); diff --git a/internal/endtoend/testdata/copyfrom/sqlc.yaml b/internal/endtoend/testdata/copyfrom/sqlc.yaml index 8c05798..b45c289 100644 --- a/internal/endtoend/testdata/copyfrom/sqlc.yaml +++ b/internal/endtoend/testdata/copyfrom/sqlc.yaml @@ -3,7 +3,7 @@ plugins: - name: py wasm: url: file://../../../../bin/sqlc-gen-python.wasm - sha256: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" + sha256: "d587b6af0bfe07dbb2c316bffb5f7f5d0073a60788c5d533b84373067c9c1916" sql: - schema: "schema.sql" queries: "query.sql" diff --git a/internal/endtoend/testdata/emit_pydantic_models/db/models.py b/internal/endtoend/testdata/emit_pydantic_models/db/models.py index 61ad3eb..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.29.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 cc36118..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.29.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 5c7749c..2bb6af9 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: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" + sha256: "d587b6af0bfe07dbb2c316bffb5f7f5d0073a60788c5d533b84373067c9c1916" 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 aa43ab1..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.29.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 5ea0264..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.29.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 a46eb47..d56db19 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: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" + sha256: "d587b6af0bfe07dbb2c316bffb5f7f5d0073a60788c5d533b84373067c9c1916" 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 6d3e9f5..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.29.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 c9c6e21..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.29.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 5b24cbd..c4d60a2 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: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" + sha256: "d587b6af0bfe07dbb2c316bffb5f7f5d0073a60788c5d533b84373067c9c1916" 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 6d3e9f5..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.29.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 a678f3d..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.29.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 5b24cbd..c4d60a2 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: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" + sha256: "d587b6af0bfe07dbb2c316bffb5f7f5d0073a60788c5d533b84373067c9c1916" 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 fc76620..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.29.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 1fc92fd..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.29.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 50aa9af..44f056e 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: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" + sha256: "d587b6af0bfe07dbb2c316bffb5f7f5d0073a60788c5d533b84373067c9c1916" sql: - schema: schema.sql queries: query.sql 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 89c0f8d..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.29.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 0d9bd97..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.29.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 618915f..6a8a76c 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: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" + sha256: "d587b6af0bfe07dbb2c316bffb5f7f5d0073a60788c5d533b84373067c9c1916" 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 dc09dab..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.29.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 49b7bd1..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.29.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 b2c2bdf..3149851 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: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" + sha256: "d587b6af0bfe07dbb2c316bffb5f7f5d0073a60788c5d533b84373067c9c1916" 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 89c0f8d..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.29.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 38e0efb..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.29.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 918faeb..b02f0d6 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: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" + sha256: "d587b6af0bfe07dbb2c316bffb5f7f5d0073a60788c5d533b84373067c9c1916" 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 bfb9b11..98ad219 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: "36baa58bf107df9d93c7fcd9191bf431b9da6bbce754e9ef2d6da3439dbf8a7c" + sha256: "d587b6af0bfe07dbb2c316bffb5f7f5d0073a60788c5d533b84373067c9c1916" sql: - schema: schema.sql queries: query.sql diff --git a/internal/gen.go b/internal/gen.go index d76c603..7fc0c1f 100644 --- a/internal/gen.go +++ b/internal/gen.go @@ -49,9 +49,10 @@ func (t pyType) Annotation() *pyast.Node { } type Field struct { - Name string - Type pyType - Comment string + Name string + Type pyType + Comment string + ParamNumber int32 } type Struct struct { @@ -256,8 +257,12 @@ func (q Query) buildStructToDictList(sourceVar, targetVar string) []*pyast.Node loopVar := "item" dict := &pyast.Dict{} for i, field := range q.Args[0].Struct.Fields { - paramName := fmt.Sprintf("p%v", i+1) - dict.Keys = append(dict.Keys, poet.Constant(paramName)) + paramNumber := field.ParamNumber + if paramNumber == 0 { + paramNumber = int32(i + 1) + } + + dict.Keys = append(dict.Keys, poet.Constant(fmt.Sprintf("p%v", paramNumber))) dict.Values = append(dict.Values, poet.Attribute(poet.Name(loopVar), field.Name)) } @@ -444,8 +449,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), + ParamNumber: c.id, }) seen[colName]++ } From 16e90a7fc95c6f69befd6dbb3bcfc96efb9b98ec Mon Sep 17 00:00:00 2001 From: Kellen Miller Date: Tue, 2 Dec 2025 20:45:26 -0500 Subject: [PATCH 05/10] revert examples until :copyfrom support is added --- examples/src/authors/models.py | 2 +- examples/src/authors/query.py | 17 ++--------------- examples/src/authors/query.sql | 3 --- 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 +- examples/src/tests/test_authors.py | 26 -------------------------- 11 files changed, 10 insertions(+), 52 deletions(-) diff --git a/examples/src/authors/models.py b/examples/src/authors/models.py index b3b9554..96553a5 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.29.0 +# sqlc v1.28.0 import dataclasses from typing import Optional diff --git a/examples/src/authors/query.py b/examples/src/authors/query.py index 4463c85..019f877 100644 --- a/examples/src/authors/query.py +++ b/examples/src/authors/query.py @@ -1,8 +1,8 @@ # Code generated by sqlc. DO NOT EDIT. # versions: -# sqlc v1.29.0 +# sqlc v1.28.0 # source: query.sql -from typing import Any, AsyncIterator, Iterator, List, Optional +from typing import AsyncIterator, Iterator, Optional import sqlalchemy import sqlalchemy.ext.asyncio @@ -20,11 +20,6 @@ """ -CREATE_AUTHORS_BATCH = """-- name: create_authors_batch \\:copyfrom -INSERT INTO authors (name, bio) VALUES (:p1, :p2) -""" - - DELETE_AUTHOR = """-- name: delete_author \\:exec DELETE FROM authors WHERE id = :p1 @@ -57,10 +52,6 @@ def create_author(self, *, name: str, bio: Optional[str]) -> Optional[models.Aut bio=row[2], ) - def create_authors_batch(self, arg_list: List[Any]) -> int: - result = self._conn.execute(sqlalchemy.text(CREATE_AUTHORS_BATCH), arg_list) - return result.rowcount - def delete_author(self, *, id: int) -> None: self._conn.execute(sqlalchemy.text(DELETE_AUTHOR), {"p1": id}) @@ -98,10 +89,6 @@ async def create_author(self, *, name: str, bio: Optional[str]) -> Optional[mode bio=row[2], ) - async def create_authors_batch(self, arg_list: List[Any]) -> int: - result = await self._conn.execute(sqlalchemy.text(CREATE_AUTHORS_BATCH), arg_list) - return result.rowcount - async def delete_author(self, *, id: int) -> None: await self._conn.execute(sqlalchemy.text(DELETE_AUTHOR), {"p1": id}) diff --git a/examples/src/authors/query.sql b/examples/src/authors/query.sql index e5e75cf..75e38b2 100644 --- a/examples/src/authors/query.sql +++ b/examples/src/authors/query.sql @@ -17,6 +17,3 @@ RETURNING *; -- name: DeleteAuthor :exec DELETE FROM authors WHERE id = $1; - --- name: CreateAuthorsBatch :copyfrom -INSERT INTO authors (name, bio) VALUES ($1, $2); diff --git a/examples/src/booktest/models.py b/examples/src/booktest/models.py index dcfbc20..d7ee131 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.29.0 +# sqlc v1.28.0 import dataclasses import datetime import enum diff --git a/examples/src/booktest/query.py b/examples/src/booktest/query.py index 12d3717..bc71f22 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.29.0 +# sqlc v1.28.0 # source: query.sql import dataclasses import datetime diff --git a/examples/src/jets/models.py b/examples/src/jets/models.py index fc5464b..0d4eb5d 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.29.0 +# sqlc v1.28.0 import dataclasses diff --git a/examples/src/jets/query-building.py b/examples/src/jets/query-building.py index adcdcdb..7651116 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.29.0 +# sqlc v1.28.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 2f2da93..5af93e9 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.29.0 +# sqlc v1.28.0 # source: city.sql from typing import AsyncIterator, Optional diff --git a/examples/src/ondeck/models.py b/examples/src/ondeck/models.py index a32fea2..1161408 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.29.0 +# sqlc v1.28.0 import dataclasses import datetime import enum diff --git a/examples/src/ondeck/venue.py b/examples/src/ondeck/venue.py index 1911cb3..6159bf6 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.29.0 +# sqlc v1.28.0 # source: venue.sql import dataclasses from typing import AsyncIterator, List, Optional diff --git a/examples/src/tests/test_authors.py b/examples/src/tests/test_authors.py index d679a62..c3031cd 100644 --- a/examples/src/tests/test_authors.py +++ b/examples/src/tests/test_authors.py @@ -29,18 +29,6 @@ def test_authors(db: sqlalchemy.engine.Connection): assert len(author_list) == 1 assert author_list[0] == new_author - # Test batch insert with copyfrom - batch_authors = [ - {"p1": "Dennis Ritchie", "p2": "Creator of C Programming Language"}, - {"p1": "Ken Thompson", "p2": "Creator of Unix and Go Programming Language"}, - {"p1": "Rob Pike", "p2": "Co-creator of Go Programming Language"}, - ] - rows_affected = querier.create_authors_batch(batch_authors) - assert rows_affected == 3 - - all_authors = list(querier.list_authors()) - assert len(all_authors) == 4 # 1 existing + 3 batch inserted - @pytest.mark.asyncio async def test_authors_async(async_db: sqlalchemy.ext.asyncio.AsyncConnection): @@ -66,17 +54,3 @@ async def test_authors_async(async_db: sqlalchemy.ext.asyncio.AsyncConnection): author_list.append(author) assert len(author_list) == 1 assert author_list[0] == new_author - - # Test batch insert with copyfrom - batch_authors = [ - {"p1": "Dennis Ritchie", "p2": "Creator of C Programming Language"}, - {"p1": "Ken Thompson", "p2": "Creator of Unix and Go Programming Language"}, - {"p1": "Rob Pike", "p2": "Co-creator of Go Programming Language"}, - ] - rows_affected = await querier.create_authors_batch(batch_authors) - assert rows_affected == 3 - - all_authors = [] - async for author in querier.list_authors(): - all_authors.append(author) - assert len(all_authors) == 4 # 1 existing + 3 batch inserted From cb4a036dd27f73f6af66819e0c05b93e75db73e3 Mon Sep 17 00:00:00 2001 From: Qubut Date: Mon, 2 Mar 2026 13:40:49 +0100 Subject: [PATCH 06/10] chore(devenv): add .envrc --- .envrc | 5 +++ .gitignore | 1 - devenv.lock | 127 ++++------------------------------------------------ 3 files changed, 13 insertions(+), 120 deletions(-) create mode 100644 .envrc diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..88c98ca --- /dev/null +++ b/.envrc @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +export DIRENV_WARN_TIMEOUT=20s + +eval "$(devenv direnvrc)" +use devenv diff --git a/.gitignore b/.gitignore index d66bae9..bb6129e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ bin # Devenv -.envrc .direnv .devenv* devenv.local.nix diff --git a/devenv.lock b/devenv.lock index ea67978..511488f 100644 --- a/devenv.lock +++ b/devenv.lock @@ -3,11 +3,11 @@ "devenv": { "locked": { "dir": "src/modules", - "lastModified": 1698243190, - "narHash": "sha256-n+SbyNQRhUcaZoU00d+7wi17HJpw/kAUrXOL4zRcqE8=", + "lastModified": 1772451846, + "narHash": "sha256-379bl/7sqmPrg/RQV/uNodVePLNsoRfri5cQxq9pPMw=", "owner": "cachix", "repo": "devenv", - "rev": "86f476f7edb86159fd20764489ab4e4df6edb4b6", + "rev": "6adac0968c8a597799966875a4c6ffd1b3c4eb0f", "type": "github" }, "original": { @@ -17,68 +17,13 @@ "type": "github" } }, - "flake-compat": { - "flake": false, - "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1685518550, - "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "gitignore": { - "inputs": { - "nixpkgs": [ - "pre-commit-hooks", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1660459072, - "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", - "type": "github" - } - }, "nixpkgs": { "locked": { - "lastModified": 1698553279, - "narHash": "sha256-T/9P8yBSLcqo/v+FTOBK+0rjzjPMctVymZydbvR/Fak=", + "lastModified": 1772359081, + "narHash": "sha256-RXs6yvhb6uceW0YE9V1ODkQrGLRD0p5/icW2lv+itlQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "90e85bc7c1a6fc0760a94ace129d3a1c61c3d035", + "rev": "42bbf3e1e000963f4f3f37daa822c67c4d8856c4", "type": "github" }, "original": { @@ -88,69 +33,13 @@ "type": "github" } }, - "nixpkgs-stable": { - "locked": { - "lastModified": 1685801374, - "narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "c37ca420157f4abc31e26f436c1145f8951ff373", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-23.05", - "repo": "nixpkgs", - "type": "github" - } - }, - "pre-commit-hooks": { - "inputs": { - "flake-compat": "flake-compat", - "flake-utils": "flake-utils", - "gitignore": "gitignore", - "nixpkgs": [ - "nixpkgs" - ], - "nixpkgs-stable": "nixpkgs-stable" - }, - "locked": { - "lastModified": 1698227354, - "narHash": "sha256-Fi5H9jbaQLmLw9qBi/mkR33CoFjNbobo5xWdX4tKz1Q=", - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "rev": "bd38df3d508dfcdff52cd243d297f218ed2257bf", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "type": "github" - } - }, "root": { "inputs": { "devenv": "devenv", - "nixpkgs": "nixpkgs", - "pre-commit-hooks": "pre-commit-hooks" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" + "nixpkgs": "nixpkgs" } } }, "root": "root", "version": 7 -} +} \ No newline at end of file From 32133e587da226b25b4558caae88f129bd80327c Mon Sep 17 00:00:00 2001 From: Qubut Date: Mon, 2 Mar 2026 13:43:44 +0100 Subject: [PATCH 07/10] chore(devenv): remove unnecessary comment --- devenv.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/devenv.nix b/devenv.nix index ca57de2..6ef89c9 100644 --- a/devenv.nix +++ b/devenv.nix @@ -1,7 +1,6 @@ { pkgs, ... }: { - # https://devenv.sh/packages/ packages = [ pkgs.go pkgs.git From a8b9088906ca695d6ef5276f3b30a6cf207d681b Mon Sep 17 00:00:00 2001 From: Qubut Date: Mon, 2 Mar 2026 13:56:27 +0100 Subject: [PATCH 08/10] ci: bump sqlc version --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 0d894e03a7df857de877c24df0b1c5f09de50862 Mon Sep 17 00:00:00 2001 From: Qubut Date: Mon, 2 Mar 2026 14:20:25 +0100 Subject: [PATCH 09/10] ci: add release workflow --- .github/workflows/release.yaml | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/release.yaml diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..c7dd096 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,40 @@ +name: Release Python Plugin WASM + +on: + push: + tags: + - 'v*' + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + + - name: Build WASM + run: make all + - name: Compute SHA256 + id: sha + run: | + HASH=$(shasum -a 256 bin/sqlc-gen-python.wasm | cut -d ' ' -f1) + echo "hash=$HASH" >> $GITHUB_OUTPUT + + - name: Create Release & Upload Asset + uses: softprops/action-gh-release@v2 + with: + files: bin/sqlc-gen-python.wasm + body: | + ${{ github.ref_name }} + + Configuration example: + + ```yaml + plugins: + - name: py + wasm: + url: https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/sqlc-gen-python_${{ github.ref_name }}.wasm + sha256: ${{ steps.sha.outputs.hash }} From a2557190d37cdd8938f8e8a02cefdf1c427fc761 Mon Sep 17 00:00:00 2001 From: Qubut Date: Mon, 2 Mar 2026 14:41:17 +0100 Subject: [PATCH 10/10] feat: update examples, correct release example --- .github/workflows/release.yaml | 2 +- devenv.nix | 15 ++++++++------- examples/sqlc.yaml | 4 ++-- 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 +- 12 files changed, 20 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index c7dd096..44a57b6 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -36,5 +36,5 @@ jobs: plugins: - name: py wasm: - url: https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/sqlc-gen-python_${{ github.ref_name }}.wasm + url: https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/sqlc-gen-python.wasm sha256: ${{ steps.sha.outputs.hash }} diff --git a/devenv.nix b/devenv.nix index 6ef89c9..2b6f7ed 100644 --- a/devenv.nix +++ b/devenv.nix @@ -1,12 +1,13 @@ { pkgs, ... }: { - packages = [ - pkgs.go - pkgs.git - pkgs.govulncheck - pkgs.gopls - pkgs.golint - pkgs.python311 + packages = with pkgs; [ + go + git + govulncheck + gopls + golint + python311 + sqlc ]; } diff --git a/examples/sqlc.yaml b/examples/sqlc.yaml index 712c5db..7f5a4e6 100644 --- a/examples/sqlc.yaml +++ b/examples/sqlc.yaml @@ -2,8 +2,8 @@ version: '2' plugins: - name: py wasm: - url: https://downloads.sqlc.dev/plugin/sqlc-gen-python_1.2.0.wasm - sha256: a6c5d174c407007c3717eea36ff0882744346e6ba991f92f71d6ab2895204c0e + url: https://github.com/Case-Traders/sqlc-gen-python/releases/download/v1.4.0/sqlc-gen-python.wasm + sha256: 3aabb1fe5b58dd4e09296c3434870678b3356eb5636bf77cca7a9aa365c36b79 sql: - schema: "src/authors/schema.sql" queries: "src/authors/query.sql" 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