diff --git a/builder/op.go b/builder/op.go index a728acd..2748fdf 100644 --- a/builder/op.go +++ b/builder/op.go @@ -124,7 +124,16 @@ func (u unaryExp) WriteSQL(sb *SQLBuilder) { sb.WriteString(u.prefix) sb.WriteRune(' ') } + + _, needsParens := u.exp.(junctionExp) + if needsParens { + sb.WriteRune('(') + } u.exp.WriteSQL(sb) + if needsParens { + sb.WriteRune(')') + } + if u.suffix != "" { sb.WriteRune(' ') sb.WriteString(u.suffix) diff --git a/root.go b/root.go index 2b2d93f..056e555 100644 --- a/root.go +++ b/root.go @@ -65,6 +65,10 @@ func Or(exps ...builder.Exp) builder.Exp { return builder.Or(exps...) } +func Not(exp builder.Exp) builder.Exp { + return builder.Not(exp) +} + func Case(exp ...builder.Exp) builder.CaseBuilder { return builder.Case(exp...) } diff --git a/select_builder_test.go b/select_builder_test.go index b957533..efd3e0d 100644 --- a/select_builder_test.go +++ b/select_builder_test.go @@ -858,6 +858,45 @@ func TestSelectBuilder_Where(t *testing.T) { q, ) }) + + t.Run("where with negated junction", func(t *testing.T) { + q := qrb.Select(qrb.N("*")). + From(qrb.N("accounts")). + Where(qrb.Not(qrb.And( + qrb.N("is_active").Eq(qrb.Bool(true)), + qrb.N("username").Eq(qrb.Arg("admin")), + ))) + + testhelper.AssertSQLWriterEquals( + t, + ` + SELECT * + FROM accounts + WHERE NOT (is_active = true AND username = $1) + `, + []any{"admin"}, + q, + ) + }) + + t.Run("where with negated comparison", func(t *testing.T) { + q := qrb.Select(qrb.N("*")). + From(qrb.N("accounts")). + Where(qrb.Not( + qrb.N("is_active").Eq(qrb.Bool(true)), + )) + + testhelper.AssertSQLWriterEquals( + t, + ` + SELECT * + FROM accounts + WHERE NOT is_active = true + `, + nil, + q, + ) + }) } func TestSelectBuilder_GroupBy(t *testing.T) {