Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds 2 new JSON critera query methods #1016

Merged
merged 4 commits into from
Mar 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions spec/avram/json_criteria_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,34 @@ describe JSON::Any::Lucky::Criteria do
preferences.not.has_all_keys(["theme", "style"]).to_sql.should eq ["SELECT #{QueryMe::COLUMN_SQL} FROM users WHERE NOT(users.preferences ?& $1)", ["theme", "style"]]
end
end

describe "includes" do
it "@>" do
json = JSON::Any.new({"theme" => JSON::Any.new("dark")})
critera = preferences.includes(json)
critera.to_sql.should eq ["SELECT #{QueryMe::COLUMN_SQL} FROM users WHERE users.preferences @> $1", "{\"theme\":\"dark\"}"]
end

it "negates with NOT()" do
json = JSON::Any.new({"theme" => JSON::Any.new("dark")})
critera = preferences.not.includes(json)
critera.to_sql.should eq ["SELECT #{QueryMe::COLUMN_SQL} FROM users WHERE NOT(users.preferences @> $1)", "{\"theme\":\"dark\"}"]
end
end

describe "in" do
it "<@" do
json = JSON::Any.new({"theme" => JSON::Any.new("dark"), "style" => JSON::Any.new("cyberpunk"), "version" => JSON::Any.new(2i64)})
critera = preferences.in(json)
critera.to_sql.should eq ["SELECT #{QueryMe::COLUMN_SQL} FROM users WHERE users.preferences <@ $1", "{\"theme\":\"dark\",\"style\":\"cyberpunk\",\"version\":2}"]
end

it "negates with NOT()" do
json = JSON::Any.new({"theme" => JSON::Any.new("dark"), "style" => JSON::Any.new("cyberpunk"), "version" => JSON::Any.new(2i64)})
critera = preferences.not.in(json)
critera.to_sql.should eq ["SELECT #{QueryMe::COLUMN_SQL} FROM users WHERE NOT(users.preferences <@ $1)", "{\"theme\":\"dark\",\"style\":\"cyberpunk\",\"version\":2}"]
end
end
end

private def preferences
Expand Down
16 changes: 13 additions & 3 deletions src/avram/charms/json_extensions.cr
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,27 @@ struct JSON::Any
class Criteria(T, V) < Avram::Criteria(T, V)
# performs `WHERE jsonb ? string`
def has_key(value : String) : T
add_clause(Avram::Where::JSONHasKey.new(column, value))
add_clause(Avram::Where::JsonbHasKey.new(column, value))
end

# performs `WHERE jsonb ?| array`
def has_any_keys(keys : Array(String)) : T
add_clause(Avram::Where::JSONHasAnyKeys.new(column, keys))
add_clause(Avram::Where::JsonbHasAnyKeys.new(column, keys))
end

# performs `WHERE jsonb ?& array`
def has_all_keys(keys : Array(String)) : T
add_clause(Avram::Where::JSONHasAllKeys.new(column, keys))
add_clause(Avram::Where::JsonbHasAllKeys.new(column, keys))
end

# performs `WHERE jsonb @> other_json`
def includes(value) : T
add_clause(Avram::Where::JsonbIncludes.new(column, value.to_json))
end

# performs `WHERE jsonb <@ other_json`
jwoertink marked this conversation as resolved.
Show resolved Hide resolved
def in(value) : T
add_clause(Avram::Where::JsonbIn.new(column, value.to_json))
end
end
end
Expand Down
84 changes: 66 additions & 18 deletions src/avram/where.cr
Original file line number Diff line number Diff line change
Expand Up @@ -245,71 +245,119 @@ module Avram::Where
end
end

class JSONHasKey < ValueHoldingSqlClause
class JsonbHasKey < ValueHoldingSqlClause
def operator : String
"?"
end

def negated : NotJSONHasKey
NotJSONHasKey.new(column, value)
def negated : NotJsonbHasKey
NotJsonbHasKey.new(column, value)
end
end

class NotJSONHasKey < ValueHoldingSqlClause
class NotJsonbHasKey < ValueHoldingSqlClause
def operator : String
"?"
end

def negated : JSONHasKey
JSONHasKey.new(column, value)
def negated : JsonbHasKey
JsonbHasKey.new(column, value)
end

def prepare(placeholder_supplier : Proc(String)) : String
"NOT(#{column} #{operator} #{placeholder_supplier.call})"
end
end

class JSONHasAnyKeys < ValueHoldingSqlClause
class JsonbHasAnyKeys < ValueHoldingSqlClause
def operator : String
"?|"
end

def negated : NotJSONHasAnyKeys
NotJSONHasAnyKeys.new(column, value)
def negated : NotJsonbHasAnyKeys
NotJsonbHasAnyKeys.new(column, value)
end
end

class NotJSONHasAnyKeys < ValueHoldingSqlClause
class NotJsonbHasAnyKeys < ValueHoldingSqlClause
def operator : String
"?|"
end

def negated : JSONHasAnyKeys
JSONHasAnyKeys.new(column, value)
def negated : JsonbHasAnyKeys
JsonbHasAnyKeys.new(column, value)
end

def prepare(placeholder_supplier : Proc(String)) : String
"NOT(#{column} #{operator} #{placeholder_supplier.call})"
end
end

class JSONHasAllKeys < ValueHoldingSqlClause
class JsonbHasAllKeys < ValueHoldingSqlClause
def operator : String
"?&"
end

def negated : NotJSONHasAllKeys
NotJSONHasAllKeys.new(column, value)
def negated : NotJsonbHasAllKeys
NotJsonbHasAllKeys.new(column, value)
end
end

class NotJSONHasAllKeys < ValueHoldingSqlClause
class NotJsonbHasAllKeys < ValueHoldingSqlClause
def operator : String
"?&"
end

def negated : JSONHasAllKeys
JSONHasAllKeys.new(column, value)
def negated : JsonbHasAllKeys
JsonbHasAllKeys.new(column, value)
end

def prepare(placeholder_supplier : Proc(String)) : String
"NOT(#{column} #{operator} #{placeholder_supplier.call})"
end
end

class JsonbIncludes < ValueHoldingSqlClause
def operator : String
"@>"
end
Comment on lines +320 to +323
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I was just thinking about... This operator can be used on Arrays too. Ref... If this just became the new Includes then it would work for both Arrays and Jsonb, but that also means that the old Includes using val = ANY(col) would not be an option without some sort of flag or rename or something...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they're equivalent operators, I see no reason not to simplify.


def negated : JsonbExcludes
JsonbExcludes.new(column, value)
end
end

class JsonbExcludes < ValueHoldingSqlClause
def operator : String
"@>"
end

def negated : JsonbIncludes
JsonbIncludes.new(column, value)
end

def prepare(placeholder_supplier : Proc(String)) : String
"NOT(#{column} #{operator} #{placeholder_supplier.call})"
end
end

class JsonbIn < ValueHoldingSqlClause
def operator : String
"<@"
end

def negated : JsonbNotIn
JsonbNotIn.new(column, value)
end
end

class JsonbNotIn < ValueHoldingSqlClause
def operator : String
"<@"
end

def negated : JsonbIn
JsonbIn.new(column, value)
end

def prepare(placeholder_supplier : Proc(String)) : String
Expand Down