Files
Rodrigo ZhouandGitHub 5d64c0b65f Unified query converter for Visibility (#8307)
## What changed?
Unify Visibility query converters.

The new query converter is the struct `query.QueryConverter[ExprT]`. The
generic parameter specify the output type of the converter (in ES is
`elastic.Query`, in SQL is `sqlparser.Expr`).

The query converter has a `query.StoreQueryConverter[ExprT]`. This is
the Visibility store specific implementation for building the output
query.

For example, the store needs to implement `BuildAndExpr(exprs)` to tell
the query converter how to build the `AND` expression. In SQL, it's
`*sqlparser.AndExpr{exprs}`. In Elasticsearch, it's
`elastic.NewBoolQuery().Filter(exprs)`.

The store query converter for SQL needs a plugin query converter called
`sqlplugin.VisibilityQueryConverter` which is implemented by each plugin
(MySQL, PostgreSQL, SQLite). This interface is needed to build DB
specific syntax for `KeywordList` and `Text` searches as well as to
build the final `SELECT` statements.

Added dynamic config `system.VisibilityEnableUnifiedQueryConverter` that
acts as switch between the legacy and the unified query converters.

## Why?
There are two query converters: one for Elasticsearch, and another for
SQL.
They are somewhat similar, and have quite a few code duplications for
validation, modifying the query, etc.
Unifying the query converter ensures that we provide the exact same
behavior across different Visibility stores.

## How did you test it?
- [x] built
- [x] run locally and tested manually
- [x] covered by existing tests
- [x] added new unit test(s)
- [ ] added new functional test(s)

I've added a lot of unit tests for the new code with coverage close to
100%.

Note that there has been basically no changes to functional tests, which
suggests that the change works well.

## Potential risks
There might be some edge cases that either query converters that this
unified query converter might deal differently.
2025-11-11 01:48:34 +00:00

96 lines
2.3 KiB
Go

package sql
import (
"errors"
"fmt"
"slices"
"go.temporal.io/server/common/config"
"go.temporal.io/server/common/log"
"go.temporal.io/server/common/metrics"
"go.temporal.io/server/common/persistence/sql/sqlplugin"
"go.temporal.io/server/common/resolver"
expmaps "golang.org/x/exp/maps"
)
var ErrPluginNotSupported = errors.New("plugin not supported")
var supportedPlugins = map[string]sqlplugin.Plugin{}
// RegisterPlugin will register a SQL plugin
func RegisterPlugin(pluginName string, plugin sqlplugin.Plugin) {
if _, ok := supportedPlugins[pluginName]; ok {
panic("plugin " + pluginName + " already registered")
}
supportedPlugins[pluginName] = plugin
}
// NewSQLDB creates a returns a reference to a logical connection to the
// underlying SQL database. The returned object is tied to a single
// SQL database and the object can be used to perform CRUD operations on
// the tables in the database.
func NewSQLDB(
dbKind sqlplugin.DbKind,
cfg *config.SQL,
r resolver.ServiceResolver,
logger log.Logger,
mh metrics.Handler,
) (sqlplugin.DB, error) {
return createDB[sqlplugin.DB](dbKind, cfg, r, logger, mh)
}
// NewSQLAdminDB returns a AdminDB.
func NewSQLAdminDB(
dbKind sqlplugin.DbKind,
cfg *config.SQL,
r resolver.ServiceResolver,
logger log.Logger,
mh metrics.Handler,
) (sqlplugin.AdminDB, error) {
return createDB[sqlplugin.AdminDB](dbKind, cfg, r, logger, mh)
}
func createDB[T any](
dbKind sqlplugin.DbKind,
cfg *config.SQL,
r resolver.ServiceResolver,
logger log.Logger,
mh metrics.Handler,
) (T, error) {
var res T
plugin, err := getPlugin(cfg.PluginName)
if err != nil {
return res, err
}
db, err := plugin.CreateDB(dbKind, cfg, r, logger, mh)
if err != nil {
return res, err
}
//revive:disable-next-line:unchecked-type-assertion
res = db.(T)
return res, err
}
func getPlugin(pluginName string) (sqlplugin.Plugin, error) {
plugin, ok := supportedPlugins[pluginName]
if !ok {
keys := expmaps.Keys(supportedPlugins)
slices.Sort(keys)
return nil, fmt.Errorf(
"%w: unknown plugin %q, supported plugins: %v",
ErrPluginNotSupported,
pluginName,
keys,
)
}
return plugin, nil
}
func GetPluginVisibilityQueryConverter(pluginName string) (sqlplugin.VisibilityQueryConverter, error) {
plugin, err := getPlugin(pluginName)
if err != nil {
return nil, err
}
return plugin.GetVisibilityQueryConverter(), nil
}