Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Optimizing GoFrame Queries with a Fluent Builder Pattern

Tech 2

Standard GoFrame interactions require explicit verification for empty parameters before appending filters. A wrapper class can automate this validation using a fluent API to reduce boilerplate and improve readability.

The following implementation defines a QBuilder struct that extends the native gdb.Model. By disabling safe mode during initialization, the underlying model instance remains mutable across chain calls, ensuring efficient state propagation without unnecessary cloning overhead. Helper methods check for null or empty values before executing SQL clauses, preventing unintended logic errors.

package helper

import (
	"github.com/gogf/gf/v2/database/gdb"
)

// QBuilder serves as a fluent interface wrapper around the native gdb.Model
type QBuilder struct {
	model *gdb.Model
}

// NewBuilder initializes the query builder, configuring safety settings for optimization
func NewBuilder(baseModel *gdb.Model) *QBuilder {
	baseModel.Safe(false) // Disable auto-cloning to allow direct mutation within the chain
	return &QBuilder{model: baseModel}
}

// Fetch executes both count retrieval and data scanning in a single operation
func (qb *QBuilder) Fetch(dest interface{}, totalCount *int, additionalFilters ...any) error {
	var total int64
	total, err := qb.model.Count(additionalFilters...)
	if err != nil {
		return err
	}
	err = qb.model.Scan(dest, additionalFilters...)
	*totalCount = int(total)
	return err
}

// WhereEq builds an exact match condition with automatic null-safety
func (qb *QBuilder) WhereEq(col string, val any) *QBuilder {
	if !isEmpty(val) {
		qb.model.Where(col, val)
	}
	return qb
}

// WhereNot constructs a non-matching condition safely
func (qb *QBuilder) WhereNot(col string, val any) *QBuilder {
	if !isEmpty(val) {
		qb.model.WhereNot(col, val)
	}
	return qb
}

// LikeLeft matches values starting with the provided string
func (qb *QBuilder) LikeLeft(col string, val string) *QBuilder {
	if val != "" {
		qb.model.WhereLike(col, "%"+val)
	}
	return qb
}

// LikeRight matches values ending with the provided string
func (qb *QBuilder) LikeRight(col string, val string) *QBuilder {
	if val != "" {
		qb.model.WhereLike(col, val+"%")
	}
	return qb
}

// FullMatch performs a wildcard search within the column
func (qb *QBuilder) FullMatch(col string, val string) *QBuilder {
	if val != "" {
		qb.model.WhereLike(col, "%"+val+"%")
	}
	return qb
}

// isEmpty checks if a value is effectively zero or empty for query purposes
func isEmpty(v any) bool {
	if v == nil {
		return true
	}
	if s, ok := v.(string); ok {
		return len(s) == 0
	}
	return false
}

Practical Application

Using this wrapper significantly reduces the cognitive load in business logic layers. Instead of checking fields individually before appending them to a Where clause, developers simply call the fluant methods. If a field is empty, the condition is silently skipped.

Consider a controller scenario filtering device records:

func GetDeviceList(ctx context.Context, req *v1.DeviceReq) (*result.Res, error) {
	var result []do.Device
	var total int

	columns := do.Columns()

	builder := NewBuilder(do.NewDao().Ctx(ctx))
	builder
		.WhereEq(columns.Name, req.TableName)
		.WhereNot(columns.Id, req.ExcludeId)
		.FullMatch(columns.Description, req.Keyword)
		.Fetch(&result, &total)

	return result.Success(result.DataWithPagination(result, total)), nil
}

For scenarios requiring extensive filtering that may span multiple lines for readability, the fluent style supports seamless continuation:

builder
	.WhereEq(columns.Name, "MainTable")
	.FullMatch(columns.Status, "Active")
	.Fetch(&result, &total)

This approach centralizes query logic, minimizes syntax errors regarding column names, and ensures consistent handling of nullable parameters throughout the application stack.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.