From a2b984c3429c09951914496cf3c37bff4f7e092c Mon Sep 17 00:00:00 2001 From: Ankit Patial Date: Sat, 8 Nov 2025 14:29:54 +0530 Subject: [PATCH] text search query --- pgm_field.go | 4 ++-- pgm_table.go | 36 +++++++++++++---------------------- playground/qry_select_test.go | 16 ++++++++++------ qry_select.go | 13 +++++++++++++ 4 files changed, 38 insertions(+), 31 deletions(-) diff --git a/pgm_field.go b/pgm_field.go index 2dba56b..1b3ad5e 100644 --- a/pgm_field.go +++ b/pgm_field.go @@ -153,8 +153,8 @@ func (f Field) DateTrunc(level, as string) Field { return Field("DATE_TRUNC('" + level + "', " + f.String() + ") AS " + as) } -func (f Field) TsRank(query, as string) Field { - return Field("TS_RANK(" + f.String() + ", " + query + ") AS " + as) +func (f Field) TsRank(fieldName, as string) Field { + return Field("TS_RANK(" + f.String() + ", " + fieldName + ") AS " + as) } // EqualFold will use LOWER(column_name) = LOWER(val) for comparision diff --git a/pgm_table.go b/pgm_table.go index 9d72d3b..9a4861e 100644 --- a/pgm_table.go +++ b/pgm_table.go @@ -1,13 +1,8 @@ package pgm -import ( - "strconv" -) - // Table in database type Table struct { - tsQuery *string - tsQueryAs *string + textSearch *textSearchCTE Name string DerivedTable Query @@ -16,6 +11,13 @@ type Table struct { 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 func (t *Table) Debug() Clause { t.debug = true @@ -38,17 +40,17 @@ func (t *Table) Insert() InsertClause { return qb } -func (t *Table) WithTsQuery(q, as string) *Table { - t.tsQuery = &q - t.tsQueryAs = &as +func (t *Table) WithTextSearch(name, alias, textToSearch string) *Table { + t.textSearch = &textSearchCTE{name: name, value: textToSearch, alias: alias} return t } // Select table statement func (t *Table) Select(field ...Field) SelectClause { qb := &selectQry{ - debug: t.debug, - fields: field, + debug: t.debug, + fields: field, + textSearch: t.textSearch, } if t.DerivedTable != nil { @@ -59,18 +61,6 @@ func (t *Table) Select(field ...Field) SelectClause { 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 } diff --git a/playground/qry_select_test.go b/playground/qry_select_test.go index c30ba37..0a41fc7 100644 --- a/playground/qry_select_test.go +++ b/playground/qry_select_test.go @@ -107,15 +107,19 @@ func TestSelectDerived(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" + - " FROM users, TO_TSQUERY('english', $1) query" + - " WHERE users.status_id = $2 AND users.search_vector @@ query" + + expected := "WITH ts AS (SELECT to_tsquery('english', $1) AS query)" + + " SELECT users.first_name, users.last_name, users.email, TS_RANK(users.search_vector, ts.query) AS rank" + + " 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" qry := db.User. - WithTsQuery("anki", "query"). - Select(user.FirstName, user.LastName, user.Email, user.SearchVector.TsRank("query", "rank")). - Where(user.StatusID.Eq(1), user.SearchVector.TsQuery("query")). + WithTextSearch("ts", "query", "text to search"). + Select(user.FirstName, user.LastName, user.Email, user.SearchVector.TsRank("ts.query", "rank")). + Join(db.UserSession, user.ID, usersession.UserID). + Where(user.StatusID.Eq(1), user.SearchVector.TsQuery("ts.query")). OrderBy(pgm.Field("rank").Desc()) got := qry.String() diff --git a/qry_select.go b/qry_select.go index f2b83de..743e053 100644 --- a/qry_select.go +++ b/qry_select.go @@ -135,6 +135,8 @@ type ( } selectQry struct { + textSearch *textSearchCTE + table string fields []Field args []any @@ -313,6 +315,12 @@ func (q *selectQry) Build(needArgs bool) (qry string, args []any) { 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 sb.WriteString("SELECT ") 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, " ")) } + // Search Query Cross join + if q.textSearch != nil { + sb.WriteString(" CROSS JOIN " + q.textSearch.name) + } + // WHERE if len(q.where) > 0 { sb.WriteString(" WHERE ")