flounder 0.8.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/HISTORY +7 -0
- data/flounder-0.3.0.gem +0 -0
- data/flounder.gemspec +2 -2
- data/lib/flounder/connection.rb +38 -10
- data/lib/flounder/connection_pool.rb +11 -2
- data/lib/flounder/domain.rb +19 -0
- data/lib/flounder/engine.rb +8 -0
- data/lib/flounder/entity.rb +55 -53
- data/lib/flounder/exceptions.rb +8 -0
- data/lib/flounder/field.rb +10 -2
- data/lib/flounder/query/base.rb +157 -0
- data/lib/flounder/{immediate.rb → query/immediate.rb} +1 -1
- data/lib/flounder/query/insert.rb +41 -0
- data/lib/flounder/query/returning.rb +19 -0
- data/lib/flounder/{query.rb → query/select.rb} +23 -106
- data/lib/flounder/query/update.rb +45 -0
- data/lib/flounder/symbol_extensions.rb +3 -0
- data/lib/flounder.rb +6 -4
- data/qed/applique/ae.rb +1 -1
- data/qed/atomic_insert_update.md +33 -0
- data/qed/conditions.md +12 -6
- data/qed/exceptions.md +22 -1
- data/qed/index.md +37 -3
- data/qed/inserts.md +5 -5
- data/qed/ordering.md +21 -14
- data/qed/projection.md +1 -0
- data/qed/updates.md +15 -10
- metadata +14 -11
- data/Gemfile.lock +0 -33
- data/lib/flounder/insert.rb +0 -73
- data/lib/flounder/update.rb +0 -114
- data/qed/insdate.md +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97e8380edd41a2232f500fc61ceb3f49e2dd3211
|
4
|
+
data.tar.gz: e0330758a5aac5eba5d509cdffa778f0dca41735
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c17571110ffbe588b794f026e5eddfc244bd479c37d1a09e4f01639caa4e6031bbb82a1ff5512a616fc4c0069cab4c0165eeddcd5a39535275376c8e8dcf5509
|
7
|
+
data.tar.gz: 76e6e016a4ac6bcaa9f2c8679909a63cd1564efeed5b5ce970d6a41e845cdc77d43c09cc188e0fbd3e367bff6725c382fae2fa223b07b15ce25c15947766417e
|
data/HISTORY
ADDED
data/flounder-0.3.0.gem
ADDED
Binary file
|
data/flounder.gemspec
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = "flounder"
|
5
|
-
s.version = '0.
|
5
|
+
s.version = '0.9.0'
|
6
6
|
s.summary = "Flounder is a way to write SQL simply in Ruby. It deals with everything BUT object relational mapping. "
|
7
7
|
s.email = "kaspar.schiess@technologyastronauts.ch"
|
8
|
-
s.homepage = "https://bitbucket.org/technologyastronauts/
|
8
|
+
s.homepage = "https://bitbucket.org/technologyastronauts/oss_flounder"
|
9
9
|
s.authors = ['Kaspar Schiess', 'Florian Hanke']
|
10
10
|
|
11
11
|
# s.description = <<-EOF
|
data/lib/flounder/connection.rb
CHANGED
@@ -6,11 +6,14 @@ module Flounder
|
|
6
6
|
|
7
7
|
def initialize pg_conn_args
|
8
8
|
@pg = PG.connect(*pg_conn_args)
|
9
|
-
@visitor = Arel::Visitors::
|
9
|
+
@visitor = Arel::Visitors::PostgreSQL.new(self)
|
10
10
|
end
|
11
11
|
|
12
12
|
attr_reader :visitor
|
13
13
|
|
14
|
+
def transaction &block
|
15
|
+
pg.transaction(&block)
|
16
|
+
end
|
14
17
|
def exec *args, &block
|
15
18
|
pg.exec *args, &block
|
16
19
|
end
|
@@ -42,7 +45,6 @@ module Flounder
|
|
42
45
|
|
43
46
|
def primary_key name
|
44
47
|
fail NotImplementedError
|
45
|
-
@primary_keys[name.to_s]
|
46
48
|
end
|
47
49
|
|
48
50
|
def table_exists? table_name
|
@@ -52,7 +54,6 @@ module Flounder
|
|
52
54
|
|
53
55
|
def columns name, message = nil
|
54
56
|
fail NotImplementedError
|
55
|
-
@columns[name.to_s]
|
56
57
|
end
|
57
58
|
|
58
59
|
def quote_table_name name
|
@@ -63,25 +64,51 @@ module Flounder
|
|
63
64
|
pg.quote_ident name.to_s
|
64
65
|
end
|
65
66
|
|
66
|
-
|
67
|
+
def schema_cache
|
67
68
|
self
|
68
69
|
end
|
69
70
|
|
70
71
|
def quote thing, column = nil
|
72
|
+
# require 'pp '
|
71
73
|
# p [:quote, thing, column]
|
74
|
+
# pp caller.first(10)
|
72
75
|
pg.escape_literal(thing.to_s)
|
73
76
|
end
|
74
|
-
|
77
|
+
|
78
|
+
# ------------------------------------------------------------------------
|
79
|
+
|
80
|
+
# Turns a PG result row into a hash-like object. There are some transformation
|
81
|
+
# rules that govern this conversion:
|
82
|
+
#
|
83
|
+
# * All data types are converted to their closest Ruby equivalent
|
84
|
+
# (type conversion)
|
85
|
+
# * Fields from the main entity (the entity that started the select)
|
86
|
+
# are returned on the top level of the hash.
|
87
|
+
# * Fields from joined entities are returned in a subhash stored under the
|
88
|
+
# singular name of the joined entity.
|
89
|
+
#
|
90
|
+
# Example:
|
91
|
+
# row = users.join(posts).on(:id => :user_id).first
|
92
|
+
# row[:id] # refers to users.id, also as row.id
|
93
|
+
# row[:post][:id] # refers to posts.id, also as row.post.id
|
94
|
+
#
|
95
|
+
# row.keys # hash keys of the row, not equal to row[:keys]!
|
96
|
+
#
|
97
|
+
# @param ent [Entity] entity that the query originated from
|
98
|
+
# @param result [PG::Result]
|
99
|
+
# @param row_idx [Fixnum] row we're interested in
|
100
|
+
# @return [Hashie::Mash] result row as hash-like object
|
101
|
+
#
|
75
102
|
def objectify_result_row ent, result, row_idx
|
76
103
|
obj = Hashie::Mash.new
|
77
104
|
|
78
105
|
each_field(ent, result, row_idx) do
|
79
106
|
|entity, name, value, type_oid, binary, idx|
|
80
|
-
# TODO remove entity resolution from each_field?
|
81
107
|
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
108
|
+
# NOTE entity resolution is done both through aliasing and through
|
109
|
+
# postgres column reporting. The above entity variable carries what
|
110
|
+
# postgres reports to us; the block below resolves aliased entity
|
111
|
+
# names:
|
85
112
|
processed_entity, processed_name = yield name if block_given?
|
86
113
|
entity = processed_entity if processed_entity
|
87
114
|
name = processed_name if processed_name
|
@@ -100,8 +127,9 @@ module Flounder
|
|
100
127
|
# The main entity and custom fields (AS something) are available on the
|
101
128
|
# top-level of the result.
|
102
129
|
if !entity || entity == ent
|
103
|
-
|
130
|
+
raise DuplicateField, "#{name.inspect} already defined in result set, aliasing occurs." \
|
104
131
|
if obj.has_key? name
|
132
|
+
|
105
133
|
obj[name] = typecast_value
|
106
134
|
end
|
107
135
|
end
|
@@ -11,18 +11,27 @@ module Flounder
|
|
11
11
|
}
|
12
12
|
end
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
# Checks out a connection from the pool and yields it to the block. The
|
15
|
+
# connection is returned to the pool at the end of the block; don't hold
|
16
|
+
# on to it.
|
17
|
+
#
|
16
18
|
def with_connection
|
17
19
|
@pool.with do |conn|
|
18
20
|
yield conn
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
24
|
+
# Checks out a connection from the pool. You have to return this
|
25
|
+
# connection manually.
|
26
|
+
#
|
22
27
|
def checkout
|
23
28
|
@pool.checkout
|
24
29
|
end
|
25
30
|
|
31
|
+
Spec = Struct.new(:config)
|
32
|
+
|
33
|
+
# This is needed to conform to arels interface.
|
34
|
+
#
|
26
35
|
def spec
|
27
36
|
Spec.new(adapter: 'pg')
|
28
37
|
end
|
data/lib/flounder/domain.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
|
2
|
+
require 'forwardable'
|
2
3
|
require 'logger'
|
3
4
|
|
4
5
|
module Flounder
|
@@ -19,15 +20,33 @@ module Flounder
|
|
19
20
|
|
20
21
|
def initialize connection_pool
|
21
22
|
@connection_pool = connection_pool
|
23
|
+
|
24
|
+
# maps from plural/singular names to entities in this domain
|
22
25
|
@plural = {}
|
23
26
|
@singular = {}
|
27
|
+
|
28
|
+
# maps OIDs of entities to entities
|
24
29
|
@oids_entity_map = {}
|
30
|
+
|
25
31
|
@logger = Logger.new(NilDevice.new)
|
26
32
|
end
|
27
33
|
|
28
34
|
attr_reader :connection_pool
|
29
35
|
attr_accessor :logger
|
30
36
|
|
37
|
+
extend Forwardable
|
38
|
+
def_delegators :connection_pool, :with_connection
|
39
|
+
|
40
|
+
def transaction &block
|
41
|
+
with_connection do |conn|
|
42
|
+
conn.transaction do
|
43
|
+
block.call(conn)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the entity with the given plural name.
|
49
|
+
#
|
31
50
|
def [] name
|
32
51
|
raise NoSuchEntity, "No such entity #{name.inspect} in this domain." \
|
33
52
|
unless @plural.has_key?(name)
|
data/lib/flounder/engine.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
module Flounder
|
2
|
+
|
3
|
+
# Intermediary class that arel wants us to create. Mostly supports the
|
4
|
+
# #connection_pool message returning our connection pool.
|
5
|
+
#
|
2
6
|
class Engine
|
3
7
|
attr_reader :connection_pool
|
4
8
|
attr_reader :connection
|
@@ -18,6 +22,10 @@ module Flounder
|
|
18
22
|
|
19
23
|
# ---------------------------------------------------- official Engine iface
|
20
24
|
|
25
|
+
# Returns the connection pool.
|
26
|
+
#
|
27
|
+
# @return [ConnectionPool]
|
28
|
+
#
|
21
29
|
def connection_pool
|
22
30
|
@connection_pool
|
23
31
|
end
|
data/lib/flounder/entity.rb
CHANGED
@@ -1,33 +1,58 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
1
3
|
module Flounder
|
4
|
+
|
5
|
+
# An entity corresponds to a table in the database. On top of its table
|
6
|
+
# name, an entity will know its code name (plural) and its singular name,
|
7
|
+
# what to call a single row returned from this entity.
|
8
|
+
#
|
9
|
+
# An entity is not a model. In fact, it is what you need to completely
|
10
|
+
# hand-roll your models, without being the model itself.
|
11
|
+
#
|
12
|
+
# Entities are mainly used to start a query via one of the query initiators.
|
13
|
+
# Almost all of the chain methods on the Query object can be used here as
|
14
|
+
# well - they will return a query object. Entities, like queries, can be
|
15
|
+
# used as enumerables.
|
16
|
+
#
|
17
|
+
# Example:
|
18
|
+
#
|
19
|
+
# foo = domain.entity(:plural, :singular, 'table_name')
|
20
|
+
#
|
21
|
+
# foo.all # SELECT * FROM table_name
|
22
|
+
# foo.where(id: 1).first # SELECT * FROM table_name WHERE "table_name"."id" = 1
|
23
|
+
#
|
2
24
|
class Entity
|
3
|
-
|
25
|
+
def initialize domain, plural, singular, table_name
|
26
|
+
@domain = domain
|
27
|
+
@name = plural
|
28
|
+
@singular = singular
|
29
|
+
@table_name = table_name
|
30
|
+
@columns_hash = nil
|
31
|
+
|
32
|
+
@table = Arel::Table.new(table_name)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Domain] Domain this entity is defined in.
|
4
36
|
attr_reader :domain
|
5
37
|
|
6
|
-
# Name of the entity in plural form.
|
38
|
+
# @return [Symbol] Name of the entity in plural form.
|
7
39
|
attr_reader :name
|
8
40
|
|
9
41
|
# Also, the name is the plural, so we'll support that as well.
|
10
42
|
alias plural name
|
11
43
|
|
12
|
-
# Name of the entity in singular form.
|
44
|
+
# @return [Symbol] Name of the entity in singular form.
|
13
45
|
attr_reader :singular
|
14
46
|
|
15
|
-
# Arel table that underlies this entity.
|
47
|
+
# @return [Arel::Table] Arel table that underlies this entity.
|
16
48
|
attr_reader :table
|
17
|
-
# Name of the table that underlies this entity.
|
49
|
+
# @return [String] Name of the table that underlies this entity.
|
18
50
|
attr_reader :table_name
|
19
51
|
|
20
|
-
|
21
|
-
|
22
|
-
@name = plural
|
23
|
-
@singular = singular
|
24
|
-
@table_name = table_name
|
25
|
-
@columns_hash = nil
|
26
|
-
|
27
|
-
@table = Arel::Table.new(table_name)
|
28
|
-
end
|
52
|
+
extend Forwardable
|
53
|
+
def_delegators :domain, :transaction, :with_connection
|
29
54
|
|
30
|
-
#
|
55
|
+
# @return [Field] Field with name of the entity.
|
31
56
|
#
|
32
57
|
def [] name
|
33
58
|
Field.new(self, name, table[name])
|
@@ -37,51 +62,28 @@ module Flounder
|
|
37
62
|
"entity(#{name}/#{table_name})"
|
38
63
|
end
|
39
64
|
|
40
|
-
|
41
|
-
|
65
|
+
# Starts a new select query and yields it to the block. Note that you don't
|
66
|
+
# need to call this method to obtain a select query - any of the methods
|
67
|
+
# on Query::Select should work on the entity and return a new query object.
|
68
|
+
#
|
69
|
+
# @return [Query::Select]
|
70
|
+
#
|
71
|
+
def select
|
72
|
+
Query::Select.new(domain, self).tap { |q|
|
42
73
|
yield q if block_given?
|
43
74
|
}
|
44
75
|
end
|
45
76
|
|
46
77
|
def insert hash
|
47
|
-
Insert.new(domain, self).tap { |
|
48
|
-
|
49
|
-
i.insert(hash)
|
50
|
-
}
|
78
|
+
Query::Insert.new(domain, self).tap { |q|
|
79
|
+
q.row(hash) }
|
51
80
|
end
|
52
81
|
|
53
82
|
def update hash
|
54
|
-
Update.new(domain, self).tap
|
55
|
-
|
56
|
-
u.update(hash)
|
57
|
-
}
|
83
|
+
Query::Update.new(domain, self).tap { |u|
|
84
|
+
u.set(hash) }
|
58
85
|
end
|
59
|
-
|
60
|
-
# Insert or update.
|
61
|
-
#
|
62
|
-
def insdate fields, hash
|
63
|
-
with_transaction do
|
64
|
-
found = where(hash.select { |k, _| fields.include?(k) }).first
|
65
|
-
if found
|
66
|
-
update(hash).where(:id => found.id).returning
|
67
|
-
else
|
68
|
-
result = insert(hash).returning
|
69
|
-
yield result if block_given?
|
70
|
-
result
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def with_transaction &block
|
76
|
-
result = nil
|
77
|
-
domain.connection_pool.with_connection do |conn|
|
78
|
-
conn.exec 'START TRANSACTION'
|
79
|
-
result = block.call
|
80
|
-
conn.exec 'COMMIT TRANSACTION'
|
81
|
-
end
|
82
|
-
result
|
83
|
-
end
|
84
|
-
|
86
|
+
|
85
87
|
# Temporarily creates a new entity that is available as if it was declared
|
86
88
|
# with the given plural and singular, but referencing to the same
|
87
89
|
# underlying relation.
|
@@ -104,14 +106,14 @@ module Flounder
|
|
104
106
|
# Query initiators
|
105
107
|
[:where, :join, :outer_join, :project, :order_by].each do |name|
|
106
108
|
define_method name do |*args|
|
107
|
-
|
109
|
+
select { |q| q.send(name, *args) }
|
108
110
|
end
|
109
111
|
end
|
110
112
|
|
111
113
|
# Kickers
|
112
114
|
[:first, :all, :size, :delete].each do |name|
|
113
115
|
define_method name do |*args|
|
114
|
-
q =
|
116
|
+
q = select
|
115
117
|
q.send(name, *args)
|
116
118
|
end
|
117
119
|
end
|
data/lib/flounder/exceptions.rb
CHANGED
@@ -5,4 +5,12 @@ module Flounder
|
|
5
5
|
# to a real field on the underlying relation.
|
6
6
|
#
|
7
7
|
class InvalidFieldReference < StandardError; end
|
8
|
+
|
9
|
+
# Indicates that a where clause with bind variables went out of bounds.
|
10
|
+
class BindIndexOutOfBounds < StandardError; end
|
11
|
+
|
12
|
+
# Exception raised whenever a result set contains two columns with the
|
13
|
+
# same name.
|
14
|
+
#
|
15
|
+
class DuplicateField < StandardError; end
|
8
16
|
end
|
data/lib/flounder/field.rb
CHANGED
@@ -6,13 +6,21 @@ module Flounder
|
|
6
6
|
@arel_field = arel_field
|
7
7
|
end
|
8
8
|
|
9
|
+
# @return [Entity] entity this field belongs to
|
9
10
|
attr_reader :entity
|
11
|
+
|
12
|
+
# @return [Arel::Attribute] arel attribute that corresponds to this field
|
10
13
|
attr_reader :arel_field
|
14
|
+
|
15
|
+
# @return [String] name of this field
|
11
16
|
attr_reader :name
|
12
17
|
|
18
|
+
# Returns a fully qualified name (table.field).
|
19
|
+
#
|
20
|
+
# @return [String] fully qualified field name
|
21
|
+
#
|
13
22
|
def fully_qualified_name
|
14
|
-
|
15
|
-
entity.domain.connection_pool.with_connection do |conn|
|
23
|
+
entity.with_connection do |conn|
|
16
24
|
table = conn.quote_table_name(entity.table_name)
|
17
25
|
column = conn.quote_column_name(name)
|
18
26
|
"#{table}.#{column}"
|
@@ -0,0 +1,157 @@
|
|
1
|
+
|
2
|
+
module Flounder::Query
|
3
|
+
class Base
|
4
|
+
def initialize domain, manager_klass, entity
|
5
|
+
@domain = domain
|
6
|
+
@engine = Flounder::Engine.new(domain.connection_pool)
|
7
|
+
@manager = manager_klass.new(engine)
|
8
|
+
@entity = entity
|
9
|
+
|
10
|
+
@bind_values = []
|
11
|
+
end
|
12
|
+
|
13
|
+
# Domain that this query was issued from.
|
14
|
+
attr_reader :domain
|
15
|
+
# Database engine that links Arel to Postgres.
|
16
|
+
attr_reader :engine
|
17
|
+
# Bound values in this query.
|
18
|
+
attr_reader :bind_values
|
19
|
+
# Arel *Manager that accumulates this query.
|
20
|
+
attr_reader :manager
|
21
|
+
# Entity this query operates on.
|
22
|
+
attr_reader :entity
|
23
|
+
|
24
|
+
# Restricts the result returned to only those records that match the
|
25
|
+
# conditions.
|
26
|
+
#
|
27
|
+
# Example:
|
28
|
+
#
|
29
|
+
# query.where(id: 1) # ... WHERE id = 1 ...
|
30
|
+
# query.where(:id => :user_id) # ... WHERE id = user_id
|
31
|
+
# query.where(:id.noteq => 1) # ... WHERE id != 1
|
32
|
+
#
|
33
|
+
def where *conditions
|
34
|
+
# is this a hash? extract the first element
|
35
|
+
if conditions.size == 1 && conditions.first.kind_of?(Hash)
|
36
|
+
conditions = conditions.first
|
37
|
+
|
38
|
+
conditions.each do |k, v|
|
39
|
+
manager.where(transform_tuple(k, v))
|
40
|
+
end
|
41
|
+
return self
|
42
|
+
end
|
43
|
+
|
44
|
+
# maybe conditions is of the second form?
|
45
|
+
conditions.each do |cond_str, *values|
|
46
|
+
manager.where(
|
47
|
+
Arel::Nodes::SqlLiteral.new(
|
48
|
+
rewrite_bind_variables(cond_str, bind_values.size, values.size)))
|
49
|
+
bind_values.concat values
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def with name, query
|
54
|
+
# Nodes::TableAlias.new(relation, name)
|
55
|
+
manager.with(query.manager)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Kickers
|
59
|
+
def to_sql
|
60
|
+
prepare_kick
|
61
|
+
|
62
|
+
manager.to_sql.tap { |sql|
|
63
|
+
domain.log_sql(sql) }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns all rows of the query result as an array. Individual rows are
|
67
|
+
# mapped to objects using the row mapper.
|
68
|
+
#
|
69
|
+
def kick connection=nil
|
70
|
+
all = nil
|
71
|
+
|
72
|
+
(connection || engine).exec(self.to_sql, bind_values) do |result|
|
73
|
+
all = Array.new(result.ntuples, nil)
|
74
|
+
result.ntuples.times do |row_idx|
|
75
|
+
all[row_idx] = engine.connection.
|
76
|
+
objectify_result_row(entity, result, row_idx) do |name|
|
77
|
+
column_name_to_entity(name)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
all
|
83
|
+
end
|
84
|
+
def column_name_to_entity name
|
85
|
+
# Implement this if your column names in the query allow inferring
|
86
|
+
# the entity and the column name. Return them as a tuple <entity, name>.
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
# Prepares a kick - meaning an execution on the database or a transform
|
91
|
+
# to an sql string. Ready Set...
|
92
|
+
#
|
93
|
+
def prepare_kick
|
94
|
+
# should be overridden
|
95
|
+
end
|
96
|
+
|
97
|
+
# Rewrites a statement that contains bind placeholders like '$1' to
|
98
|
+
# contain placeholders starting at offset+1. Also checks that no
|
99
|
+
# placeholder exceeds the limit.
|
100
|
+
#
|
101
|
+
def rewrite_bind_variables str, offset, limit
|
102
|
+
str.gsub(%r(\$(?<idx>\d+))) do |match|
|
103
|
+
idx = Integer($~[:idx])
|
104
|
+
|
105
|
+
raise Flounder::BindIndexOutOfBounds,
|
106
|
+
"Binding to $#{idx} in #{str.inspect}, but only #{limit} variables provided" \
|
107
|
+
if idx-1 >= limit
|
108
|
+
|
109
|
+
"$#{idx + offset}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Called on each key/value pair of a
|
114
|
+
# * condition
|
115
|
+
# * join
|
116
|
+
# clause, this returns a field that can be passed to Arel
|
117
|
+
# * #where
|
118
|
+
# * #on
|
119
|
+
#
|
120
|
+
def transform_tuple field, value
|
121
|
+
if value.kind_of? Flounder::Field
|
122
|
+
value = value.arel_field
|
123
|
+
end
|
124
|
+
|
125
|
+
case field
|
126
|
+
# covers: :field_a => ...
|
127
|
+
when Symbol
|
128
|
+
join_and_condition_part(entity[field].arel_field, value)
|
129
|
+
# covers: entity[:field] => ...
|
130
|
+
when Flounder::Field
|
131
|
+
join_and_condition_part(field.arel_field, value)
|
132
|
+
# covers: :field_a.noteq => ...
|
133
|
+
when Flounder::SymbolExtensions::Modifier
|
134
|
+
join_and_condition_part(
|
135
|
+
field.to_arel_field(entity),
|
136
|
+
value,
|
137
|
+
field.kind)
|
138
|
+
else
|
139
|
+
fail "Could not transform condition part. (#{field.inspect}, #{value.inspect})"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
def join_and_condition_part arel_field, value, kind=:eq
|
143
|
+
case value
|
144
|
+
# covers :field_a => :field_b
|
145
|
+
when Symbol
|
146
|
+
value_field = entity[value].arel_field
|
147
|
+
arel_field.send(kind, value_field)
|
148
|
+
# covers: :field => (1..100)
|
149
|
+
when Range
|
150
|
+
arel_field.in(value)
|
151
|
+
else
|
152
|
+
arel_field.send(kind, value)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
require_relative 'base'
|
3
|
+
require_relative 'returning'
|
4
|
+
|
5
|
+
module Flounder::Query
|
6
|
+
|
7
|
+
# An insert.
|
8
|
+
#
|
9
|
+
class Insert < Base
|
10
|
+
def initialize domain, into_entity
|
11
|
+
super domain, Arel::InsertManager, into_entity
|
12
|
+
|
13
|
+
manager.into entity.table
|
14
|
+
end
|
15
|
+
|
16
|
+
# Add one row to the inserts.
|
17
|
+
#
|
18
|
+
def row fields
|
19
|
+
manager.insert(
|
20
|
+
fields.map { |k, v|
|
21
|
+
transform_values(k, v) })
|
22
|
+
end
|
23
|
+
|
24
|
+
include Returning
|
25
|
+
private
|
26
|
+
|
27
|
+
# Called on each key/value pair of an insert clause, this returns a
|
28
|
+
# hash that can be passed to Arel #insert.
|
29
|
+
#
|
30
|
+
def transform_values field, value
|
31
|
+
case field
|
32
|
+
when Symbol, String
|
33
|
+
[entity[field.to_sym].arel_field, value]
|
34
|
+
when Flounder::Field
|
35
|
+
[field.arel_field, value]
|
36
|
+
else
|
37
|
+
fail "Could not transform value. (#{field.inspect}, #{value.inspect})"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end # class
|
41
|
+
end # module Flounder
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Flounder::Query
|
4
|
+
module Returning
|
5
|
+
def returning_fields
|
6
|
+
@returning_fields || '*'
|
7
|
+
end
|
8
|
+
|
9
|
+
def returning fields
|
10
|
+
@returning_fields = fields
|
11
|
+
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_sql
|
16
|
+
super << " RETURNING #{returning_fields}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|