Skip to content

Conversation

stephencelis
Copy link
Member

@stephencelis stephencelis commented Sep 19, 2025

The @Selection and @Table macros currently overlap in many ways, but there lacks a cohesive story. @Selection generates a nested Columns type that can used in a query to select from expressions, while @Table does not, even though it would be just as beneficial to do so.

And then we have @Table @Selection for combining this logic together for common table expressions, which can be queried like tables. But we should probably just bake everything into a single macro.

This PR explores a rethinking of @Table and @Selection to allow them to better compose together. Both macros now output most of the same functionality. There is just a small semantic difference.

@Table should be used for globally queryable table-like things. This includes:

  • Database tables
  • Virtual tables
  • Database views

@Selection should be used for groups of columns. This includes:

  • A set of columns selected in a query
  • A common table expression that can be queried

With their functionality now almost entirely overlapping, we can compose them together. You can now group a bunch of a table's columns using @Selection:

@Selection
struct Timestamps {
  let createdAt: Date
  var updatedAt: Date
}

@Table
struct Reminder {
  let id: UUID
  let timestamps: Timestamps
}

(You can nest these selections arbitrarily deep.)

You can specify composite primary keys:

@Table
struct Enrollment {
  @Selection
  struct ID {
    let courseID: Course.ID
    let studentID: Student.ID
  }

  let id: ID
  // ...
}

You can even define database functions that take full tables/selections as arguments:

@DatabaseFunction
func isValid(_ reminder: Reminder) -> Bool {
  // Validate reminder...
}

Reminder.select { $isValid($0) }
// SELECT "isValid"("reminders".id, …)
// FROM "reminders"

Finally, we are introducing a new StructuredQueriesCasePaths trait, which allows for enum-based tables and selections, which can be used in all the ways above.

@CasePathable
@Table
enum TimelineItem {
  case note(Note)
  case photo(Photo)
  case video(Video)
}

The `@Selection` and `@Table` type overlap in many ways, but there is
no reason why `@Selection`'s `Columns` type should ever be omitted from
`@Table`, and so this PR introduces the machinery for just that.

Draft for now because there's much to discuss:

  - Should we deprecate `@Selection` entirely, and push people to use
    `@Table` for everything? It might be a little weird, but we've
    already blurred the line with CTEs and database views that maybe we
    should consider `@Table` to mean groups of columns that can be
    assembled in Swift. It's also less library code to
    maintain/document, though it is _more_ code generation (arguably not
    a ton, but who knows the impact on the compiler/binary).

  - Or should we keep `@Selection` around as a lightweight subset of
    `@Table`?

      - If so, should we somehow compose our macro code better, so that
        `@Table` calls down to `@Selection`?

      - Or should it still live in a parallel world, but be updated to
        use some formal machinery, like the `TableExpression` protocol
        introduced in this PR.
@stephencelis stephencelis marked this pull request as draft September 20, 2025 00:00
@stephencelis stephencelis changed the title Incorporate @Selection logic into @Table Nested @Table selections via a new @Columns macro Sep 22, 2025
@stephencelis stephencelis marked this pull request as ready for review September 22, 2025 19:51
@stephencelis stephencelis mentioned this pull request Sep 22, 2025
Comment on lines +14 to +15
/// A type that describes this table as a query expression.
associatedtype Selection: TableExpression<Self>
Copy link
Member Author

Choose a reason for hiding this comment

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

Bikeshed opportunity: this is aliased to Columns, but named Selection here to distinguish itself better from TableColumns. Do we want to consider alternate naming, though to avoid overloading things with aliases?

/// Don't create instances of this value directly. Instead, use the `@Table` and `@Columns` macros
/// to generate values of this type.
@dynamicMemberLookup
public struct ColumnGroup<Root: Table, Values: Table>: _TableColumnExpression
Copy link
Member Author

Choose a reason for hiding this comment

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

Bikeshed opportunity: Should we call this TableColumnGroup for consistent prefixing?

@stephencelis stephencelis changed the title Nested @Table selections via a new @Columns macro Nested @Table selections Sep 28, 2025
@stephencelis stephencelis changed the title Nested @Table selections Nested @Tables, selections, and more Sep 28, 2025
@stephencelis stephencelis changed the title Nested @Tables, selections, and more Better @Table/@Selection composition: nested column groupings, enums, and more Sep 29, 2025
@stephencelis stephencelis merged commit 49ce85d into main Oct 2, 2025
2 of 3 checks passed
@stephencelis stephencelis deleted the table-expression branch October 2, 2025 18:10
bok- pushed a commit to bok-/swift-structured-queries-with-traits that referenced this pull request Oct 12, 2025
…ums, and more (pointfreeco#184)

* Incorporate `@Selection` logic into `@Table`

The `@Selection` and `@Table` type overlap in many ways, but there is
no reason why `@Selection`'s `Columns` type should ever be omitted from
`@Table`, and so this PR introduces the machinery for just that.

Draft for now because there's much to discuss:

  - Should we deprecate `@Selection` entirely, and push people to use
    `@Table` for everything? It might be a little weird, but we've
    already blurred the line with CTEs and database views that maybe we
    should consider `@Table` to mean groups of columns that can be
    assembled in Swift. It's also less library code to
    maintain/document, though it is _more_ code generation (arguably not
    a ton, but who knows the impact on the compiler/binary).

  - Or should we keep `@Selection` around as a lightweight subset of
    `@Table`?

      - If so, should we somehow compose our macro code better, so that
        `@Table` calls down to `@Selection`?

      - Or should it still live in a parallel world, but be updated to
        use some formal machinery, like the `TableExpression` protocol
        introduced in this PR.

* wip

* Support tuple operations with `Table` records

* Support nested tables

* fixes

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Basic docs pass

* wip

* Fixes

* wip

* fixes

* wip

* wip

* db function support

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Infer multi-column when possible

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Docs and tests

* wip

* wip

* wip

* wip

* wip

* wip

* wip

---------

Co-authored-by: Brandon Williams <[email protected]>
coenttb pushed a commit to coenttb/swift-structured-queries-postgres that referenced this pull request Oct 14, 2025
…ums, and more (pointfreeco#184)

* Incorporate `@Selection` logic into `@Table`

The `@Selection` and `@Table` type overlap in many ways, but there is
no reason why `@Selection`'s `Columns` type should ever be omitted from
`@Table`, and so this PR introduces the machinery for just that.

Draft for now because there's much to discuss:

  - Should we deprecate `@Selection` entirely, and push people to use
    `@Table` for everything? It might be a little weird, but we've
    already blurred the line with CTEs and database views that maybe we
    should consider `@Table` to mean groups of columns that can be
    assembled in Swift. It's also less library code to
    maintain/document, though it is _more_ code generation (arguably not
    a ton, but who knows the impact on the compiler/binary).

  - Or should we keep `@Selection` around as a lightweight subset of
    `@Table`?

      - If so, should we somehow compose our macro code better, so that
        `@Table` calls down to `@Selection`?

      - Or should it still live in a parallel world, but be updated to
        use some formal machinery, like the `TableExpression` protocol
        introduced in this PR.

* wip

* Support tuple operations with `Table` records

* Support nested tables

* fixes

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Basic docs pass

* wip

* Fixes

* wip

* fixes

* wip

* wip

* db function support

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Infer multi-column when possible

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Docs and tests

* wip

* wip

* wip

* wip

* wip

* wip

* wip

---------

Co-authored-by: Brandon Williams <[email protected]>
coenttb pushed a commit to coenttb/swift-structured-queries-postgres that referenced this pull request Oct 15, 2025
…ums, and more (pointfreeco#184)

* Incorporate `@Selection` logic into `@Table`

The `@Selection` and `@Table` type overlap in many ways, but there is
no reason why `@Selection`'s `Columns` type should ever be omitted from
`@Table`, and so this PR introduces the machinery for just that.

Draft for now because there's much to discuss:

  - Should we deprecate `@Selection` entirely, and push people to use
    `@Table` for everything? It might be a little weird, but we've
    already blurred the line with CTEs and database views that maybe we
    should consider `@Table` to mean groups of columns that can be
    assembled in Swift. It's also less library code to
    maintain/document, though it is _more_ code generation (arguably not
    a ton, but who knows the impact on the compiler/binary).

  - Or should we keep `@Selection` around as a lightweight subset of
    `@Table`?

      - If so, should we somehow compose our macro code better, so that
        `@Table` calls down to `@Selection`?

      - Or should it still live in a parallel world, but be updated to
        use some formal machinery, like the `TableExpression` protocol
        introduced in this PR.

* wip

* Support tuple operations with `Table` records

* Support nested tables

* fixes

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Basic docs pass

* wip

* Fixes

* wip

* fixes

* wip

* wip

* db function support

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Infer multi-column when possible

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Docs and tests

* wip

* wip

* wip

* wip

* wip

* wip

* wip

---------

Co-authored-by: Brandon Williams <[email protected]>
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