Skip to content

Commit

Permalink
Enable identifier quoting in mapper (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
vearutop authored Apr 28, 2021
1 parent 953f461 commit 9cc8116
Show file tree
Hide file tree
Showing 18 changed files with 381 additions and 64 deletions.
52 changes: 39 additions & 13 deletions .github/workflows/bench.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
name: bench
on:
push:
tags:
- v*
branches:
- master
- main
pull_request:
workflow_dispatch:
inputs:
old:
description: 'Old Ref'
required: false
default: 'master'
new:
description: 'New Ref'
required: true

env:
GO111MODULE: "on"
CACHE_BENCHMARK: "off" # Enables benchmark result reuse between runs, may skew latency results.
RUN_BASE_BENCHMARK: "on" # Runs benchmark for PR base in case benchmark result is missing.
jobs:
bench:
strategy:
Expand All @@ -22,32 +28,52 @@ jobs:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Restore vendor
with:
ref: ${{ (github.event.inputs.new != '') && github.event.inputs.new || github.event.ref }}
- name: Go cache
uses: actions/cache@v2
with:
# In order:
# * Module download cache
# * Build cache (Linux)
path: |
vendor
key: ${{ runner.os }}-go${{ matrix.go-version }}-vendor-${{ hashFiles('**/go.mod') }}
- name: Populate dependencies
run: |
(test -d vendor && echo vendor found) || (go mod vendor && du -sh vendor && du -sh ~/go/pkg/mod)
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-cache-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-cache
- name: Restore benchstat
uses: actions/cache@v2
with:
path: ~/go/bin/benchstat
key: ${{ runner.os }}-benchstat
- name: Restore base benchmark result
if: env.CACHE_BENCHMARK == 'on'
id: benchmark-base
uses: actions/cache@v2
with:
path: |
bench-master.txt
bench-main.txt
# Use base sha for PR or new commit hash for master/main push in benchmark result key.
key: ${{ runner.os }}-bench-${{ (github.event.pull_request.base.sha != github.event.after) && github.event.pull_request.base.sha || github.event.after }}
- name: Checkout base code
if: env.RUN_BASE_BENCHMARK == 'on' && steps.benchmark-base.outputs.cache-hit != 'true' && (github.event.pull_request.base.sha != '' || github.event.inputs.old != '')
uses: actions/checkout@v2
with:
ref: ${{ (github.event.pull_request.base.sha != '' ) && github.event.pull_request.base.sha || github.event.inputs.old }}
path: __base
- name: Run base benchmark
if: env.RUN_BASE_BENCHMARK == 'on' && steps.benchmark-base.outputs.cache-hit != 'true' && (github.event.pull_request.base.sha != '' || github.event.inputs.old != '')
run: |
export REF_NAME=master
cd __base
BENCH_COUNT=5 make bench-run bench-stat
cp bench-master.txt ../bench-master.txt
- name: Benchmark
id: bench
run: |
export REF_NAME=${GITHUB_REF##*/}
export REF_NAME=new
BENCH_COUNT=5 make bench-run bench-stat
OUTPUT=$(make bench-stat)
OUTPUT="${OUTPUT//'%'/'%25'}"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v2.4.0
uses: golangci/golangci-lint-action@v2.5.2
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.37.0
version: v1.39.0

# Optional: working directory, useful for monorepos
# working-directory: somedir
Expand Down
31 changes: 24 additions & 7 deletions .github/workflows/test-unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
pull_request:
env:
GO111MODULE: "on"
RUN_BASE_COVERAGE: "on" # Runs test for PR base in case base test coverage is missing.
jobs:
test:
strategy:
Expand All @@ -20,22 +21,38 @@ jobs:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Go cache
uses: actions/cache@v2
with:
# In order:
# * Module download cache
# * Build cache (Linux)
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-cache-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-cache
- name: Restore base test coverage
if: matrix.go-version == '1.16.x'
uses: actions/cache@v2
with:
path: |
unit-base.txt
# Use base sha for PR or new commit hash for master/main push in test result key.
key: ${{ runner.os }}-unit-test-coverage-${{ (github.event.pull_request.base.sha != github.event.after) && github.event.pull_request.base.sha || github.event.after }}
- name: Restore vendor
uses: actions/cache@v2
- name: Checkout base code
if: matrix.go-version == '1.16.x' && env.RUN_BASE_COVERAGE == 'on' && steps.benchmark-base.outputs.cache-hit != 'true' && github.event.pull_request.base.sha != ''
uses: actions/checkout@v2
with:
path: |
vendor
key: ${{ runner.os }}-go${{ matrix.go-version }}-vendor-${{ hashFiles('**/go.mod') }}
- name: Populate dependencies
ref: ${{ github.event.pull_request.base.sha }}
path: __base
- name: Run test for base code
if: matrix.go-version == '1.16.x' && env.RUN_BASE_COVERAGE == 'on' && steps.benchmark-base.outputs.cache-hit != 'true' && github.event.pull_request.base.sha != ''
run: |
(test -d vendor && echo vendor found) || (go mod vendor && du -sh vendor && du -sh ~/go/pkg/mod)
cd __base
make test-unit
go tool cover -func=./unit.coverprofile | sed -e 's/.go:[0-9]*:\t/.go\t/g' | sed -e 's/\t\t*/\t/g' > ../unit-base.txt
- name: Test
id: test
run: |
Expand Down
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/.idea
/bench-*
/unit.coverprofile
/*.coverprofile
/.vscode
/bench-*.txt
/vendor
4 changes: 4 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ linters:
- paralleltest
- forbidigo
- exhaustivestruct
- interfacer # deprecated
- forcetypeassert
- scopelint # deprecated
- ifshort # too many false positives

issues:
exclude-use-default: false
Expand Down
10 changes: 6 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#GOLANGCI_LINT_VERSION := "v1.39.0" # Optional configuration to pinpoint golangci-lint version.

# The head of Makefile determines location of dev-go to include standard targets.
GO ?= go
export GO111MODULE = on
Expand Down Expand Up @@ -26,12 +28,12 @@ ifeq ($(DEVGO_PATH),)
endif

-include $(DEVGO_PATH)/makefiles/main.mk
-include $(DEVGO_PATH)/makefiles/lint.mk
-include $(DEVGO_PATH)/makefiles/test-unit.mk
-include $(DEVGO_PATH)/makefiles/bench.mk
-include $(DEVGO_PATH)/makefiles/lint.mk
-include $(DEVGO_PATH)/makefiles/github-actions.mk
-include $(DEVGO_PATH)/makefiles/reset-ci.mk

# Add your custom targets here.

## Run tests
test: test-unit


54 changes: 53 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,23 @@ This module integrates [`github.com/Masterminds/squirrel`](https://github.com/Ma
and [`github.com/jmoiron/sqlx`](https://github.com/jmoiron/sqlx) to allow seamless operation based on field tags of row
structure.

All three libraries collaborate with standard `database/sql` and do not take away low level control from user.

This library helps to eliminate literal string column references (e.g. `"created_at"`) and use field references
instead (e.g. `s.Col(&row, &row.CreatedAt)` and other mapping functions).
instead (e.g. `rf.Ref(&row.CreatedAt)` and other mapping functions).

Field tags (`db` by default) act as a source of truth for column names to allow better maintainability and fewer errors.

## Components

`Storage` is a high level service that provides query building, query executing and result fetching facilities
as easy to use facades.

`Mapper` is a lower level tool that focuses on managing `squirrel` query builder with row structures.

`Referencer` helps to build complex statements by providing fully qualified and properly escaped names for
participating columns.

## Simple CRUD

```go
Expand Down Expand Up @@ -135,3 +147,43 @@ fmt.Println(args)

## Omitting Zero Values

When building `WHERE` conditions from row structure it is often needed skip empty fields from condition.

Behavior with empty fields (zero values) can be controlled via `omitempty` field tag flag and `sqluct.IgnoreOmitEmpty`,
`sqluct.SkipZeroValues` options.

Please check example below to learn about behavior differences.

```go
var s sqluct.Storage

type Product struct {
ID int `db:"id,omitempty"`
Name string `db:"name,omitempty"`
Price int `db:"price"`
}

query, args, err := s.SelectStmt("products", Product{}).Where(s.WhereEq(Product{
ID: 123,
Price: 0,
})).ToSql()
fmt.Println(query, args, err)
// This query skips `name` in where condition for its zero value and `omitempty` flag.
// SELECT id, name, price FROM products WHERE id = $1 AND price = $2 [123 0] <nil>

query, args, err = s.SelectStmt("products", Product{}).Where(s.WhereEq(Product{
ID: 123,
Price: 0,
}, sqluct.IgnoreOmitEmpty)).ToSql()
fmt.Println(query, args, err)
// This query adds `name` in where condition because IgnoreOmitEmpty is applied and `omitempty` flag is ignored.
// SELECT id, name, price FROM products WHERE id = $1 AND name = $2 AND price = $3 [123 0] <nil>

query, args, err = s.SelectStmt("products", Product{}).Where(s.WhereEq(Product{
ID: 123,
Price: 0,
}, sqluct.SkipZeroValues)).ToSql()
fmt.Println(query, args, err)
// This query adds skips both price and name from where condition because SkipZeroValues option is applied.
// SELECT id, name, price FROM products WHERE id = $1 [123] <nil>
```
2 changes: 1 addition & 1 deletion dev_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package sqluct_test

import _ "github.com/bool64/dev"
import _ "github.com/bool64/dev" // Include CI/Dev scripts to project.
2 changes: 1 addition & 1 deletion doc.go
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// Package sqluct provides integration of sqlx and squirrel.v1 with Go structures.
// Package sqluct provides integration of sqlx and squirrel with Go structures.
package sqluct
43 changes: 41 additions & 2 deletions example_mapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,50 @@ func ExampleMapper_WhereEq() {
o.UserID = 123

q := sm.
Select(squirrel.Select(), o).
Select(squirrel.Select().From("orders"), o).
Where(sm.WhereEq(o.OrderData))

query, args, err := q.ToSql()
fmt.Println(query, args, err)

// Output: SELECT order_id, amount, user_id WHERE amount = ? AND user_id = ? [100 123] <nil>
// Output: SELECT order_id, amount, user_id FROM orders WHERE amount = ? AND user_id = ? [100 123] <nil>
}

func ExampleMapper_WhereEq_columnsOf() {
sm := sqluct.Mapper{}

type OrderData struct {
Amount int `db:"amount"`
UserID int `db:"user_id,omitempty"`
}

type Order struct {
ID int `db:"id"`
OrderData
}

type User struct {
ID int `db:"id"`
Name string `db:"name"`
}

rf := sqluct.Referencer{}
o := &Order{}
u := &User{}

rf.AddTableAlias(o, "orders")
rf.AddTableAlias(u, "users")

q := sm.
Select(squirrel.Select().From(rf.Ref(o)), o, rf.ColumnsOf(o)).
Join(rf.Fmt("%s ON %s = %s", u, &o.UserID, &u.ID)).
Where(sm.WhereEq(OrderData{
Amount: 100,
UserID: 123,
}, rf.ColumnsOf(o)))

query, args, err := q.ToSql()
fmt.Println(query, args, err)

// Output: SELECT orders.id, orders.amount, orders.user_id FROM orders JOIN users ON orders.user_id = users.id WHERE orders.amount = ? AND orders.user_id = ? [100 123] <nil>
}
Loading

0 comments on commit 9cc8116

Please sign in to comment.