flounder 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|