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

Added Avram::Join::Raw. #1050

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
56 changes: 56 additions & 0 deletions src/avram/join.cr
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,60 @@ module Avram::Join
"FULL"
end
end

class Raw
@clause : String

def self.new(statement : String, *bind_vars)
new(statement, args: bind_vars.to_a)
end

def initialize(statement : String, *, args bind_vars : Array)
ensure_enough_bind_variables_for!(statement, bind_vars)
@clause = build_clause(statement, bind_vars)
end

def prepare(placeholder_supplier : Proc(String)) : String
@clause
end

def to_sql : String
@clause
end

def clone : self
self
end

private def ensure_enough_bind_variables_for!(statement, bind_vars)
bindings = statement.chars.select(&.== '?')
if bindings.size != bind_vars.size
raise "wrong number of bind variables (#{bind_vars.size} for #{bindings.size}) in #{statement}"
end
end

private def build_clause(statement, bind_vars)
bind_vars.each do |arg|
encoded_arg = prepare_for_execution(arg)
statement = statement.sub('?', encoded_arg)
end
statement
end

private def prepare_for_execution(value)
if value.is_a?(Array)
"'#{PQ::Param.encode_array(value)}'"
else
escape_if_needed(value)
end
end

private def escape_if_needed(value)
if value.is_a?(String) || value.is_a?(Slice(UInt8))
PG::EscapeHelper.escape_literal(value)
else
value
end
end
end
end
10 changes: 10 additions & 0 deletions src/avram/query_builder.cr
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Avram::QueryBuilder
@offset : Int32?
@wheres = [] of Avram::Where::Condition
@joins = [] of Avram::Join::SqlClause
@raw_joins = [] of Avram::Join::Raw
@orders = [] of Avram::OrderByClause
@groups = [] of ColumnName
@selections : String = "*"
Expand Down Expand Up @@ -318,6 +319,15 @@ class Avram::QueryBuilder
@joins.uniq(&.to_sql)
end

def join(raw_join_clause : Avram::Join::Raw) : self
@raw_joins << raw_join_clause
self
end

def joins : Array(Avram::Join::Raw)
Copy link
Member

Choose a reason for hiding this comment

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

this method would override the methods on 318 of the same name

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this method would override the methods on 318 of the same name

I thought override is not so accurately, because the return value of both method is different, the issue is, when joins_sql invoke joins metrhod, it can't distinguish the difference between the two method.

I have no idea about how to fix this, could you please give me a little clue?

Copy link
Contributor

Choose a reason for hiding this comment

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

Crystal does not factor in return type when determining a method's signature, at least for overrides. There is no way during method call to determine which of the variants to dispatch to if they only differ in return types. So in this case, yes, the second method will override the first.

How about renaming the method to #raw_joins?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Crystal does not factor in return type when determining a method's signature, at least for overrides. There is no way during method call to determine which of the variants to dispatch to if they only differ in return types.

I guess it's can?

	def hello : String
	  "hello"
	end

	def hello : Int32
	  100
	end

	def say_hello : Int32
	  hello
	end

	p! say_hello # => 100

How about renaming the method to #raw_joins?

I remember i tried it like this, but that #raw_joins method never by invoked some where, there are some(probably macro?) magic happen some where, i didn't found it.

@raw_joins.uniq(&.to_sql)
end

private def joins_sql : String
joins.join(" ", &.to_sql)
end
Expand Down
8 changes: 8 additions & 0 deletions src/avram/queryable.cr
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ module Avram::Queryable(T)
clone.tap &.query.join(join_clause)
end

def join(statement : String, *bind_vars) : self
join(statement, args: bind_vars.to_a)
end

def join(statement : String, *, args bind_vars : Array) : self
clone.tap &.query.join(Avram::Join::Raw.new(statement, args: bind_vars))
end

def where(column : Symbol, value) : self
clone.tap &.query.where(Avram::Where::Equal.new(column, value.to_s))
end
Expand Down
Loading