ar_jdbc_pg_array 0.1.0-java
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.
- data/.gitignore +6 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +48 -0
- data/Rakefile +7 -0
- data/ar_jdbc_pg_array.gemspec +24 -0
- data/lib/ar_jdbc_pg_array.rb +12 -0
- data/lib/ar_jdbc_pg_array/allways_save.rb +38 -0
- data/lib/ar_jdbc_pg_array/parser.rb +174 -0
- data/lib/ar_jdbc_pg_array/querying.rb +72 -0
- data/lib/ar_jdbc_pg_array/querying_arel.rb +172 -0
- data/lib/ar_jdbc_pg_array/references_by.rb +154 -0
- data/lib/ar_jdbc_pg_array/schema.rb +248 -0
- data/lib/ar_jdbc_pg_array/schema_arel.rb +47 -0
- data/lib/ar_jdbc_pg_array/schema_cacheable.rb +6 -0
- data/lib/ar_jdbc_pg_array/schema_fix_will_change.rb +13 -0
- data/spec/fixtures/bulk.rb +5 -0
- data/spec/fixtures/bulks.yml +36 -0
- data/spec/fixtures/item.rb +4 -0
- data/spec/fixtures/items.yml +22 -0
- data/spec/fixtures/schema.rb +30 -0
- data/spec/fixtures/tag.rb +2 -0
- data/spec/fixtures/tags.yml +15 -0
- data/spec/fixtures/unrelated.rb +21 -0
- data/spec/fixtures/unrelateds.yml +3 -0
- data/spec/pg_array_spec.rb +380 -0
- data/spec/spec_helper.rb +55 -0
- metadata +186 -0
@@ -0,0 +1,154 @@
|
|
1
|
+
module PGArrays
|
2
|
+
module ReferencesBy
|
3
|
+
if defined? ::Arel
|
4
|
+
HAS_AREL=true
|
5
|
+
NAMED_SCOPE='scope'
|
6
|
+
else
|
7
|
+
NAMED_SCOPE='named_scope'
|
8
|
+
if defined? ::FakeArel
|
9
|
+
HAS_AREL= true
|
10
|
+
else
|
11
|
+
HAS_AREL=false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class RelationHolder < Struct.new(:relation, :field, :klass)
|
16
|
+
|
17
|
+
def referenced(obj)
|
18
|
+
ids = (obj[field] || []).map{|i| i.to_i}
|
19
|
+
objs = klass.find_all_by_id( ids.sort )
|
20
|
+
if ids.size < 20
|
21
|
+
objs.sort_by{|o| ids.index(o.id)}
|
22
|
+
else
|
23
|
+
to_ind = ids.each_with_index.inject({}){|h, (v,i)| h[v]=i; h}
|
24
|
+
objs.sort_by{|o| to_ind[o.id]}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_referenced(obj, value)
|
29
|
+
obj[field] = map_to_ids(value)
|
30
|
+
end
|
31
|
+
|
32
|
+
if HAS_AREL
|
33
|
+
def validate(obj)
|
34
|
+
has_ids = klass.where(:id=>obj.read_attribute(field)).
|
35
|
+
select('id').all.map(&:id)
|
36
|
+
unless has_ids.sort == obj.read_attribute(field).sort
|
37
|
+
obj.errors.add(relation, :wrong_array_reference)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def for_referenced(obj_klass)
|
42
|
+
myself = self
|
43
|
+
obj_klass.class_exec do
|
44
|
+
puts "named_scope #{NAMED_SCOPE} #{myself.relation}_include"
|
45
|
+
self.send NAMED_SCOPE, "#{myself.relation}_include", lambda{|*objs|
|
46
|
+
objs = myself.map_to_ids objs.flatten
|
47
|
+
where(myself.field.to_sym => objs.search_all)
|
48
|
+
}
|
49
|
+
|
50
|
+
self.send NAMED_SCOPE, "#{myself.relation}_have_all", lambda{|*objs|
|
51
|
+
objs = myself.map_to_ids objs.flatten
|
52
|
+
where(myself.field.to_sym => objs.search_all)
|
53
|
+
}
|
54
|
+
|
55
|
+
self.send NAMED_SCOPE, "#{myself.relation}_have_any", lambda{|*objs|
|
56
|
+
objs = myself.map_to_ids objs.flatten
|
57
|
+
where(myself.field.to_sym => objs.search_any)
|
58
|
+
}
|
59
|
+
|
60
|
+
self.send NAMED_SCOPE, "#{myself.relation}_included_into", lambda{|*objs|
|
61
|
+
objs = myself.map_to_ids objs.flatten
|
62
|
+
where(myself.field.to_sym => objs.search_subarray)
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
else
|
67
|
+
def validate(obj)
|
68
|
+
has_ids = klass.find(:all,
|
69
|
+
:select=>'id',
|
70
|
+
:conditions=>{:id=>obj.read_attribute(field)}
|
71
|
+
).map(&:id)
|
72
|
+
unless has_ids.sort == obj.read_attribute(field).sort
|
73
|
+
obj.errors.add(relation, :wrong_array_reference)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def for_referenced(obj_klass)
|
78
|
+
myself = self
|
79
|
+
obj_klass.class_exec do
|
80
|
+
named_scope "#{myself.relation}_include", lambda{|*objs|
|
81
|
+
objs = myself.map_to_ids objs.flatten
|
82
|
+
{ :conditions=>{ myself.field.to_sym => objs.search_all } }
|
83
|
+
}
|
84
|
+
|
85
|
+
named_scope "#{myself.relation}_have_all", lambda{|*objs|
|
86
|
+
objs = myself.map_to_ids objs.flatten
|
87
|
+
{ :conditions=>{ myself.field.to_sym => objs.search_all } }
|
88
|
+
}
|
89
|
+
|
90
|
+
named_scope "#{myself.relation}_have_any", lambda{|*objs|
|
91
|
+
objs = myself.map_to_ids objs.flatten
|
92
|
+
{ :conditions=>{ myself.field.to_sym => objs.search_any } }
|
93
|
+
}
|
94
|
+
|
95
|
+
named_scope "#{myself.relation}_included_into", lambda{|*objs|
|
96
|
+
objs = myself.map_to_ids objs.flatten
|
97
|
+
{ :conditions=>{ myself.field.to_sym => objs.search_subarray } }
|
98
|
+
}
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def val_to_id(val)
|
104
|
+
case val
|
105
|
+
when ActiveRecord::Base then val.id
|
106
|
+
when nil then nil
|
107
|
+
else val.to_i
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def map_to_ids(vals)
|
112
|
+
(r = vals.map{|v| val_to_id(v)}).compact!
|
113
|
+
r
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
module ClassMethods
|
118
|
+
def references_by_array( relation, options = {} )
|
119
|
+
unless ActiveSupport::Memoizable === self
|
120
|
+
extend ActiveSupport::Memoizable
|
121
|
+
end
|
122
|
+
|
123
|
+
relation = relation.to_s.pluralize
|
124
|
+
field = "#{relation.singularize}_ids"
|
125
|
+
klass_name = (options[:class_name] || relation).to_s.singularize.camelize
|
126
|
+
klass = klass_name.constantize
|
127
|
+
holder = RelationHolder.new(relation, field, klass )
|
128
|
+
|
129
|
+
meths = Module.new do
|
130
|
+
define_method(relation) do
|
131
|
+
holder.referenced(self)
|
132
|
+
end
|
133
|
+
|
134
|
+
define_method("#{relation}=") do |value|
|
135
|
+
flush_cache(relation)
|
136
|
+
holder.set_referenced(self, value)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
include meths
|
140
|
+
|
141
|
+
memoize relation
|
142
|
+
|
143
|
+
if options[:validate]
|
144
|
+
validate {|o| holder.validate(o)}
|
145
|
+
end
|
146
|
+
|
147
|
+
holder.for_referenced(self)
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
ActiveRecord::Base.extend PGArrays::ReferencesBy::ClassMethods
|
@@ -0,0 +1,248 @@
|
|
1
|
+
require 'ar_jdbc_pg_array/parser'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
class PostgreSQLColumn
|
6
|
+
include PgArrayParser
|
7
|
+
extend PgArrayParser
|
8
|
+
|
9
|
+
BASE_TYPE_COLUMNS = Hash.new { |h, base_type|
|
10
|
+
base_column = new(nil, nil, base_type.to_s, true)
|
11
|
+
h[base_type] = h[base_column.type] = base_column
|
12
|
+
}
|
13
|
+
attr_reader :base_column
|
14
|
+
|
15
|
+
def initialize(name, default, sql_type = nil, null = true)
|
16
|
+
if sql_type =~ /^(.+)\[\]$/
|
17
|
+
@base_sql_type = $1
|
18
|
+
@base_column = BASE_TYPE_COLUMNS[@base_sql_type]
|
19
|
+
end
|
20
|
+
|
21
|
+
if Hash === name
|
22
|
+
super
|
23
|
+
else
|
24
|
+
super(nil, name, default, sql_type, null)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def simplified_type_with_postgresql_arrays(field_type)
|
29
|
+
if field_type =~ /^(.+)\[\]$/
|
30
|
+
:"#{simplified_type_without_postgresql_arrays($1)}_array"
|
31
|
+
else
|
32
|
+
simplified_type_without_postgresql_arrays(field_type)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
alias_method_chain :simplified_type, :postgresql_arrays
|
36
|
+
|
37
|
+
def klass
|
38
|
+
if type.to_s =~ /_array$/
|
39
|
+
Array
|
40
|
+
else
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def type_cast(value)
|
46
|
+
return nil if value.nil?
|
47
|
+
case type
|
48
|
+
when :integer_array, :float_array
|
49
|
+
string_to_num_array(value)
|
50
|
+
when :decimal_array, :date_array, :boolean_array
|
51
|
+
safe_string_to_array(value)
|
52
|
+
when :timestamp_array, :time_array, :datetime_array, :binary_array
|
53
|
+
string_to_array(value)
|
54
|
+
when :text_array, :string_array
|
55
|
+
string_to_text_array(value)
|
56
|
+
else super
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def type_cast_code(var_name)
|
61
|
+
case type
|
62
|
+
when :integer_array, :float_array
|
63
|
+
"#{self.class.name}.string_to_num_array(#{var_name})"
|
64
|
+
when :decimal_array, :date_array, :boolean_array
|
65
|
+
"#{self.class.name}.safe_string_to_array(#{var_name}, #{@base_sql_type.inspect})"
|
66
|
+
when :timestamp_array, :time_array, :datetime_array, :binary_array
|
67
|
+
"#{self.class.name}.string_to_array(#{var_name}, #{@base_sql_type.inspect})"
|
68
|
+
when :text_array, :string_array
|
69
|
+
"#{self.class.name}.string_to_text_array(#{var_name})"
|
70
|
+
else super
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def default
|
75
|
+
res = super
|
76
|
+
Array === res ? res.dup : res
|
77
|
+
end
|
78
|
+
|
79
|
+
def self._string_to_array(string)
|
80
|
+
return string unless string.is_a? String
|
81
|
+
return nil if string.empty?
|
82
|
+
|
83
|
+
yield
|
84
|
+
end
|
85
|
+
|
86
|
+
def _string_to_array(string)
|
87
|
+
return string unless string.is_a? String
|
88
|
+
return nil if string.empty?
|
89
|
+
|
90
|
+
yield
|
91
|
+
end
|
92
|
+
|
93
|
+
def safe_string_to_array(string)
|
94
|
+
_string_to_array(string) do
|
95
|
+
parse_safe_pgarray(string){|v| @base_column.type_cast(v)}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.safe_string_to_array(string, sql_type)
|
100
|
+
_string_to_array(string) do
|
101
|
+
base_column = BASE_TYPE_COLUMNS[sql_type]
|
102
|
+
parse_safe_pgarray(string){|v| base_column.type_cast(v)}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def string_to_array(string)
|
107
|
+
_string_to_array(string) do
|
108
|
+
parse_pgarray(string){|v| @base_column.type_cast(v)}
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.string_to_array(string, sql_type)
|
113
|
+
_string_to_array(string) do
|
114
|
+
base_column = BASE_TYPE_COLUMNS[sql_type]
|
115
|
+
parse_pgarray(string){|v| base_column.type_cast(v)}
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def string_to_num_array(string)
|
120
|
+
_string_to_array(string) do
|
121
|
+
parse_numeric_pgarray(string)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.string_to_num_array(string)
|
126
|
+
_string_to_array(string) do
|
127
|
+
parse_numeric_pgarray(string)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def string_to_text_array(string)
|
132
|
+
_string_to_array(string) do
|
133
|
+
parse_pgarray(string){|v| v}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.string_to_text_array(string)
|
138
|
+
_string_to_array(string) do
|
139
|
+
parse_pgarray(string){|v| v}
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
class PostgreSQLAdapter #:nodoc:
|
145
|
+
include PgArrayParser
|
146
|
+
def quote_with_postgresql_arrays(value, column = nil)
|
147
|
+
if Array === value && column && "#{column.type}" =~ /^(.+)_array$/
|
148
|
+
quote_array_by_base_type(value, $1, column)
|
149
|
+
else
|
150
|
+
quote_without_postgresql_arrays(value, column)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
alias_method_chain :quote, :postgresql_arrays
|
154
|
+
|
155
|
+
def quote_array_by_base_type(value, base_type, column = nil)
|
156
|
+
case base_type.to_sym
|
157
|
+
when :integer, :float, :decimal, :boolean, :date, :safe, :datetime, :timestamp, :time
|
158
|
+
"'#{ prepare_array_for_arel_by_base_type(value, base_type) }'"
|
159
|
+
when :string, :text, :other
|
160
|
+
pa = prepare_array_for_arel_by_base_type(value, base_type)
|
161
|
+
"'#{ quote_string(pa) }'"
|
162
|
+
else
|
163
|
+
"'#{ prepare_pg_string_array(value, base_type, column) }'"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def prepare_array_for_arel_by_base_type(value, base_type)
|
168
|
+
case base_type.to_sym
|
169
|
+
when :integer
|
170
|
+
prepare_pg_integer_array(value)
|
171
|
+
when :float
|
172
|
+
prepare_pg_float_array(value)
|
173
|
+
when :string, :text, :other
|
174
|
+
prepare_pg_text_array(value)
|
175
|
+
when :datetime, :timestamp, :time
|
176
|
+
prepare_pg_string_array(value, base_type)
|
177
|
+
when :decimal, :boolean, :date, :safe
|
178
|
+
prepare_pg_safe_array(value)
|
179
|
+
else
|
180
|
+
raise "Unsupported array base type #{base_type} for arel"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def prepare_pg_string_array(value, base_type, column = nil)
|
185
|
+
base_column = if column
|
186
|
+
column.base_column
|
187
|
+
else
|
188
|
+
PostgreSQLColumn::BASE_TYPE_COLUMNS[base_type.to_sym]
|
189
|
+
end
|
190
|
+
_prepare_pg_string_array(value) { |v| quote_without_postgresql_arrays(v, base_column) }
|
191
|
+
end
|
192
|
+
|
193
|
+
NATIVE_DATABASE_TYPES.keys.each do |key|
|
194
|
+
unless key == :primary_key
|
195
|
+
base = NATIVE_DATABASE_TYPES[key].dup
|
196
|
+
base[:name] = base[:name] + '[]'
|
197
|
+
NATIVE_DATABASE_TYPES[:"#{key}_array"] = base
|
198
|
+
TableDefinition.class_eval <<-EOV
|
199
|
+
def #{key}_array(*args) # def string_array(*args)
|
200
|
+
options = args.extract_options! # options = args.extract_options!
|
201
|
+
column_names = args # column_names = args
|
202
|
+
#
|
203
|
+
column_names.each { |name| column(name, :'#{key}_array', options) } # column_names.each { |name| column(name, :string_array, options) }
|
204
|
+
end # end
|
205
|
+
EOV
|
206
|
+
Table.class_eval <<-EOV
|
207
|
+
def #{key}_array(*args) # def string_array(*args)
|
208
|
+
options = args.extract_options! # options = args.extract_options!
|
209
|
+
column_names = args # column_names = args
|
210
|
+
#
|
211
|
+
column_names.each { |name| # column_names.each { |name|
|
212
|
+
@base.add_column(@table_name, name, :'#{key}_array', options) # @base.add_column(@table_name, name, :string_array, options) }
|
213
|
+
} # }
|
214
|
+
end # end
|
215
|
+
EOV
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def add_column_with_postgresql_arrays(table, column, type, options = {})
|
220
|
+
if type.to_s =~ /^(.+)_array$/ && options[:default].is_a?(Array)
|
221
|
+
options = options.merge(:default => prepare_array_for_arel_by_base_type(options[:default], $1))
|
222
|
+
end
|
223
|
+
add_column_without_postgresql_arrays(table, column, type, options)
|
224
|
+
end
|
225
|
+
alias_method_chain :add_column, :postgresql_arrays
|
226
|
+
|
227
|
+
def type_to_sql_with_postgresql_arrays(type, limit = nil, precision = nil, scale = nil)
|
228
|
+
if type.to_s =~ /^(.+)_array$/
|
229
|
+
type_to_sql_without_postgresql_arrays($1.to_sym, limit, precision, scale) + '[]'
|
230
|
+
else
|
231
|
+
type_to_sql_without_postgresql_arrays(type, limit, precision, scale)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
alias_method_chain :type_to_sql, :postgresql_arrays
|
235
|
+
|
236
|
+
if method_defined?(:type_cast)
|
237
|
+
def type_cast_with_postgresql_arrays(value, column)
|
238
|
+
if Array === value && column && "#{column.type}" =~ /^(.+)_array$/
|
239
|
+
prepare_array_for_arel_by_base_type(value, $1)
|
240
|
+
else
|
241
|
+
type_cast_without_postgresql_arrays(value, column)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
alias_method_chain :type_cast, :postgresql_arrays
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Arel
|
2
|
+
module Attributes
|
3
|
+
%w{Integer Float Decimal Boolean String Time}.each do |basetype|
|
4
|
+
module_eval <<-"END"
|
5
|
+
class #{basetype}Array < Attribute
|
6
|
+
end
|
7
|
+
END
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Attributes
|
12
|
+
class << self
|
13
|
+
def for_with_postgresql_arrays(column)
|
14
|
+
if column.type.to_s =~ /^(.+)_array$/
|
15
|
+
('Arel::Attributes::' + for_without_postgresql_arrays(column.base_column).name.split('::').last + 'Array').constantize
|
16
|
+
else
|
17
|
+
for_without_postgresql_arrays(column)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
alias_method_chain :for, :postgresql_arrays
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module ActiveRecord
|
26
|
+
class << Base
|
27
|
+
if method_defined?(:column_defaults)
|
28
|
+
alias column_defaults_without_extradup column_defaults
|
29
|
+
def column_defaults_with_extradup
|
30
|
+
res = {}
|
31
|
+
column_defaults_without_extradup.each{|k, v|
|
32
|
+
res[k] = Array === v ? v.dup : v
|
33
|
+
}
|
34
|
+
res
|
35
|
+
end
|
36
|
+
def column_defaults
|
37
|
+
defaults = column_defaults_without_extradup
|
38
|
+
if defaults.values.grep(Array).empty?
|
39
|
+
alias column_defaults column_defaults_without_extradup
|
40
|
+
else
|
41
|
+
alias column_defaults column_defaults_with_extradup
|
42
|
+
end
|
43
|
+
column_defaults
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|