diff --git a/pkg/db/db.go b/pkg/db/db.go index 72f69b86..258cb816 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -17,6 +17,7 @@ type Query struct { Relations []string WhereAndList *[]string WhereOrList *[]string + IsList *[]string OrderList *[]string LimitValue int OffsetValue int @@ -147,6 +148,11 @@ func buildQueryURI(q Query) string { output += fmt.Sprintf("&or=(%s)", list) } + if q.IsList != nil && len(*q.IsList) > 0 { + list := strings.Join(*q.IsList, ",") + output += fmt.Sprintf("&%s", list) + } + if q.OrderList != nil && len(*q.OrderList) > 0 { orders := strings.Join(*q.OrderList, ",") output += fmt.Sprintf("&order=%s", orders) diff --git a/pkg/db/db_test.go b/pkg/db/db_test.go index ff130b07..4a033ccd 100644 --- a/pkg/db/db_test.go +++ b/pkg/db/db_test.go @@ -16,12 +16,13 @@ var mockRaidenContext = raiden.Ctx{ type ArticleMockModel struct { ModelBase - Id int64 `json:"id,omitempty" column:"name:id;type:bigint;primaryKey;autoIncrement;nullable:false"` - UserId int64 `json:"user_id,omitempty" column:"name:user_id;type:bigint;nullable:false"` - Title string `json:"title,omitempty" column:"name:title;type:text;nullable:false"` - Body string `json:"body,omitempty" column:"name:body;type:text;nullable:true"` - Rating int64 `json:"rating,omitempty" column:"name:rating;type:bigint;nullable:false"` - CreatedAt time.Time `json:"created_at,omitempty" column:"name:created_at;type:timestampz;nullable:false;default:now()"` + Id int64 `json:"id,omitempty" column:"name:id;type:bigint;primaryKey;autoIncrement;nullable:false"` + UserId int64 `json:"user_id,omitempty" column:"name:user_id;type:bigint;nullable:false"` + Title string `json:"title,omitempty" column:"name:title;type:text;nullable:false"` + Body string `json:"body,omitempty" column:"name:body;type:text;nullable:true"` + Rating int64 `json:"rating,omitempty" column:"name:rating;type:bigint;nullable:false"` + IsFeatured bool `json:"is_featured,omitempty" column:"name:is_featured;type:bool;nullable:false"` + CreatedAt time.Time `json:"created_at,omitempty" column:"name:created_at;type:timestampz;nullable:false;default:now()"` Metadata string `json:"-" schema:"public" tableName:"articles" rlsEnable:"true" rlsForced:"false"` diff --git a/pkg/db/where.go b/pkg/db/where.go index 5f71306f..cb2faed5 100644 --- a/pkg/db/where.go +++ b/pkg/db/where.go @@ -21,6 +21,20 @@ func (q *Query) Eq(column string, value any) *Query { return q } +func (q *Query) NotEq(column string, value any) *Query { + + if q.WhereAndList == nil { + q.WhereAndList = &[]string{} + } + + *q.WhereAndList = append( + *q.WhereAndList, + fmt.Sprintf("%s=not.eq.%s", column, getStringValue(value)), + ) + + return q +} + func (q *Query) OrEq(column string, value any) *Query { if q.WhereOrList == nil { @@ -49,6 +63,20 @@ func (q *Query) Neq(column string, value any) *Query { return q } +func (q *Query) NotNeq(column string, value any) *Query { + + if q.WhereAndList == nil { + q.WhereAndList = &[]string{} + } + + *q.WhereAndList = append( + *q.WhereAndList, + fmt.Sprintf("%s=not.neq.%s", column, getStringValue(value)), + ) + + return q +} + func (q *Query) OrNeq(column string, value any) *Query { if q.WhereOrList == nil { @@ -77,6 +105,20 @@ func (q *Query) Lt(column string, value any) *Query { return q } +func (q *Query) NotLt(column string, value any) *Query { + + if q.WhereAndList == nil { + q.WhereAndList = &[]string{} + } + + *q.WhereAndList = append( + *q.WhereAndList, + fmt.Sprintf("%s=not.lt.%s", column, getStringValue(value)), + ) + + return q +} + func (q *Query) OrLt(column string, value any) *Query { if q.WhereOrList == nil { @@ -105,6 +147,20 @@ func (q *Query) Lte(column string, value any) *Query { return q } +func (q *Query) NotLte(column string, value any) *Query { + + if q.WhereAndList == nil { + q.WhereAndList = &[]string{} + } + + *q.WhereAndList = append( + *q.WhereAndList, + fmt.Sprintf("%s=not.lte.%s", column, getStringValue(value)), + ) + + return q +} + func (q *Query) OrLte(column string, value any) *Query { if q.WhereOrList == nil { @@ -133,6 +189,20 @@ func (q *Query) Gt(column string, value int) *Query { return q } +func (q *Query) NotGt(column string, value int) *Query { + + if q.WhereAndList == nil { + q.WhereAndList = &[]string{} + } + + *q.WhereAndList = append( + *q.WhereAndList, + fmt.Sprintf("%s=not.gt.%s", column, getStringValue(value)), + ) + + return q +} + func (q *Query) OrGt(column string, value int) *Query { if q.WhereOrList == nil { @@ -161,6 +231,20 @@ func (q *Query) Gte(column string, value any) *Query { return q } +func (q *Query) NotGte(column string, value any) *Query { + + if q.WhereAndList == nil { + q.WhereAndList = &[]string{} + } + + *q.WhereAndList = append( + *q.WhereAndList, + fmt.Sprintf("%s=not.gte.%s", column, getStringValue(value)), + ) + + return q +} + func (q *Query) OrGte(column string, value any) *Query { if q.WhereOrList == nil { @@ -191,6 +275,22 @@ func (q *Query) In(column string, value any) *Query { return q } +func (q *Query) NotIn(column string, value any) *Query { + + if q.WhereAndList == nil { + q.WhereAndList = &[]string{} + } + + strValues := strings.Join(SliceToStringSlice(value), ",") + + *q.WhereAndList = append( + *q.WhereAndList, + fmt.Sprintf("%s=not.in.(%s)", column, strValues), + ) + + return q +} + func (q *Query) OrIn(column string, value any) *Query { if q.WhereOrList == nil { @@ -223,6 +323,22 @@ func (q *Query) Like(column string, value string) *Query { return q } +func (q *Query) NotLike(column string, value string) *Query { + + if q.WhereAndList == nil { + q.WhereAndList = &[]string{} + } + + value = strings.ReplaceAll(value, "%", "*") + + *q.WhereAndList = append( + *q.WhereAndList, + fmt.Sprintf("%s=not.like.%s", column, getStringWithSpace(value)), + ) + + return q +} + func (q *Query) OrLike(column string, value string) *Query { if q.WhereOrList == nil { @@ -255,6 +371,22 @@ func (q *Query) Ilike(column string, value string) *Query { return q } +func (q *Query) NotIlike(column string, value string) *Query { + + if q.WhereAndList == nil { + q.WhereAndList = &[]string{} + } + + value = strings.ReplaceAll(value, "%", "*") + + *q.WhereAndList = append( + *q.WhereAndList, + fmt.Sprintf("%s=not.ilike.%s", column, getStringWithSpace(value)), + ) + + return q +} + func (q *Query) OrIlike(column string, value string) *Query { if q.WhereOrList == nil { @@ -271,6 +403,42 @@ func (q *Query) OrIlike(column string, value string) *Query { return q } +func (q *Query) Is(column string, value any) *Query { + + if getWhitelistIsValue(value) == "" { + panic("getWhitelistIsValue: only \"true\", \"false\", \"nil\", \"null\", or \"unknown\" are allowed") + } + + if q.IsList == nil { + q.IsList = &[]string{} + } + + *q.IsList = append( + *q.IsList, + fmt.Sprintf("%s=is.%s", column, getWhitelistIsValue(value)), + ) + + return q +} + +func (q *Query) NotIs(column string, value any) *Query { + + if getWhitelistIsValue(value) == "" { + panic("getWhitelistIsValue: only \"true\", \"false\", \"nil\", \"null\", or \"unknown\" are allowed") + } + + if q.IsList == nil { + q.IsList = &[]string{} + } + + *q.IsList = append( + *q.IsList, + fmt.Sprintf("%s=not.is.%s", column, getWhitelistIsValue(value)), + ) + + return q +} + func getStringValue(value any) string { return fmt.Sprintf("%v", value) } @@ -304,3 +472,23 @@ func SliceToStringSlice(slice interface{}) []string { } return stringSlice } + +func getWhitelistIsValue(value any) string { + if getStringValue(value) == "true" { + return "true" + } + + if getStringValue(value) == "false" { + return "false" + } + + if value == nil || getStringValue(value) == "null" { + return "null" + } + + if getStringValue(value) == "unknown" { + return "unknown" + } + + return "" +} diff --git a/pkg/db/where_test.go b/pkg/db/where_test.go index 9b7d1d85..ee7e3501 100644 --- a/pkg/db/where_test.go +++ b/pkg/db/where_test.go @@ -16,6 +16,16 @@ func TestEq(t *testing.T) { assert.Equalf(t, "/rest/v1/articles?select=*&id=eq.1", q.GetUrl(), "the url should match") } +func TestNotEq(t *testing.T) { + q := NewQuery(&mockRaidenContext).Model(articleMockModel).NotEq("id", 1) + + if q.WhereAndList == nil { + t.Error("Expected where clause not to be nil") + } + + assert.Equalf(t, "/rest/v1/articles?select=*&id=not.eq.1", q.GetUrl(), "the url should match") +} + func TestOrEq(t *testing.T) { q := NewQuery(&mockRaidenContext).Model(articleMockModel).OrEq("id", 1) @@ -36,6 +46,16 @@ func TestNeq(t *testing.T) { assert.Equalf(t, "/rest/v1/articles?select=*&id=neq.1", q.GetUrl(), "the url should match") } +func TestNotNeq(t *testing.T) { + q := NewQuery(&mockRaidenContext).Model(articleMockModel).NotNeq("id", 1) + + if q.WhereAndList == nil { + t.Error("Expected where clause not to be nil") + } + + assert.Equalf(t, "/rest/v1/articles?select=*&id=not.neq.1", q.GetUrl(), "the url should match") +} + func TestOrNeq(t *testing.T) { q := NewQuery(&mockRaidenContext).Model(articleMockModel).OrNeq("id", 1) @@ -56,6 +76,16 @@ func TestLt(t *testing.T) { assert.Equalf(t, "/rest/v1/articles?select=*&id=lt.1", q.GetUrl(), "the url should match") } +func TestNotLt(t *testing.T) { + q := NewQuery(&mockRaidenContext).Model(articleMockModel).NotLt("id", 1) + + if q.WhereAndList == nil { + t.Error("Expected where clause not to be nil") + } + + assert.Equalf(t, "/rest/v1/articles?select=*&id=not.lt.1", q.GetUrl(), "the url should match") +} + func TestOrLt(t *testing.T) { q := NewQuery(&mockRaidenContext).Model(articleMockModel).OrLt("id", 1) @@ -76,6 +106,16 @@ func TestLte(t *testing.T) { assert.Equalf(t, "/rest/v1/articles?select=*&id=lte.1", q.GetUrl(), "the url should match") } +func TestNotLte(t *testing.T) { + q := NewQuery(&mockRaidenContext).Model(articleMockModel).NotLte("id", 1) + + if q.WhereAndList == nil { + t.Error("Expected where clause not to be nil") + } + + assert.Equalf(t, "/rest/v1/articles?select=*&id=not.lte.1", q.GetUrl(), "the url should match") +} + func TestOrLte(t *testing.T) { q := NewQuery(&mockRaidenContext).Model(articleMockModel).OrLte("id", 1) @@ -96,6 +136,16 @@ func TestGt(t *testing.T) { assert.Equalf(t, "/rest/v1/articles?select=*&id=gt.1", q.GetUrl(), "the url should match") } +func TestNotGt(t *testing.T) { + q := NewQuery(&mockRaidenContext).Model(articleMockModel).NotGt("id", 1) + + if q.WhereAndList == nil { + t.Error("Expected where clause not to be nil") + } + + assert.Equalf(t, "/rest/v1/articles?select=*&id=not.gt.1", q.GetUrl(), "the url should match") +} + func TestOrGt(t *testing.T) { q := NewQuery(&mockRaidenContext).Model(articleMockModel).OrGt("id", 1) @@ -116,6 +166,16 @@ func TestGte(t *testing.T) { assert.Equalf(t, "/rest/v1/articles?select=*&id=gte.1", q.GetUrl(), "the url should match") } +func TestNotGte(t *testing.T) { + q := NewQuery(&mockRaidenContext).Model(articleMockModel).NotGte("id", 1) + + if q.WhereAndList == nil { + t.Error("Expected where clause not to be nil") + } + + assert.Equalf(t, "/rest/v1/articles?select=*&id=not.gte.1", q.GetUrl(), "the url should match") +} + func TestOrGte(t *testing.T) { q := NewQuery(&mockRaidenContext).Model(articleMockModel).OrGte("id", 1) @@ -176,6 +236,16 @@ func TestIn(t *testing.T) { assert.Equalf(t, "/rest/v1/articles?select=*&is_allowed=in.(true)", q.GetUrl(), "the url should match") }) + + t.Run("where not in", func(t *testing.T) { + q := NewQuery(&mockRaidenContext).Model(articleMockModel).NotIn("id", []uint{1, 2, 3}) + + if q.WhereAndList == nil { + t.Error("Expected where clause not to be nil") + } + + assert.Equalf(t, "/rest/v1/articles?select=*&id=not.in.(1,2,3)", q.GetUrl(), "the url should match") + }) } func TestOrIn(t *testing.T) { @@ -240,6 +310,16 @@ func TestLike(t *testing.T) { assert.Equalf(t, "/rest/v1/articles?select=*&name=like.*supa*", q.GetUrl(), "the url should match") } +func TestNotLike(t *testing.T) { + q := NewQuery(&mockRaidenContext).Model(articleMockModel).NotLike("name", "%supa%") + + if q.WhereAndList == nil { + t.Error("Expected where clause not to be nil") + } + + assert.Equalf(t, "/rest/v1/articles?select=*&name=not.like.*supa*", q.GetUrl(), "the url should match") +} + func TestOrLike(t *testing.T) { q := NewQuery(&mockRaidenContext).Model(articleMockModel).OrLike("name", "%supa%") @@ -260,6 +340,16 @@ func TestIlike(t *testing.T) { assert.Equalf(t, "/rest/v1/articles?select=*&name=ilike.*supa*", q.GetUrl(), "the url should match") } +func TestNotIlike(t *testing.T) { + q := NewQuery(&mockRaidenContext).Model(articleMockModel).NotIlike("name", "%supa%") + + if q.WhereAndList == nil { + t.Error("Expected where clause not to be nil") + } + + assert.Equalf(t, "/rest/v1/articles?select=*&name=not.ilike.*supa*", q.GetUrl(), "the url should match") +} + func TestOrIlike(t *testing.T) { q := NewQuery(&mockRaidenContext).Model(articleMockModel).OrIlike("name", "%supa%") @@ -269,3 +359,87 @@ func TestOrIlike(t *testing.T) { assert.Equalf(t, "/rest/v1/articles?select=*&or=(name.ilike.*supa*)", q.GetUrl(), "the url should match") } + +func TestIs(t *testing.T) { + t.Run("is true", func(t *testing.T) { + q := NewQuery(&mockRaidenContext).Model(articleMockModel).Is("is_featured", true) + + if q.IsList == nil { + t.Error("Expected where clause not to be nil") + } + + assert.Equalf(t, "/rest/v1/articles?select=*&is_featured=is.true", q.GetUrl(), "the url should match") + }) + + t.Run("is false", func(t *testing.T) { + q := NewQuery(&mockRaidenContext).Model(articleMockModel).Is("is_featured", false) + + if q.IsList == nil { + t.Error("Expected where clause not to be nil") + } + + assert.Equalf(t, "/rest/v1/articles?select=*&is_featured=is.false", q.GetUrl(), "the url should match") + }) + + t.Run("is null", func(t *testing.T) { + q := NewQuery(&mockRaidenContext).Model(articleMockModel).Is("is_featured", nil) + + if q.IsList == nil { + t.Error("Expected where clause not to be nil") + } + + assert.Equalf(t, "/rest/v1/articles?select=*&is_featured=is.null", q.GetUrl(), "the url should match") + }) + + t.Run("is unknown", func(t *testing.T) { + q := NewQuery(&mockRaidenContext).Model(articleMockModel).Is("is_featured", "unknown") + + if q.IsList == nil { + t.Error("Expected where clause not to be nil") + } + + assert.Equalf(t, "/rest/v1/articles?select=*&is_featured=is.unknown", q.GetUrl(), "the url should match") + }) +} + +func TestNotIs(t *testing.T) { + t.Run("not is true", func(t *testing.T) { + q := NewQuery(&mockRaidenContext).Model(articleMockModel).NotIs("is_featured", true) + + if q.IsList == nil { + t.Error("Expected where clause not to be nil") + } + + assert.Equalf(t, "/rest/v1/articles?select=*&is_featured=not.is.true", q.GetUrl(), "the url should match") + }) + + t.Run("not is false", func(t *testing.T) { + q := NewQuery(&mockRaidenContext).Model(articleMockModel).NotIs("is_featured", false) + + if q.IsList == nil { + t.Error("Expected where clause not to be nil") + } + + assert.Equalf(t, "/rest/v1/articles?select=*&is_featured=not.is.false", q.GetUrl(), "the url should match") + }) + + t.Run("not is null", func(t *testing.T) { + q := NewQuery(&mockRaidenContext).Model(articleMockModel).NotIs("is_featured", nil) + + if q.IsList == nil { + t.Error("Expected where clause not to be nil") + } + + assert.Equalf(t, "/rest/v1/articles?select=*&is_featured=not.is.null", q.GetUrl(), "the url should match") + }) + + t.Run("not is unknown", func(t *testing.T) { + q := NewQuery(&mockRaidenContext).Model(articleMockModel).NotIs("is_featured", "unknown") + + if q.IsList == nil { + t.Error("Expected where clause not to be nil") + } + + assert.Equalf(t, "/rest/v1/articles?select=*&is_featured=not.is.unknown", q.GetUrl(), "the url should match") + }) +}