safing-portmaster/base/database/query/query.go
2025-02-25 11:48:16 +01:00

175 lines
3.7 KiB
Go

package query
import (
"fmt"
"strings"
"github.com/safing/portmaster/base/database/accessor"
"github.com/safing/portmaster/base/database/record"
)
// Example:
// q.New("core:/",
// q.Where("a", q.GreaterThan, 0),
// q.Where("b", q.Equals, 0),
// q.Or(
// q.Where("c", q.StartsWith, "x"),
// q.Where("d", q.Contains, "y")
// )
// )
// Query contains a compiled query.
type Query struct {
checked bool
dbName string
dbKeyPrefix string
where Condition
orderBy string
limit int
offset int
}
// New creates a new query with the supplied prefix.
func New(prefix string) *Query {
dbName, dbKeyPrefix := record.ParseKey(prefix)
return &Query{
dbName: dbName,
dbKeyPrefix: dbKeyPrefix,
}
}
// Where adds filtering.
func (q *Query) Where(condition Condition) *Query {
q.where = condition
return q
}
// Limit limits the number of returned results.
func (q *Query) Limit(limit int) *Query {
q.limit = limit
return q
}
// Offset sets the query offset.
func (q *Query) Offset(offset int) *Query {
q.offset = offset
return q
}
// OrderBy orders the results by the given key.
func (q *Query) OrderBy(key string) *Query {
q.orderBy = key
return q
}
// Check checks for errors in the query.
func (q *Query) Check() (*Query, error) {
if q.checked {
return q, nil
}
// check condition
if q.where != nil {
err := q.where.check()
if err != nil {
return nil, err
}
}
q.checked = true
return q, nil
}
// MustBeValid checks for errors in the query and panics if there is an error.
func (q *Query) MustBeValid() *Query {
_, err := q.Check()
if err != nil {
panic(err)
}
return q
}
// IsChecked returns whether they query was checked.
func (q *Query) IsChecked() bool {
return q.checked
}
// MatchesKey checks whether the query matches the supplied database key (key without database prefix).
func (q *Query) MatchesKey(dbKey string) bool {
return strings.HasPrefix(dbKey, q.dbKeyPrefix)
}
// HasWhereCondition returns whether the query has a "where" condition set.
func (q *Query) HasWhereCondition() bool {
return q.where != nil
}
// MatchesRecord checks whether the query matches the supplied database record (value only).
func (q *Query) MatchesRecord(r record.Record) bool {
if q.where == nil {
return true
}
acc := r.GetAccessor(r)
if acc == nil {
return false
}
return q.where.complies(acc)
}
// MatchesAccessor checks whether the query matches the supplied accessor (value only).
func (q *Query) MatchesAccessor(acc accessor.Accessor) bool {
if q.where == nil {
return true
}
return q.where.complies(acc)
}
// Matches checks whether the query matches the supplied database record.
func (q *Query) Matches(r record.Record) bool {
if !q.MatchesKey(r.DatabaseKey()) {
return false
}
return q.MatchesRecord(r)
}
// Print returns the string representation of the query.
func (q *Query) Print() string {
var where string
if q.where != nil {
where = q.where.string()
if where != "" {
if strings.HasPrefix(where, "(") {
where = where[1 : len(where)-1]
}
where = fmt.Sprintf(" where %s", where)
}
}
var orderBy string
if q.orderBy != "" {
orderBy = fmt.Sprintf(" orderby %s", q.orderBy)
}
var limit string
if q.limit > 0 {
limit = fmt.Sprintf(" limit %d", q.limit)
}
var offset string
if q.offset > 0 {
offset = fmt.Sprintf(" offset %d", q.offset)
}
return fmt.Sprintf("query %s:%s%s%s%s%s", q.dbName, q.dbKeyPrefix, where, orderBy, limit, offset)
}
// DatabaseName returns the name of the database.
func (q *Query) DatabaseName() string {
return q.dbName
}
// DatabaseKeyPrefix returns the key prefix for the database.
func (q *Query) DatabaseKeyPrefix() string {
return q.dbKeyPrefix
}