Skip to content
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
31 changes: 31 additions & 0 deletions internal/translator/function/cache_warmup.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package function

import (
"fmt"
"strconv"
"strings"

"github.com/viant/datly/view"
Expand Down Expand Up @@ -42,6 +43,13 @@ func (c *cacheWarmup) Apply(args []string, column *sqlparser.Column, resource *v
warmup.FieldNames = fieldNames
continue
}
if limit, ok, err := parseWarmupLimit(raw); ok || err != nil {
if err != nil {
return err
}
warmup.Limit = limit
continue
}
param, err := parseWarmupParam(raw)
if err != nil {
return err
Expand Down Expand Up @@ -121,6 +129,29 @@ func parseWarmupFieldNames(raw string) ([]string, bool, error) {
return result, true, nil
}

func parseWarmupLimit(raw string) (*int, bool, error) {
name, value, ok := splitWarmupOption(raw)
if !ok {
return nil, false, nil
}
switch strings.ToLower(name) {
case "limit":
default:
return nil, false, nil
}
if value == "" {
return nil, true, fmt.Errorf("warmup limit was empty")
}
limit, err := strconv.Atoi(value)
if err != nil {
return nil, true, fmt.Errorf("warmup limit %q was invalid: %w", value, err)
}
if limit < 0 {
return nil, true, fmt.Errorf("warmup limit %q must be zero or greater", value)
}
return &limit, true, nil
}

func parseWarmupParam(raw string) (*view.ParamValue, error) {
name, rawValues, ok := splitWarmupOption(raw)
if !ok {
Expand Down
20 changes: 20 additions & 0 deletions internal/translator/function/cache_warmup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func TestCacheWarmupApply(t *testing.T) {
"order_id",
"Connector=bq_metrics_prewarm",
"IndexParameter=OrderId",
"Limit=0",
"FieldNames=Id,Name",
"Period=today,yesterday",
"Granularity=hour,day",
Expand All @@ -30,6 +31,9 @@ func TestCacheWarmupApply(t *testing.T) {
if aView.Cache.Warmup.IndexParameter != "OrderId" {
t.Fatalf("unexpected index parameter: %v", aView.Cache.Warmup.IndexParameter)
}
if aView.Cache.Warmup.Limit == nil || *aView.Cache.Warmup.Limit != 0 {
t.Fatalf("unexpected warmup limit: %#v", aView.Cache.Warmup.Limit)
}
if len(aView.Cache.Warmup.FieldNames) != 2 || aView.Cache.Warmup.FieldNames[0] != "Id" || aView.Cache.Warmup.FieldNames[1] != "Name" {
t.Fatalf("unexpected field names: %#v", aView.Cache.Warmup.FieldNames)
}
Expand Down Expand Up @@ -74,3 +78,19 @@ func TestCacheWarmupApplyRejectsEmptyFieldNames(t *testing.T) {
t.Fatalf("expected error")
}
}

func TestCacheWarmupApplyRejectsInvalidLimit(t *testing.T) {
aView := &view.View{Cache: view.NewRefCache("aerospike")}
err := (&cacheWarmup{}).Apply([]string{"order_id", "Limit=invalid"}, nil, &view.Resource{}, aView)
if err == nil {
t.Fatalf("expected error")
}
}

func TestCacheWarmupApplyRejectsNegativeLimit(t *testing.T) {
aView := &view.View{Cache: view.NewRefCache("aerospike")}
err := (&cacheWarmup{}).Apply([]string{"order_id", "Limit=-1"}, nil, &view.Resource{}, aView)
if err == nil {
t.Fatalf("expected error")
}
}
36 changes: 28 additions & 8 deletions internal/translator/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package translator
import (
"fmt"
"path"
"strconv"
"strings"

"github.com/viant/datly/internal/asset"
Expand Down Expand Up @@ -43,11 +44,10 @@ func (v *View) applyHintSettings(namespace *Viewlet) error {
return fmt.Errorf("invalid view %v hint, %w, %s", v, err, viewJSONHint)
}

v.applyShorthands(namespace)
return nil
return v.applyShorthands(namespace)
}

func (v *View) applyShorthands(viewlet *Viewlet) {
func (v *View) applyShorthands(viewlet *Viewlet) error {
if v.Self != nil {
v.SelfReference = v.Self
}
Expand Down Expand Up @@ -79,20 +79,27 @@ func (v *View) applyShorthands(viewlet *Viewlet) {
}

if len(v.Warmup) > 0 {
v.View.Cache.Warmup = v.buildCacheWarmup(v.Warmup, viewlet)
warmup, err := v.buildCacheWarmup(v.Warmup, viewlet)
if err != nil {
return err
}
v.View.Cache.Warmup = warmup
}
return nil
}

func (v *View) buildCacheWarmup(warmup map[string]interface{}, viewlet *Viewlet) *view.Warmup {
func (v *View) buildCacheWarmup(warmup map[string]interface{}, viewlet *Viewlet) (*view.Warmup, error) {
if warmup == nil {
return nil
return nil, nil
}
warmup = copyWarmup(warmup)

explicitIndex, _ := warmup["IndexColumn"]
delete(warmup, "IndexColumn")
indexParameter, _ := warmup["IndexParameter"]
delete(warmup, "IndexParameter")
limit, hasLimit := warmup["Limit"]
delete(warmup, "Limit")
connector, _ := warmup["Connector"]
delete(warmup, "Connector")
var refColumn string
Expand All @@ -107,11 +114,24 @@ func (v *View) buildCacheWarmup(warmup map[string]interface{}, viewlet *Viewlet)
result.IndexColumn = explicit
}
if result.IndexColumn == "" {
return nil
return nil, nil
}
if parameterName := strings.TrimSpace(fmt.Sprint(indexParameter)); parameterName != "" && parameterName != "<nil>" {
result.IndexParameter = parameterName
}
if hasLimit {
limitValue := strings.TrimSpace(fmt.Sprint(limit))
if limitValue != "" && limitValue != "<nil>" {
parsed, err := strconv.Atoi(limitValue)
if err != nil {
return nil, fmt.Errorf("invalid warmup limit %q: %w", limitValue, err)
}
if parsed < 0 {
return nil, fmt.Errorf("invalid warmup limit %q: must be zero or greater", limitValue)
}
result.Limit = &parsed
}
}
if connectorName := strings.TrimSpace(fmt.Sprint(connector)); connectorName != "" && connectorName != "<nil>" {
result.Connector = view.NewRefConnector(connectorName)
}
Expand All @@ -127,7 +147,7 @@ func (v *View) buildCacheWarmup(warmup map[string]interface{}, viewlet *Viewlet)
}

result.Cases = append(result.Cases, multiSet)
return result
return result, nil
}

func copyWarmup(warmup map[string]interface{}) map[string]interface{} {
Expand Down
32 changes: 31 additions & 1 deletion internal/translator/view_warmup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,47 @@ func TestViewBuildCacheWarmup_RootViewUsesExplicitIndexColumn(t *testing.T) {
subject := &View{}
viewlet := &Viewlet{Name: "adOrderRoot"}

warmup := subject.buildCacheWarmup(map[string]interface{}{
warmup, err := subject.buildCacheWarmup(map[string]interface{}{
"IndexColumn": "ad_order_id",
"IndexParameter": "AdOrderId",
"Limit": "0",
"Connector": "bq_metrics_prewarm",
}, viewlet)

require.NoError(t, err)
require.NotNil(t, warmup)
require.Equal(t, "ad_order_id", warmup.IndexColumn)
require.Equal(t, "AdOrderId", warmup.IndexParameter)
require.NotNil(t, warmup.Limit)
require.Equal(t, 0, *warmup.Limit)
require.NotNil(t, warmup.Connector)
require.Equal(t, "bq_metrics_prewarm", warmup.Connector.Ref)
require.Len(t, warmup.Cases, 1)
require.Len(t, warmup.Cases[0].Set, 0)
}

func TestViewBuildCacheWarmup_InvalidLimitReturnsError(t *testing.T) {
subject := &View{}
viewlet := &Viewlet{Name: "adOrderRoot"}

warmup, err := subject.buildCacheWarmup(map[string]interface{}{
"IndexColumn": "ad_order_id",
"Limit": "invalid",
}, viewlet)

require.Nil(t, warmup)
require.Error(t, err)
}

func TestViewBuildCacheWarmup_NegativeLimitReturnsError(t *testing.T) {
subject := &View{}
viewlet := &Viewlet{Name: "adOrderRoot"}

warmup, err := subject.buildCacheWarmup(map[string]interface{}{
"IndexColumn": "ad_order_id",
"Limit": "-1",
}, viewlet)

require.Nil(t, warmup)
require.Error(t, err)
}
3 changes: 3 additions & 0 deletions service/reader/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,9 @@ func (b *Builder) lookupRelationColumn(aView *view.View, relation *view.Relation
}

func actualLimit(aView *view.View, selector *view.Statelet) int {
if selector.WarmupNoLimit {
return 0
}
if selector.Limit != 0 {
return selector.Limit
}
Expand Down
Loading
Loading