added strftime filter function
This commit is contained in:
@@ -209,6 +209,345 @@ func TestTokenFunctionsGeoDistanceExec(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenFunctionsStrftime(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testDB, err := createTestDB()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer testDB.Close()
|
||||
|
||||
fn, ok := TokenFunctions["strftime"]
|
||||
if !ok {
|
||||
t.Error("Expected strftime token function to be registered.")
|
||||
}
|
||||
|
||||
baseTokenResolver := func(t fexpr.Token) (*ResolverResult, error) {
|
||||
placeholder := "t" + security.PseudorandomString(5)
|
||||
return &ResolverResult{Identifier: "{:" + placeholder + "}", Params: map[string]any{placeholder: t.Literal}}, nil
|
||||
}
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
args []fexpr.Token
|
||||
resolver func(t fexpr.Token) (*ResolverResult, error)
|
||||
result *ResolverResult
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
"no args",
|
||||
nil,
|
||||
baseTokenResolver,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
// format arg
|
||||
// -----------------------------------------------------------
|
||||
{
|
||||
"(format arg) invalid token type function",
|
||||
[]fexpr.Token{
|
||||
{Literal: "abc", Type: fexpr.TokenFunction},
|
||||
},
|
||||
baseTokenResolver,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"(format arg) invalid token type ws",
|
||||
[]fexpr.Token{
|
||||
{Literal: "abc", Type: fexpr.TokenWS},
|
||||
},
|
||||
baseTokenResolver,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"(format arg) invalid token type number",
|
||||
[]fexpr.Token{
|
||||
{Literal: "abc", Type: fexpr.TokenNumber},
|
||||
},
|
||||
baseTokenResolver,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"(format arg) invalid token type identifier",
|
||||
[]fexpr.Token{
|
||||
{Literal: "abc", Type: fexpr.TokenIdentifier},
|
||||
},
|
||||
baseTokenResolver,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"(format arg) valid token type text",
|
||||
[]fexpr.Token{
|
||||
{Literal: "abc", Type: fexpr.TokenText},
|
||||
},
|
||||
baseTokenResolver,
|
||||
&ResolverResult{
|
||||
Identifier: `strftime({:format})`,
|
||||
Params: map[string]any{"format": "abc"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
// format + time-value args
|
||||
// -----------------------------------------------------------
|
||||
{
|
||||
"(format arg) invalid token type function",
|
||||
[]fexpr.Token{
|
||||
{Literal: "1", Type: fexpr.TokenText},
|
||||
{Literal: "2", Type: fexpr.TokenFunction},
|
||||
},
|
||||
baseTokenResolver,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"(format arg) invalid token type ws",
|
||||
[]fexpr.Token{
|
||||
{Literal: "1", Type: fexpr.TokenText},
|
||||
{Literal: "2", Type: fexpr.TokenWS},
|
||||
},
|
||||
baseTokenResolver,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"(format arg) valid token type number",
|
||||
[]fexpr.Token{
|
||||
{Literal: "1", Type: fexpr.TokenText},
|
||||
{Literal: "2", Type: fexpr.TokenNumber},
|
||||
},
|
||||
baseTokenResolver,
|
||||
&ResolverResult{
|
||||
Identifier: `strftime({:format},{:time})`,
|
||||
Params: map[string]any{"format": "1", "time": "2"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"(format arg) valid token type identifier",
|
||||
[]fexpr.Token{
|
||||
{Literal: "1", Type: fexpr.TokenText},
|
||||
{Literal: "2", Type: fexpr.TokenIdentifier},
|
||||
},
|
||||
baseTokenResolver,
|
||||
&ResolverResult{
|
||||
Identifier: `strftime({:format},{:time})`,
|
||||
Params: map[string]any{"format": "1", "time": "2"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"(format arg) valid token type text",
|
||||
[]fexpr.Token{
|
||||
{Literal: "1", Type: fexpr.TokenText},
|
||||
{Literal: "2", Type: fexpr.TokenText},
|
||||
},
|
||||
baseTokenResolver,
|
||||
&ResolverResult{
|
||||
Identifier: `strftime({:format},{:time})`,
|
||||
Params: map[string]any{"format": "1", "time": "2"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
// format + time-value + modifier args
|
||||
// -----------------------------------------------------------
|
||||
{
|
||||
"(modifiers arg) invalid token type function",
|
||||
[]fexpr.Token{
|
||||
{Literal: "1", Type: fexpr.TokenText}, // valid format
|
||||
{Literal: "2", Type: fexpr.TokenText}, // valid time-value
|
||||
{Literal: "3", Type: fexpr.TokenText}, // valid modifier
|
||||
{Literal: "4", Type: fexpr.TokenFunction},
|
||||
},
|
||||
baseTokenResolver,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"(modifiers arg) invalid token type ws",
|
||||
[]fexpr.Token{
|
||||
{Literal: "1", Type: fexpr.TokenText}, // valid format
|
||||
{Literal: "2", Type: fexpr.TokenText}, // valid time-value
|
||||
{Literal: "3", Type: fexpr.TokenText}, // valid modifier
|
||||
{Literal: "4", Type: fexpr.TokenWS},
|
||||
},
|
||||
baseTokenResolver,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"(modifiers arg) valid token type number",
|
||||
[]fexpr.Token{
|
||||
{Literal: "1", Type: fexpr.TokenText}, // valid format
|
||||
{Literal: "2", Type: fexpr.TokenText}, // valid time-value
|
||||
{Literal: "3", Type: fexpr.TokenText}, // valid modifier
|
||||
{Literal: "4", Type: fexpr.TokenNumber},
|
||||
},
|
||||
baseTokenResolver,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"(modifiers arg) valid token type identifier",
|
||||
[]fexpr.Token{
|
||||
{Literal: "1", Type: fexpr.TokenText}, // valid format
|
||||
{Literal: "2", Type: fexpr.TokenText}, // valid time-value
|
||||
{Literal: "3", Type: fexpr.TokenText}, // valid modifier
|
||||
{Literal: "4", Type: fexpr.TokenIdentifier},
|
||||
},
|
||||
baseTokenResolver,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"(modifiers arg) valid token type text",
|
||||
[]fexpr.Token{
|
||||
{Literal: "1", Type: fexpr.TokenText}, // valid format
|
||||
{Literal: "2", Type: fexpr.TokenText}, // valid time-value
|
||||
{Literal: "3", Type: fexpr.TokenText}, // valid modifier
|
||||
{Literal: "4", Type: fexpr.TokenText}, // valid modifier
|
||||
},
|
||||
baseTokenResolver,
|
||||
&ResolverResult{
|
||||
Identifier: `strftime({:format},{:time},{:m1},{:m2})`,
|
||||
Params: map[string]any{"format": "1", "time": "2", "m1": "3", "m2": "4"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
// -----------------------------------------------------------
|
||||
|
||||
{
|
||||
"= 10 args limit",
|
||||
[]fexpr.Token{
|
||||
{Literal: "1", Type: fexpr.TokenText},
|
||||
{Literal: "2", Type: fexpr.TokenText},
|
||||
{Literal: "3", Type: fexpr.TokenText},
|
||||
{Literal: "4", Type: fexpr.TokenText},
|
||||
{Literal: "5", Type: fexpr.TokenText},
|
||||
{Literal: "6", Type: fexpr.TokenText},
|
||||
{Literal: "7", Type: fexpr.TokenText},
|
||||
{Literal: "8", Type: fexpr.TokenText},
|
||||
{Literal: "9", Type: fexpr.TokenText},
|
||||
{Literal: "10", Type: fexpr.TokenText},
|
||||
},
|
||||
baseTokenResolver,
|
||||
&ResolverResult{
|
||||
Identifier: `strftime({:format},{:time},{:m1},{:m2},{:m3},{:m4},{:m5},{:m6},{:m7},{:m8})`,
|
||||
Params: map[string]any{
|
||||
"format": "1",
|
||||
"time": "2",
|
||||
"m1": "3",
|
||||
"m2": "4",
|
||||
"m3": "5",
|
||||
"m4": "6",
|
||||
"m5": "7",
|
||||
"m6": "8",
|
||||
"m7": "9",
|
||||
"m8": "10",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"> 10 args limit",
|
||||
[]fexpr.Token{
|
||||
{Literal: "1", Type: fexpr.TokenText},
|
||||
{Literal: "2", Type: fexpr.TokenText},
|
||||
{Literal: "3", Type: fexpr.TokenText},
|
||||
{Literal: "4", Type: fexpr.TokenText},
|
||||
{Literal: "5", Type: fexpr.TokenText},
|
||||
{Literal: "6", Type: fexpr.TokenText},
|
||||
{Literal: "7", Type: fexpr.TokenText},
|
||||
{Literal: "8", Type: fexpr.TokenText},
|
||||
{Literal: "9", Type: fexpr.TokenText},
|
||||
{Literal: "10", Type: fexpr.TokenText},
|
||||
{Literal: "11", Type: fexpr.TokenText},
|
||||
},
|
||||
baseTokenResolver,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"valid arguments but with resolver error",
|
||||
[]fexpr.Token{
|
||||
{Literal: "1", Type: fexpr.TokenText},
|
||||
{Literal: "2", Type: fexpr.TokenText},
|
||||
{Literal: "3", Type: fexpr.TokenText},
|
||||
},
|
||||
func(t fexpr.Token) (*ResolverResult, error) {
|
||||
return nil, errors.New("test")
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result, err := fn(s.resolver, s.args...)
|
||||
|
||||
hasErr := err != nil
|
||||
if hasErr != s.expectErr {
|
||||
t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectErr, hasErr, err)
|
||||
}
|
||||
|
||||
testCompareResults(t, s.result, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenFunctionsStrftimeExec(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testDB, err := createTestDB()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer testDB.Close()
|
||||
|
||||
fn, ok := TokenFunctions["strftime"]
|
||||
if !ok {
|
||||
t.Error("Expected strftime token function to be registered.")
|
||||
}
|
||||
|
||||
result, err := fn(
|
||||
func(t fexpr.Token) (*ResolverResult, error) {
|
||||
placeholder := "t" + security.PseudorandomString(5)
|
||||
return &ResolverResult{Identifier: "{:" + placeholder + "}", Params: map[string]any{placeholder: t.Literal}}, nil
|
||||
},
|
||||
fexpr.Token{Literal: "%Y-%m", Type: fexpr.TokenText},
|
||||
fexpr.Token{Literal: "2026-01-02 01:02:03.456Z", Type: fexpr.TokenText},
|
||||
fexpr.Token{Literal: "+1 years", Type: fexpr.TokenText},
|
||||
fexpr.Token{Literal: "+5 months", Type: fexpr.TokenText},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
column := []string{}
|
||||
err = testDB.NewQuery("select " + result.Identifier).Bind(result.Params).Column(&column)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(column) != 1 {
|
||||
t.Fatalf("Expected exactly 1 column value as result, got %v", column)
|
||||
}
|
||||
|
||||
expected := "2027-06"
|
||||
if column[0] != expected {
|
||||
t.Fatalf("Expected date value %s, got %s", expected, column[0])
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
func testCompareResults(t *testing.T, a, b *ResolverResult) {
|
||||
@@ -262,6 +601,10 @@ func testCompareResults(t *testing.T, a, b *ResolverResult) {
|
||||
t.Fatalf("Expected NoCoalesce to match, got %v vs %v", a.NoCoalesce, b.NoCoalesce)
|
||||
}
|
||||
|
||||
if len(a.Params) != len(b.Params) {
|
||||
t.Fatalf("Expected equal number of params, got %v vs %v", len(a.Params), len(b.Params))
|
||||
}
|
||||
|
||||
// loose placeholders replacement
|
||||
var aResolved = a.Identifier
|
||||
for k, v := range a.Params {
|
||||
|
||||
Reference in New Issue
Block a user