(DB/FEAT): Implemented full text search vector for database searching!
This is a HUGE upgrade and can mark the searching nearly complete! Other than the scrolling and some other smaller UI things. The search appears to be working.
This commit is contained in:
parent
70536147b7
commit
3c5109c7d0
@ -0,0 +1,17 @@
|
||||
-- Author: Hayden Hargreaves (hhargreaves2006@gmail.com)
|
||||
-- Desc: Create a full text index on the recipes table.
|
||||
-- Date: 07/10/2025
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- Update recipes table with the search vector column
|
||||
ALTER TABLE Recipes
|
||||
ADD search_vector tsvector GENERATED ALWAYS AS (
|
||||
setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
|
||||
setweight(to_tsvector('english', coalesce(description, '')), 'B')
|
||||
) STORED;
|
||||
|
||||
-- Create the search index
|
||||
CREATE INDEX idx_recipe_search_vector ON recipes USING GIN (search_vector);
|
||||
|
||||
COMMIT;
|
||||
@ -99,7 +99,13 @@ func (r *RecipeRepository) GetRecipe(id int) (*domain.Recipe, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := "SELECT * FROM recipes WHERE id = $1"
|
||||
query := `
|
||||
SELECT
|
||||
id, title, description, instructions, serves, difficulty, duration, category, ingredients,
|
||||
userid, modified, created
|
||||
FROM recipes
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
var durationBytes []byte
|
||||
var ingredientBytes []byte
|
||||
@ -160,10 +166,10 @@ func isBitActive(bits, pos int) bool {
|
||||
}
|
||||
|
||||
// SearchRecipes will search the recipe table using the provided filters and return an unbound list
|
||||
// of recipes. The filters are fairly complex, they are stored as bit masks. A more details
|
||||
// description can be found in the recipe service implementation. Any errors will be bubbled to the
|
||||
// caller.
|
||||
//
|
||||
// of recipes. The filters are fairly complex, they are stored as bit masks. A more details
|
||||
// description can be found in the recipe service implementation. Any errors will be bubbled to the
|
||||
// caller.
|
||||
//
|
||||
// TODO: Pagination is required, to provide infinite scroll.
|
||||
func (r *RecipeRepository) SearchRecipes(filters domain.SearchFilters) ([]domain.Recipe, error) {
|
||||
tx, err := r.db.Begin()
|
||||
@ -172,9 +178,6 @@ func (r *RecipeRepository) SearchRecipes(filters domain.SearchFilters) ([]domain
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate the query
|
||||
query := "SELECT * FROM recipes"
|
||||
|
||||
// Compute meals type filters (there are 7 bits)
|
||||
var mealConditions []string
|
||||
for i := range 7 {
|
||||
@ -257,15 +260,56 @@ func (r *RecipeRepository) SearchRecipes(filters domain.SearchFilters) ([]domain
|
||||
conditions = append(conditions, servingString)
|
||||
}
|
||||
|
||||
// Convert and append conditions
|
||||
if len(conditions) > 0 {
|
||||
conditionsString := fmt.Sprintf("WHERE %s", strings.Join(conditions, " AND "))
|
||||
query = fmt.Sprintf("%s %s;", query, conditionsString)
|
||||
} else {
|
||||
query += ";"
|
||||
// Define columns to select. More fields can be added if the full text search is required
|
||||
columns := []string{
|
||||
"id",
|
||||
"title",
|
||||
"description",
|
||||
"instructions",
|
||||
"serves",
|
||||
"difficulty",
|
||||
"duration",
|
||||
"category",
|
||||
"ingredients",
|
||||
"userid",
|
||||
"modified",
|
||||
"created",
|
||||
}
|
||||
|
||||
fmt.Println(query)
|
||||
// Create search vector query
|
||||
var orderBy string = ""
|
||||
if filters.Search != "" {
|
||||
vector_query := strings.ReplaceAll(filters.Search, " ", " | ")
|
||||
|
||||
conditions = append(
|
||||
conditions,
|
||||
fmt.Sprintf("search_vector @@ to_tsquery('english', '%s')", vector_query),
|
||||
)
|
||||
|
||||
template := `
|
||||
ORDER BY
|
||||
ts_rank(search_vector, to_tsquery('english', '%s')) DESC,
|
||||
ts_rank_cd(search_vector, to_tsquery('english', '%s')) DESC
|
||||
`
|
||||
orderBy = fmt.Sprintf(template, vector_query, vector_query)
|
||||
}
|
||||
|
||||
// Generate the query
|
||||
query := fmt.Sprintf("SELECT %s FROM recipes", strings.Join(columns, ","))
|
||||
|
||||
// Convert and append conditions if provided
|
||||
if len(conditions) > 0 {
|
||||
conditionsString := fmt.Sprintf("WHERE %s", strings.Join(conditions, " AND "))
|
||||
query = fmt.Sprintf("%s %s", query, conditionsString)
|
||||
}
|
||||
|
||||
// Append sorting order if exists
|
||||
if len(orderBy) > 0 {
|
||||
query = fmt.Sprintf("%s %s", query, orderBy)
|
||||
}
|
||||
|
||||
// Finish it off with a colon!
|
||||
query += ";"
|
||||
|
||||
// Execute the query
|
||||
rows, err := tx.Query(query)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user