Skip to content

Conversation

@mercury-kuba
Copy link
Contributor

@mercury-kuba mercury-kuba commented Jul 1, 2025

Context

We'd like to be able to generate a structured representation of the proposed Postgres migrations when mocking migrations, instead of only having access to the raw text of statements.

This PR adds migrateStructured to Database.Persist.Postgresql.Internal to generate AlterDB data, which the existing implementation then converts to text as before.

It also modifies the structure of AlterDB(AddTable) to store structured information about the table that we're generating, instead of the raw text of the DB statements.

This PR does not change any semantic behavior of the existing system, and only moves code around into Internal.

I've run the Postgres test suite locally and it fully passed.

Checklist

Before submitting your PR, check that you've:

  • Documented new APIs with Haddock markup
  • Added @since declarations to the Haddock
  • Ran fourmolu on any changed files (restyled will do this for you, so
    accept the suggested changes if it makes them)
  • Adhered to the code style (see the .editorconfig and fourmolu.yaml files for details)

After submitting your PR:

  • Update the Changelog.md file with a link to your PR
  • Bumped the version number if there isn't an (unreleased) on the Changelog
  • Check that CI passes (or if it fails, for reasons unrelated to your change, like CI timeouts)

@@ -28,13 +28,10 @@ module Database.Persist.Postgresql
( withPostgresqlPool
, withPostgresqlPoolWithVersion
, withPostgresqlPoolWithConf

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A lot of these are due to Fourmolu auto-formatting.

--
-- @since 2.17.0.0
data AlterDB
= AddTable EntityNameDB EntityIdDef [Column]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This constructor has changed to store table information instead of the raw text.

Comment on lines +642 to +671
idtxt =
case entityId of
EntityIdNaturalKey pdef ->
T.concat
[ " PRIMARY KEY ("
, T.intercalate "," $ map (escapeF . fieldDB) $ NEL.toList $ compositeFields pdef
, ")"
]
EntityIdField field ->
let
defText = defaultAttribute $ fieldAttrs field
sType = fieldSqlType field
in
T.concat
[ escapeF $ fieldDB field
, maySerial sType defText
, " PRIMARY KEY UNIQUE"
, mayDefault defText
]
rawText =
T.concat
-- Lower case e: see Database.Persist.Sql.Migration
[ "CREATe TABLE " -- DO NOT FIX THE CAPITALIZATION!
, escapeE name
, "("
, idtxt
, if null nonIdCols then "" else ","
, T.intercalate "," $ map showColumn nonIdCols
, ")"
]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We move the code that generates the AddTable migration text down here, but it's the same logic.

@parsonsmatt
Copy link
Collaborator

oof i should have just mass formatted everything in one commit instead of doing it incrementally. apologies

Copy link
Collaborator

@parsonsmatt parsonsmatt left a comment

Choose a reason for hiding this comment

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

Versions should be 2.17.1.0.

I have a few questions/refactors- if we're exposing things (even internally) it may be nice to make these refactors ahead of time. Nothing blocking though.

The more structured representations are very nice!


-- | Indicates whether a Postgres Column is safe to drop.
--
-- @since 2.17.0.0
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is going to go out in at least 2.17.1.0

Also, if we're exposing it, let's newtype it, or make it a separate datatype.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Both are done.

= ChangeType Column SqlType Text
| IsNull Column
| NotNull Column
| Add' Column
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this AddColumn?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to AddColumn

| Drop Column SafeToRemove
| Default Column Text
| NoDefault Column
| Update' Column Text
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would also like to see a more descriptive name on here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to UpdateNullToValue

--
-- @since 2.17.0.0
data AlterTable
= AddUniqueConstraint ConstraintNameDB [FieldNameDB]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it valid to have [] here? Or can we do NonEmpty?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed for AddReference - it's a little more annoying for AddUniqueConstraint. But I don't think it's valid to have it empty here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

ok, we can just do followup issue then

-> EntityDef
-> IO (Either [Text] [AlterDB])
mockMigrateStructured allDefs entity = do
case partitionEithers [] of
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you need to modify this :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure why that was there, but fixed: #1600 (comment)

Comment on lines -1560 to -1563
mockMigrate allDefs _ entity = fmap (fmap $ map showAlterDb) $ do
case partitionEithers [] of
([], old'') -> return $ Right $ migrationText False old''
(errs, _) -> return $ Left errs
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is where the partitionEithers is coming from - not sure why it was doing this, but I guess I can clean it up.

@mercury-kuba
Copy link
Contributor Author

Ran the tests, here's the end of the output:

ERATED ALWAYS AS (5) STORED)
  should read a generated column [✔]
  should support adding or removing generation expressions from columns [ ]Migrating: CREATe TABLE "gen_migrate_test"("id" SERIAL8  PRIMARY KEY UNIQUE,"sickness" INT8 NOT NULL GENERATED ALWAYS AS (3) STORED,"cromulence" INT8 NOT NULL)
  should support adding or removing generation expressions from columns [✔]

Finished in 2.4905 seconds
283 examples, 0 failures

@mercury-kuba mercury-kuba requested a review from parsonsmatt July 2, 2025 18:16
--
-- @since 2.17.0.0
data AlterTable
= AddUniqueConstraint ConstraintNameDB [FieldNameDB]
Copy link
Collaborator

Choose a reason for hiding this comment

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

ok, we can just do followup issue then

:: [EntityDef]
-> EntityDef
-> IO (Either [Text] [AlterDB])
mockMigrateStructured allDefs entity = return $ Right migrationText
Copy link
Collaborator

Choose a reason for hiding this comment

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

This feels odd to me - if we're always succeeding with Right, why not just do that? Same re IO. Is this suppose dto be doing more?

"RESTRICT" ->
Just Restrict
_ ->
error $ "Unexpected value in parseCascade: " <> show txt
Copy link
Collaborator

Choose a reason for hiding this comment

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

feels like this should throwError instead of error

Kuba Karpierz added 2 commits July 3, 2025 08:01
Copy link
Collaborator

@parsonsmatt parsonsmatt left a comment

Choose a reason for hiding this comment

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

Code all lgtm. Let's test in the work repo and ensure this does what we want before merging.

Comment on lines 3 to 8
# 2.17.1.0

* [#1600](https://github.com/yesodweb/persistent/pull/1600)
* Add `migrateStructured` to `Database.Persist.Postgresql.Internal`.
This allows you to access a structured representation of the proposed migrations
for use in your application.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh shoot. All the changes in this PR are actually in persistent-postgresql package. So this CHANGELOG entry needs to be relocated into persistent-postgresql/CHANGELOG.md and the relevant versioning followed.

@parsonsmatt parsonsmatt merged commit 5a9deb2 into yesodweb:master Jul 7, 2025
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants