Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 551e2123bc | |||
| a2b984c342 |
27
pgm.go
27
pgm.go
@@ -10,6 +10,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -106,3 +107,29 @@ func PgTime(t time.Time) pgtype.Timestamptz {
|
|||||||
func PgTimeNow() pgtype.Timestamptz {
|
func PgTimeNow() pgtype.Timestamptz {
|
||||||
return pgtype.Timestamptz{Time: time.Now(), Valid: true}
|
return pgtype.Timestamptz{Time: time.Now(), Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TsAndQuery(q string) string {
|
||||||
|
return strings.Join(strings.Fields(q), " & ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TsPrefixAndQuery(q string) string {
|
||||||
|
return strings.Join(fieldsWithSufix(q, ":*"), " & ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TsOrQuery(q string) string {
|
||||||
|
return strings.Join(strings.Fields(q), " | ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TsPrefixOrQuery(q string) string {
|
||||||
|
return strings.Join(fieldsWithSufix(q, ":*"), " | ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func fieldsWithSufix(v, sufix string) []string {
|
||||||
|
fields := strings.Fields(v)
|
||||||
|
prefixed := make([]string, len(fields))
|
||||||
|
for i, f := range fields {
|
||||||
|
prefixed[i] = f + sufix
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefixed
|
||||||
|
}
|
||||||
|
|||||||
@@ -153,8 +153,8 @@ func (f Field) DateTrunc(level, as string) Field {
|
|||||||
return Field("DATE_TRUNC('" + level + "', " + f.String() + ") AS " + as)
|
return Field("DATE_TRUNC('" + level + "', " + f.String() + ") AS " + as)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Field) TsRank(query, as string) Field {
|
func (f Field) TsRank(fieldName, as string) Field {
|
||||||
return Field("TS_RANK(" + f.String() + ", " + query + ") AS " + as)
|
return Field("TS_RANK(" + f.String() + ", " + fieldName + ") AS " + as)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EqualFold will use LOWER(column_name) = LOWER(val) for comparision
|
// EqualFold will use LOWER(column_name) = LOWER(val) for comparision
|
||||||
|
|||||||
36
pgm_table.go
36
pgm_table.go
@@ -1,13 +1,8 @@
|
|||||||
package pgm
|
package pgm
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Table in database
|
// Table in database
|
||||||
type Table struct {
|
type Table struct {
|
||||||
tsQuery *string
|
textSearch *textSearchCTE
|
||||||
tsQueryAs *string
|
|
||||||
|
|
||||||
Name string
|
Name string
|
||||||
DerivedTable Query
|
DerivedTable Query
|
||||||
@@ -16,6 +11,13 @@ type Table struct {
|
|||||||
debug bool
|
debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// text search Common Table Expression
|
||||||
|
type textSearchCTE struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
alias string
|
||||||
|
}
|
||||||
|
|
||||||
// Debug when set true will print generated query string in stdout
|
// Debug when set true will print generated query string in stdout
|
||||||
func (t *Table) Debug() Clause {
|
func (t *Table) Debug() Clause {
|
||||||
t.debug = true
|
t.debug = true
|
||||||
@@ -38,17 +40,17 @@ func (t *Table) Insert() InsertClause {
|
|||||||
return qb
|
return qb
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Table) WithTsQuery(q, as string) *Table {
|
func (t *Table) WithTextSearch(name, alias, textToSearch string) *Table {
|
||||||
t.tsQuery = &q
|
t.textSearch = &textSearchCTE{name: name, value: textToSearch, alias: alias}
|
||||||
t.tsQueryAs = &as
|
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select table statement
|
// Select table statement
|
||||||
func (t *Table) Select(field ...Field) SelectClause {
|
func (t *Table) Select(field ...Field) SelectClause {
|
||||||
qb := &selectQry{
|
qb := &selectQry{
|
||||||
debug: t.debug,
|
debug: t.debug,
|
||||||
fields: field,
|
fields: field,
|
||||||
|
textSearch: t.textSearch,
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.DerivedTable != nil {
|
if t.DerivedTable != nil {
|
||||||
@@ -59,18 +61,6 @@ func (t *Table) Select(field ...Field) SelectClause {
|
|||||||
qb.table = t.Name
|
qb.table = t.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.tsQuery != nil {
|
|
||||||
var as string
|
|
||||||
if t.tsQueryAs != nil && *t.tsQueryAs != "" {
|
|
||||||
as = *t.tsQueryAs
|
|
||||||
} else {
|
|
||||||
// add default as field
|
|
||||||
as = "query"
|
|
||||||
}
|
|
||||||
qb.args = append(qb.args, as)
|
|
||||||
qb.table += ", TO_TSQUERY('english', $" + strconv.Itoa(len(qb.args)) + ") " + as
|
|
||||||
}
|
|
||||||
|
|
||||||
return qb
|
return qb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,15 +107,19 @@ func TestSelectDerived(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSelectTV(t *testing.T) {
|
func TestSelectTV(t *testing.T) {
|
||||||
expected := "SELECT users.first_name, users.last_name, users.email, TS_RANK(users.search_vector, query) AS rank" +
|
expected := "WITH ts AS (SELECT to_tsquery('english', $1) AS query)" +
|
||||||
" FROM users, TO_TSQUERY('english', $1) query" +
|
" SELECT users.first_name, users.last_name, users.email, TS_RANK(users.search_vector, ts.query) AS rank" +
|
||||||
" WHERE users.status_id = $2 AND users.search_vector @@ query" +
|
" FROM users" +
|
||||||
|
" JOIN user_sessions ON users.id = user_sessions.user_id" +
|
||||||
|
" CROSS JOIN ts" +
|
||||||
|
" WHERE users.status_id = $2 AND users.search_vector @@ ts.query" +
|
||||||
" ORDER BY rank DESC"
|
" ORDER BY rank DESC"
|
||||||
|
|
||||||
qry := db.User.
|
qry := db.User.
|
||||||
WithTsQuery("anki", "query").
|
WithTextSearch("ts", "query", "text to search").
|
||||||
Select(user.FirstName, user.LastName, user.Email, user.SearchVector.TsRank("query", "rank")).
|
Select(user.FirstName, user.LastName, user.Email, user.SearchVector.TsRank("ts.query", "rank")).
|
||||||
Where(user.StatusID.Eq(1), user.SearchVector.TsQuery("query")).
|
Join(db.UserSession, user.ID, usersession.UserID).
|
||||||
|
Where(user.StatusID.Eq(1), user.SearchVector.TsQuery("ts.query")).
|
||||||
OrderBy(pgm.Field("rank").Desc())
|
OrderBy(pgm.Field("rank").Desc())
|
||||||
|
|
||||||
got := qry.String()
|
got := qry.String()
|
||||||
|
|||||||
@@ -135,6 +135,8 @@ type (
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectQry struct {
|
selectQry struct {
|
||||||
|
textSearch *textSearchCTE
|
||||||
|
|
||||||
table string
|
table string
|
||||||
fields []Field
|
fields []Field
|
||||||
args []any
|
args []any
|
||||||
@@ -313,6 +315,12 @@ func (q *selectQry) Build(needArgs bool) (qry string, args []any) {
|
|||||||
|
|
||||||
sb.Grow(q.averageLen())
|
sb.Grow(q.averageLen())
|
||||||
|
|
||||||
|
if q.textSearch != nil {
|
||||||
|
var ts = q.textSearch
|
||||||
|
q.args = slices.Insert(q.args, 0, any(ts.value))
|
||||||
|
sb.WriteString("WITH " + ts.name + " AS (SELECT to_tsquery('english', $1) AS " + ts.alias + ") ")
|
||||||
|
}
|
||||||
|
|
||||||
// SELECT
|
// SELECT
|
||||||
sb.WriteString("SELECT ")
|
sb.WriteString("SELECT ")
|
||||||
sb.WriteString(joinFileds(q.fields))
|
sb.WriteString(joinFileds(q.fields))
|
||||||
@@ -323,6 +331,11 @@ func (q *selectQry) Build(needArgs bool) (qry string, args []any) {
|
|||||||
sb.WriteString(" " + strings.Join(q.join, " "))
|
sb.WriteString(" " + strings.Join(q.join, " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search Query Cross join
|
||||||
|
if q.textSearch != nil {
|
||||||
|
sb.WriteString(" CROSS JOIN " + q.textSearch.name)
|
||||||
|
}
|
||||||
|
|
||||||
// WHERE
|
// WHERE
|
||||||
if len(q.where) > 0 {
|
if len(q.where) > 0 {
|
||||||
sb.WriteString(" WHERE ")
|
sb.WriteString(" WHERE ")
|
||||||
|
|||||||
Reference in New Issue
Block a user