Skip to content

Conversation

@atzedus
Copy link

@atzedus atzedus commented Jan 30, 2026

  • Added Merge function to create MERGE queries in psql dialect.
  • Introduced mm package for building MERGE query components including Into, Using, WhenMatched, and WhenNotMatched clauses.
  • Implemented various actions for matched and not matched conditions, including updates, inserts, and deletions.
  • Created extensive test cases covering multiple scenarios for the MERGE statement, ensuring correct SQL generation and argument handling.

Roman A. Grigorovich added 3 commits January 30, 2026 14:48
…e test cases

- Added Merge function to create MERGE queries in psql dialect.
- Introduced mm package for building MERGE query components including Into, Using, WhenMatched, and WhenNotMatched clauses.
- Implemented various actions for matched and not matched conditions, including updates, inserts, and deletions.
- Created extensive test cases covering multiple scenarios for the MERGE statement, ensuring correct SQL generation and argument handling.
@stephenafamo
Copy link
Owner

stephenafamo commented Feb 1, 2026

Thank you so so much for this PR.

From my first overview it seems good. However, I think I'd like to do some reorganisation of some of the things placed in the dialect/merge.go file and the mm/qm.go file.

In addition, remember to add a note in the CHANGELOG.md file

@atzedus
Copy link
Author

atzedus commented Feb 1, 2026

@stephenafamo what do you think about this commit? 46eb21a

Background

The MERGE statement implementation in psql requires awareness of the PostgreSQL version because certain features are only available in specific versions:

  • PostgreSQL 15+: Basic MERGE support
  • PostgreSQL 17+: MERGE with RETURNING clause, NOT MATCHED BY SOURCE clause

When using Table.Merge() from the ORM layer, we want to automatically add the RETURNING clause (similar to Insert, Update, Delete) but only when the database version supports it.

Alternatives Considered

1. Field in dialect struct

type dialect struct {
    Version int
}

func SetVersion(version int) {
    Dialect.Version = version
}

Pros: Centralized, can be set once at startup
Cons: Global state, cannot support multiple PostgreSQL versions in the same application

2. Field in Table struct

type Table[...] struct {
    Version int
}
table.Version = 17

Pros: Per-table configuration, explicit in code
Cons: Requires modifying every table instance, not practical for generated code

3. Functional Options at Table Creation

NewTablex(..., WithVersion(17))

Pros: Explicit configuration, immutable after creation
Cons: Breaking API change, version fixed at init time

4. Context-based Configuration (Chosen)

ctx := psql.SetVersion(ctx, 17)
results, err := table.Merge(...).All(ctx, db)

Pros: Thread-safe, supports different versions per request, no global state, follows Go idioms
Cons: Requires setting version explicitly in context chain

Decision

Context-based configuration was chosen because:

  1. Flexibility: Supports connecting to databases with different PostgreSQL versions in the same application
  2. Go Idioms: Context for request-scoped values is a well-established pattern
  3. Minimal API Changes: No changes to existing Table.Merge() API

Usage Examples

// Set version in context
ctx := psql.SetVersion(context.Background(), 17)

// MERGE will automatically add RETURNING for version 17+
results, err := table.Merge(...).All(ctx, db)

API Reference

// SetVersion sets the major version in the context.
func SetVersion(ctx context.Context, version int) context.Context

// GetVersion returns the major version from the context (0 if not set).
func GetVersion(ctx context.Context) int

// VersionAtLeast checks if the version in context is at least the given version.
func VersionAtLeast(ctx context.Context, minVersion int) bool

@atzedus atzedus requested a review from stephenafamo February 1, 2026 11:21
@stephenafamo
Copy link
Owner

I think the versioning method is quite elegant. Would work quite nicely for me since I usually set my parent context at the very beginning of main().

@atzedus
Copy link
Author

atzedus commented Feb 1, 2026

Thank you! The context-based approach was chosen specifically to avoid global state and make version configuration thread-safe and flexible.

For MERGE specifically, setting version to 17+ enables automatic RETURNING * in Table.Merge().

A similar versioning approach could potentially be applied to other dialects (MySQL, SQLite) that have version-specific features as well. Additionally, for PostgreSQL itself, it might be useful for supporting other PostgreSQL 17+ features like the extended RETURNING WITH (OLD | NEW AS alias) syntax — I'm planning to propose a solution for that in an upcoming PR.

I understand that the changes in this PR are quite extensive, so please take your time to review and think through the code. To be honest, I'm not entirely sure I've done everything correctly myself, so I would really appreciate your feedback and guidance. I'm happy to address any concerns or make adjustments as needed.

@stephenafamo
Copy link
Owner

Thanks again. I'll need to find a block of time to be able to properly review this, so it may take a bit.

But feel free to nudge me if you feel I'm taking too long 😅

@atzedus atzedus force-pushed the merge-psql-operation branch from 3b49cef to f8f30c5 Compare February 1, 2026 20:58
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