Skip to content

Commit

Permalink
Use mods to define windows, not chain methods
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenafamo committed Dec 26, 2024
1 parent 23853ab commit 91f9bef
Show file tree
Hide file tree
Showing 21 changed files with 507 additions and 447 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `gen/QueriesTemplates` which in the future will contain base templates for generating code for parsed qureries.
- Added a `QueryTemplate` field to `bobgen_helpers.Templates` for drivers to include additional templates for queries.
- Added a new reserved output key `queries`. This is handled specially for each query folder supplied by the driver.
- Added new `wm` package to each dialect for mods that modify `Window` clauses.

### Changed

Expand All @@ -25,6 +26,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Previously, singleton templates should be kept in a `singleton` folder. Now, any template not inside a folder is considered a singleton template.
- Previoulsy, templates in the root folder are merged and run for each table. Now, this will happen to templates in the `table/` folder.
- Previoulsy, the entire file tree and every subdirectory is walked to find templates. Now only templates in the root folder and the `table/` folder are considered.
- Change `From` in `clause.Window` to `BasedOn` to avoid confusion with `FromPreceding` and `FromFollowing`. Also change `SetFrom` to `SetBasedOn`.
- Embed `clause.OrderBy` in `clause.Window` to make it possible to reuse `OrderBy` mods in window definitions.
- Change the `Definition` field in `clause.NamedWindow` from `any` to `clause.Window` for extra type safety.
- `sm.Window` now takes mods to modify the window clause.
- `fm.Over` now takes mods to modify the window for the window function.

### Deprecated

Expand All @@ -34,6 +40,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Remove redundatnt type parameters from `orm.ExecQuery`.
- Remove unnecessary interface in `orm.Query` and `orm.ExecQuery`.
- Remove the redundant `clause.IWindow` interface.
- Remove `dialect.WindowMod` and `dialect.WindowMods` which use chainable methods to modify `Window` clauses. This is now handled by the `wm` package which used mods.

### Fixed

Expand Down
31 changes: 9 additions & 22 deletions clause/window.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,24 @@ import (
"github.com/stephenafamo/bob"
)

type IWindow interface {
SetFrom(string)
AddPartitionBy(...any)
AddOrderBy(...any)
SetMode(string)
SetStart(any)
SetEnd(any)
SetExclusion(string)
}

type Window struct {
From string // an existing window name
orderBy []any
BasedOn string // an existing window name
OrderBy
partitionBy []any
Frame
}

func (wi *Window) SetFrom(from string) {
wi.From = from
func (wi *Window) SetBasedOn(from string) {
wi.BasedOn = from
}

func (wi *Window) AddPartitionBy(condition ...any) {
wi.partitionBy = append(wi.partitionBy, condition...)
}

func (wi *Window) AddOrderBy(order ...any) {
wi.orderBy = append(wi.orderBy, order...)
}

func (wi Window) WriteSQL(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
if wi.From != "" {
w.Write([]byte(wi.From))
if wi.BasedOn != "" {
w.Write([]byte(wi.BasedOn))
w.Write([]byte(" "))
}

Expand All @@ -47,7 +33,8 @@ func (wi Window) WriteSQL(ctx context.Context, w io.Writer, d bob.Dialect, start
return nil, err
}

orderArgs, err := bob.ExpressSlice(ctx, w, d, start, wi.orderBy, "ORDER BY ", ", ", "")
orderArgs, err := bob.ExpressIf(ctx, w, d, start+len(args), wi.OrderBy,
len(wi.OrderBy.Expressions) > 0, " ", "")
if err != nil {
return nil, err
}
Expand All @@ -64,7 +51,7 @@ func (wi Window) WriteSQL(ctx context.Context, w io.Writer, d bob.Dialect, start

type NamedWindow struct {
Name string
Definition any
Definition Window
}

func (n NamedWindow) WriteSQL(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
Expand Down
109 changes: 0 additions & 109 deletions dialect/mysql/dialect/mods.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package dialect

import (
"context"
"io"

"github.com/stephenafamo/bob"
"github.com/stephenafamo/bob/clause"
"github.com/stephenafamo/bob/expr"
Expand Down Expand Up @@ -328,109 +325,3 @@ func (l LockChain[Q]) SkipLocked() LockChain[Q] {
return lock
})
}

type WindowMod[Q interface{ SetWindow(clause.Window) }] struct {
*WindowChain[*WindowMod[Q]]
}

func (w WindowMod[Q]) Apply(q Q) {
q.SetWindow(w.def)
}

type WindowsMod[Q interface{ AppendWindow(clause.NamedWindow) }] struct {
Name string
*WindowChain[*WindowsMod[Q]]
}

func (w WindowsMod[Q]) Apply(q Q) {
q.AppendWindow(clause.NamedWindow{
Name: w.Name,
Definition: w.def,
})
}

type WindowChain[T any] struct {
def clause.Window
Wrap T
}

func (w *WindowChain[T]) From(name string) T {
w.def.SetFrom(name)
return w.Wrap
}

func (w *WindowChain[T]) PartitionBy(condition ...any) T {
w.def.AddPartitionBy(condition...)
return w.Wrap
}

func (w *WindowChain[T]) OrderBy(order ...any) T {
w.def.AddOrderBy(order...)
return w.Wrap
}

func (w *WindowChain[T]) Range() T {
w.def.SetMode("RANGE")
return w.Wrap
}

func (w *WindowChain[T]) Rows() T {
w.def.SetMode("ROWS")
return w.Wrap
}

func (w *WindowChain[T]) FromUnboundedPreceding() T {
w.def.SetStart("UNBOUNDED PRECEDING")
return w.Wrap
}

func (w *WindowChain[T]) FromPreceding(ctx context.Context, exp any) T {
w.def.SetStart(bob.ExpressionFunc(
func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
return bob.ExpressIf(ctx, w, d, start, exp, true, "", " PRECEDING")
}),
)
return w.Wrap
}

func (w *WindowChain[T]) FromCurrentRow() T {
w.def.SetStart("CURRENT ROW")
return w.Wrap
}

func (w *WindowChain[T]) FromFollowing(exp any) T {
w.def.SetStart(bob.ExpressionFunc(
func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
return bob.ExpressIf(ctx, w, d, start, exp, true, "", " FOLLOWING")
}),
)
return w.Wrap
}

func (w *WindowChain[T]) ToPreceding(exp any) T {
w.def.SetEnd(bob.ExpressionFunc(
func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
return bob.ExpressIf(ctx, w, d, start, exp, true, "", " PRECEDING")
}),
)
return w.Wrap
}

func (w *WindowChain[T]) ToCurrentRow(count int) T {
w.def.SetEnd("CURRENT ROW")
return w.Wrap
}

func (w *WindowChain[T]) ToFollowing(exp any) T {
w.def.SetEnd(bob.ExpressionFunc(
func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
return bob.ExpressIf(ctx, w, d, start, exp, true, "", " FOLLOWING")
}),
)
return w.Wrap
}

func (w *WindowChain[T]) ToUnboundedFollowing() T {
w.def.SetEnd("UNBOUNDED FOLLOWING")
return w.Wrap
}
12 changes: 7 additions & 5 deletions dialect/mysql/fm/qm.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/stephenafamo/bob"
"github.com/stephenafamo/bob/clause"
"github.com/stephenafamo/bob/dialect/mysql/dialect"
"github.com/stephenafamo/bob/mods"
)

func Distinct() bob.Mod[*dialect.Function] {
Expand All @@ -26,10 +27,11 @@ func Filter(e ...any) bob.Mod[*dialect.Function] {
})
}

func Over() dialect.WindowMod[*dialect.Function] {
m := dialect.WindowMod[*dialect.Function]{}
m.WindowChain = &dialect.WindowChain[*dialect.WindowMod[*dialect.Function]]{
Wrap: &m,
func Over(winMods ...bob.Mod[*clause.Window]) bob.Mod[*dialect.Function] {
w := clause.Window{}
for _, mod := range winMods {
mod.Apply(&w)
}
return m

return mods.Window[*dialect.Function](w)
}
6 changes: 5 additions & 1 deletion dialect/mysql/select_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/stephenafamo/bob/dialect/mysql/dialect"
"github.com/stephenafamo/bob/dialect/mysql/fm"
"github.com/stephenafamo/bob/dialect/mysql/sm"
"github.com/stephenafamo/bob/dialect/mysql/wm"
testutils "github.com/stephenafamo/bob/test/utils"
mysqlparser "github.com/stephenafamo/sqlparser/mysql"
)
Expand Down Expand Up @@ -86,7 +87,10 @@ func TestSelect(t *testing.T) {
sm.Columns(
"status",
mysql.F("LEAD", "created_date", 1, mysql.F("NOW"))(
fm.Over().PartitionBy("presale_id").OrderBy("created_date"),
fm.Over(
wm.PartitionBy("presale_id"),
wm.OrderBy("created_date"),
),
).Minus(mysql.Quote("created_date")).As("difference")),
sm.From("presales_presalestatus")),
).As("differnce_by_status"),
Expand Down
15 changes: 8 additions & 7 deletions dialect/mysql/sm/qm.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,16 @@ func WithRollup(distinct bool) bob.Mod[*dialect.SelectQuery] {
})
}

func Window(name string) dialect.WindowsMod[*dialect.SelectQuery] {
m := dialect.WindowsMod[*dialect.SelectQuery]{
Name: name,
func Window(name string, winMods ...bob.Mod[*clause.Window]) bob.Mod[*dialect.SelectQuery] {
w := clause.Window{}
for _, mod := range winMods {
mod.Apply(&w)
}

m.WindowChain = &dialect.WindowChain[*dialect.WindowsMod[*dialect.SelectQuery]]{
Wrap: &m,
}
return m
return mods.NamedWindow[*dialect.SelectQuery](clause.NamedWindow{
Name: name,
Definition: w,
})
}

func OrderBy(e any) dialect.OrderBy[*dialect.SelectQuery] {
Expand Down
106 changes: 106 additions & 0 deletions dialect/mysql/wm/qm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package wm

import (
"context"
"io"

"github.com/stephenafamo/bob"
"github.com/stephenafamo/bob/clause"
"github.com/stephenafamo/bob/dialect/mysql/dialect"
)

func BasedOn(name string) bob.Mod[*clause.Window] {
return bob.ModFunc[*clause.Window](func(w *clause.Window) {
w.SetBasedOn(name)
})
}

func PartitionBy(condition any) bob.Mod[*clause.Window] {
return bob.ModFunc[*clause.Window](func(w *clause.Window) {
w.AddPartitionBy(condition)
})
}

func OrderBy(e any) dialect.OrderBy[*clause.Window] {
return dialect.OrderBy[*clause.Window](func() clause.OrderDef {
return clause.OrderDef{
Expression: e,
}
})
}

func Range() bob.Mod[*clause.Window] {
return bob.ModFunc[*clause.Window](func(w *clause.Window) {
w.SetMode("RANGE")
})
}

func Rows() bob.Mod[*clause.Window] {
return bob.ModFunc[*clause.Window](func(w *clause.Window) {
w.SetMode("ROWS")
})
}

func FromUnboundedPreceding() bob.Mod[*clause.Window] {
return bob.ModFunc[*clause.Window](func(w *clause.Window) {
w.SetStart("UNBOUNDED PRECEDING")
})
}

func FromPreceding(exp any) bob.Mod[*clause.Window] {
return bob.ModFunc[*clause.Window](func(w *clause.Window) {
w.SetStart(bob.ExpressionFunc(
func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
return bob.ExpressIf(ctx, w, d, start, exp, true, "", " PRECEDING")
}),
)
})
}

func FromCurrentRow() bob.Mod[*clause.Window] {
return bob.ModFunc[*clause.Window](func(w *clause.Window) {
w.SetStart("CURRENT ROW")
})
}

func FromFollowing(exp any) bob.Mod[*clause.Window] {
return bob.ModFunc[*clause.Window](func(w *clause.Window) {
w.SetStart(bob.ExpressionFunc(
func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
return bob.ExpressIf(ctx, w, d, start, exp, true, "", " FOLLOWING")
}),
)
})
}

func ToPreceding(exp any) bob.Mod[*clause.Window] {
return bob.ModFunc[*clause.Window](func(w *clause.Window) {
w.SetEnd(bob.ExpressionFunc(
func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
return bob.ExpressIf(ctx, w, d, start, exp, true, "", " PRECEDING")
}),
)
})
}

func ToCurrentRow() bob.Mod[*clause.Window] {
return bob.ModFunc[*clause.Window](func(w *clause.Window) {
w.SetEnd("CURRENT ROW")
})
}

func ToFollowing(exp any) bob.Mod[*clause.Window] {
return bob.ModFunc[*clause.Window](func(w *clause.Window) {
w.SetEnd(bob.ExpressionFunc(
func(ctx context.Context, w io.Writer, d bob.Dialect, start int) ([]any, error) {
return bob.ExpressIf(ctx, w, d, start, exp, true, "", " FOLLOWING")
}),
)
})
}

func ToUnboundedFollowing() bob.Mod[*clause.Window] {
return bob.ModFunc[*clause.Window](func(w *clause.Window) {
w.SetEnd("UNBOUNDED FOLLOWING")
})
}
Loading

0 comments on commit 91f9bef

Please sign in to comment.