pgdevkit 0.11.0__tar.gz → 0.13.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/PKG-INFO +54 -7
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/README.md +52 -5
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/cli.py +98 -24
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/connection.py +16 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/db/__init__.py +5 -0
- pgdevkit-0.13.0/pgdevkit/db/fetch.py +269 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/migrate.py +83 -23
- pgdevkit-0.13.0/pgdevkit/migrate_mssql.py +270 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pyproject.toml +3 -3
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/skills/pgdevkit/SKILL.md +27 -13
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/skills/pgdevkit/references/dynamic-sql.md +3 -0
- pgdevkit-0.13.0/tests/db/test_fetch.py +253 -0
- pgdevkit-0.13.0/tests/test_migrate_mssql.py +341 -0
- pgdevkit-0.13.0/tests/test_migrate_mssql_live.py +49 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/uv.lock +3 -3
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/.github/workflows/auto-release.yml +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/.github/workflows/python-publish.yml +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/.github/workflows/python-test.yml +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/.gitignore +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/.python-version +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/docs/database-layout.md +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/__init__.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/areas.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/backends/__init__.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/backends/base.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/backends/mssql.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/backends/postgres.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/db/complex_types.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/db/connection.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/db/crud.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/db/loader.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/db/model.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/db/mssql_crud.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/db/mssql_sql.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/dialect.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/diff.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/envtag.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/fetch_missing.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/introspect.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/lakebase.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/models.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/mssql_introspect.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/parser.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/schemas.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/sql_text.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/stats.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/testdb/__init__.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/testdb/_docker.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/testdb/api.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/testdb/config.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/testdb/constants.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/testdb/container.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/testdb/mssql/__init__.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/testdb/mssql/api.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/testdb/mssql/constants.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/testdb/mssql/container.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/testdb/naming.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/testdb/query.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/pgdevkit/testdb/schema.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/skills/pgdevkit/references/temporal-tables.md +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/__init__.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/conftest.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/db/__init__.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/db/test_complex_types.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/db/test_connection.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/db/test_crud.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/db/test_loader.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/db/test_mssql_crud_live.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/db/test_mssql_crud_sql.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/fixtures/01_schema.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/fixtures/02_types.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/fixtures/03_tables.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/fixtures/04_views.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/fixtures/05_functions.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/fixtures/06_indexes.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_areas.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_cli_compare.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_compare.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_compare_mssql_live.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_connection.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_dialect.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_diff_mssql.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_envtag.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_fetch_missing.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_fetch_missing_cli.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_lakebase.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_migrate.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_migrate_areas.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_migrate_env.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_migrate_grants.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_migrate_schemas.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_mssql_introspect.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_parser_areas.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_parser_mssql.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_parser_schemas.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_schemas.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_stats_cli.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/test_stats_mssql_live.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/__init__.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/conftest.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database/app/migrations/001_add_gadget_note.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database/app/tables/event.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database/app/tables/event_kind.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database/app/tables/gadget.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database/app/tables/gadget.test_data.json +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database/app/tables/prod_only.prod.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database/app/tables/widget.init.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database/app/tables/widget.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database/app/tables/widget.test_data.json +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database/app/tables/widget_part.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database/app/tables/widget_part_detail.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database/app/types/dimensions.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database/app/types/mood.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database/app/views/a_wrapper_view.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database/app/views/b_base_view.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database/permissions/grants.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database/schema/app.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database_mssql/app/tables/widget.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database_mssql/app/tables/widget.test_data.json +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database_mssql/app/views/a_wrapper_view.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database_mssql/app/views/b_base_view.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/fixtures/database_mssql/schema/app.sql +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/test_api.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/test_api_mssql.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/test_api_mssql_live.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/test_cli.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/test_config.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/test_config_mssql.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/test_constants.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/test_container.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/test_container_mssql.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/test_docker.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/test_mssql_constants.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/test_naming.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/test_query.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/test_schema.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/test_schema_filtering.py +0 -0
- {pgdevkit-0.11.0 → pgdevkit-0.13.0}/tests/testdb/test_schema_mssql.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pgdevkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: A helper for developing with Postgres
|
|
5
5
|
Requires-Python: >=3.14
|
|
6
6
|
Requires-Dist: docker>=7.1.0
|
|
7
|
-
Requires-Dist: psycopg[binary]>=3.
|
|
7
|
+
Requires-Dist: psycopg[binary]>=3.3.0
|
|
8
8
|
Requires-Dist: sqlglot>=30.11.0
|
|
9
9
|
Provides-Extra: azure
|
|
10
10
|
Requires-Dist: azure-identity>=1.19.0; extra == 'azure'
|
|
@@ -73,6 +73,12 @@ Requires the `mssql` extra: `pip install pgdevkit[mssql]` (pulls in
|
|
|
73
73
|
own driver — no system ODBC driver install needed). MSSQL has no composite
|
|
74
74
|
type or native enum equivalent, so those areas of a `database/` tree don't
|
|
75
75
|
have a direct equivalent on this backend — see `docs/database-layout.md`.
|
|
76
|
+
|
|
77
|
+
`pgdb compare --dialect mssql --entra-user <identity>` appends
|
|
78
|
+
`Authentication=ActiveDirectoryDefault` to the connection string so mssql-python
|
|
79
|
+
gets an Entra ID token via `DefaultAzureCredential` (also install the `azure`
|
|
80
|
+
extra). The identity is whatever that credential chain resolves; the flag's
|
|
81
|
+
value only switches Entra auth on.
|
|
76
82
|
Current Azure SQL/SQL Server (2025+) does have a native `json` column type,
|
|
77
83
|
which parses/introspects/diffs like any other column type; see
|
|
78
84
|
"`pgdevkit.db` — helpers for application code" below for how JSON values are
|
|
@@ -324,16 +330,46 @@ REPL.
|
|
|
324
330
|
## `pgdb migrate`
|
|
325
331
|
|
|
326
332
|
Applies numbered, forward-only SQL migration files from a directory to a live
|
|
327
|
-
Postgres database, tracking each one in a `schema.table` (default
|
|
328
|
-
`public.schema_migrations`) so repeat runs only
|
|
329
|
-
|
|
333
|
+
Postgres or MSSQL database, tracking each one in a `schema.table` (default
|
|
334
|
+
`public.schema_migrations`, `dbo.schema_migrations` for MSSQL) so repeat runs only
|
|
335
|
+
apply what's pending.
|
|
330
336
|
|
|
331
337
|
```bash
|
|
332
338
|
pgdb migrate check path/to/database/_migration_scripts --url postgresql://user:pass@host:port/db
|
|
333
339
|
pgdb migrate apply path/to/database/_migration_scripts --url postgresql://user:pass@host:port/db
|
|
334
340
|
```
|
|
335
341
|
|
|
336
|
-
|
|
342
|
+
### MSSQL
|
|
343
|
+
|
|
344
|
+
Pass `--dialect mssql` (needs the `mssql` extra) to both `check` and `apply`, with
|
|
345
|
+
`--url` as an ODBC connection string:
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
pgdb migrate apply path/to/database/_migration_scripts --dialect mssql \
|
|
349
|
+
--url "Server=host,1433;Database=db;UID=user;PWD=pass"
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
- Migration files are T-SQL: they are split into batches on standalone `GO` lines
|
|
353
|
+
(needed for e.g. `CREATE VIEW`, which must be first in its batch), and all batches
|
|
354
|
+
of one file run in a single transaction that is rolled back if any batch fails
|
|
355
|
+
(including a failure in a later statement of a multi-statement batch). `GO <count>`
|
|
356
|
+
is not honored: the batch runs once.
|
|
357
|
+
- The tracking table needs `filename nvarchar(450) primary key, applied_at
|
|
358
|
+
datetimeoffset not null default sysdatetimeoffset(), applied_by nvarchar(128) not
|
|
359
|
+
null default suser_sname()`. As on Postgres, a migration creating it can bootstrap it.
|
|
360
|
+
Override its name with `mssql_migrations_table` in `[tool.pgdevkit]` (the Postgres
|
|
361
|
+
`migrations_table` key is deliberately ignored for MSSQL) or with `--tracking-table`.
|
|
362
|
+
- `--ask` auto-detects "already done" for single-statement `GO` batches that create a
|
|
363
|
+
table, view or schema, or add a column; anything else is asked about.
|
|
364
|
+
- Post-apply verification checks every `CREATE TABLE` target via `OBJECT_ID`.
|
|
365
|
+
- `--entra-user` appends `Authentication=ActiveDirectoryDefault` to the connection
|
|
366
|
+
string, so mssql-python fetches an Entra ID token through azure-identity's
|
|
367
|
+
`DefaultAzureCredential` (install `pgdevkit[mssql,azure]`). The identity is whatever
|
|
368
|
+
that credential chain resolves — the flag's value only switches Entra auth on. It
|
|
369
|
+
can't be combined with an `Authentication=` already in the connection string.
|
|
370
|
+
- `pgdevkit.migrate.missing_privileges` is not available for MSSQL.
|
|
371
|
+
|
|
372
|
+
For Postgres, `--entra-user` works the same as `pgdb compare` (see above). The tracking
|
|
337
373
|
table needs `filename text primary key, applied_at timestamptz not null
|
|
338
374
|
default now(), applied_by text not null default current_user` (a migration
|
|
339
375
|
file that creates it, in the same directory, is the usual way to bootstrap
|
|
@@ -373,7 +409,7 @@ for a real one.
|
|
|
373
409
|
`pgdevkit.migrate` is also usable directly as a library — `list_migration_files`,
|
|
374
410
|
`applied_migrations`, `pending_migrations`, and `apply_migration` are the same
|
|
375
411
|
functions the CLI calls, so a project can script around them without shelling
|
|
376
|
-
out.
|
|
412
|
+
out. The database-touching ones take `dialect="postgres" | "mssql"`.
|
|
377
413
|
|
|
378
414
|
## `pgdevkit.db` — helpers for application code
|
|
379
415
|
|
|
@@ -391,6 +427,17 @@ Install with the `db` extra: `pip install pgdevkit[db]`.
|
|
|
391
427
|
`--entra-user`. For Lakebase hosts, also set the
|
|
392
428
|
`{env_prefix}DATABRICKS_WORKSPACE_HOST` and `{env_prefix}DATABRICKS_INSTANCE`
|
|
393
429
|
env vars.
|
|
430
|
+
- **`fetch_all(query, params=None, *, model=None, row_mapper=None, con=None, pool=None, cancel=None)`**
|
|
431
|
+
— run one custom query and get every row back as dicts, as validated
|
|
432
|
+
Pydantic `model` instances, or through a `row_mapper`. Pass `con` to run on a
|
|
433
|
+
connection you already hold (it is borrowed: never committed, closed or
|
|
434
|
+
released); otherwise a connection is taken from `pool` or the pool registered
|
|
435
|
+
with `set_default_pool()`. Set the `cancel` `asyncio.Event` to abort the query on the server
|
|
436
|
+
(`QueryCanceled`); cancelling the awaiting task cancels it there too, and
|
|
437
|
+
`CancelledError` propagates as usual. `query` must be a literal string, a sqlglot
|
|
438
|
+
expression, a `psycopg.sql` composable or a t-string — never an f-string (a sqlglot
|
|
439
|
+
expression is only as safe as the strings it was built from: pass user values as
|
|
440
|
+
`exp.Placeholder` + `params`).
|
|
394
441
|
- **CRUD functions** — `pg_retrieve`, `pg_retrieve_many`, `pg_insert`,
|
|
395
442
|
`pg_insert_many`, `pg_update`, `pg_update_dict`, `pg_upsert`,
|
|
396
443
|
`pg_upsert_dict`, `pg_upsert_many`, `pg_upsert_many_dict`, `pg_delete`,
|
|
@@ -52,6 +52,12 @@ Requires the `mssql` extra: `pip install pgdevkit[mssql]` (pulls in
|
|
|
52
52
|
own driver — no system ODBC driver install needed). MSSQL has no composite
|
|
53
53
|
type or native enum equivalent, so those areas of a `database/` tree don't
|
|
54
54
|
have a direct equivalent on this backend — see `docs/database-layout.md`.
|
|
55
|
+
|
|
56
|
+
`pgdb compare --dialect mssql --entra-user <identity>` appends
|
|
57
|
+
`Authentication=ActiveDirectoryDefault` to the connection string so mssql-python
|
|
58
|
+
gets an Entra ID token via `DefaultAzureCredential` (also install the `azure`
|
|
59
|
+
extra). The identity is whatever that credential chain resolves; the flag's
|
|
60
|
+
value only switches Entra auth on.
|
|
55
61
|
Current Azure SQL/SQL Server (2025+) does have a native `json` column type,
|
|
56
62
|
which parses/introspects/diffs like any other column type; see
|
|
57
63
|
"`pgdevkit.db` — helpers for application code" below for how JSON values are
|
|
@@ -303,16 +309,46 @@ REPL.
|
|
|
303
309
|
## `pgdb migrate`
|
|
304
310
|
|
|
305
311
|
Applies numbered, forward-only SQL migration files from a directory to a live
|
|
306
|
-
Postgres database, tracking each one in a `schema.table` (default
|
|
307
|
-
`public.schema_migrations`) so repeat runs only
|
|
308
|
-
|
|
312
|
+
Postgres or MSSQL database, tracking each one in a `schema.table` (default
|
|
313
|
+
`public.schema_migrations`, `dbo.schema_migrations` for MSSQL) so repeat runs only
|
|
314
|
+
apply what's pending.
|
|
309
315
|
|
|
310
316
|
```bash
|
|
311
317
|
pgdb migrate check path/to/database/_migration_scripts --url postgresql://user:pass@host:port/db
|
|
312
318
|
pgdb migrate apply path/to/database/_migration_scripts --url postgresql://user:pass@host:port/db
|
|
313
319
|
```
|
|
314
320
|
|
|
315
|
-
|
|
321
|
+
### MSSQL
|
|
322
|
+
|
|
323
|
+
Pass `--dialect mssql` (needs the `mssql` extra) to both `check` and `apply`, with
|
|
324
|
+
`--url` as an ODBC connection string:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
pgdb migrate apply path/to/database/_migration_scripts --dialect mssql \
|
|
328
|
+
--url "Server=host,1433;Database=db;UID=user;PWD=pass"
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
- Migration files are T-SQL: they are split into batches on standalone `GO` lines
|
|
332
|
+
(needed for e.g. `CREATE VIEW`, which must be first in its batch), and all batches
|
|
333
|
+
of one file run in a single transaction that is rolled back if any batch fails
|
|
334
|
+
(including a failure in a later statement of a multi-statement batch). `GO <count>`
|
|
335
|
+
is not honored: the batch runs once.
|
|
336
|
+
- The tracking table needs `filename nvarchar(450) primary key, applied_at
|
|
337
|
+
datetimeoffset not null default sysdatetimeoffset(), applied_by nvarchar(128) not
|
|
338
|
+
null default suser_sname()`. As on Postgres, a migration creating it can bootstrap it.
|
|
339
|
+
Override its name with `mssql_migrations_table` in `[tool.pgdevkit]` (the Postgres
|
|
340
|
+
`migrations_table` key is deliberately ignored for MSSQL) or with `--tracking-table`.
|
|
341
|
+
- `--ask` auto-detects "already done" for single-statement `GO` batches that create a
|
|
342
|
+
table, view or schema, or add a column; anything else is asked about.
|
|
343
|
+
- Post-apply verification checks every `CREATE TABLE` target via `OBJECT_ID`.
|
|
344
|
+
- `--entra-user` appends `Authentication=ActiveDirectoryDefault` to the connection
|
|
345
|
+
string, so mssql-python fetches an Entra ID token through azure-identity's
|
|
346
|
+
`DefaultAzureCredential` (install `pgdevkit[mssql,azure]`). The identity is whatever
|
|
347
|
+
that credential chain resolves — the flag's value only switches Entra auth on. It
|
|
348
|
+
can't be combined with an `Authentication=` already in the connection string.
|
|
349
|
+
- `pgdevkit.migrate.missing_privileges` is not available for MSSQL.
|
|
350
|
+
|
|
351
|
+
For Postgres, `--entra-user` works the same as `pgdb compare` (see above). The tracking
|
|
316
352
|
table needs `filename text primary key, applied_at timestamptz not null
|
|
317
353
|
default now(), applied_by text not null default current_user` (a migration
|
|
318
354
|
file that creates it, in the same directory, is the usual way to bootstrap
|
|
@@ -352,7 +388,7 @@ for a real one.
|
|
|
352
388
|
`pgdevkit.migrate` is also usable directly as a library — `list_migration_files`,
|
|
353
389
|
`applied_migrations`, `pending_migrations`, and `apply_migration` are the same
|
|
354
390
|
functions the CLI calls, so a project can script around them without shelling
|
|
355
|
-
out.
|
|
391
|
+
out. The database-touching ones take `dialect="postgres" | "mssql"`.
|
|
356
392
|
|
|
357
393
|
## `pgdevkit.db` — helpers for application code
|
|
358
394
|
|
|
@@ -370,6 +406,17 @@ Install with the `db` extra: `pip install pgdevkit[db]`.
|
|
|
370
406
|
`--entra-user`. For Lakebase hosts, also set the
|
|
371
407
|
`{env_prefix}DATABRICKS_WORKSPACE_HOST` and `{env_prefix}DATABRICKS_INSTANCE`
|
|
372
408
|
env vars.
|
|
409
|
+
- **`fetch_all(query, params=None, *, model=None, row_mapper=None, con=None, pool=None, cancel=None)`**
|
|
410
|
+
— run one custom query and get every row back as dicts, as validated
|
|
411
|
+
Pydantic `model` instances, or through a `row_mapper`. Pass `con` to run on a
|
|
412
|
+
connection you already hold (it is borrowed: never committed, closed or
|
|
413
|
+
released); otherwise a connection is taken from `pool` or the pool registered
|
|
414
|
+
with `set_default_pool()`. Set the `cancel` `asyncio.Event` to abort the query on the server
|
|
415
|
+
(`QueryCanceled`); cancelling the awaiting task cancels it there too, and
|
|
416
|
+
`CancelledError` propagates as usual. `query` must be a literal string, a sqlglot
|
|
417
|
+
expression, a `psycopg.sql` composable or a t-string — never an f-string (a sqlglot
|
|
418
|
+
expression is only as safe as the strings it was built from: pass user values as
|
|
419
|
+
`exp.Placeholder` + `params`).
|
|
373
420
|
- **CRUD functions** — `pg_retrieve`, `pg_retrieve_many`, `pg_insert`,
|
|
374
421
|
`pg_insert_many`, `pg_update`, `pg_update_dict`, `pg_upsert`,
|
|
375
422
|
`pg_upsert_dict`, `pg_upsert_many`, `pg_upsert_many_dict`, `pg_delete`,
|
|
@@ -9,13 +9,14 @@ from pathlib import Path
|
|
|
9
9
|
import psycopg
|
|
10
10
|
import typer
|
|
11
11
|
from rich.console import Console
|
|
12
|
+
from rich.markup import escape
|
|
12
13
|
from rich.table import Table
|
|
13
14
|
from rich import box
|
|
14
15
|
from tqdm import tqdm
|
|
15
16
|
|
|
16
|
-
from . import migrate, stats, testdb
|
|
17
|
+
from . import migrate, migrate_mssql, stats, testdb
|
|
17
18
|
from .backends import get_backend
|
|
18
|
-
from .connection import build_conninfo
|
|
19
|
+
from .connection import build_conninfo, build_mssql_conninfo
|
|
19
20
|
from .diff import DiffKind, compute_diff
|
|
20
21
|
from .fetch_missing import SUBFOLDER, find_missing_objects, layer_folder_for, reconstruct_ddl
|
|
21
22
|
from .parser import parse_directory
|
|
@@ -47,6 +48,9 @@ _TESTDB_ENV_OPTION = typer.Option(
|
|
|
47
48
|
"--env",
|
|
48
49
|
help="Environment to apply: skips any <name>.<other-env>.sql file (e.g. grants.prod.sql); untagged files always apply",
|
|
49
50
|
)
|
|
51
|
+
_MIGRATE_DIALECT_OPTION = typer.Option(
|
|
52
|
+
"postgres", "--dialect", help="postgres (default) or mssql; mssql needs the mssql extra and uses --url as an ODBC connection string"
|
|
53
|
+
)
|
|
50
54
|
_MIGRATE_ENV_OPTION = typer.Option(
|
|
51
55
|
None,
|
|
52
56
|
"--env",
|
|
@@ -70,7 +74,12 @@ app.add_typer(migrate_app, name="migrate")
|
|
|
70
74
|
@app.command()
|
|
71
75
|
def compare(
|
|
72
76
|
url: str = typer.Option(..., "--url", help="PostgreSQL DSN (postgresql://user:pass@host:port/db)"),
|
|
73
|
-
entra_user: str | None = typer.Option(
|
|
77
|
+
entra_user: str | None = typer.Option(
|
|
78
|
+
None,
|
|
79
|
+
"--entra-user",
|
|
80
|
+
help="Azure Entra user (triggers token auth); with --dialect mssql this adds "
|
|
81
|
+
"Authentication=ActiveDirectoryDefault and the value itself is not used",
|
|
82
|
+
),
|
|
74
83
|
databricks_workspace_host: str | None = typer.Option(
|
|
75
84
|
None,
|
|
76
85
|
"--databricks-workspace-host",
|
|
@@ -100,18 +109,21 @@ def compare(
|
|
|
100
109
|
)
|
|
101
110
|
|
|
102
111
|
try:
|
|
103
|
-
|
|
104
|
-
url,
|
|
105
|
-
entra_user,
|
|
106
|
-
databricks_workspace_host=databricks_workspace_host,
|
|
107
|
-
databricks_instance=databricks_instance,
|
|
108
|
-
)
|
|
112
|
+
backend = get_backend(dialect)
|
|
109
113
|
except ValueError as e:
|
|
110
114
|
err_console.print(f"[red]Error:[/red] {e}")
|
|
111
115
|
raise typer.Exit(2)
|
|
112
116
|
|
|
113
117
|
try:
|
|
114
|
-
backend
|
|
118
|
+
if backend.dialect.name == "mssql":
|
|
119
|
+
conninfo = build_mssql_conninfo(url, entra_user)
|
|
120
|
+
else:
|
|
121
|
+
conninfo = build_conninfo(
|
|
122
|
+
url,
|
|
123
|
+
entra_user,
|
|
124
|
+
databricks_workspace_host=databricks_workspace_host,
|
|
125
|
+
databricks_instance=databricks_instance,
|
|
126
|
+
)
|
|
115
127
|
except ValueError as e:
|
|
116
128
|
err_console.print(f"[red]Error:[/red] {e}")
|
|
117
129
|
raise typer.Exit(2)
|
|
@@ -376,30 +388,78 @@ def testdb_list_orphaned() -> None:
|
|
|
376
388
|
console.print(name)
|
|
377
389
|
|
|
378
390
|
|
|
391
|
+
def _describe_target(url: str, dialect: str) -> str:
|
|
392
|
+
"""Where a migrate command points, without credentials: host/db for a Postgres URL,
|
|
393
|
+
Server/Database only for an MSSQL connection string (never UID/PWD)."""
|
|
394
|
+
if dialect == "mssql":
|
|
395
|
+
parts = dict(
|
|
396
|
+
(k.strip().lower(), v.strip()) for k, _, v in (p.partition("=") for p in url.split(";")) if k.strip()
|
|
397
|
+
)
|
|
398
|
+
server = parts.get("server") or parts.get("data source") or "?"
|
|
399
|
+
database = parts.get("database") or parts.get("initial catalog")
|
|
400
|
+
return f"{server}/{database}" if database else server
|
|
401
|
+
return url.rsplit("@", 1)[-1] if "@" in url else url
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def _migrate_target(
|
|
405
|
+
url: str, entra_user: str | None, dialect: str, tracking_table: str | None, migrations_dir: Path
|
|
406
|
+
) -> tuple[str, str, str]:
|
|
407
|
+
"""Resolve (conninfo, dialect name, tracking table) for a migrate command."""
|
|
408
|
+
try:
|
|
409
|
+
resolved = get_backend(dialect).dialect
|
|
410
|
+
except ValueError as e:
|
|
411
|
+
err_console.print(f"[red]Error:[/red] {e}")
|
|
412
|
+
raise typer.Exit(2)
|
|
413
|
+
if resolved.name == "mssql":
|
|
414
|
+
try:
|
|
415
|
+
migrate_mssql.require_driver()
|
|
416
|
+
except ImportError as e:
|
|
417
|
+
err_console.print(f"[red]Error:[/red] {escape(str(e))}")
|
|
418
|
+
raise typer.Exit(2)
|
|
419
|
+
try:
|
|
420
|
+
conninfo = (build_mssql_conninfo if resolved.name == "mssql" else build_conninfo)(url, entra_user)
|
|
421
|
+
except ValueError as e:
|
|
422
|
+
err_console.print(f"[red]Error:[/red] {e}")
|
|
423
|
+
raise typer.Exit(2)
|
|
424
|
+
tracking_table = tracking_table or migrate.default_tracking_table(migrations_dir, dialect=resolved)
|
|
425
|
+
return conninfo, resolved.name, tracking_table
|
|
426
|
+
|
|
427
|
+
|
|
379
428
|
@migrate_app.command("check")
|
|
380
429
|
def migrate_check(
|
|
381
430
|
migrations_dir: Path = typer.Argument(..., help="Directory of numbered .sql migration files"),
|
|
382
|
-
url: str = typer.Option(
|
|
383
|
-
|
|
431
|
+
url: str = typer.Option(
|
|
432
|
+
...,
|
|
433
|
+
"--url",
|
|
434
|
+
help="PostgreSQL DSN (postgresql://user:pass@host:port/db), or with --dialect mssql "
|
|
435
|
+
"a connection string (Server=host,1433;Database=db;UID=user;PWD=pass)",
|
|
436
|
+
),
|
|
437
|
+
entra_user: str | None = typer.Option(
|
|
438
|
+
None,
|
|
439
|
+
"--entra-user",
|
|
440
|
+
help="Azure Entra user (triggers token auth); with --dialect mssql this adds "
|
|
441
|
+
"Authentication=ActiveDirectoryDefault and the value itself is not used",
|
|
442
|
+
),
|
|
384
443
|
tracking_table: str | None = typer.Option(
|
|
385
444
|
None,
|
|
386
445
|
"--tracking-table",
|
|
387
446
|
help="schema.table recording applied migrations "
|
|
388
|
-
"(default: tool.pgdevkit.migrations_table in pyproject.toml, else public.schema_migrations
|
|
447
|
+
"(default: tool.pgdevkit.migrations_table in pyproject.toml, else public.schema_migrations "
|
|
448
|
+
"-- dbo.schema_migrations with --dialect mssql)",
|
|
389
449
|
),
|
|
390
450
|
area: list[str] = _AREA_OPTION,
|
|
391
451
|
exclude_area: list[str] = _EXCLUDE_AREA_OPTION,
|
|
392
452
|
schema: list[str] = _SCHEMA_OPTION,
|
|
393
453
|
exclude_schema: list[str] = _EXCLUDE_SCHEMA_OPTION,
|
|
394
454
|
env: str | None = _MIGRATE_ENV_OPTION,
|
|
455
|
+
dialect: str = _MIGRATE_DIALECT_OPTION,
|
|
395
456
|
) -> None:
|
|
396
457
|
"""List which migration files under migrations_dir are applied vs. pending."""
|
|
397
458
|
if not migrations_dir.is_dir():
|
|
398
459
|
err_console.print(f"[red]Error:[/red] {migrations_dir} is not a directory")
|
|
399
460
|
raise typer.Exit(2)
|
|
400
461
|
|
|
401
|
-
conninfo =
|
|
402
|
-
tracking_table = tracking_table or migrate.default_tracking_table(migrations_dir)
|
|
462
|
+
conninfo, dialect, tracking_table = _migrate_target(url, entra_user, dialect, tracking_table, migrations_dir)
|
|
403
463
|
local_files = migrate.list_migration_files(
|
|
404
464
|
migrations_dir,
|
|
405
465
|
areas=_as_set(area),
|
|
@@ -409,7 +469,7 @@ def migrate_check(
|
|
|
409
469
|
env=env,
|
|
410
470
|
)
|
|
411
471
|
try:
|
|
412
|
-
applied = migrate.applied_migrations(conninfo, tracking_table)
|
|
472
|
+
applied = migrate.applied_migrations(conninfo, tracking_table, dialect)
|
|
413
473
|
except migrate.TrackingTableMissing:
|
|
414
474
|
err_console.print(f"[yellow]⚠[/yellow] {tracking_table} not found — nothing recorded as applied yet")
|
|
415
475
|
applied = {}
|
|
@@ -433,13 +493,24 @@ def migrate_check(
|
|
|
433
493
|
@migrate_app.command("apply")
|
|
434
494
|
def migrate_apply(
|
|
435
495
|
migrations_dir: Path = typer.Argument(..., help="Directory of numbered .sql migration files"),
|
|
436
|
-
url: str = typer.Option(
|
|
437
|
-
|
|
496
|
+
url: str = typer.Option(
|
|
497
|
+
...,
|
|
498
|
+
"--url",
|
|
499
|
+
help="PostgreSQL DSN (postgresql://user:pass@host:port/db), or with --dialect mssql "
|
|
500
|
+
"a connection string (Server=host,1433;Database=db;UID=user;PWD=pass)",
|
|
501
|
+
),
|
|
502
|
+
entra_user: str | None = typer.Option(
|
|
503
|
+
None,
|
|
504
|
+
"--entra-user",
|
|
505
|
+
help="Azure Entra user (triggers token auth); with --dialect mssql this adds "
|
|
506
|
+
"Authentication=ActiveDirectoryDefault and the value itself is not used",
|
|
507
|
+
),
|
|
438
508
|
tracking_table: str | None = typer.Option(
|
|
439
509
|
None,
|
|
440
510
|
"--tracking-table",
|
|
441
511
|
help="schema.table recording applied migrations "
|
|
442
|
-
"(default: tool.pgdevkit.migrations_table in pyproject.toml, else public.schema_migrations
|
|
512
|
+
"(default: tool.pgdevkit.migrations_table in pyproject.toml, else public.schema_migrations "
|
|
513
|
+
"-- dbo.schema_migrations with --dialect mssql)",
|
|
443
514
|
),
|
|
444
515
|
file: str | None = typer.Option(
|
|
445
516
|
None, "--file", help="Apply only this one filename (relative to migrations_dir) instead of all pending"
|
|
@@ -451,17 +522,17 @@ def migrate_apply(
|
|
|
451
522
|
schema: list[str] = _SCHEMA_OPTION,
|
|
452
523
|
exclude_schema: list[str] = _EXCLUDE_SCHEMA_OPTION,
|
|
453
524
|
env: str | None = _MIGRATE_ENV_OPTION,
|
|
525
|
+
dialect: str = _MIGRATE_DIALECT_OPTION,
|
|
454
526
|
) -> None:
|
|
455
527
|
"""Apply pending migration files, in filename order, tracking each in tracking_table."""
|
|
456
528
|
if not migrations_dir.is_dir():
|
|
457
529
|
err_console.print(f"[red]Error:[/red] {migrations_dir} is not a directory")
|
|
458
530
|
raise typer.Exit(2)
|
|
459
531
|
|
|
460
|
-
conninfo =
|
|
461
|
-
tracking_table = tracking_table or migrate.default_tracking_table(migrations_dir)
|
|
532
|
+
conninfo, dialect, tracking_table = _migrate_target(url, entra_user, dialect, tracking_table, migrations_dir)
|
|
462
533
|
areas, exclude_areas = _as_set(area), _as_set(exclude_area)
|
|
463
534
|
schemas, exclude_schemas = _as_set(schema), _as_set(exclude_schema)
|
|
464
|
-
target_desc = url
|
|
535
|
+
target_desc = _describe_target(url, dialect)
|
|
465
536
|
if not yes:
|
|
466
537
|
typer.confirm(f"About to run migrations against {target_desc}. Continue?", abort=True)
|
|
467
538
|
|
|
@@ -478,6 +549,7 @@ def migrate_apply(
|
|
|
478
549
|
schemas=schemas,
|
|
479
550
|
exclude_schemas=exclude_schemas,
|
|
480
551
|
env=env,
|
|
552
|
+
dialect=dialect,
|
|
481
553
|
)
|
|
482
554
|
except migrate.TrackingTableMissing:
|
|
483
555
|
err_console.print(
|
|
@@ -517,7 +589,9 @@ def migrate_apply(
|
|
|
517
589
|
for path, already_done in iter(work_q.get, None):
|
|
518
590
|
if not stop.is_set():
|
|
519
591
|
try:
|
|
520
|
-
result = migrate.apply_migration(
|
|
592
|
+
result = migrate.apply_migration(
|
|
593
|
+
conninfo, path, tracking_table, already_done=already_done, dialect=dialect
|
|
594
|
+
)
|
|
521
595
|
except Exception as e: # noqa: BLE001
|
|
522
596
|
failure = e
|
|
523
597
|
stop.set()
|
|
@@ -544,7 +618,7 @@ def migrate_apply(
|
|
|
544
618
|
break
|
|
545
619
|
already_done = False
|
|
546
620
|
if ask:
|
|
547
|
-
already_done = migrate.already_fully_applied(conninfo, path)
|
|
621
|
+
already_done = migrate.already_fully_applied(conninfo, path, dialect)
|
|
548
622
|
if already_done:
|
|
549
623
|
bar_write(f"Auto: {path.name} is already fully present in the database — marking as already done")
|
|
550
624
|
else:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import re
|
|
3
4
|
from typing import Literal
|
|
4
5
|
from urllib.parse import quote, urlparse, urlunparse
|
|
5
6
|
|
|
@@ -67,6 +68,21 @@ def get_azure_postgres_password(
|
|
|
67
68
|
return token.token
|
|
68
69
|
|
|
69
70
|
|
|
71
|
+
def build_mssql_conninfo(conn_str: str, entra_user: str | None = None) -> str:
|
|
72
|
+
"""MSSQL counterpart of `build_conninfo`. Without `entra_user` the ODBC connection
|
|
73
|
+
string is used as-is. With it, `Authentication=ActiveDirectoryDefault` is appended so
|
|
74
|
+
mssql-python acquires an Entra ID token itself via azure-identity's
|
|
75
|
+
`DefaultAzureCredential` (needs the `azure` extra); the identity is whatever that
|
|
76
|
+
credential chain resolves, so `entra_user` only switches Entra auth on and is not
|
|
77
|
+
sent to the server. A connection string that already sets `Authentication` is left
|
|
78
|
+
to speak for itself and rejected here to avoid ambiguity."""
|
|
79
|
+
if entra_user is None:
|
|
80
|
+
return conn_str
|
|
81
|
+
if re.search(r"(^|;)\s*Authentication\s*=", conn_str, re.IGNORECASE):
|
|
82
|
+
raise ValueError("--entra-user can't be combined with an Authentication= setting in the connection string")
|
|
83
|
+
return f"{conn_str.rstrip().rstrip(';')};Authentication=ActiveDirectoryDefault"
|
|
84
|
+
|
|
85
|
+
|
|
70
86
|
def build_conninfo(
|
|
71
87
|
url: str,
|
|
72
88
|
entra_user: str | None = None,
|
|
@@ -16,15 +16,19 @@ from .crud import (
|
|
|
16
16
|
pg_upsert_many,
|
|
17
17
|
pg_upsert_many_dict,
|
|
18
18
|
)
|
|
19
|
+
from .fetch import ConnectionSource, SqlQuery, fetch_all, set_default_pool
|
|
19
20
|
from .loader import SqlLoader
|
|
20
21
|
from .model import PostgresTableModel, TableModel
|
|
21
22
|
|
|
22
23
|
__all__ = [
|
|
23
24
|
"ComplexHelper",
|
|
25
|
+
"ConnectionSource",
|
|
24
26
|
"PgPool",
|
|
25
27
|
"PostgresTableModel",
|
|
26
28
|
"SqlLoader",
|
|
29
|
+
"SqlQuery",
|
|
27
30
|
"TableModel",
|
|
31
|
+
"fetch_all",
|
|
28
32
|
"pg_delete",
|
|
29
33
|
"pg_delete_dict",
|
|
30
34
|
"pg_insert",
|
|
@@ -37,4 +41,5 @@ __all__ = [
|
|
|
37
41
|
"pg_upsert_dict",
|
|
38
42
|
"pg_upsert_many",
|
|
39
43
|
"pg_upsert_many_dict",
|
|
44
|
+
"set_default_pool",
|
|
40
45
|
]
|