Skip to content

feat: support Spanner PostgreSQL databases#199

Merged
olavloite merged 3 commits into
mainfrom
postgresql-dialect
Feb 12, 2026
Merged

feat: support Spanner PostgreSQL databases#199
olavloite merged 3 commits into
mainfrom
postgresql-dialect

Conversation

@olavloite

@olavloite olavloite commented Jan 12, 2026

Copy link
Copy Markdown
Collaborator

Adds support for using the gorm PostgreSQL dialect with Spanner. Connections to Spanner use the Spanner database/sql driver, and not one of the PostgreSQL database drivers (e.g. it does not use pgx). Using this dialect with Spanner does not require the use of PGAdapter.

Fixes #198

@olavloite
olavloite force-pushed the postgresql-dialect branch 7 times, most recently from fc18997 to d3ba44b Compare January 12, 2026 13:55
olavloite added a commit to googleapis/google-cloud-go that referenced this pull request Jan 12, 2026
Add gorm data type names for PostgreSQL-specific types. This prevents
gorm from interpreting these structs as models, and automatically
creates columns with the correct data type when used as a field in a
model.

Needed for googleapis/go-gorm-spanner#199
rahul2393 pushed a commit to googleapis/google-cloud-go that referenced this pull request Jan 12, 2026
Add gorm data type names for PostgreSQL-specific types. This prevents
gorm from interpreting these structs as models, and automatically
creates columns with the correct data type when used as a field in a
model.

Needed for googleapis/go-gorm-spanner#199
@olavloite
olavloite force-pushed the postgresql-dialect branch 8 times, most recently from 1058973 to f03f0c6 Compare January 15, 2026 13:47
@olavloite
olavloite force-pushed the postgresql-dialect branch 12 times, most recently from 1746416 to 2c3095e Compare January 28, 2026 09:12
@olavloite
olavloite marked this pull request as ready for review January 28, 2026 09:24
@olavloite
olavloite requested a review from a team January 28, 2026 09:24
Adds support for using the gorm PostgreSQL dialect with Spanner. Connections to
Spanner use the Spanner database/sql driver, and not one of the PostgreSQL
database drivers (e.g. it does not use pgx). Using this dialect with Spanner
does not require the use of PGAdapter.
Comment thread postgresql/migrator.go Outdated

func (m spannerPostgresMigrator) disableAutoMigrateBatching() bool {
if cfg, ok := m.Dialector.(Dialector); ok {
return cfg.SpannerConfig.AutoOrderByPk

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should it not return cfg.SpannerConfig.DisableAutoMigrateBatching?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch, done (and added test for this)

Comment thread postgresql/migrator_emulator_test.go Outdated
}

// TODO: Remove
_ = os.Setenv("SPANNER_EMULATOR_HOST", "localhost:9010")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should we remove this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, done.

Comment thread postgresql/spanner.go Outdated
conn = c
} else {
sqlDB, _ := db.DB()
conn, _ = sqlDB.Conn(context.Background())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If db.DB() returns an error (e.g., pool exhausted, connection closed), sqlDB will be nil and sqlDB.Conn() will panic with a nil pointer dereference.

NOTE: The GoogleSQL version has the same pattern, so this might be copied intentionally, but it's still a bug in both. At the very least, return a migrator that reports errors rather than crashing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, that is a good point. The probability of this actually returning an error is minimal, as you can only get to this point if you already have a valid database connection. But nonetheless you're right that we should check this. I've changed this to return a migrator that uses the default ConnPool. That again will then cause the same error to happen when the application tries to use the migrator.

Comment thread postgresql/spanner.go Outdated

func BeforeUpdate(db *gorm.DB) {
if db.Statement != nil && db.Statement.Schema != nil && db.Statement.Schema.PrimaryFieldDBNames != nil {
omit := append(db.Statement.Omits, db.Statement.Schema.PrimaryFieldDBNames...)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

append(db.Statement.Omits, ...) may mutate the underlying array of db.Statement.Omits if it has capacity, causing a data race in concurrent usage.

Per the https://go.dev/ref/spec#Appending_and_copying_slices, append may reuse the underlying
array. This is different from the GoogleSQL version which calls db.Statement.Omit(db.Statement.Schema.PrimaryFieldDBNames...) directly. Use the same pattern or explicitly copy the slice first.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed

Comment thread postgresql/migrator.go
tx.Exec(fmt.Sprintf(`alter database "%s" set spanner.default_sequence_kind = 'bit_reversed_positive'`, m.CurrentDatabase()))
}
err = m.Migrator.AutoMigrate(values...)
if err == nil {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

in case of error StartBatchDDL() will be open, should we call AbortBatch()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, I've added a defer func for this.

Comment thread postgresql/migrator.go Outdated
stmt.Schema.Fields = append(stmt.Schema.Fields, pk)
stmt.Schema.FieldsByDBName[pk.DBName] = pk
stmt.Schema.FieldsByName[pk.Name] = pk
// stmt.Schema.PrimaryFields = append(stmt.Schema.PrimaryFields, pk)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: here and everywhere we remove the commented out lines OR make comment on why they were disabled?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done

Comment thread postgresql/spanner.go
@@ -0,0 +1,260 @@
package spannerpg

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: add copyright/license header

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done

Comment thread postgresql/migrator.go
@@ -0,0 +1,768 @@
package spannerpg

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: add copyright/license header

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done

Comment thread postgresql/migrator.go Outdated

func (m spannerPostgresMigrator) dropTableIndexes(tx *gorm.DB, stmt *gorm.Statement) error {
currentSchema, curTable := m.CurrentSchema(stmt, stmt.Table)
fk := m.queryRawWithTx(tx, "select index_name from information_schema.indexes WHERE table_schema = ? AND table_name = ? AND index_type in ('INDEX') AND spanner_is_managed='NO'", currentSchema, curTable)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: The variable is named fk (suggesting foreign key) but holds index query results. This was copy-pasted from dropForeignKeysReferencingTable where fk makes sense. Minor but confusing for readers.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed

Comment thread postgresql/migrator.go Outdated
@olavloite
olavloite merged commit 7ebcabc into main Feb 12, 2026
18 checks passed
@olavloite
olavloite deleted the postgresql-dialect branch February 12, 2026 07:23
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.

Support Spanner PostgreSQL-dialect databases

2 participants