Distinguish inferred migrations by timestamp for statements within the same transaction (#362)

Set `created_at` and `updated_at` explicitly when inserting inferred
migrations into the migrations table.

When two statements are run in a transaction, we need to explicitly
insert `statement_timestamp()` into the `created_at` and `updated_at`
fields rather than relying on the table default of `current_timestamp`.

`current_timestamp` is the same for all statements in a transaction,
which causes problems when ordering statements by `created_at`.


Fixes #361
This commit is contained in:
Andrew Farries 2024-06-25 14:57:18 +01:00 committed by GitHub
parent 84ef318cb1
commit 7cef8b1213
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 55 additions and 2 deletions

View File

@ -309,7 +309,7 @@ BEGIN
-- Someone did a schema change without pgroll, include it in the history
SELECT INTO migration_id pg_catalog.format('sql_%%s',pg_catalog.substr(pg_catalog.md5(pg_catalog.random()::text), 0, 15));
INSERT INTO %[1]s.migrations (schema, name, migration, resulting_schema, done, parent, migration_type)
INSERT INTO %[1]s.migrations (schema, name, migration, resulting_schema, done, parent, migration_type, created_at, updated_at)
VALUES (
schemaname,
migration_id,
@ -328,7 +328,9 @@ BEGIN
%[1]s.read_schema(schemaname),
true,
%[1]s.latest_version(schemaname),
'inferred'
'inferred',
statement_timestamp(),
statement_timestamp()
);
END;
$$;

View File

@ -10,10 +10,12 @@ import (
"strings"
"sync"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/xataio/pgroll/pkg/migrations"
"github.com/xataio/pgroll/pkg/schema"
"github.com/xataio/pgroll/pkg/state"
@ -272,6 +274,55 @@ func TestInferredMigration(t *testing.T) {
})
}
func TestInferredMigrationsInTransactionHaveDifferentTimestamps(t *testing.T) {
ctx := context.Background()
testutils.WithStateAndConnectionToContainer(t, func(state *state.State, db *sql.DB) {
// Start a transaction
tx, err := db.BeginTx(ctx, nil)
require.NoError(t, err)
defer tx.Rollback()
// Create two tables in the transaction
_, err = tx.ExecContext(ctx, "CREATE TABLE table1 (id int)")
require.NoError(t, err)
_, err = tx.ExecContext(ctx, "CREATE TABLE table2 (id int)")
require.NoError(t, err)
// Commit the transaction
tx.Commit()
// Read the migrations from the migrations table
rows, err := db.QueryContext(ctx,
fmt.Sprintf("SELECT name, created_at, updated_at FROM %s.migrations ORDER BY created_at ASC", state.Schema()))
if err != nil {
t.Fatal(err)
}
defer rows.Close()
type m struct {
name string
createdAt time.Time
updatedAt time.Time
}
var migrations []m
for rows.Next() {
var migration m
if err := rows.Scan(&migration.name, &migration.createdAt, &migration.updatedAt); err != nil {
t.Fatal(err)
}
migrations = append(migrations, migration)
}
// Ensure that the two inferred migrations have different timestamps
assert.Equal(t, 2, len(migrations), "unexpected number of migrations")
assert.NotEqual(t, migrations[0].createdAt, migrations[1].createdAt, "migrations have the same timestamp")
})
}
func TestPgRollInitializationInANonDefaultSchema(t *testing.T) {
t.Parallel()