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

✅(add): postgres search #305

Merged
merged 11 commits into from
Feb 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions internal/api/handlers/shipment/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@
}

handler := func(fc *fiber.Ctx, filter *ports.LimitOffsetQueryOptions) (*ports.ListResult[*shipmentdomain.Shipment], error) {
if err = fc.QueryParser(filter); err != nil {
return nil, h.eh.HandleError(fc, err)
}

Check warning on line 103 in internal/api/handlers/shipment/handler.go

View check run for this annotation

Codecov / codecov/patch

internal/api/handlers/shipment/handler.go#L101-L103

Added lines #L101 - L103 were not covered by tests

return h.ss.List(fc.UserContext(), &repositories.ListShipmentOptions{
ShipmentOptions: repositories.ShipmentOptions{
ExpandShipmentDetails: c.QueryBool("expandShipmentDetails"),
Expand Down
35 changes: 32 additions & 3 deletions internal/core/domain/shipment/shipment.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
var (
_ bun.BeforeAppendModelHook = (*Shipment)(nil)
_ domain.Validatable = (*Shipment)(nil)
_ infra.PostgresSearchable = (*Shipment)(nil)
)

type Shipment struct {
Expand Down Expand Up @@ -68,9 +69,10 @@
ActualShipDate *int64 `json:"actualShipDate" bun:"actual_ship_date,type:BIGINT,nullzero"`

// Metadata
Version int64 `json:"version" bun:"version,type:BIGINT"`
CreatedAt int64 `json:"createdAt" bun:"created_at,notnull,default:extract(epoch from current_timestamp)::bigint"`
UpdatedAt int64 `json:"updatedAt" bun:"updated_at,notnull,default:extract(epoch from current_timestamp)::bigint"`
Version int64 `json:"version" bun:"version,type:BIGINT"`
CreatedAt int64 `json:"createdAt" bun:"created_at,notnull,default:extract(epoch from current_timestamp)::bigint"`
UpdatedAt int64 `json:"updatedAt" bun:"updated_at,notnull,default:extract(epoch from current_timestamp)::bigint"`
SearchVector string `json:"-" bun:"search_vector,type:TSVECTOR"`

// Relationships
BusinessUnit *businessunit.BusinessUnit `json:"businessUnit,omitempty" bun:"rel:belongs-to,join:business_unit_id=id"`
Expand Down Expand Up @@ -209,3 +211,30 @@

return nil
}

func (st Shipment) GetPostgresSearchConfig() infra.PostgresSearchConfig {
return infra.PostgresSearchConfig{
TableAlias: "sp",
Fields: []infra.PostgresSearchableField{
{
Name: "pro_number",
Weight: "A",
Type: infra.PostgresSearchTypeComposite,
},
{
Name: "bol",
Weight: "A",
Type: infra.PostgresSearchTypeComposite,
},
{
Name: "status",
Weight: "B",
Type: infra.PostgresSearchTypeEnum,
Dictionary: "english",
},
},
MinLength: 2,
MaxTerms: 6,
UsePartialMatch: true,
}

Check warning on line 239 in internal/core/domain/shipment/shipment.go

View check run for this annotation

Codecov / codecov/patch

internal/core/domain/shipment/shipment.go#L215-L239

Added lines #L215 - L239 were not covered by tests
}
32 changes: 32 additions & 0 deletions internal/core/ports/infra/postgres.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package infra

type PostgresSearchType string

const (
PostgresSearchTypeText = PostgresSearchType("text") // Regular text search
PostgresSearchTypeNumber = PostgresSearchType("number") // Number search (uses pattern matching)
PostgresSearchTypeEnum = PostgresSearchType("enum") // Enum search (exact match)
PostgresSearchTypeComposite = PostgresSearchType("composite") // Composite fields (like pro_number)
)

// SearchableField represents a field that can be searched
type PostgresSearchableField struct {
Name string // Database column name
Weight string // Weight for ranking (A, B, C, D)
Type PostgresSearchType // Type of search to perform
Dictionary string // PostgreSQL dictionary to use (default: 'english')
}

// Config holds the configuration for search functionality
type PostgresSearchConfig struct {
TableAlias string // Database table alias
Fields []PostgresSearchableField // Fields to search
MinLength int // Minimum search query length
MaxTerms int // Maximum number of search terms
UsePartialMatch bool // Whether to use pattern matching for partial matches
CustomRank string // Optional custom ranking expression
}

type PostgresSearchable interface {
GetPostgresSearchConfig() PostgresSearchConfig
}
6 changes: 3 additions & 3 deletions internal/core/ports/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ type FilterQueryOptions struct {
// LimitOffsetQueryOptions is a struct that contains the options for a limit/offset pagination
type LimitOffsetQueryOptions struct {
TenantOpts *TenantOptions `json:"tenantOpts"`
Limit int `json:"limit"`
Offset int `json:"offset"`
Query string `json:"query" query:"search"`
Limit int `json:"limit" query:"limit"`
Offset int `json:"offset" query:"offset"`
Query string `json:"query" query:"query"`
}

type Response[T any] struct {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
CREATE EXTENSION IF NOT EXISTS pg_trgm;

CREATE TYPE gender_enum AS ENUM ('Male', 'Female', 'Other');
CREATE EXTENSION IF NOT EXISTS unaccent;

CREATE TYPE gender_enum AS ENUM(
'Male',
'Female',
'Other'
);

Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,12 @@ CREATE TRIGGER trigger_check_user_organization_mapping
FOR EACH ROW
EXECUTE FUNCTION check_user_organization_mapping();

ALTER TABLE users
ALTER COLUMN status SET STATISTICS 1000;

ALTER TABLE users
ALTER COLUMN business_unit_id SET STATISTICS 1000;

ALTER TABLE users
ALTER COLUMN current_organization_id SET STATISTICS 1000;

Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@ CREATE INDEX "idx_roles_created_updated" ON "roles"("created_at", "updated_at");

CREATE INDEX "idx_roles_metadata" ON "roles" USING gin("metadata");

ALTER TABLE roles
ALTER COLUMN status SET STATISTICS 1000;

ALTER TABLE roles
ALTER COLUMN business_unit_id SET STATISTICS 1000;

ALTER TABLE roles
ALTER COLUMN organization_id SET STATISTICS 1000;

-- Table and column comments
COMMENT ON TABLE roles IS 'Stores role definitions for access control management';

Expand Down Expand Up @@ -221,6 +230,21 @@ CREATE INDEX "idx_permission_grants_conditions" ON "permission_grants" USING gin

CREATE INDEX "idx_permission_grants_audit_trail" ON "permission_grants" USING gin("audit_trail");

ALTER TABLE permission_grants
ALTER COLUMN status SET STATISTICS 1000;

ALTER TABLE permission_grants
ALTER COLUMN business_unit_id SET STATISTICS 1000;

ALTER TABLE permission_grants
ALTER COLUMN organization_id SET STATISTICS 1000;

ALTER TABLE permission_grants
ALTER COLUMN user_id SET STATISTICS 1000;

ALTER TABLE permission_grants
ALTER COLUMN permission_id SET STATISTICS 1000;

-- Table and column comments
COMMENT ON TABLE permission_grants IS 'Stores granted permissions to users with associated metadata and audit information';

Expand Down Expand Up @@ -303,5 +327,13 @@ WHERE

CREATE INDEX "idx_user_roles_created" ON "user_roles"("created_at");

--bun:split
ALTER TABLE user_roles
ALTER COLUMN business_unit_id SET STATISTICS 1000;

--bun:split
ALTER TABLE user_roles
ALTER COLUMN organization_id SET STATISTICS 1000;

COMMENT ON TABLE user_roles IS 'Junction table linking users to their assigned roles';

Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,64 @@ CREATE INDEX IF NOT EXISTS "idx_shipments_billing_status" ON "shipments"("ready_

COMMENT ON TABLE shipments IS 'Stores information about shipments and their billing status';

--bun:split
ALTER TABLE "shipments"
ADD COLUMN IF NOT EXISTS search_vector tsvector;

--bun:split
CREATE INDEX IF NOT EXISTS idx_shipments_search ON shipments USING GIN(search_vector);

--bun:split
CREATE INDEX IF NOT EXISTS idx_shipments_status_composite ON shipments(status, organization_id, business_unit_id) INCLUDE (pro_number, bol, ready_to_bill, sent_to_billing);

--bun:split
CREATE INDEX IF NOT EXISTS idx_shipments_billing_composite ON shipments(ready_to_bill, sent_to_billing, billed) INCLUDE (bill_date, total_charge_amount)
WHERE
ready_to_bill = TRUE OR sent_to_billing = TRUE OR billed = TRUE;

--bun:split
CREATE INDEX IF NOT EXISTS idx_shipments_dates_brin ON shipments USING BRIN(actual_ship_date, actual_delivery_date, created_at) WITH (pages_per_range = 128);

--bun:split
CREATE OR REPLACE FUNCTION shipments_search_vector_update()
RETURNS TRIGGER
AS $$
BEGIN
NEW.search_vector := setweight(to_tsvector('simple', COALESCE(NEW.pro_number, '')), 'A') || setweight(to_tsvector('simple', COALESCE(NEW.bol, '')), 'A') || setweight(to_tsvector('english', COALESCE(CAST(NEW.status AS text), '')), 'B') || setweight(to_tsvector('english', COALESCE(CAST(NEW.rating_method AS text), '')), 'C');
-- Update total_charge_amount if it's changed
NEW.total_charge_amount := NEW.freight_charge_amount + NEW.other_charge_amount;
-- Auto-update timestamps
NEW.updated_at := EXTRACT(EPOCH FROM CURRENT_TIMESTAMP)::bigint;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;

--bun:split
DROP TRIGGER IF EXISTS shipments_search_vector_trigger ON shipments;

--bun:split
CREATE TRIGGER shipments_search_vector_trigger
BEFORE INSERT OR UPDATE ON shipments
FOR EACH ROW
EXECUTE FUNCTION shipments_search_vector_update();

--bun:split
CREATE INDEX IF NOT EXISTS idx_shipments_active ON shipments(created_at DESC)
WHERE
status NOT IN ('Completed', 'Canceled', 'Billed');

--bun:split
ALTER TABLE shipments
ALTER COLUMN status SET STATISTICS 1000;

--bun:split
ALTER TABLE shipments
ALTER COLUMN organization_id SET STATISTICS 1000;

ALTER TABLE shipments
ALTER COLUMN business_unit_id SET STATISTICS 1000;

--bun:split
CREATE INDEX IF NOT EXISTS idx_shipments_trgm_pro_bol ON shipments USING gin((pro_number || ' ' || bol) gin_trgm_ops);

Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
func TestCustomerRepository(t *testing.T) {
org := ts.Fixture.MustRow("Organization.trenova").(*organization.Organization)
bu := ts.Fixture.MustRow("BusinessUnit.trenova").(*businessunit.BusinessUnit)
loc := ts.Fixture.MustRow("Customer.test_customer").(*customer.Customer)
loc := ts.Fixture.MustRow("Customer.honeywell_customer").(*customer.Customer)
usState := ts.Fixture.MustRow("UsState.ca").(*usstate.UsState)

repo := repositories.NewCustomerRepository(repositories.CustomerRepositoryParams{
Expand Down Expand Up @@ -52,7 +52,7 @@ func TestCustomerRepository(t *testing.T) {
OrgID: org.ID,
BuID: bu.ID,
},
Query: "Test customer",
Query: "Honeywell",
},
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"github.com/emoss08/trenova/internal/core/ports/repositories"
"github.com/emoss08/trenova/internal/pkg/errors"
"github.com/emoss08/trenova/internal/pkg/logger"
"github.com/emoss08/trenova/internal/pkg/postgressearch"
"github.com/emoss08/trenova/internal/pkg/utils/queryutils/queryfilters"
"github.com/rotisserie/eris"
"github.com/rs/zerolog"
Expand Down Expand Up @@ -67,7 +68,11 @@
})

if opts.Filter.Query != "" {
q = q.Where("sp.pro_number ILIKE ?", "%"+opts.Filter.Query+"%")
q = postgressearch.BuildSearchQuery[shipment.Shipment](
q,
opts.Filter.Query,
shipment.Shipment{},
)

Check warning on line 75 in internal/infrastructure/database/postgres/repositories/shipment.go

View check run for this annotation

Codecov / codecov/patch

internal/infrastructure/database/postgres/repositories/shipment.go#L71-L75

Added lines #L71 - L75 were not covered by tests
}

q = sr.addOptions(q, opts.ShipmentOptions)
Expand Down
Loading
Loading