From 4b0e739e6d1ca16c8efc76eee2164bd43b91a1ff Mon Sep 17 00:00:00 2001 From: profe-ajedrez Date: Mon, 28 Oct 2024 16:33:59 -0300 Subject: [PATCH 1/7] documenting insert.go --- .github/workflows/go.yml | 56 ++++++++++++++++++++++++++++++++++++++++ README.md | 1 + insert.go | 51 ++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 .github/workflows/go.yml diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..80fe008 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,56 @@ +name: Go # The name of the workflow that will appear on Github + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + go: [1.16, 1.17] + steps: + - uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + + - name: Build + run: go install + + - name: Test + run: | + go test -v -cover ./... -coverprofile coverage.out -coverpkg ./... + go tool cover -func coverage.out -o coverage.out # Replaces coverage.out with the analysis of coverage.out + + - name: Go Coverage Badge + uses: tj-actions/coverage-badge-go@v1 + if: ${{ runner.os == 'Linux' && matrix.go == '1.17' }} # Runs this on only one of the ci builds. + with: + green: 80 + filename: coverage.out + + - uses: stefanzweifel/git-auto-commit-action@v4 + id: auto-commit-action + with: + commit_message: Apply Code Coverage Badge + skip_fetch: true + skip_checkout: true + file_pattern: ./README.md + + - name: Push Changes + if: steps.auto-commit-action.outputs.changes_detected == 'true' + uses: ad-m/github-push-action@master + with: + github_token: ${{ github.token }} + branch: ${{ github.ref }} + \ No newline at end of file diff --git a/README.md b/README.md index 05c5560..1c38169 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Fast and cheap sql builder. [![Go Reference](https://pkg.go.dev/badge/github.com/profe-ajedrez/obreron/v2.svg)](https://pkg.go.dev/github.com/profe-ajedrez/obreron/v2) [![Go Report Card](https://goreportcard.com/badge/github.com/profe-ajedrez/obreron/v2)](https://goreportcard.com/report/github.com/profe-ajedrez/obreron/v2) + ## Supported Dialects - [x] Mysql diff --git a/insert.go b/insert.go index 84b41fa..1382583 100644 --- a/insert.go +++ b/insert.go @@ -7,11 +7,21 @@ import ( "unsafe" ) +// InsertStament represents an insert stament type InsertStament struct { *stament withSelect bool } +// Insert Returns an insert stament +// +// # Example +// +// ins := Insert().Into("client").Col("name", "'some name'").Col("value", "'somemail@mail.net'").ColIf(true, "data", "'some data'").ColIf(false, "info", 12) +// +// query, p := ins.Build() +// +// r, err := db.Exec(q, p...) func Insert() *InsertStament { i := &InsertStament{pool.New().(*stament), false} i.add(insertS, "INSERT", "") @@ -21,16 +31,35 @@ func Insert() *InsertStament { return i } +// Ignore adds Ignore clause to the insert stament +// +// # Example +// +// ins := Insert().Ignore().Into("client").Col("name, value", "'some name'", "'somemail@mail.net'") +// +// query, p := ins.Build() +// +// r, err := db.Exec(q, p...) func (in *InsertStament) Ignore() *InsertStament { in.add(insertS, "IGNORE", "") return in } +// Into adds into clause to the insert stament func (in *InsertStament) Into(table string) *InsertStament { in.add(insertS, "INTO", table) return in } +// Col adds columns and values to the insert clause +// +// # Example +// +// ins := insInsert().Col("name, value", "'some name'", "'somemail@mail.net'").Into("client") +// +// query, p := ins.Build() +// +// r, err := db.Exec(q, p...) func (in *InsertStament) Col(col string, p ...any) *InsertStament { if !in.firstCol { @@ -57,6 +86,15 @@ func (in *InsertStament) Col(col string, p ...any) *InsertStament { return in } +// ColIf adds columns and values to the insert clause when the cond parameter is true +// +// # Example +// +// ins := insInsert().ColIf(true, "name, value", "'some name'", "'somemail@mail.net'").Into("client") +// +// query, p := ins.Build() +// +// r, err := db.Exec(q, p...) func (in *InsertStament) ColIf(cond bool, col string, p ...any) *InsertStament { if cond { return in.Col(col, p...) @@ -64,6 +102,17 @@ func (in *InsertStament) ColIf(cond bool, col string, p ...any) *InsertStament { return in } +// ColSelect is a helper method used to build insert select... staments +// +// # Example +// +// ins := Insert().Into("courses").ColSelectIf(true, "name, location, gid", Select().Col("name, location, 1").From("courses").Where("cid = 2")).ColSelectIf(false, "last_name, last_location, grid", Select().Col("last_name, last_location, 11").From("courses").Where("cid = 2")), +// +// query, p := ins.Build() +// +// r, err := db.Exec(q, p...) +// +// Produces: INSERT INTO courses ( name, location, gid ) SELECT name, location, 1 FROM courses WHERE cid = 2 func (in *InsertStament) ColSelect(col string, expr *SelectStm) *InsertStament { in.firstCol = true in.add(colsS, "(", col) @@ -85,6 +134,7 @@ func (in *InsertStament) Clause(clause, expr string, p ...any) *InsertStament { return in } +// Build returns the query and the parameters as to be used by *sql.DB.query or *sql.DB.Exec func (in *InsertStament) Build() (string, []any) { b := bytes.Buffer{} @@ -177,6 +227,7 @@ func posParams(i int, in *InsertStament, b *bytes.Buffer, buf []byte) { } } +// Close free resources used by the stament func (in *InsertStament) Close() { closeStament(in.stament) } From f8802c0a1e3f746beb502d11ae634b57c731e559 Mon Sep 17 00:00:00 2001 From: profe-ajedrez Date: Tue, 29 Oct 2024 11:44:00 -0300 Subject: [PATCH 2/7] added method InArgs to staments --- delete.go | 43 ++++++++++-------- insert.go | 2 +- obreron.go | 7 +++ obreron_test.go | 41 ++++++++++++++++-- select.go | 19 ++++++++ update.go | 113 +++++++++++++++++++++++++++++++++++++----------- 6 files changed, 177 insertions(+), 48 deletions(-) diff --git a/delete.go b/delete.go index ee6395b..830d6b1 100644 --- a/delete.go +++ b/delete.go @@ -1,11 +1,11 @@ package obreron -type DeleteStament struct { +type DeleteStm struct { *stament } -func Delete() *DeleteStament { - d := &DeleteStament{ +func Delete() *DeleteStm { + d := &DeleteStm{ stament: pool.New().(*stament), } @@ -14,82 +14,89 @@ func Delete() *DeleteStament { return d } -func (dst *DeleteStament) From(source string) *DeleteStament { +func (dst *DeleteStm) From(source string) *DeleteStm { dst.add(fromS, "FROM", source) return dst } -func (dst *DeleteStament) Where(cond string, p ...any) *DeleteStament { +func (dst *DeleteStm) Where(cond string, p ...any) *DeleteStm { dst.where(cond, p...) return dst } -func (dst *DeleteStament) Y() *DeleteStament { +func (dst *DeleteStm) Y() *DeleteStm { dst.clause("AND", "") return dst } -func (dst *DeleteStament) And(expr string, p ...any) *DeleteStament { +func (dst *DeleteStm) And(expr string, p ...any) *DeleteStm { dst.clause("AND", expr, p...) return dst } -func (dst *DeleteStament) AndIf(cond bool, expr string, p ...any) *DeleteStament { +func (dst *DeleteStm) AndIf(cond bool, expr string, p ...any) *DeleteStm { if cond { dst.clause("AND", expr, p...) } return dst } -func (dst *DeleteStament) Or(expr string, p ...any) *DeleteStament { +func (dst *DeleteStm) Or(expr string, p ...any) *DeleteStm { dst.clause("OR", expr, p...) return dst } -func (dst *DeleteStament) OrIf(cond bool, expr string, p ...any) *DeleteStament { +func (dst *DeleteStm) OrIf(cond bool, expr string, p ...any) *DeleteStm { if cond { dst.clause("OR", expr, p...) } return dst } -func (dst *DeleteStament) Like(expr string, p ...any) *DeleteStament { +func (dst *DeleteStm) Like(expr string, p ...any) *DeleteStm { dst.clause("LIKE", expr, p...) return dst } -func (dst *DeleteStament) LikeIf(cond bool, expr string, p ...any) *DeleteStament { +func (dst *DeleteStm) LikeIf(cond bool, expr string, p ...any) *DeleteStm { if cond { dst.Like(expr, p...) } return dst } -func (dst *DeleteStament) In(value, expr string, p ...any) *DeleteStament { +func (dst *DeleteStm) In(value, expr string, p ...any) *DeleteStm { dst.clause(value+" IN ("+expr+")", "", p...) return dst } -func (dst *DeleteStament) Close() { +// InArgs adds an In clause to the stament automatically setting the positional parameters of the query based on the +// passed parameters +func (up *DeleteStm) InArgs(value string, p ...any) *DeleteStm { + up.stament.inArgs(value, p...) + return up +} + +func (dst *DeleteStm) Close() { closeStament(dst.stament) } -func (dst *DeleteStament) OrderBy(expr string, p ...any) *DeleteStament { +func (dst *DeleteStm) OrderBy(expr string, p ...any) *DeleteStm { dst.add(limitS, "ORDER BY", expr, p...) return dst } -func (dst *DeleteStament) Limit(limit int) *DeleteStament { +func (dst *DeleteStm) Limit(limit int) *DeleteStm { dst.add(limitS, "LIMIT", "?", limit) return dst } -func (dst *DeleteStament) Clause(clause, expr string, p ...any) *DeleteStament { +func (dst *DeleteStm) Clause(clause, expr string, p ...any) *DeleteStm { dst.add(dst.lastPos, clause, expr, p...) return dst } -func (dst *DeleteStament) ClauseIf(cond bool, clause, expr string, p ...any) *DeleteStament { +func (dst *DeleteStm) ClauseIf(cond bool, clause, expr string, p ...any) *DeleteStm { if cond { dst.Clause(clause, expr, p...) } diff --git a/insert.go b/insert.go index 1382583..c8331fe 100644 --- a/insert.go +++ b/insert.go @@ -106,7 +106,7 @@ func (in *InsertStament) ColIf(cond bool, col string, p ...any) *InsertStament { // // # Example // -// ins := Insert().Into("courses").ColSelectIf(true, "name, location, gid", Select().Col("name, location, 1").From("courses").Where("cid = 2")).ColSelectIf(false, "last_name, last_location, grid", Select().Col("last_name, last_location, 11").From("courses").Where("cid = 2")), +// ins := Insert().Into("courses").ColSelectIf(true, "name, location, gid", Select().Col("name, location, 1").From("courses").Where("cid = 2")).ColSelectIf(false, "last_name, last_location, grid", Select().Col("last_name, last_location, 11").From("courses").Where("cid = 2")) // // query, p := ins.Build() // diff --git a/obreron.go b/obreron.go index 10efff8..2a2e8e7 100644 --- a/obreron.go +++ b/obreron.go @@ -3,6 +3,7 @@ package obreron import ( "bytes" "slices" + "strings" "sync" "unsafe" ) @@ -47,6 +48,12 @@ func (st *stament) clause(clause, expr string, p ...any) { st.add(st.lastPos, clause, expr, p...) } +func (st *stament) inArgs(value string, p ...any) { + l := len(p) + expr := strings.Repeat("?, ", l)[:l*3-2] + st.clause(value+" IN ("+expr+")", "", p...) +} + func (st *stament) where(cond string, p ...any) { if !st.whereAdded { st.add(whereS, "WHERE", cond, p...) diff --git a/obreron_test.go b/obreron_test.go index ea6c60d..be109e2 100644 --- a/obreron_test.go +++ b/obreron_test.go @@ -307,6 +307,12 @@ func selectTestCases() (cases []struct { expectedParams: nil, tc: Select().Col("a1, a2, a3").From("client").Where("1 = 1").And("status").In("0, 1, 2, 3"), }, + { + name: "columns - where in", + expected: "SELECT a1, a2, a3 FROM client WHERE 1 = 1 AND status IN (?, ?, ?, ?)", + expectedParams: []any{0, 1, 2, 3}, + tc: Select().Col("a1, a2, a3").From("client").Where("1 = 1").Y().InArgs("status", 0, 1, 2, 3), + }, { name: "columns - where like", expected: "SELECT a1, a2, a3 FROM client WHERE 1 = 1 AND city LIKE '%ago%'", @@ -319,13 +325,13 @@ func selectTestCases() (cases []struct { } func deleteTestCases() []struct { - tc *DeleteStament + tc *DeleteStm name string expected string expectedParams []any } { return []struct { - tc *DeleteStament + tc *DeleteStm name string expected string expectedParams []any @@ -352,6 +358,15 @@ func deleteTestCases() []struct { And("estado_cliente = 0"). Y().In("regime_cliente", "'G01','G02', ?", "'G03'").And("a").LikeIf(true, "'%ago%'").ClauseIf(true, "-- Comment\n", ""), }, + { + name: "del where conditions", + expected: "DELETE FROM client WHERE client_id = 100 AND estado_cliente = 0 AND regime_cliente IN (?, ?, ?) AND a LIKE '%ago%' -- Comment\n", + expectedParams: []any{"G01", "G02", "G03"}, + tc: Delete().From("client"). + Where("client_id = 100"). + And("estado_cliente = 0"). + Y().InArgs("regime_cliente", "G01", "G02", "G03").And("a").LikeIf(true, "'%ago%'").ClauseIf(true, "-- Comment\n", ""), + }, { name: "del where conditions limit", expected: "DELETE FROM client WHERE client_id = 100 AND estado_cliente = 0 AND regime_cliente IN ('G01','G02', ?) LIMIT ?", @@ -400,13 +415,13 @@ func deleteTestCases() []struct { } func updateTestCases() (tcs []struct { - tc *UpdateStament + tc *UpdateStm name string expected string expectedParams []any }) { tcs = append(tcs, []struct { - tc *UpdateStament + tc *UpdateStm name string expected string expectedParams []any @@ -417,6 +432,24 @@ func updateTestCases() (tcs []struct { expectedParams: nil, tc: Update("client").Set("status = 0"), }, + { + name: "update simple", + expected: "UPDATE client SET status = 0, name = ?", + expectedParams: []any{"stitch"}, + tc: Update("client").Set("status = 0").Set("name = ?", "stitch"), + }, + { + name: "update simple", + expected: "UPDATE client SET status = 0 WHERE country = ? AND status IN (?, ?, ?, ?)", + expectedParams: []any{"CL", 1, 2, 3, 4}, + tc: Update("client").Set("status = 0").Where("country = ?", "CL").Y().In("status", "?, ?, ?, ?", 1, 2, 3, 4), + }, + { + name: "update simple", + expected: "UPDATE client SET status = 0 WHERE country = ? AND status IN (?, ?, ?, ?)", + expectedParams: []any{"CL", 1, 2, 3, 4}, + tc: Update("client").Set("status = 0").Where("country = ?", "CL").Y().InArgs("status", 1, 2, 3, 4), + }, { name: "update where", expected: "UPDATE client SET status = 0 WHERE status = ?", diff --git a/select.go b/select.go index 6c7c892..9ef93ee 100644 --- a/select.go +++ b/select.go @@ -231,6 +231,18 @@ func (st *SelectStm) AndIf(cond bool, expr string, p ...any) *SelectStm { return st } +// Y adds an AND conector to the stament where is called. Its helpful when used with In() +// +// # Example +// +// Update("client").Set("status = 0").Where("country = ?", "CL").Y().In("status", "", 1, 2, 3, 4) +// +// Produces: UPDATE client SET status = 0 WHERE country = ? AND status IN (?, ?, ?, ?) +func (up *SelectStm) Y() *SelectStm { + up.clause("AND", "") + return up +} + func (st *SelectStm) Or(expr string, p ...any) *SelectStm { st.clause("OR", expr, p...) return st @@ -277,6 +289,13 @@ func (st *SelectStm) In(expr string, p ...any) *SelectStm { return st } +// InArgs adds an In clause to the stament automatically setting the positional parameters of the query based on the +// passed parameters +func (up *SelectStm) InArgs(value string, p ...any) *SelectStm { + up.stament.inArgs(value, p...) + return up +} + // GroupBy adds a GROUP BY clause to the query // // # Example diff --git a/update.go b/update.go index a538083..b3d5bb5 100644 --- a/update.go +++ b/update.go @@ -1,11 +1,21 @@ package obreron -type UpdateStament struct { +// UpdateStm represents an update stament +type UpdateStm struct { *stament } -func Update(table string) *UpdateStament { - d := &UpdateStament{ +// Update returns an update stament +// +// # Example +// +// upd := Update("client").Set("status = 0").Where("status = ?", 1) +// +// query, p := upd.Build() // builds UPDATE client SET status = 0 WHERE status = ? and stores in p []any{1} +// +// r, err := db.Exec(query, p...) +func Update(table string) *UpdateStm { + d := &UpdateStm{ stament: pool.New().(*stament), } d.firstCol = true @@ -13,7 +23,25 @@ func Update(table string) *UpdateStament { return d } -func (up *UpdateStament) ColSelect(col *SelectStm, alias string) *UpdateStament { +// ColSelect is a helper method which provides a way to build an update (select ...) stament +// +// # Example +// +// upd := Update("items"). +// ColSelectIf( +// true, +// Select(). +// Col("id, retail / wholesale AS markup, quantity"). +// From("items"), "discounted" +// ).Set("items.retail = items.retail * 0.9"). +// Set("a = 2"). +// SetIf(true, "c = 3"). +// Where("discounted.markup >= 1.3"). +// And("discounted.quantity < 100"). +// And("items.id = discounted.id"). +// +// query, p := upd.Build() // builds UPDATE items, ( SELECT id, retail / wholesale AS markup, quantity FROM items) discounted SET a = 2, c = 3 WHERE 1 = 1 AND discounted.markup >= 1.3 AND discounted.quantity < 100 AND items.id = discounted.id +func (up *UpdateStm) ColSelect(col *SelectStm, alias string) *UpdateStm { up.Clause(",(", "") @@ -25,7 +53,8 @@ func (up *UpdateStament) ColSelect(col *SelectStm, alias string) *UpdateStament return up } -func (up *UpdateStament) ColSelectIf(cond bool, col *SelectStm, alias string) *UpdateStament { +// ColSelectIf does the same work as [ColSelect] only when the cond parameter is true +func (up *UpdateStm) ColSelectIf(cond bool, col *SelectStm, alias string) *UpdateStm { if cond { up.ColSelect(col, alias) } @@ -33,7 +62,14 @@ func (up *UpdateStament) ColSelectIf(cond bool, col *SelectStm, alias string) *U return up } -func (up *UpdateStament) Set(expr string, p ...any) *UpdateStament { +// Set adds set clause to the update stament +// +// # Examples +// +// upd := Update("client").Set("status = 0").Where("status = ?", 1) +// up2 := Update("client").Set("status = ?", 0).Where("status = ?", 1) +// up3 := Update("client").Set("status = ?", 0).Set("name = ?", "stitch").Where("status = ?", 1) +func (up *UpdateStm) Set(expr string, p ...any) *UpdateStm { if !up.firstCol { up.Clause(", ", "") up.add(setS, "", expr, p...) @@ -45,7 +81,8 @@ func (up *UpdateStament) Set(expr string, p ...any) *UpdateStament { return up } -func (up *UpdateStament) SetIf(cond bool, expr string, p ...any) *UpdateStament { +// Set adds set clause to the update stament when the cond param is true +func (up *UpdateStm) SetIf(cond bool, expr string, p ...any) *UpdateStm { if cond { up.Set(expr, p...) } @@ -53,89 +90,115 @@ func (up *UpdateStament) SetIf(cond bool, expr string, p ...any) *UpdateStament return up } -func (up *UpdateStament) Where(cond string, p ...any) *UpdateStament { +// Where adds a where clause to the update stament +func (up *UpdateStm) Where(cond string, p ...any) *UpdateStm { up.where(cond, p...) return up } -func (up *UpdateStament) Y() *UpdateStament { +// Y adds an AND conector to the stament where is called. Its helpful when used with In() +// +// # Example +// +// Update("client").Set("status = 0").Where("country = ?", "CL").Y().In("status", "", 1, 2, 3, 4) +// +// Produces: UPDATE client SET status = 0 WHERE country = ? AND status IN (?, ?, ?, ?) +func (up *UpdateStm) Y() *UpdateStm { up.clause("AND", "") return up } -func (up *UpdateStament) And(expr string, p ...any) *UpdateStament { +// And adds an AND conector with eventual parameters to the stament where is called +func (up *UpdateStm) And(expr string, p ...any) *UpdateStm { up.clause("AND", expr, p...) return up } -func (up *UpdateStament) AndIf(cond bool, expr string, p ...any) *UpdateStament { +// And adds an AND conector with eventual parameters to the stament where is called, only when +// cond parameter is true +func (up *UpdateStm) AndIf(cond bool, expr string, p ...any) *UpdateStm { if cond { up.clause("AND", expr, p...) } return up } -func (up *UpdateStament) Or(expr string, p ...any) *UpdateStament { +// Or adds an Or connector with eventual parameters to the stament where is called +func (up *UpdateStm) Or(expr string, p ...any) *UpdateStm { up.clause("OR", expr, p...) return up } -func (up *UpdateStament) OrIf(cond bool, expr string, p ...any) *UpdateStament { +func (up *UpdateStm) OrIf(cond bool, expr string, p ...any) *UpdateStm { if cond { up.clause("OR", expr, p...) } return up } -func (up *UpdateStament) Like(expr string, p ...any) *UpdateStament { +func (up *UpdateStm) Like(expr string, p ...any) *UpdateStm { up.clause("LIKE", expr, p...) return up } -func (up *UpdateStament) LikeIf(cond bool, expr string, p ...any) *UpdateStament { +func (up *UpdateStm) LikeIf(cond bool, expr string, p ...any) *UpdateStm { if cond { up.clause("LIKE", expr, p...) } return up } -func (up *UpdateStament) In(value, expr string, p ...any) *UpdateStament { +func (up *UpdateStm) In(value, expr string, p ...any) *UpdateStm { up.clause(value+" IN ("+expr+")", "", p...) return up } -func (up *UpdateStament) Close() { +// InArgs adds an In clause to the stament automatically setting the positional parameters of the query based on the +// passed parameters +// +// # Example +// +// Update("client").Set("status = 0").Where("country = ?", "CL").Y().InArgs("status", 1, 2, 3, 4) +// +// Produces: UPDATE client SET status = 0 WHERE country = ? AND status IN (?, ?, ?, ?)" +func (up *UpdateStm) InArgs(value string, p ...any) *UpdateStm { + up.stament.inArgs(value, p...) + return up +} + +// Close frees up the resources used in the stament +func (up *UpdateStm) Close() { closeStament(up.stament) } -func (up *UpdateStament) OrderBy(expr string, p ...any) *UpdateStament { +func (up *UpdateStm) OrderBy(expr string, p ...any) *UpdateStm { up.add(limitS, "ORDER BY", expr, p...) return up } -func (up *UpdateStament) Limit(limit int) *UpdateStament { +func (up *UpdateStm) Limit(limit int) *UpdateStm { up.add(limitS, "LIMIT", "?", limit) return up } -func (up *UpdateStament) Clause(clause, expr string, p ...any) *UpdateStament { +func (up *UpdateStm) Clause(clause, expr string, p ...any) *UpdateStm { up.add(up.lastPos, clause, expr, p...) return up } -func (up *UpdateStament) ClauseIf(cond bool, clause, expr string, p ...any) *UpdateStament { +func (up *UpdateStm) ClauseIf(cond bool, clause, expr string, p ...any) *UpdateStm { if cond { up.Clause(clause, expr, p...) } return up } -func (up *UpdateStament) Join(expr string, p ...any) *UpdateStament { +func (up *UpdateStm) Join(expr string, p ...any) *UpdateStm { up.add(updateS, "JOIN", expr, p...) return up } -func (up *UpdateStament) JoinIf(cond bool, expr string, p ...any) *UpdateStament { +func (up *UpdateStm) JoinIf(cond bool, expr string, p ...any) *UpdateStm { if cond { up.Join(expr, p...) @@ -143,12 +206,12 @@ func (up *UpdateStament) JoinIf(cond bool, expr string, p ...any) *UpdateStament return up } -func (up *UpdateStament) On(on string, p ...any) *UpdateStament { +func (up *UpdateStm) On(on string, p ...any) *UpdateStm { up.clause("ON", on, p...) return up } -func (up *UpdateStament) OnIf(cond bool, expr string, p ...any) *UpdateStament { +func (up *UpdateStm) OnIf(cond bool, expr string, p ...any) *UpdateStm { if cond { up.On(expr, p...) } From dfd95da0626d9722f35930953a7589cc5f15de3d Mon Sep 17 00:00:00 2001 From: profe-ajedrez Date: Tue, 29 Oct 2024 11:47:44 -0300 Subject: [PATCH 3/7] fixed workflow --- .github/workflows/go.yml | 75 +++++++++++++++--------------------- .github/workflows/tester.yml | 43 --------------------- 2 files changed, 31 insertions(+), 87 deletions(-) delete mode 100644 .github/workflows/tester.yml diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 80fe008..b50ab5b 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,56 +1,43 @@ -name: Go # The name of the workflow that will appear on Github +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: +name: Go -jobs: +on: ["push", "pull_request"] - build: - runs-on: ${{ matrix.os }} +jobs: + ci: + name: "Run CI" strategy: + fail-fast: false matrix: - os: [ubuntu-latest, windows-latest] - go: [1.16, 1.17] + os: ["ubuntu-latest"] + go: ["1.23.x"] + runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 - - - name: Set up Go - uses: actions/setup-go@v2 + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + - uses: WillAbides/setup-go-faster@v1.14.0 with: go-version: ${{ matrix.go }} - - - name: Build - run: go install - - - name: Test - run: | - go test -v -cover ./... -coverprofile coverage.out -coverpkg ./... - go tool cover -func coverage.out -o coverage.out # Replaces coverage.out with the analysis of coverage.out - - - name: Go Coverage Badge - uses: tj-actions/coverage-badge-go@v1 - if: ${{ runner.os == 'Linux' && matrix.go == '1.17' }} # Runs this on only one of the ci builds. + - run: "go test ./..." + - run: "go vet ./..." + - uses: dominikh/staticcheck-action@v1 with: - green: 80 - filename: coverage.out + version: "latest" + install-go: false + cache-key: ${{ matrix.go }} + working-directory: ${{ matrix.dir }} + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 - - uses: stefanzweifel/git-auto-commit-action@v4 - id: auto-commit-action + - name: Set up Go + uses: actions/setup-go@v4 with: - commit_message: Apply Code Coverage Badge - skip_fetch: true - skip_checkout: true - file_pattern: ./README.md + go-version: '1.22' - - name: Push Changes - if: steps.auto-commit-action.outputs.changes_detected == 'true' - uses: ad-m/github-push-action@master - with: - github_token: ${{ github.token }} - branch: ${{ github.ref }} - \ No newline at end of file + - name: Test + run: go test -v ./... diff --git a/.github/workflows/tester.yml b/.github/workflows/tester.yml deleted file mode 100644 index b50ab5b..0000000 --- a/.github/workflows/tester.yml +++ /dev/null @@ -1,43 +0,0 @@ -# This workflow will build a golang project -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go - -name: Go - -on: ["push", "pull_request"] - -jobs: - ci: - name: "Run CI" - strategy: - fail-fast: false - matrix: - os: ["ubuntu-latest"] - go: ["1.23.x"] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - uses: WillAbides/setup-go-faster@v1.14.0 - with: - go-version: ${{ matrix.go }} - - run: "go test ./..." - - run: "go vet ./..." - - uses: dominikh/staticcheck-action@v1 - with: - version: "latest" - install-go: false - cache-key: ${{ matrix.go }} - working-directory: ${{ matrix.dir }} - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.22' - - - name: Test - run: go test -v ./... From 19d0488206552d7997c073b5390ed4f3781b5cbf Mon Sep 17 00:00:00 2001 From: profe-ajedrez Date: Tue, 29 Oct 2024 11:48:23 -0300 Subject: [PATCH 4/7] fixed workflow --- .github/workflows/go.yml | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index b50ab5b..f795a49 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -39,5 +39,33 @@ jobs: with: go-version: '1.22' + - name: Build + run: go install + - name: Test - run: go test -v ./... + run: | + go test -v ./... -coverprofile coverage.out -coverpkg ./... + go tool cover -func coverage.out -o coverage.out # Replaces coverage.out with the analysis of coverage.out + + - name: Go Coverage Badge + uses: tj-actions/coverage-badge-go@v1 + if: ${{ runner.os == 'Linux' && matrix.go == '1.17' }} # Runs this on only one of the ci builds. + with: + green: 80 + filename: coverage.out + + - uses: stefanzweifel/git-auto-commit-action@v4 + id: auto-commit-action + with: + commit_message: Apply Code Coverage Badge + skip_fetch: true + skip_checkout: true + file_pattern: ./README.md + + - name: Push Changes + if: steps.auto-commit-action.outputs.changes_detected == 'true' + uses: ad-m/github-push-action@master + with: + github_token: ${{ github.token }} + branch: ${{ github.ref }} + From ad007086eb1bffbdfd6714f71a44745e3764cc6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Reyes=20el=20Programador=20Pobre?= Date: Tue, 29 Oct 2024 12:27:04 -0300 Subject: [PATCH 5/7] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I'm a fool and I deserve mr T punching my face off Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Andrés Reyes el Programador Pobre --- .github/workflows/go.yml | 5 ++--- README.md | 7 +++++++ delete.go | 20 +++++++++++++++----- obreron.go | 20 +++++++++++++++++--- obreron_test.go | 6 ++++++ select.go | 11 ++++++++--- 6 files changed, 55 insertions(+), 14 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index f795a49..a9184b2 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -45,11 +45,10 @@ jobs: - name: Test run: | go test -v ./... -coverprofile coverage.out -coverpkg ./... - go tool cover -func coverage.out -o coverage.out # Replaces coverage.out with the analysis of coverage.out - + go tool cover -func coverage.out -o coverage.out # Replaces coverage.out with the analysis of coverage.out - name: Go Coverage Badge uses: tj-actions/coverage-badge-go@v1 - if: ${{ runner.os == 'Linux' && matrix.go == '1.17' }} # Runs this on only one of the ci builds. + if: ${{ runner.os == 'Linux' && matrix.go == '1.22' }} # Runs this on only one of the ci builds. with: green: 80 filename: coverage.out diff --git a/README.md b/README.md index 1c38169..4f5f62e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,14 @@ Fast and cheap sql builder. [![Go Reference](https://pkg.go.dev/badge/github.com/profe-ajedrez/obreron/v2.svg)](https://pkg.go.dev/github.com/profe-ajedrez/obreron/v2) [![Go Report Card](https://goreportcard.com/badge/github.com/profe-ajedrez/obreron/v2)](https://goreportcard.com/report/github.com/profe-ajedrez/obreron/v2) +# Obreron + +Fast and cheap sql builder. +[![Go Reference](https://pkg.go.dev/badge/github.com/profe-ajedrez/obreron/v2.svg)](https://pkg.go.dev/github.com/profe-ajedrez/obreron/v2) +[![Go Report Card](https://goreportcard.com/badge/github.com/profe-ajedrez/obreron/v2)](https://goreportcard.com/report/github.com/profe-ajedrez/obreron/v2) + +## Supported Dialects ## Supported Dialects - [x] Mysql diff --git a/delete.go b/delete.go index 830d6b1..e4a8fd2 100644 --- a/delete.go +++ b/delete.go @@ -1,5 +1,7 @@ package obreron +// DeleteStm represents a DELETE SQL statement builder. +// It provides a fluent interface for constructing DELETE queries. type DeleteStm struct { *stament } @@ -70,15 +72,23 @@ func (dst *DeleteStm) In(value, expr string, p ...any) *DeleteStm { return dst } -// InArgs adds an In clause to the stament automatically setting the positional parameters of the query based on the -// passed parameters -func (up *DeleteStm) InArgs(value string, p ...any) *DeleteStm { - up.stament.inArgs(value, p...) - return up +// InArgs adds an IN clause to the statement with automatically generated positional parameters. +// Example: +// Delete().From("users").Where("active = ?", true).InArgs("id", 1, 2, 3) +// Generates: DELETE FROM users WHERE active = ? AND id IN (?, ?, ?) +func (dst *DeleteStm) InArgs(value string, p ...any) *DeleteStm { + dst.stament.inArgs(value, p...) + return dst } +// Close releases the statement back to the pool. +// After calling Close, the statement should not be used. func (dst *DeleteStm) Close() { + if dst.stament == nil { + return + } closeStament(dst.stament) + dst.stament = nil } func (dst *DeleteStm) OrderBy(expr string, p ...any) *DeleteStm { diff --git a/obreron.go b/obreron.go index 2a2e8e7..1d9a6ed 100644 --- a/obreron.go +++ b/obreron.go @@ -49,9 +49,23 @@ func (st *stament) clause(clause, expr string, p ...any) { } func (st *stament) inArgs(value string, p ...any) { - l := len(p) - expr := strings.Repeat("?, ", l)[:l*3-2] - st.clause(value+" IN ("+expr+")", "", p...) + if len(p) == 0 { + panic("I pity the fool who passes no parameters to IN clause!") + } + + if len(p) == 1 { + st.clause(value+" = ?", "", p...) + return + } + + l := len(p) + var builder strings.Builder + builder.Grow(l * 2) // Pre-allocate capacity, fool! + builder.WriteString("?") + for i := 1; i < l; i++ { + builder.WriteString(", ?") + } + st.clause(value+" IN ("+builder.String()+")", "", p...) } func (st *stament) where(cond string, p ...any) { diff --git a/obreron_test.go b/obreron_test.go index be109e2..80c29fe 100644 --- a/obreron_test.go +++ b/obreron_test.go @@ -450,6 +450,12 @@ func updateTestCases() (tcs []struct { expectedParams: []any{"CL", 1, 2, 3, 4}, tc: Update("client").Set("status = 0").Where("country = ?", "CL").Y().InArgs("status", 1, 2, 3, 4), }, + { + name: "update with empty in args", + expected: "UPDATE client SET status = 0 WHERE country = ?", + expectedParams: []any{"CL"}, + tc: Update("client").Set("status = 0").Where("country = ?", "CL").Y().InArgs("status"), + }, { name: "update where", expected: "UPDATE client SET status = 0 WHERE status = ?", diff --git a/select.go b/select.go index 9ef93ee..733ef54 100644 --- a/select.go +++ b/select.go @@ -235,9 +235,14 @@ func (st *SelectStm) AndIf(cond bool, expr string, p ...any) *SelectStm { // // # Example // -// Update("client").Set("status = 0").Where("country = ?", "CL").Y().In("status", "", 1, 2, 3, 4) -// -// Produces: UPDATE client SET status = 0 WHERE country = ? AND status IN (?, ?, ?, ?) +// Select(). +// Col("*"). +// From("client"). +// Where("country = ?", "CL"). +// Y(). +// In("status", "", 1, 2, 3, 4) +// +// Produces: SELECT * FROM client WHERE country = ? AND status IN (?, ?, ?, ?) func (up *SelectStm) Y() *SelectStm { up.clause("AND", "") return up From 98062665e8d1c35d5b400878b7ba162f56bf7936 Mon Sep 17 00:00:00 2001 From: profe-ajedrez Date: Tue, 29 Oct 2024 15:15:13 -0300 Subject: [PATCH 6/7] fixes linter --- delete.go | 10 ++++------ insert.go | 2 +- obreron.go | 42 +++++++++++++++++++++++++----------------- obreron_test.go | 14 ++++++++++++-- update.go | 2 +- 5 files changed, 43 insertions(+), 27 deletions(-) diff --git a/delete.go b/delete.go index e4a8fd2..89957a6 100644 --- a/delete.go +++ b/delete.go @@ -8,7 +8,7 @@ type DeleteStm struct { func Delete() *DeleteStm { d := &DeleteStm{ - stament: pool.New().(*stament), + stament: pool.Get().(*stament), } d.add(deleteS, "DELETE", "") @@ -74,7 +74,9 @@ func (dst *DeleteStm) In(value, expr string, p ...any) *DeleteStm { // InArgs adds an IN clause to the statement with automatically generated positional parameters. // Example: -// Delete().From("users").Where("active = ?", true).InArgs("id", 1, 2, 3) +// +// Delete().From("users").Where("active = ?", true).InArgs("id", 1, 2, 3) +// // Generates: DELETE FROM users WHERE active = ? AND id IN (?, ?, ?) func (dst *DeleteStm) InArgs(value string, p ...any) *DeleteStm { dst.stament.inArgs(value, p...) @@ -84,11 +86,7 @@ func (dst *DeleteStm) InArgs(value string, p ...any) *DeleteStm { // Close releases the statement back to the pool. // After calling Close, the statement should not be used. func (dst *DeleteStm) Close() { - if dst.stament == nil { - return - } closeStament(dst.stament) - dst.stament = nil } func (dst *DeleteStm) OrderBy(expr string, p ...any) *DeleteStm { diff --git a/insert.go b/insert.go index c8331fe..3051351 100644 --- a/insert.go +++ b/insert.go @@ -23,7 +23,7 @@ type InsertStament struct { // // r, err := db.Exec(q, p...) func Insert() *InsertStament { - i := &InsertStament{pool.New().(*stament), false} + i := &InsertStament{pool.Get().(*stament), false} i.add(insertS, "INSERT", "") i.firstCol = true diff --git a/obreron.go b/obreron.go index 1d9a6ed..cfe07fa 100644 --- a/obreron.go +++ b/obreron.go @@ -15,6 +15,10 @@ var pool = &sync.Pool{ } func closeStament(st *stament) { + if st == nil { + return + } + for i := range st.p { st.p[i] = nil } @@ -28,6 +32,8 @@ func closeStament(st *stament) { st.buff.Reset() pool.Put(st) + + st = nil } type segment struct { @@ -49,23 +55,25 @@ func (st *stament) clause(clause, expr string, p ...any) { } func (st *stament) inArgs(value string, p ...any) { - if len(p) == 0 { - panic("I pity the fool who passes no parameters to IN clause!") - } - - if len(p) == 1 { - st.clause(value+" = ?", "", p...) - return - } - - l := len(p) - var builder strings.Builder - builder.Grow(l * 2) // Pre-allocate capacity, fool! - builder.WriteString("?") - for i := 1; i < l; i++ { - builder.WriteString(", ?") - } - st.clause(value+" IN ("+builder.String()+")", "", p...) + + if len(p) == 0 { + st.clause(value+" IN ()", "") + return + } + + if len(p) == 1 { + st.clause(value+" IN (?)", "", p...) + return + } + + l := len(p) + var builder strings.Builder + builder.Grow(l * 2) // Pre-allocate capacity, fool! + builder.WriteString("?") + for i := 1; i < l; i++ { + builder.WriteString(", ?") + } + st.clause(value+" IN ("+builder.String()+")", "", p...) } func (st *stament) where(cond string, p ...any) { diff --git a/obreron_test.go b/obreron_test.go index 80c29fe..a4a1159 100644 --- a/obreron_test.go +++ b/obreron_test.go @@ -1,6 +1,8 @@ package obreron -import "testing" +import ( + "testing" +) // SELECT a1, a2, ? AS diez, colIf1, colIf2, ? AS zero, a3, ? AS cien FROM client c JOIN addresses a ON a.id_cliente = a.id_cliente JOIN phones p ON p.id_cliente = c.id_cliente JOIN mailes m ON m.id_cliente = m.id_cliente AND c.estado_cliente = ? LEFT JOIN left_joined lj ON lj.a1 = c.a1 WHERE a1 = ? AND a2 = ? AND a3 = 10 AND a16 = ? --- Got // SELECT a1, a2, ? AS diez, colIf1, colIf2, ? AS zero, a3, ? AS cien FROM client c LEFT JOIN left_joined lj ON lj.a1 = c.a1 JOIN addresses a ON a.id_cliente = a.id_cliente JOIN phones p ON p.id_cliente = c.id_cliente JOIN mailes m ON m.id_cliente = m.id_cliente AND c.estado_cliente = ? WHERE a1 = ? AND a2 = ? AND a3 = 10 AND a16 = ? @@ -81,6 +83,7 @@ func BenchmarkDelete(b *testing.B) { func TestUpdate(t *testing.T) { for i, tc := range updateTestCases() { + sql, p := tc.tc.Build() if sql != tc.expected { @@ -426,6 +429,7 @@ func updateTestCases() (tcs []struct { expected string expectedParams []any }{ + { name: "update simple", expected: "UPDATE client SET status = 0", @@ -452,10 +456,16 @@ func updateTestCases() (tcs []struct { }, { name: "update with empty in args", - expected: "UPDATE client SET status = 0 WHERE country = ?", + expected: "UPDATE client SET status = 0 WHERE country = ? AND status IN ()", expectedParams: []any{"CL"}, tc: Update("client").Set("status = 0").Where("country = ?", "CL").Y().InArgs("status"), }, + { + name: "update with empty in args", + expected: "UPDATE client SET status = 0 WHERE country = ? AND status IN (?)", + expectedParams: []any{"CL", 1}, + tc: Update("client").Set("status = 0").Where("country = ?", "CL").Y().InArgs("status", 1), + }, { name: "update where", expected: "UPDATE client SET status = 0 WHERE status = ?", diff --git a/update.go b/update.go index b3d5bb5..6483ffe 100644 --- a/update.go +++ b/update.go @@ -16,7 +16,7 @@ type UpdateStm struct { // r, err := db.Exec(query, p...) func Update(table string) *UpdateStm { d := &UpdateStm{ - stament: pool.New().(*stament), + stament: pool.Get().(*stament), } d.firstCol = true d.add(updateS, "UPDATE", table) From d91b68450cbb6bbe9036340e356b235562bbae8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Reyes=20el=20Programador=20Pobre?= Date: Tue, 29 Oct 2024 15:18:49 -0300 Subject: [PATCH 7/7] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Andrés Reyes el Programador Pobre --- .github/workflows/go.yml | 2 +- README.md | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index a9184b2..6b3f67d 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -48,7 +48,7 @@ jobs: go tool cover -func coverage.out -o coverage.out # Replaces coverage.out with the analysis of coverage.out - name: Go Coverage Badge uses: tj-actions/coverage-badge-go@v1 - if: ${{ runner.os == 'Linux' && matrix.go == '1.22' }} # Runs this on only one of the ci builds. + if: ${{ runner.os == 'Linux' }} # Runs this on only one of the ci builds. with: green: 80 filename: coverage.out diff --git a/README.md b/README.md index 4f5f62e..05c5560 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,6 @@ Fast and cheap sql builder. [![Go Reference](https://pkg.go.dev/badge/github.com/profe-ajedrez/obreron/v2.svg)](https://pkg.go.dev/github.com/profe-ajedrez/obreron/v2) [![Go Report Card](https://goreportcard.com/badge/github.com/profe-ajedrez/obreron/v2)](https://goreportcard.com/report/github.com/profe-ajedrez/obreron/v2) -# Obreron - -Fast and cheap sql builder. - -[![Go Reference](https://pkg.go.dev/badge/github.com/profe-ajedrez/obreron/v2.svg)](https://pkg.go.dev/github.com/profe-ajedrez/obreron/v2) -[![Go Report Card](https://goreportcard.com/badge/github.com/profe-ajedrez/obreron/v2)](https://goreportcard.com/report/github.com/profe-ajedrez/obreron/v2) - -## Supported Dialects ## Supported Dialects - [x] Mysql