Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Override view name in querier #167

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions internal/test/sql/mssql_init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ CREATE TABLE [people] (
[updated_at] datetime2
);

-- EXEC is workaround for "'CREATE VIEW' must be the first statement in a query batch."
EXEC('CREATE VIEW [people_0] AS SELECT * FROM [people] WHERE (id % 3) = 0');
EXEC('CREATE VIEW [people_1] AS SELECT * FROM [people] WHERE (id % 3) = 1');
EXEC('CREATE VIEW [people_2] AS SELECT * FROM [people] WHERE (id % 3) = 2');

CREATE TABLE [projects] (
[name] varchar(255) NOT NULL,
[id] varchar(255) PRIMARY KEY,
Expand Down
4 changes: 4 additions & 0 deletions internal/test/sql/mysql_init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ CREATE TABLE people (
PRIMARY KEY (id)
);

CREATE VIEW people_0 AS SELECT * FROM people WHERE (id % 3) = 0;
CREATE VIEW people_1 AS SELECT * FROM people WHERE (id % 3) = 1;
CREATE VIEW people_2 AS SELECT * FROM people WHERE (id % 3) = 2;

CREATE TABLE projects (
name varchar(255) NOT NULL,
id varchar(255) NOT NULL,
Expand Down
4 changes: 4 additions & 0 deletions internal/test/sql/postgres_init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ CREATE TABLE people (
-- updated_at timestamp without time zone
);

CREATE VIEW people_0 AS SELECT * FROM people WHERE (id % 3) = 0;
CREATE VIEW people_1 AS SELECT * FROM people WHERE (id % 3) = 1;
CREATE VIEW people_2 AS SELECT * FROM people WHERE (id % 3) = 2;

CREATE TABLE projects (
name varchar NOT NULL,
id varchar PRIMARY KEY,
Expand Down
4 changes: 4 additions & 0 deletions internal/test/sql/sqlite3_init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ CREATE TABLE people (
updated_at datetime
);

CREATE VIEW people_0 AS SELECT * FROM people WHERE (id % 3) = 0;
CREATE VIEW people_1 AS SELECT * FROM people WHERE (id % 3) = 1;
CREATE VIEW people_2 AS SELECT * FROM people WHERE (id % 3) = 2;

CREATE TABLE projects (
name varchar NOT NULL,
id varchar NOT NULL PRIMARY KEY,
Expand Down
22 changes: 17 additions & 5 deletions querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import (

// Querier performs queries and commands.
type Querier struct {
dbtx DBTX
tag string
dbtx DBTX
tag string
qualifiedViewName string
Dialect
Logger Logger
}
Expand Down Expand Up @@ -50,14 +51,25 @@ func (q *Querier) WithTag(format string, args ...interface{}) *Querier {
} else {
newQ.tag = fmt.Sprintf(format, args...)
}
newQ.qualifiedViewName = q.qualifiedViewName
return newQ
}

// QualifiedView returns quoted qualified view name.
// WithQualifiedViewName returns a copy of Querier with set qualified view name.
// Returned Querier is tied to the same DB or TX.
// TODO Support INSERT/UPDATE/DELETE. More test.
Copy link
Member

Choose a reason for hiding this comment

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

[Optional linters] reported by reviewdog 🐶
querier.go:73: Line contains TODO/BUG/FIXME: "TODO Support INSERT/UPDATE/DELETE. More ..." (godox)

func (q *Querier) WithQualifiedViewName(qualifiedViewName string) *Querier {
newQ := newQuerier(q.dbtx, q.Dialect, q.Logger)
newQ.tag = q.tag
newQ.qualifiedViewName = qualifiedViewName
return newQ
Copy link
Member Author

Choose a reason for hiding this comment

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

We are losing tag here.

}

// QualifiedView returns quoted qualified view name of given view.
func (q *Querier) QualifiedView(view View) string {
v := q.QuoteIdentifier(view.Name())
if view.Schema() != "" {
v = q.QuoteIdentifier(view.Schema()) + "." + v
if s := view.Schema(); s != "" {
v = q.QuoteIdentifier(s) + "." + v
}
return v
}
Expand Down
14 changes: 14 additions & 0 deletions querier_examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,20 @@ func ExampleQuerier_WithTag() {
// Name: `Vicious Baron` (string), ID: `baron` (string), Start: 2014-06-01 00:00:00 +0000 UTC (time.Time), End: 2016-02-21 00:00:00 +0000 UTC (*time.Time)
}

func ExampleQuerier_WithQualifiedViewName() {
_, err := DB.WithQualifiedViewName("people_0").FindByPrimaryKeyFrom(PersonTable, 1)
if err != reform.ErrNoRows {
Copy link
Member

Choose a reason for hiding this comment

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

[Optional linters] reported by reviewdog 🐶
err113: do not compare errors directly, use errors.Is() instead: "err != reform.ErrNoRows" (goerr113)

log.Fatal(err)
}
person, err := DB.WithQualifiedViewName("people_1").FindByPrimaryKeyFrom(PersonTable, 1)
if err != nil {
log.Fatal(err)
}
fmt.Println(person)
// Output:
// ID: 1 (int32), GroupID: 65534 (*int32), Name: `Denis Mills` (string), Email: <nil> (*string), CreatedAt: 2009-11-10 23:00:00 +0000 UTC (time.Time), UpdatedAt: <nil> (*time.Time)
}

func ExampleQuerier_SelectRows() {
tail := fmt.Sprintf("WHERE created_at < %s ORDER BY id", DB.Placeholder(1))
y2010 := time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC)
Expand Down
18 changes: 11 additions & 7 deletions querier_selects.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ func (q *Querier) selectQuery(view View, tail string, limit1 bool) string {
query += " TOP 1"
}

return fmt.Sprintf("%s %s FROM %s %s",
query, strings.Join(q.QualifiedColumns(view), ", "), q.QualifiedView(view), tail)
from := q.QualifiedView(view)
if q.qualifiedViewName != "" {
from = q.qualifiedViewName + " AS " + from
}

return fmt.Sprintf("%s %s FROM %s %s", query, strings.Join(q.QualifiedColumns(view), ", "), from, tail)
Copy link
Member Author

Choose a reason for hiding this comment

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

Here we only change table name in FROM clause and alias it to the old name. That should do the trick for all SELECTs. @bitxel, please check it out.

Copy link

Choose a reason for hiding this comment

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

understand, it's a neat way that only change the view name when making SQL,
but it may lose the schema name, so it's caller's responsibility to add and quote the schema name

}

// SelectOneTo queries str's View with tail and args and scans first result to str.
Expand Down Expand Up @@ -120,8 +124,8 @@ func (q *Querier) SelectAllFrom(view View, tail string, args ...interface{}) (st
}

// findTail returns a tail of SELECT query for given view, column and arg.
func (q *Querier) findTail(view string, column string, arg interface{}, limit1 bool) (tail string, needArg bool) {
qi := q.QuoteIdentifier(view) + "." + q.QuoteIdentifier(column)
func (q *Querier) findTail(view View, column string, arg interface{}, limit1 bool) (tail string, needArg bool) {
qi := q.QualifiedView(view) + "." + q.QuoteIdentifier(column)
if arg == nil {
tail = fmt.Sprintf("WHERE %s IS NULL", qi)
} else {
Expand All @@ -142,7 +146,7 @@ func (q *Querier) findTail(view string, column string, arg interface{}, limit1 b
// If there are no rows in result, it returns ErrNoRows. It also may return QueryRow(), Scan()
// and AfterFinder errors.
func (q *Querier) FindOneTo(str Struct, column string, arg interface{}) error {
tail, needArg := q.findTail(str.View().Name(), column, arg, true)
tail, needArg := q.findTail(str.View(), column, arg, true)
if needArg {
return q.SelectOneTo(str, tail, arg)
}
Expand All @@ -155,7 +159,7 @@ func (q *Querier) FindOneTo(str Struct, column string, arg interface{}) error {
// If there are no rows in result, it returns nil, ErrNoRows. It also may return QueryRow(), Scan()
// and AfterFinder errors.
func (q *Querier) FindOneFrom(view View, column string, arg interface{}) (Struct, error) {
tail, needArg := q.findTail(view.Name(), column, arg, true)
tail, needArg := q.findTail(view, column, arg, true)
if needArg {
return q.SelectOneFrom(view, tail, arg)
}
Expand All @@ -169,7 +173,7 @@ func (q *Querier) FindOneFrom(view View, column string, arg interface{}) (Struct
//
// See SelectRows example for idiomatic usage.
func (q *Querier) FindRows(view View, column string, arg interface{}) (*sql.Rows, error) {
tail, needArg := q.findTail(view.Name(), column, arg, false)
tail, needArg := q.findTail(view, column, arg, false)
if needArg {
return q.SelectRows(view, tail, arg)
}
Expand Down
35 changes: 35 additions & 0 deletions querier_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package reform_test

import (
"gopkg.in/reform.v1/dialects/mssql"
"gopkg.in/reform.v1/dialects/mysql"
"gopkg.in/reform.v1/dialects/postgresql"
"gopkg.in/reform.v1/dialects/sqlite3"
"gopkg.in/reform.v1/dialects/sqlserver"
. "gopkg.in/reform.v1/internal/test/models"
)

func (s *ReformSuite) TestQualifiedView() {
switch s.q.Dialect {
case postgresql.Dialect:
s.Equal(`"people"`, s.q.QualifiedView(PersonTable))
s.Equal(`"people"`, s.q.WithQualifiedViewName("ignored").QualifiedView(PersonTable))
s.Equal(`"legacy"."people"`, s.q.QualifiedView(LegacyPersonTable))
s.Equal(`"legacy"."people"`, s.q.WithQualifiedViewName("ignored").QualifiedView(LegacyPersonTable))

case mysql.Dialect:
s.Equal("`people`", s.q.QualifiedView(PersonTable))
s.Equal("`people`", s.q.WithQualifiedViewName("ignored").QualifiedView(PersonTable))

case sqlite3.Dialect:
s.Equal(`"people"`, s.q.QualifiedView(PersonTable))
s.Equal(`"people"`, s.q.WithQualifiedViewName("ignored").QualifiedView(PersonTable))

case mssql.Dialect, sqlserver.Dialect:
Copy link
Member

Choose a reason for hiding this comment

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

[Optional linters] reported by reviewdog 🐶
SA1019: mssql.Dialect is deprecated: Use sqlserver.Dialect instead. https://github.com/denisenkom/go-mssqldb#deprecated (staticcheck)

s.Equal(`[people]`, s.q.QualifiedView(PersonTable))
s.Equal(`[people]`, s.q.WithQualifiedViewName("ignored").QualifiedView(PersonTable))

default:
s.Fail("Unhandled dialect", s.q.Dialect.String())
}
}
2 changes: 1 addition & 1 deletion reform-db/cmd_init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (s *ReformDBSuite) TestInit() {

fis, err := ioutil.ReadDir(dir)
s.Require().NoError(err)
s.Require().Len(fis, 4)
s.Require().Len(fis, 7) // 4 tables + views people_0, people_1, people_2

ff := filepath.Join(dir, "people.go")
actual, err := parse.File(ff)
Expand Down