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 qualifed projections to schemas & update dataset#select calls #373

Merged
merged 1 commit into from
Apr 14, 2020
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
19 changes: 17 additions & 2 deletions lib/rom/sql/attribute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,21 @@ def qualified(table_alias = nil)
end
end

# Return a new attribute that is aliased and marked as qualified
#
# Intended to be used when passing attributes to `dataset#select`
#
# @return [SQL::Attribute]
#
# @api public
def qualified_projection(table_alias = nil)
if aliased?
qualified(table_alias).aliased(self.alias)
else
qualified(table_alias)
end
end

# Return a new attribute marked as joined
#
# Whenever you join two schemas, the right schema's attribute
Expand Down Expand Up @@ -285,11 +300,11 @@ def sql_literal(ds)
# @api private
def to_sql_name
@_to_sql_name ||=
if qualified? && aliased?
if qualified? && aliased_projection?
Sequel.qualify(table_name, name).as(self.alias)
elsif qualified?
Sequel.qualify(table_name, name)
elsif aliased?
elsif aliased_projection?
Sequel.as(name, self.alias)
else
Sequel[name]
Expand Down
28 changes: 28 additions & 0 deletions lib/rom/sql/attribute_aliasing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,34 @@ def aliased(alias_name)
end
alias as aliased


# Return true if this attribute is an aliased projection
#
# @example
# class Tasks < ROM::Relation[:memory]
# schema do
# attribute :user_id, Types::Integer, alias: :id
# attribute :name, Types::String
# end
# end
#
# Users.schema[:user_id].aliased?
# # => true
# Users.schema[:user_id].aliased_projection?
# # => false
#
# Users.schema[:user_id].qualified_projection.aliased?
# # => true
# Users.schema[:user_id].qualified_projection.aliased_projection?
# # => true
#
# @return [TrueClass,FalseClass]
#
# @api private
def aliased_projection?
self.meta[:sql_expr].is_a?(Sequel::SQL::AliasedExpression)
end

private

# @api private
Expand Down
2 changes: 1 addition & 1 deletion lib/rom/sql/extensions/postgres/commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module Core
#
# @api private
def returning_dataset
relation.dataset.returning(*relation.qualified_columns)
relation.dataset.returning(*relation.schema.qualified_projection)
end
end

Expand Down
9 changes: 9 additions & 0 deletions lib/rom/sql/function.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ def qualified(table_alias = nil)
)
end

# @see Attribute#qualified_projection
#
# @api private
def qualified_projection(table_alias = nil)
meta(
func: ::Sequel::SQL::Function.new(func.name, *func.args.map { |arg| arg.respond_to?(:qualified_projection) ? arg.qualified_projection(table_alias) : arg })
)
end

# @see Attribute#qualified?
#
# @api private
Expand Down
2 changes: 1 addition & 1 deletion lib/rom/sql/relation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class Relation < ROM::Relation
table = opts[:from].first

if db.table_exists?(table)
select(*schema.map(&:qualified)).order(*schema.project(*schema.primary_key_names).qualified)
select(*schema.qualified_projection).order(*schema.project(*schema.primary_key_names).qualified)
else
self
end
Expand Down
14 changes: 13 additions & 1 deletion lib/rom/sql/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ def qualified(table_alias = nil)
new(map { |attr| attr.qualified(table_alias) })
end

# Return a new schema with attributes that are aliased
# and marked as qualified
#
# Intended to be used when passing attributes to `dataset#select`
#
# @return [Schema]
#
# @api public
def qualified_projection(table_alias = nil)
new(map { |attr| attr.qualified_projection(table_alias) })
end

# Project a schema
#
# @see ROM::Schema#project
Expand Down Expand Up @@ -129,7 +141,7 @@ def joined
#
# @api public
def call(relation)
relation.new(relation.dataset.select(*self), schema: self)
relation.new(relation.dataset.select(*self.qualified_projection), schema: self)
end

# Return an empty schema
Expand Down
17 changes: 17 additions & 0 deletions spec/unit/relation/inner_join_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@
])
end

it 'joins relations using inner join and attributes with alias set' do
relation.insert id: 3, name: 'Jade'
user_id = users[:id].with(alias: :key)
id = tasks[:user_id].with(alias: :user_key)

result = relation
.inner_join(:tasks, user_id => id)
.select(:name, tasks[:title])

expect(result.schema.map(&:name)).to eql(%i[name title])

expect(result.to_a).to eql([
{ name: 'Jane', title: "Jane's task" },
{ name: 'Joe', title: "Joe's task" }
])
end

it 'allows specifying table_aliases' do
relation.insert id: 3, name: 'Jade'

Expand Down
19 changes: 19 additions & 0 deletions spec/unit/relation/order_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
to eql([{ id: 3, name: 'Jade' }, { id: 1, name: 'Jane' }, { id: 2, name: 'Joe' }])
end

it 'orders by provided attributes with alias set' do
attribs = [relation.schema[:name].with(alias: :user_name), :id]
ordered = relation.order(*attribs)

expect(ordered.to_a).
to eql([{ id: 3, name: 'Jade' }, { id: 1, name: 'Jane' }, { id: 2, name: 'Joe' }])
end

it 'orders by provided attribute using a block' do
ordered = relation.
qualified.
Expand All @@ -26,6 +34,17 @@
to eql([{ id: 2, name: 'Joe' }, { id: 1, name: 'Jane' }, { id: 3, name: 'Jade' }])
end

it 'orders by provided attribute when aliased using a block' do
ordered = relation.
qualified.
rename(name: :user_name).
select(:id, :name).
order { name.qualified.desc }

expect(ordered.to_a).
to eql([{ id: 2, user_name: 'Joe' }, { id: 1, user_name: 'Jane' }, { id: 3, user_name: 'Jade' }])
end

it 'orders by provided attribute from another relation' do
ordered = relation.
select(:id).
Expand Down