Add full text search support using TSV indexes
This commit is contained in:
24
psql/psql.go
24
psql/psql.go
@ -397,12 +397,28 @@ func (v *selectBlock) renderRelationship(w io.Writer, schema *DBSchema) {
|
||||
|
||||
func (v *selectBlock) renderWhere(w io.Writer) error {
|
||||
if v.sel.Where.Op == qcode.OpEqID {
|
||||
col, ok := v.schema.PCols[v.sel.Table]
|
||||
if !ok {
|
||||
return fmt.Errorf("no primary key defined for %s", v.sel.Table)
|
||||
t, err := v.schema.GetTable(v.sel.Table)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(t.PrimaryCol) == 0 {
|
||||
return fmt.Errorf("no primary key column defined for %s", v.sel.Table)
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, `(("%s") = ('%s'))`, col.Name, v.sel.Where.Val)
|
||||
fmt.Fprintf(w, `(("%s") = ('%s'))`, t.PrimaryCol, v.sel.Where.Val)
|
||||
return nil
|
||||
}
|
||||
|
||||
if v.sel.Where.Op == qcode.OpTsQuery {
|
||||
t, err := v.schema.GetTable(v.sel.Table)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(t.TSVCol) == 0 {
|
||||
return fmt.Errorf("no tsv column defined for %s", v.sel.Table)
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, `(("%s") @@ to_tsquery('%s'))`, t.TSVCol, v.sel.Where.Val)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -337,6 +337,26 @@ func fetchByID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func searchQuery(t *testing.T) {
|
||||
gql := `query {
|
||||
products(search: "Amazing") {
|
||||
id
|
||||
name
|
||||
}
|
||||
}`
|
||||
|
||||
sql := `SELECT json_object_agg('products', products) FROM (SELECT coalesce(json_agg("products"), '[]') AS "products" FROM (SELECT row_to_json((SELECT "sel_0" FROM (SELECT "products_0"."id" AS "id", "products_0"."name" AS "name") AS "sel_0")) AS "products" FROM (SELECT "products"."id", "products"."name" FROM "products" WHERE ((("tsv") @@ to_tsquery('Amazing'))) LIMIT ('20') :: integer) AS "products_0" LIMIT ('20') :: integer) AS "products_0") AS "done_1337";`
|
||||
|
||||
resSQL, err := compileGQLToPSQL(gql)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resSQL != sql {
|
||||
t.Fatal(errNotExpected)
|
||||
}
|
||||
}
|
||||
|
||||
func aggFunction(t *testing.T) {
|
||||
gql := `query {
|
||||
products {
|
||||
@ -383,6 +403,7 @@ func TestCompileGQL(t *testing.T) {
|
||||
t.Run("withWhereIsNull", withWhereIsNull)
|
||||
t.Run("withWhereMultiOr", withWhereMultiOr)
|
||||
t.Run("fetchByID", fetchByID)
|
||||
t.Run("searchQuery", searchQuery)
|
||||
t.Run("belongsTo", belongsTo)
|
||||
t.Run("oneToMany", oneToMany)
|
||||
t.Run("manyToMany", manyToMany)
|
||||
|
@ -18,11 +18,16 @@ type TTKey struct {
|
||||
type DBSchema struct {
|
||||
ColMap map[TCKey]*DBColumn
|
||||
ColIDMap map[int]*DBColumn
|
||||
PCols map[string]*DBColumn
|
||||
Tables map[string]*DBTableInfo
|
||||
|
||||
RelMap map[TTKey]*DBRel
|
||||
}
|
||||
|
||||
type DBTableInfo struct {
|
||||
PrimaryCol string
|
||||
TSVCol string
|
||||
}
|
||||
|
||||
type RelType int
|
||||
|
||||
const (
|
||||
@ -63,7 +68,7 @@ func initSchema() *DBSchema {
|
||||
return &DBSchema{
|
||||
ColMap: make(map[TCKey]*DBColumn),
|
||||
ColIDMap: make(map[int]*DBColumn),
|
||||
PCols: make(map[string]*DBColumn),
|
||||
Tables: make(map[string]*DBTableInfo),
|
||||
RelMap: make(map[TTKey]*DBRel),
|
||||
}
|
||||
}
|
||||
@ -71,6 +76,7 @@ func initSchema() *DBSchema {
|
||||
func updateSchema(schema *DBSchema, t *DBTable, cols []*DBColumn) {
|
||||
// Current table
|
||||
ct := strings.ToLower(t.Name)
|
||||
schema.Tables[ct] = &DBTableInfo{}
|
||||
|
||||
// Foreign key columns in current table
|
||||
var jcols []*DBColumn
|
||||
@ -82,8 +88,11 @@ func updateSchema(schema *DBSchema, t *DBTable, cols []*DBColumn) {
|
||||
|
||||
for _, c := range cols {
|
||||
switch {
|
||||
case c.Type == "tsvector":
|
||||
schema.Tables[ct].TSVCol = c.Name
|
||||
|
||||
case c.PrimaryKey:
|
||||
schema.PCols[ct] = c
|
||||
schema.Tables[ct].PrimaryCol = c.Name
|
||||
|
||||
case len(c.FKeyTable) != 0:
|
||||
if len(c.FKeyColID) == 0 {
|
||||
@ -244,3 +253,11 @@ WHERE c.relkind = 'r'::char
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (s *DBSchema) GetTable(table string) (*DBTableInfo, error) {
|
||||
t, ok := s.Tables[table]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("table info not found '%s'", table)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user