mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
feat: support now()
time functions
This commit is contained in:
@@ -2,6 +2,7 @@ package filter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
exprv1 "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
exprv1 "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||||
)
|
)
|
||||||
@@ -37,3 +38,90 @@ func GetIdentExprName(expr *exprv1.Expr) (string, error) {
|
|||||||
}
|
}
|
||||||
return expr.GetIdentExpr().GetName(), nil
|
return expr.GetIdentExpr().GetName(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFunctionValue evaluates CEL function calls and returns their value.
|
||||||
|
// This is specifically for time functions like now().
|
||||||
|
func GetFunctionValue(expr *exprv1.Expr) (any, error) {
|
||||||
|
callExpr, ok := expr.ExprKind.(*exprv1.Expr_CallExpr)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("invalid function call expression")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch callExpr.CallExpr.Function {
|
||||||
|
case "now":
|
||||||
|
if len(callExpr.CallExpr.Args) != 0 {
|
||||||
|
return nil, errors.New("now() function takes no arguments")
|
||||||
|
}
|
||||||
|
return time.Now().Unix(), nil
|
||||||
|
case "_-_":
|
||||||
|
// Handle subtraction for expressions like "now() - 60 * 60 * 24"
|
||||||
|
if len(callExpr.CallExpr.Args) != 2 {
|
||||||
|
return nil, errors.New("subtraction requires exactly two arguments")
|
||||||
|
}
|
||||||
|
left, err := GetExprValue(callExpr.CallExpr.Args[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
right, err := GetExprValue(callExpr.CallExpr.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
leftInt, ok1 := left.(int64)
|
||||||
|
rightInt, ok2 := right.(int64)
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
return nil, errors.New("subtraction operands must be integers")
|
||||||
|
}
|
||||||
|
return leftInt - rightInt, nil
|
||||||
|
case "_*_":
|
||||||
|
// Handle multiplication for expressions like "60 * 60 * 24"
|
||||||
|
if len(callExpr.CallExpr.Args) != 2 {
|
||||||
|
return nil, errors.New("multiplication requires exactly two arguments")
|
||||||
|
}
|
||||||
|
left, err := GetExprValue(callExpr.CallExpr.Args[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
right, err := GetExprValue(callExpr.CallExpr.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
leftInt, ok1 := left.(int64)
|
||||||
|
rightInt, ok2 := right.(int64)
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
return nil, errors.New("multiplication operands must be integers")
|
||||||
|
}
|
||||||
|
return leftInt * rightInt, nil
|
||||||
|
case "_+_":
|
||||||
|
// Handle addition
|
||||||
|
if len(callExpr.CallExpr.Args) != 2 {
|
||||||
|
return nil, errors.New("addition requires exactly two arguments")
|
||||||
|
}
|
||||||
|
left, err := GetExprValue(callExpr.CallExpr.Args[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
right, err := GetExprValue(callExpr.CallExpr.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
leftInt, ok1 := left.(int64)
|
||||||
|
rightInt, ok2 := right.(int64)
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
return nil, errors.New("addition operands must be integers")
|
||||||
|
}
|
||||||
|
return leftInt + rightInt, nil
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unsupported function: " + callExpr.CallExpr.Function)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExprValue attempts to get a value from an expression, trying constants first, then functions.
|
||||||
|
func GetExprValue(expr *exprv1.Expr) (any, error) {
|
||||||
|
// Try to get constant value first
|
||||||
|
if constValue, err := GetConstValue(expr); err == nil {
|
||||||
|
return constValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not a constant, try to evaluate as a function
|
||||||
|
return GetFunctionValue(expr)
|
||||||
|
}
|
||||||
|
@@ -1,7 +1,11 @@
|
|||||||
package filter
|
package filter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/cel-go/cel"
|
"github.com/google/cel-go/cel"
|
||||||
|
"github.com/google/cel-go/common/types"
|
||||||
|
"github.com/google/cel-go/common/types/ref"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
exprv1 "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
exprv1 "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||||
)
|
)
|
||||||
@@ -10,14 +14,22 @@ import (
|
|||||||
var MemoFilterCELAttributes = []cel.EnvOption{
|
var MemoFilterCELAttributes = []cel.EnvOption{
|
||||||
cel.Variable("content", cel.StringType),
|
cel.Variable("content", cel.StringType),
|
||||||
cel.Variable("creator_id", cel.IntType),
|
cel.Variable("creator_id", cel.IntType),
|
||||||
// As the built-in timestamp type is deprecated, we use string type for now.
|
cel.Variable("created_ts", cel.IntType),
|
||||||
// e.g., "2021-01-01T00:00:00Z"
|
cel.Variable("updated_ts", cel.IntType),
|
||||||
cel.Variable("create_time", cel.StringType),
|
|
||||||
cel.Variable("pinned", cel.BoolType),
|
cel.Variable("pinned", cel.BoolType),
|
||||||
cel.Variable("tag", cel.StringType),
|
cel.Variable("tag", cel.StringType),
|
||||||
cel.Variable("update_time", cel.StringType),
|
|
||||||
cel.Variable("visibility", cel.StringType),
|
cel.Variable("visibility", cel.StringType),
|
||||||
cel.Variable("has_task_list", cel.BoolType),
|
cel.Variable("has_task_list", cel.BoolType),
|
||||||
|
// Current timestamp function.
|
||||||
|
cel.Function("now",
|
||||||
|
cel.Overload("now",
|
||||||
|
[]*cel.Type{},
|
||||||
|
cel.IntType,
|
||||||
|
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
|
||||||
|
return types.Int(time.Now().Unix())
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses the filter string and returns the parsed expression.
|
// Parse parses the filter string and returns the parsed expression.
|
||||||
|
@@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
exprv1 "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
exprv1 "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||||
@@ -59,10 +58,10 @@ func (d *DB) ConvertExprToSQL(ctx *filter.ConvertContext, expr *exprv1.Expr) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !slices.Contains([]string{"creator_id", "create_time", "update_time", "visibility", "content", "has_task_list"}, identifier) {
|
if !slices.Contains([]string{"creator_id", "created_ts", "updated_ts", "visibility", "content", "has_task_list"}, identifier) {
|
||||||
return errors.Errorf("invalid identifier for %s", v.CallExpr.Function)
|
return errors.Errorf("invalid identifier for %s", v.CallExpr.Function)
|
||||||
}
|
}
|
||||||
value, err := filter.GetConstValue(v.CallExpr.Args[1])
|
value, err := filter.GetExprValue(v.CallExpr.Args[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -82,26 +81,22 @@ func (d *DB) ConvertExprToSQL(ctx *filter.ConvertContext, expr *exprv1.Expr) err
|
|||||||
operator = ">="
|
operator = ">="
|
||||||
}
|
}
|
||||||
|
|
||||||
if identifier == "create_time" || identifier == "update_time" {
|
if identifier == "created_ts" || identifier == "updated_ts" {
|
||||||
timestampStr, ok := value.(string)
|
timestampInt, ok := value.(int64)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("invalid timestamp value")
|
return errors.New("invalid timestamp value")
|
||||||
}
|
}
|
||||||
timestamp, err := time.Parse(time.RFC3339, timestampStr)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to parse timestamp")
|
|
||||||
}
|
|
||||||
|
|
||||||
var factor string
|
var factor string
|
||||||
if identifier == "create_time" {
|
if identifier == "created_ts" {
|
||||||
factor = "`memo`.`created_ts`"
|
factor = "UNIX_TIMESTAMP(`memo`.`created_ts`)"
|
||||||
} else if identifier == "update_time" {
|
} else if identifier == "updated_ts" {
|
||||||
factor = "`memo`.`updated_ts`"
|
factor = "UNIX_TIMESTAMP(`memo`.`updated_ts`)"
|
||||||
}
|
}
|
||||||
if _, err := ctx.Buffer.WriteString(fmt.Sprintf("UNIX_TIMESTAMP(%s) %s ?", factor, operator)); err != nil {
|
if _, err := ctx.Buffer.WriteString(fmt.Sprintf("%s %s ?", factor, operator)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx.Args = append(ctx.Args, timestamp.Unix())
|
ctx.Args = append(ctx.Args, timestampInt)
|
||||||
} else if identifier == "visibility" || identifier == "content" {
|
} else if identifier == "visibility" || identifier == "content" {
|
||||||
if operator != "=" && operator != "!=" {
|
if operator != "=" && operator != "!=" {
|
||||||
return errors.Errorf("invalid operator for %s", v.CallExpr.Function)
|
return errors.Errorf("invalid operator for %s", v.CallExpr.Function)
|
||||||
|
@@ -2,6 +2,7 @@ package mysql
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
@@ -39,11 +40,6 @@ func TestConvertExprToSQL(t *testing.T) {
|
|||||||
want: "`memo`.`visibility` IN (?,?)",
|
want: "`memo`.`visibility` IN (?,?)",
|
||||||
args: []any{"PUBLIC", "PRIVATE"},
|
args: []any{"PUBLIC", "PRIVATE"},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
filter: `create_time == "2006-01-02T15:04:05+07:00"`,
|
|
||||||
want: "UNIX_TIMESTAMP(`memo`.`created_ts`) = ?",
|
|
||||||
args: []any{int64(1136189045)},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
filter: `tag in ['tag1'] || content.contains('hello')`,
|
filter: `tag in ['tag1'] || content.contains('hello')`,
|
||||||
want: "(JSON_CONTAINS(JSON_EXTRACT(`memo`.`payload`, '$.tags'), ?) OR `memo`.`content` LIKE ?)",
|
want: "(JSON_CONTAINS(JSON_EXTRACT(`memo`.`payload`, '$.tags'), ?) OR `memo`.`content` LIKE ?)",
|
||||||
@@ -94,6 +90,11 @@ func TestConvertExprToSQL(t *testing.T) {
|
|||||||
want: "(JSON_EXTRACT(`memo`.`payload`, '$.property.hasTaskList') = CAST('true' AS JSON) AND `memo`.`content` LIKE ?)",
|
want: "(JSON_EXTRACT(`memo`.`payload`, '$.property.hasTaskList') = CAST('true' AS JSON) AND `memo`.`content` LIKE ?)",
|
||||||
args: []any{"%todo%"},
|
args: []any{"%todo%"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
filter: `created_ts > now() - 60 * 60 * 24`,
|
||||||
|
want: "UNIX_TIMESTAMP(`memo`.`created_ts`) > ?",
|
||||||
|
args: []any{time.Now().Unix() - 60*60*24},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
exprv1 "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
exprv1 "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||||
@@ -59,10 +58,10 @@ func (d *DB) ConvertExprToSQL(ctx *filter.ConvertContext, expr *exprv1.Expr) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !slices.Contains([]string{"creator_id", "create_time", "update_time", "visibility", "content", "has_task_list"}, identifier) {
|
if !slices.Contains([]string{"creator_id", "created_ts", "updated_ts", "visibility", "content", "has_task_list"}, identifier) {
|
||||||
return errors.Errorf("invalid identifier for %s", v.CallExpr.Function)
|
return errors.Errorf("invalid identifier for %s", v.CallExpr.Function)
|
||||||
}
|
}
|
||||||
value, err := filter.GetConstValue(v.CallExpr.Args[1])
|
value, err := filter.GetExprValue(v.CallExpr.Args[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -82,26 +81,22 @@ func (d *DB) ConvertExprToSQL(ctx *filter.ConvertContext, expr *exprv1.Expr) err
|
|||||||
operator = ">="
|
operator = ">="
|
||||||
}
|
}
|
||||||
|
|
||||||
if identifier == "create_time" || identifier == "update_time" {
|
if identifier == "created_ts" || identifier == "updated_ts" {
|
||||||
timestampStr, ok := value.(string)
|
timestampInt, ok := value.(int64)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("invalid timestamp value")
|
return errors.New("invalid timestamp value")
|
||||||
}
|
}
|
||||||
timestamp, err := time.Parse(time.RFC3339, timestampStr)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to parse timestamp")
|
|
||||||
}
|
|
||||||
|
|
||||||
var factor string
|
var factor string
|
||||||
if identifier == "create_time" {
|
if identifier == "created_ts" {
|
||||||
factor = "memo.created_ts"
|
factor = "EXTRACT(EPOCH FROM memo.created_ts)"
|
||||||
} else if identifier == "update_time" {
|
} else if identifier == "updated_ts" {
|
||||||
factor = "memo.updated_ts"
|
factor = "EXTRACT(EPOCH FROM memo.updated_ts)"
|
||||||
}
|
}
|
||||||
if _, err := ctx.Buffer.WriteString(fmt.Sprintf("%s %s %s", factor, operator, placeholder(len(ctx.Args)+ctx.ArgsOffset+1))); err != nil {
|
if _, err := ctx.Buffer.WriteString(fmt.Sprintf("%s %s %s", factor, operator, placeholder(len(ctx.Args)+ctx.ArgsOffset+1))); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx.Args = append(ctx.Args, timestamp.Unix())
|
ctx.Args = append(ctx.Args, timestampInt)
|
||||||
} else if identifier == "visibility" || identifier == "content" {
|
} else if identifier == "visibility" || identifier == "content" {
|
||||||
if operator != "=" && operator != "!=" {
|
if operator != "=" && operator != "!=" {
|
||||||
return errors.Errorf("invalid operator for %s", v.CallExpr.Function)
|
return errors.Errorf("invalid operator for %s", v.CallExpr.Function)
|
||||||
|
@@ -2,6 +2,7 @@ package postgres
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
@@ -39,11 +40,6 @@ func TestRestoreExprToSQL(t *testing.T) {
|
|||||||
want: "memo.visibility IN ($1,$2)",
|
want: "memo.visibility IN ($1,$2)",
|
||||||
args: []any{"PUBLIC", "PRIVATE"},
|
args: []any{"PUBLIC", "PRIVATE"},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
filter: `create_time == "2006-01-02T15:04:05+07:00"`,
|
|
||||||
want: "memo.created_ts = $1",
|
|
||||||
args: []any{int64(1136189045)},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
filter: `tag in ['tag1'] || content.contains('hello')`,
|
filter: `tag in ['tag1'] || content.contains('hello')`,
|
||||||
want: "(memo.payload->'tags' @> jsonb_build_array($1) OR memo.content ILIKE $2)",
|
want: "(memo.payload->'tags' @> jsonb_build_array($1) OR memo.content ILIKE $2)",
|
||||||
@@ -94,6 +90,11 @@ func TestRestoreExprToSQL(t *testing.T) {
|
|||||||
want: "((memo.payload->'property'->>'hasTaskList')::boolean IS TRUE AND memo.content ILIKE $1)",
|
want: "((memo.payload->'property'->>'hasTaskList')::boolean IS TRUE AND memo.content ILIKE $1)",
|
||||||
args: []any{"%todo%"},
|
args: []any{"%todo%"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
filter: `created_ts > now() - 60 * 60 * 24`,
|
||||||
|
want: "EXTRACT(EPOCH FROM memo.created_ts) > $1",
|
||||||
|
args: []any{time.Now().Unix() - 60*60*24},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
exprv1 "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
exprv1 "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||||
@@ -59,10 +58,10 @@ func (d *DB) ConvertExprToSQL(ctx *filter.ConvertContext, expr *exprv1.Expr) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !slices.Contains([]string{"creator_id", "create_time", "update_time", "visibility", "content", "has_task_list"}, identifier) {
|
if !slices.Contains([]string{"creator_id", "created_ts", "updated_ts", "visibility", "content", "has_task_list"}, identifier) {
|
||||||
return errors.Errorf("invalid identifier for %s", v.CallExpr.Function)
|
return errors.Errorf("invalid identifier for %s", v.CallExpr.Function)
|
||||||
}
|
}
|
||||||
value, err := filter.GetConstValue(v.CallExpr.Args[1])
|
value, err := filter.GetExprValue(v.CallExpr.Args[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -82,26 +81,22 @@ func (d *DB) ConvertExprToSQL(ctx *filter.ConvertContext, expr *exprv1.Expr) err
|
|||||||
operator = ">="
|
operator = ">="
|
||||||
}
|
}
|
||||||
|
|
||||||
if identifier == "create_time" || identifier == "update_time" {
|
if identifier == "created_ts" || identifier == "updated_ts" {
|
||||||
timestampStr, ok := value.(string)
|
valueInt, ok := value.(int64)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("invalid timestamp value")
|
return errors.New("invalid integer timestamp value")
|
||||||
}
|
|
||||||
timestamp, err := time.Parse(time.RFC3339, timestampStr)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to parse timestamp")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var factor string
|
var factor string
|
||||||
if identifier == "create_time" {
|
if identifier == "created_ts" {
|
||||||
factor = "`memo`.`created_ts`"
|
factor = "`memo`.`created_ts`"
|
||||||
} else if identifier == "update_time" {
|
} else if identifier == "updated_ts" {
|
||||||
factor = "`memo`.`updated_ts`"
|
factor = "`memo`.`updated_ts`"
|
||||||
}
|
}
|
||||||
if _, err := ctx.Buffer.WriteString(fmt.Sprintf("%s %s ?", factor, operator)); err != nil {
|
if _, err := ctx.Buffer.WriteString(fmt.Sprintf("%s %s ?", factor, operator)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx.Args = append(ctx.Args, timestamp.Unix())
|
ctx.Args = append(ctx.Args, valueInt)
|
||||||
} else if identifier == "visibility" || identifier == "content" {
|
} else if identifier == "visibility" || identifier == "content" {
|
||||||
if operator != "=" && operator != "!=" {
|
if operator != "=" && operator != "!=" {
|
||||||
return errors.Errorf("invalid operator for %s", v.CallExpr.Function)
|
return errors.Errorf("invalid operator for %s", v.CallExpr.Function)
|
||||||
|
@@ -2,6 +2,7 @@ package sqlite
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
@@ -44,11 +45,6 @@ func TestConvertExprToSQL(t *testing.T) {
|
|||||||
want: "`memo`.`visibility` IN (?,?)",
|
want: "`memo`.`visibility` IN (?,?)",
|
||||||
args: []any{"PUBLIC", "PRIVATE"},
|
args: []any{"PUBLIC", "PRIVATE"},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
filter: `create_time == "2006-01-02T15:04:05+07:00"`,
|
|
||||||
want: "`memo`.`created_ts` = ?",
|
|
||||||
args: []any{int64(1136189045)},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
filter: `tag in ['tag1'] || content.contains('hello')`,
|
filter: `tag in ['tag1'] || content.contains('hello')`,
|
||||||
want: "(JSON_EXTRACT(`memo`.`payload`, '$.tags') LIKE ? OR `memo`.`content` LIKE ?)",
|
want: "(JSON_EXTRACT(`memo`.`payload`, '$.tags') LIKE ? OR `memo`.`content` LIKE ?)",
|
||||||
@@ -109,6 +105,11 @@ func TestConvertExprToSQL(t *testing.T) {
|
|||||||
want: "(JSON_EXTRACT(`memo`.`payload`, '$.property.hasTaskList') IS TRUE AND `memo`.`content` LIKE ?)",
|
want: "(JSON_EXTRACT(`memo`.`payload`, '$.property.hasTaskList') IS TRUE AND `memo`.`content` LIKE ?)",
|
||||||
args: []any{"%todo%"},
|
args: []any{"%todo%"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
filter: `created_ts > now() - 60 * 60 * 24`,
|
||||||
|
want: "`memo`.`created_ts` > ?",
|
||||||
|
args: []any{time.Now().Unix() - 60*60*24},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
Reference in New Issue
Block a user