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

Add limit and offset to Builder/Assembler with some fixes #284

Merged
merged 6 commits into from
Oct 8, 2018
Merged
Show file tree
Hide file tree
Changes from 4 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
21 changes: 20 additions & 1 deletion spec/granite/query/assembler/postgresql_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ require "../spec_helper"
query.raw_sql.should match ignore_whitespace sql

assembler = query.assembler
assembler.build_where
assembler.where
assembler.numbered_parameters.should eq ["bob", "23"]
end
end
Expand All @@ -41,5 +41,24 @@ require "../spec_helper"
builder.order(id: :asc).raw_sql.should match ignore_whitespace sql
end
end

context "offset" do
it "adds offset for select query" do
sql = "select #{query_fields} from table order by id desc offset 8"
builder.offset(8).raw_sql.should match ignore_whitespace sql
end

it "adds offset for first query" do
sql = "select #{query_fields} from table order by id desc limit 1 offset 3"
builder.offset(3).assembler.first.raw_sql.should match ignore_whitespace sql
end
end

context "limit" do
it "adds limit for select query" do
sql = "select #{query_fields} from table order by id desc limit 5"
builder.limit(5).raw_sql.should match ignore_whitespace sql
end
end
end
{% end %}
10 changes: 10 additions & 0 deletions spec/granite/query/builder_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,14 @@ describe Granite::Query::Builder(Model) do
]
query.order_fields.should eq expected
end

it "stores limit" do
query = builder.limit(7)
query.limit.should eq 7
end

it "stores offset" do
query = builder.offset(17)
query.offset.should eq 17
end
end
18 changes: 18 additions & 0 deletions src/granite/query/assemblers/base.cr
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,26 @@ module Granite::Query::Assembler
[Model.fields].flatten.join ", "
end

def build_sql
builder = SQLBuilder.new
yield builder
builder.build
end
robacarp marked this conversation as resolved.
Show resolved Hide resolved

abstract def count : Int64
abstract def first(n : Int32 = 1) : Array(Model)
abstract def delete

class SQLBuilder
@clauses = [] of String

def <<(clause)
clause.try { |c| @clauses << c }
end

def build
@clauses.join " "
end
end
Copy link
Member

Choose a reason for hiding this comment

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

This is a clever shortcut, but I'm not sure it needs to be a class. Why not just implement this in bulid_sql?

def build_sql(&block)
  clauses = [] of String
  yield clauses
  clauses.compact.join ' '
end

end
end
100 changes: 59 additions & 41 deletions src/granite/query/assemblers/postgresql.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@
# This will likely require adapter specific subclassing :[.
module Granite::Query::Assembler
class Postgresql(Model) < Base(Model)
def build_where
@where : String?
@order : String?
@limit : String?
@offset : String?
@group_by : String?

def where
return @where if @where
Copy link
Member

Choose a reason for hiding this comment

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

This code isn't fresh enough in my mind to remember why I had build_* methods instead, but assuming there isn't some DSL caveat I think this is a fine refactor.


clauses = @query.where_fields.map do |field, value|
add_aggregate_field field

Expand All @@ -14,19 +22,21 @@ module Granite::Query::Assembler
end
end

return "" if clauses.none?
return nil if clauses.none?

"WHERE #{clauses.join " AND "}"
@where = "WHERE #{clauses.join " AND "}"
end

def build_order(use_default_order = true)
def order(use_default_order = true)
return @order if @order

order_fields = @query.order_fields

if order_fields.none?
if use_default_order
order_fields = default_order
else
return ""
return nil
end
end

Expand All @@ -40,7 +50,19 @@ module Granite::Query::Assembler
end
end

"ORDER BY #{order_clauses.join ", "}"
@order = "ORDER BY #{order_clauses.join ", "}"
end

def limit
@limit ||= if limit = @query.limit
"LIMIT #{limit}"
end
end

def offset
@offset ||= if offset = @query.offset
"OFFSET #{offset}"
end
end

def log(*stuff)
Expand All @@ -50,48 +72,42 @@ module Granite::Query::Assembler
[{field: Model.primary_name, direction: "ASC"}]
end

def build_group_by
if @aggregate_fields.any?
"GROUP BY #{@aggregate_fields.join ", "}"
else
""
end
def group_by
@group_by ||= if @aggregate_fields.any?
"GROUP BY #{@aggregate_fields.join ", "}"
end
end

def count : Executor::Value(Model, Int64)
where = build_where
order = build_order(false)
group = build_group_by

sql = <<-SQL
SELECT COUNT(*)
FROM #{table_name}
#{where}
#{group}
#{order}
SQL
sql = build_sql do |s|
s << "SELECT COUNT(*)"
s << "FROM #{table_name}"
s << where if where
Copy link
Member

Choose a reason for hiding this comment

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

because of the logic in build_sql, can these if suffixes be dropped?

s << group_by if group_by
s << order if order
end

Executor::Value(Model, Int64).new sql, numbered_parameters, default: 0_i64
end

def first(n : Int32 = 1) : Executor::List(Model)
sql = <<-SQL
SELECT #{field_list}
FROM #{table_name}
#{build_where}
#{build_order}
LIMIT #{n}
SQL
sql = build_sql do |s|
s << "SELECT #{field_list}"
s << "FROM #{table_name}"
s << where if where
s << order if order
s << "LIMIT #{n}"
s << offset if offset
end

Executor::List(Model).new sql, numbered_parameters
end

def delete
sql = <<-SQL
DELETE
FROM #{table_name}
#{build_where}
SQL
sql = build_sql do |s|
s << "DELETE FROM #{table_name}"
s << where if where
end

log sql, numbered_parameters
Model.adapter.open do |db|
Expand All @@ -100,12 +116,14 @@ module Granite::Query::Assembler
end

def select
sql = <<-SQL
SELECT #{field_list}
FROM #{table_name}
#{build_where}
#{build_order}
SQL
sql = build_sql do |s|
s << "SELECT #{field_list}"
s << "FROM #{table_name}"
s << where if where
s << order if order
s << limit if limit
s << offset if offset
end

Executor::List(Model).new sql, numbered_parameters
end
Expand Down
28 changes: 24 additions & 4 deletions src/granite/query/builder.cr
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ class Granite::Query::Builder(Model)
Descending
end

getter where_fields
getter order_fields
getter where_fields = {} of FieldName => FieldData
getter order_fields = [] of NamedTuple(field: String, direction: Sort)
getter offset : Int64?
getter limit : Int64?

def initialize(@boolean_operator = :and)
@where_fields = {} of FieldName => FieldData
@order_fields = [] of NamedTuple(field: String, direction: Sort)
end

def assembler
Expand All @@ -40,6 +40,10 @@ class Granite::Query::Builder(Model)
end

def where(**matches)
where(matches)
end

def where(matches)
matches.each do |field, data|
@where_fields[field.to_s] = data
end
Expand All @@ -62,6 +66,10 @@ class Granite::Query::Builder(Model)
end

def order(**dsl)
order(dsl)
end

def order(dsl)
dsl.each do |field, dsl_direction|
direction = Sort::Ascending

Expand All @@ -75,6 +83,18 @@ class Granite::Query::Builder(Model)
self
end

def offset(num)
@offset = num.to_i64

self
end

def limit(num)
@limit = num.to_i64

self
end

def raw_sql
assembler.select.raw_sql
end
Expand Down
20 changes: 3 additions & 17 deletions src/granite/query/builder_methods.cr
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
# DSL to be included into a model
# DSL to be extended into a model
# To activate, simply
#
# class Model < Granite::Base
# include Query::BuilderMethods
# extend Query::BuilderMethods
# end
module Granite::Query::BuilderMethods
def __builder
Builder(self).new
end

def count : Executor::Value(self, Int64)
__builder.count
end

def where(**match_data) : Builder
__builder.where **match_data
end

def first : self?
__builder.first
end

def first(n : Int32) : Executor::List(self)
__builder.first n
end
delegate where, count, order, offset, limit, first, to: __builder
end