pgdevkit 0.3.4__tar.gz → 0.3.6__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.
Files changed (108) hide show
  1. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/PKG-INFO +1 -1
  2. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/cli.py +30 -18
  3. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/migrate.py +77 -0
  4. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pyproject.toml +1 -1
  5. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/test_migrate.py +59 -1
  6. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/uv.lock +1 -1
  7. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/.github/workflows/python-publish.yml +0 -0
  8. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/.github/workflows/python-test.yml +0 -0
  9. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/.gitignore +0 -0
  10. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/.python-version +0 -0
  11. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/README.md +0 -0
  12. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/docs/database-layout.md +0 -0
  13. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/__init__.py +0 -0
  14. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/backends/__init__.py +0 -0
  15. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/backends/base.py +0 -0
  16. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/backends/mssql.py +0 -0
  17. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/backends/postgres.py +0 -0
  18. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/connection.py +0 -0
  19. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/db/__init__.py +0 -0
  20. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/db/complex_types.py +0 -0
  21. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/db/connection.py +0 -0
  22. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/db/crud.py +0 -0
  23. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/db/loader.py +0 -0
  24. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/db/model.py +0 -0
  25. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/db/mssql_crud.py +0 -0
  26. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/db/mssql_sql.py +0 -0
  27. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/dialect.py +0 -0
  28. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/diff.py +0 -0
  29. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/fetch_missing.py +0 -0
  30. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/introspect.py +0 -0
  31. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/lakebase.py +0 -0
  32. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/models.py +0 -0
  33. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/mssql_introspect.py +0 -0
  34. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/parser.py +0 -0
  35. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/testdb/__init__.py +0 -0
  36. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/testdb/_docker.py +0 -0
  37. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/testdb/api.py +0 -0
  38. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/testdb/config.py +0 -0
  39. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/testdb/constants.py +0 -0
  40. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/testdb/container.py +0 -0
  41. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/testdb/mssql/__init__.py +0 -0
  42. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/testdb/mssql/api.py +0 -0
  43. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/testdb/mssql/constants.py +0 -0
  44. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/testdb/mssql/container.py +0 -0
  45. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/testdb/naming.py +0 -0
  46. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/testdb/query.py +0 -0
  47. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/pgdevkit/testdb/schema.py +0 -0
  48. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/skills/pgdevkit/SKILL.md +0 -0
  49. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/skills/pgdevkit/references/dynamic-sql.md +0 -0
  50. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/skills/pgdevkit/references/temporal-tables.md +0 -0
  51. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/__init__.py +0 -0
  52. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/conftest.py +0 -0
  53. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/db/__init__.py +0 -0
  54. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/db/test_complex_types.py +0 -0
  55. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/db/test_connection.py +0 -0
  56. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/db/test_crud.py +0 -0
  57. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/db/test_loader.py +0 -0
  58. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/db/test_mssql_crud_live.py +0 -0
  59. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/db/test_mssql_crud_sql.py +0 -0
  60. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/fixtures/01_schema.sql +0 -0
  61. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/fixtures/02_types.sql +0 -0
  62. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/fixtures/03_tables.sql +0 -0
  63. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/fixtures/04_views.sql +0 -0
  64. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/fixtures/05_functions.sql +0 -0
  65. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/fixtures/06_indexes.sql +0 -0
  66. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/test_cli_compare.py +0 -0
  67. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/test_compare.py +0 -0
  68. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/test_compare_mssql_live.py +0 -0
  69. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/test_connection.py +0 -0
  70. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/test_dialect.py +0 -0
  71. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/test_diff_mssql.py +0 -0
  72. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/test_fetch_missing.py +0 -0
  73. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/test_fetch_missing_cli.py +0 -0
  74. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/test_lakebase.py +0 -0
  75. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/test_mssql_introspect.py +0 -0
  76. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/test_parser_mssql.py +0 -0
  77. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/__init__.py +0 -0
  78. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/conftest.py +0 -0
  79. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/fixtures/database/app/migrations/001_add_gadget_note.sql +0 -0
  80. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/fixtures/database/app/tables/gadget.sql +0 -0
  81. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/fixtures/database/app/tables/gadget.test_data.json +0 -0
  82. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/fixtures/database/app/tables/widget.sql +0 -0
  83. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/fixtures/database/app/tables/widget.test_data.json +0 -0
  84. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/fixtures/database/app/tables/widget_part.sql +0 -0
  85. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/fixtures/database/app/tables/widget_part_detail.sql +0 -0
  86. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/fixtures/database/app/types/dimensions.sql +0 -0
  87. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/fixtures/database/app/types/mood.sql +0 -0
  88. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/fixtures/database/app/views/a_wrapper_view.sql +0 -0
  89. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/fixtures/database/app/views/b_base_view.sql +0 -0
  90. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/fixtures/database/schema/app.sql +0 -0
  91. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/fixtures/database_mssql/app/tables/widget.sql +0 -0
  92. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/fixtures/database_mssql/app/tables/widget.test_data.json +0 -0
  93. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/fixtures/database_mssql/app/views/a_wrapper_view.sql +0 -0
  94. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/fixtures/database_mssql/app/views/b_base_view.sql +0 -0
  95. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/fixtures/database_mssql/schema/app.sql +0 -0
  96. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/test_api.py +0 -0
  97. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/test_api_mssql.py +0 -0
  98. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/test_api_mssql_live.py +0 -0
  99. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/test_cli.py +0 -0
  100. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/test_config.py +0 -0
  101. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/test_config_mssql.py +0 -0
  102. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/test_constants.py +0 -0
  103. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/test_container.py +0 -0
  104. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/test_mssql_constants.py +0 -0
  105. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/test_naming.py +0 -0
  106. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/test_query.py +0 -0
  107. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/test_schema.py +0 -0
  108. {pgdevkit-0.3.4 → pgdevkit-0.3.6}/tests/testdb/test_schema_mssql.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pgdevkit
3
- Version: 0.3.4
3
+ Version: 0.3.6
4
4
  Summary: A helper for developing with Postgres
5
5
  Requires-Python: >=3.14
6
6
  Requires-Dist: docker>=7.1.0
@@ -371,24 +371,36 @@ def migrate_apply(
371
371
  break
372
372
  already_done = False
373
373
  if ask:
374
- bar_write(f"\n=== {path.name} ===")
375
- bar_write(path.read_text(encoding="utf-8"))
376
- answer = typer.prompt("[Y]es execute / [n]o skip / [a]lready done / [q]uit", default="y").strip().lower()
377
- if answer in ("q", "quit"):
378
- quit_requested = True
379
- break
380
- if answer in ("n", "no"):
381
- bar_write(f"Skipped {path.name}")
382
- outcomes.append((path.name, "skipped"))
383
- bar_step()
384
- continue
385
- if answer in ("a", "already", "already done"):
386
- already_done = True
387
- elif answer not in ("", "y", "yes"):
388
- bar_write(f"Skipped {path.name}")
389
- outcomes.append((path.name, "skipped"))
390
- bar_step()
391
- continue
374
+ already_done = migrate.already_fully_applied(conninfo, path)
375
+ if already_done:
376
+ bar_write(f"Auto: {path.name} is already fully present in the database marking as already done")
377
+ else:
378
+ # Hold the lock across the whole show-file+prompt step (not just each write) so
379
+ # the worker's bar_write() calls block until the question is answered instead
380
+ # of interleaving with the prompt or the migration text while it's being read.
381
+ with bar_lock:
382
+ bar.write(f"\n=== {path.name} ===")
383
+ bar.write(path.read_text(encoding="utf-8"))
384
+ answer = (
385
+ typer.prompt("[Y]es execute / [n]o skip / [a]lready done / [q]uit", default="y")
386
+ .strip()
387
+ .lower()
388
+ )
389
+ if answer in ("q", "quit"):
390
+ quit_requested = True
391
+ break
392
+ if answer in ("n", "no"):
393
+ bar_write(f"Skipped {path.name}")
394
+ outcomes.append((path.name, "skipped"))
395
+ bar_step()
396
+ continue
397
+ if answer in ("a", "already", "already done"):
398
+ already_done = True
399
+ elif answer not in ("", "y", "yes"):
400
+ bar_write(f"Skipped {path.name}")
401
+ outcomes.append((path.name, "skipped"))
402
+ bar_step()
403
+ continue
392
404
 
393
405
  work_q.put((path, already_done))
394
406
 
@@ -197,6 +197,83 @@ def _created_table_names(stmts: list[str]) -> list[str]:
197
197
  return names
198
198
 
199
199
 
200
+ _ADD_COLUMN_RE = re.compile(
201
+ rf"ALTER\s+TABLE\s+(?:IF\s+EXISTS\s+)?([\w.\"]+)\s+ADD\s+COLUMN\s+"
202
+ rf"(?:IF\s+NOT\s+EXISTS\s+)?\"?({_IDENTIFIER})\"?",
203
+ re.IGNORECASE,
204
+ )
205
+ _CREATE_RELATION_RE = re.compile(
206
+ r"CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:CONCURRENTLY\s+)?(?:IF\s+NOT\s+EXISTS\s+)?([\w.\"]+)"
207
+ r"|CREATE\s+SEQUENCE\s+(?:IF\s+NOT\s+EXISTS\s+)?([\w.\"]+)"
208
+ r"|CREATE\s+(?:MATERIALIZED\s+)?VIEW\s+(?:IF\s+NOT\s+EXISTS\s+)?([\w.\"]+)",
209
+ re.IGNORECASE,
210
+ )
211
+ _CREATE_SCHEMA_RE = re.compile(r"CREATE\s+SCHEMA\s+(?:IF\s+NOT\s+EXISTS\s+)?([\w\"]+)", re.IGNORECASE)
212
+
213
+
214
+ def _idempotent_target(stmt: str) -> tuple[str, ...] | None:
215
+ """Classify a single statement as one of the create-if-missing DDL shapes this module
216
+ can check for "already applied" with no ambiguity: ("relation", name) for a table,
217
+ index, sequence or view; ("schema", name); or ("column", table, column) for an ADD
218
+ COLUMN. None if the statement isn't one of these shapes — including any CREATE OR
219
+ REPLACE, which is never safe to treat as a no-op just because the object exists, since
220
+ the migration could be replacing it with different content."""
221
+ stripped = _strip_line_comments(stmt).strip()
222
+ if re.search(r"\bOR\s+REPLACE\b", stripped, re.IGNORECASE):
223
+ return None
224
+
225
+ if re.match(r"CREATE\s+TABLE\b", stripped, re.IGNORECASE):
226
+ names = _created_table_names([stmt])
227
+ return ("relation", names[0]) if names else None
228
+
229
+ m = _CREATE_RELATION_RE.match(stripped)
230
+ if m:
231
+ name = next(g for g in m.groups() if g is not None)
232
+ return ("relation", name)
233
+
234
+ m = _CREATE_SCHEMA_RE.match(stripped)
235
+ if m:
236
+ return ("schema", m.group(1))
237
+
238
+ m = _ADD_COLUMN_RE.match(stripped)
239
+ if m:
240
+ return ("column", m.group(1), m.group(2))
241
+
242
+ return None
243
+
244
+
245
+ def _target_exists(con: psycopg.Connection, target: tuple[str, ...]) -> bool:
246
+ kind = target[0]
247
+ if kind == "relation":
248
+ row = con.execute("select to_regclass(%s)", (target[1],)).fetchone()
249
+ elif kind == "schema":
250
+ row = con.execute("select to_regnamespace(%s)", (target[1],)).fetchone()
251
+ else: # column
252
+ row = con.execute(
253
+ "select 1 from pg_attribute where attrelid = to_regclass(%s) "
254
+ "and attname = %s and not attisdropped",
255
+ (target[1], target[2]),
256
+ ).fetchone()
257
+ return bool(row and row[0])
258
+
259
+
260
+ def already_fully_applied(conninfo: str, path: Path) -> bool:
261
+ """Whether every statement in this migration is a recognized create-if-missing shape
262
+ (table/index/sequence/view/schema, or add-column) AND its target already exists in the
263
+ database — i.e. re-running the migration would do nothing. Used by `--ask` to
264
+ auto-answer "already done" without prompting, so migrations that are trivially no-ops
265
+ don't interrupt review. A single unrecognized or not-yet-applied statement means
266
+ False — this never guesses."""
267
+ stmts = _split_sql(path.read_text(encoding="utf-8"))
268
+ if not stmts:
269
+ return False
270
+ targets = [_idempotent_target(s) for s in stmts]
271
+ if any(t is None for t in targets):
272
+ return False
273
+ with psycopg.connect(conninfo) as con:
274
+ return all(_target_exists(con, cast(tuple[str, ...], t)) for t in targets)
275
+
276
+
200
277
  def list_migration_files(migrations_dir: Path) -> list[Path]:
201
278
  return sorted(migrations_dir.glob("*.sql"))
202
279
 
@@ -11,7 +11,7 @@ packages = ["pgdevkit"]
11
11
 
12
12
  [project]
13
13
  name = "pgdevkit"
14
- version = "0.3.4"
14
+ version = "0.3.6"
15
15
  description = "A helper for developing with Postgres"
16
16
  readme = "README.md"
17
17
  requires-python = ">=3.14"
@@ -1,6 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
- from pgdevkit.migrate import _created_table_names, _split_sql, _strip_line_comments, default_tracking_table
3
+ from pgdevkit.migrate import (
4
+ _created_table_names,
5
+ _idempotent_target,
6
+ _split_sql,
7
+ _strip_line_comments,
8
+ default_tracking_table,
9
+ )
4
10
 
5
11
 
6
12
  def test_created_table_names_ignores_create_table_mentioned_in_a_comment():
@@ -74,3 +80,55 @@ def test_default_tracking_table_searches_parent_directories(tmp_path):
74
80
 
75
81
  def test_default_tracking_table_falls_back_with_no_pyproject_at_all(tmp_path):
76
82
  assert default_tracking_table(tmp_path / "nonexistent") == "public.schema_migrations"
83
+
84
+
85
+ def test_idempotent_target_recognizes_create_table_if_not_exists():
86
+ assert _idempotent_target("CREATE TABLE IF NOT EXISTS app.widgets (id int)") == ("relation", "app.widgets")
87
+
88
+
89
+ def test_idempotent_target_recognizes_create_index():
90
+ assert _idempotent_target(
91
+ "CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS widgets_name_idx ON app.widgets (name)"
92
+ ) == ("relation", "widgets_name_idx")
93
+
94
+
95
+ def test_idempotent_target_recognizes_create_sequence():
96
+ assert _idempotent_target("CREATE SEQUENCE IF NOT EXISTS app.widgets_seq") == ("relation", "app.widgets_seq")
97
+
98
+
99
+ def test_idempotent_target_recognizes_create_view():
100
+ assert _idempotent_target("CREATE VIEW IF NOT EXISTS app.widgets_v AS SELECT * FROM app.widgets") == (
101
+ "relation",
102
+ "app.widgets_v",
103
+ )
104
+
105
+
106
+ def test_idempotent_target_recognizes_create_schema():
107
+ assert _idempotent_target("CREATE SCHEMA IF NOT EXISTS app") == ("schema", "app")
108
+
109
+
110
+ def test_idempotent_target_recognizes_add_column():
111
+ assert _idempotent_target("ALTER TABLE app.widgets ADD COLUMN IF NOT EXISTS name text") == (
112
+ "column",
113
+ "app.widgets",
114
+ "name",
115
+ )
116
+
117
+
118
+ def test_idempotent_target_recognizes_add_column_without_if_not_exists_guard():
119
+ assert _idempotent_target("ALTER TABLE app.widgets ADD COLUMN name text") == (
120
+ "column",
121
+ "app.widgets",
122
+ "name",
123
+ )
124
+
125
+
126
+ def test_idempotent_target_ignores_create_or_replace():
127
+ # A view/function could be replaced with different content, so existence alone never
128
+ # means "already applied" for these.
129
+ assert _idempotent_target("CREATE OR REPLACE VIEW app.widgets_v AS SELECT 1") is None
130
+
131
+
132
+ def test_idempotent_target_ignores_unrecognized_statements():
133
+ assert _idempotent_target("ALTER TABLE app.widgets DROP COLUMN name") is None
134
+ assert _idempotent_target("INSERT INTO app.widgets (id) VALUES (1)") is None
@@ -313,7 +313,7 @@ wheels = [
313
313
 
314
314
  [[package]]
315
315
  name = "pgdevkit"
316
- version = "0.3.4"
316
+ version = "0.3.6"
317
317
  source = { editable = "." }
318
318
  dependencies = [
319
319
  { name = "docker" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes