filter/render_test.go
Rama main · réplica de lectura del repositorio original.
package filter
import (
"context"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
// Callers append their own predicates after the filter fragment and bind
// positionally, so an argument left behind by a folded-away operand would bind
// to the caller's next placeholder. Folded operands must take their arguments
// with them.
func TestRenderFoldedOperandsDropTheirArgs(t *testing.T) {
t.Parallel()
engine, err := NewEngine(NewSchema())
require.NoError(t, err)
tests := []struct {
filter string
sql string // "" for trivially true, "1 = 0" for unsatisfiable, "*" for any non-constant fragment
args []any
}{
{filter: `creator_id == 1 || true`, sql: "", args: []any{}},
{filter: `true || creator_id == 1`, sql: "", args: []any{}},
{filter: `creator_id == 1 && false`, sql: "1 = 0", args: []any{}},
{filter: `false && creator_id == 1`, sql: "1 = 0", args: []any{}},
{filter: `!(creator_id == 1 || true)`, sql: "1 = 0", args: []any{}},
{filter: `!(creator_id == 1 && false)`, sql: "", args: []any{}},
{filter: `(creator_id == 1 || true) && visibility == "PUBLIC"`, sql: "*", args: []any{"PUBLIC"}},
{filter: `visibility == "PUBLIC" && (creator_id == 1 || true)`, sql: "*", args: []any{"PUBLIC"}},
{filter: `(creator_id == 1 && false) || visibility == "PUBLIC"`, sql: "*", args: []any{"PUBLIC"}},
{filter: `visibility == "PUBLIC" || (creator_id == 1 && false)`, sql: "*", args: []any{"PUBLIC"}},
{filter: `!(creator_id == 1 || true) || visibility == "PUBLIC"`, sql: "*", args: []any{"PUBLIC"}},
{filter: `(creator_id == 1 && true) || (tags.exists(t, t == "a") && false)`, sql: "*", args: []any{int64(1)}},
{
filter: `(visibility == "PRIVATE" || true) && (creator_id == 1 || true) && visibility == "PUBLIC" && creator_id == 2`,
sql: "*",
args: []any{"PUBLIC", int64(2)},
},
}
for _, dialect := range []DialectName{DialectSQLite, DialectMySQL, DialectPostgres} {
for _, offset := range []int{0, 3} {
for _, tc := range tests {
t.Run(fmt.Sprintf("%s/offset=%d/%s", dialect, offset, tc.filter), func(t *testing.T) {
stmt, err := engine.CompileToStatement(context.Background(), tc.filter, RenderOptions{
Dialect: dialect,
PlaceholderOffset: offset,
})
require.NoError(t, err)
require.Equal(t, tc.args, stmt.Args)
if tc.sql == "*" {
require.NotContains(t, []string{"", "1 = 0"}, stmt.SQL)
} else {
require.Equal(t, tc.sql, stmt.SQL)
}
require.Equal(t, expectedPlaceholders(dialect, offset, len(tc.args)), placeholdersIn(dialect, stmt.SQL))
})
}
}
}
}
func TestCheckPlaceholderParityRejectsMismatch(t *testing.T) {
t.Parallel()
tests := []struct {
name string
dialect DialectName
sql string
args []any
offset int
wantErr bool
}{
{name: "sqlite match", dialect: DialectSQLite, sql: "a = ? AND b = ?", args: []any{1, 2}},
{name: "sqlite surplus arg", dialect: DialectSQLite, sql: "a = ?", args: []any{1, 2}, wantErr: true},
{name: "mysql missing arg", dialect: DialectMySQL, sql: "a = ? AND b = ?", args: []any{1}, wantErr: true},
{name: "postgres match with offset", dialect: DialectPostgres, sql: "a = $3 AND b = $4", args: []any{1, 2}, offset: 2},
{name: "postgres surplus arg", dialect: DialectPostgres, sql: "a = $1", args: []any{1, 2}, wantErr: true},
{name: "postgres below offset", dialect: DialectPostgres, sql: "a = $1", args: []any{1}, offset: 1, wantErr: true},
{name: "postgres beyond args", dialect: DialectPostgres, sql: "a = $3", args: []any{1}, offset: 1, wantErr: true},
{name: "postgres repeated", dialect: DialectPostgres, sql: "a = $1 AND b = $1", args: []any{1, 2}, wantErr: true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
err := checkPlaceholderParity(tc.sql, tc.args, tc.dialect, tc.offset)
if tc.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
func expectedPlaceholders(dialect DialectName, offset, count int) []string {
placeholders := []string{}
for i := 1; i <= count; i++ {
if dialect == DialectPostgres {
placeholders = append(placeholders, fmt.Sprintf("$%d", offset+i))
} else {
placeholders = append(placeholders, "?")
}
}
return placeholders
}
func placeholdersIn(dialect DialectName, sql string) []string {
if dialect == DialectPostgres {
matches := postgresPlaceholderPattern.FindAllString(sql, -1)
if matches == nil {
return []string{}
}
return matches
}
return expectedPlaceholders(dialect, 0, strings.Count(sql, "?"))
}