ar_jdbc_pg_array 0.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.lock
2
+ *.swo
3
+ *.swp
4
+ rdoc
5
+ pkg
6
+ *~
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Sokolov Yura aka funny_falcon
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ PostgresArrays
2
+ ==============
3
+
4
+ This library adds ability to use PostgreSQL array types with ActiveRecord.
5
+
6
+ > User.find(:all, :conditions=>['arr @> ?', [1,2,3].pg])
7
+ SELECT * FROM "users" WHERE ('arr' @> E'{"1", "2", "3"}')
8
+ > User.find(:all, :conditions=>['arr @> ?', [1,2,3].pg(:integer)])
9
+ SELECT * FROM "users" WHERE (arr @> '{1,2,3}')
10
+ > User.find(:all, :conditions=>['arr @> ?', [1,2,3].pg(:float)])
11
+ SELECT * FROM "users" WHERE (arr @> '{1.0,2.0,3.0}')
12
+ > u = User.find(1)
13
+ SELECT * FROM "users" WHERE ("users"."id" = 1)
14
+ => #<User id: 1, ..., arr: [1,2]>
15
+ > u.arr = [3,4]
16
+ > u.save
17
+ UPDATE "users" SET "db_ar" = '{3.0,4.0}' WHERE "id" = 19
18
+ > User.find(:all, :conditions=>{:arr=>[3,4].pg})
19
+ SELECT * FROM "users" WHERE ("users"."arr" = E'{"3", "4"}')
20
+ > User.find(:all, :conditions=>{:arr=>[3,4].search_any(:float)})
21
+ SELECT * FROM "users" WHERE ("users"."arr" && '{3.0,4.0}')
22
+ > User.find(:all, :conditions=>{:arr=>[3,4].search_all(:integer)})
23
+ SELECT * FROM "users" WHERE ("users"."arr" @> '{3,4}')
24
+ > User.find(:all, :conditions=>{:arr=>[3,4].search_subarray(:safe)})
25
+ SELECT * FROM "users" WHERE ("users"."arr" <@ '{3,4}')
26
+
27
+ class U < ActiveRecord::Migration
28
+ def self.up
29
+ create_table :users do |t|
30
+ t.integer_array :int_ar
31
+ end
32
+ add_column :users, :fl_ar, :float_array
33
+ end
34
+ end
35
+
36
+ Installation
37
+ ============
38
+
39
+ gem install ar_jdbc_pg_array
40
+
41
+ Changelog
42
+ =========
43
+
44
+ 0.1.0
45
+
46
+ Initial jdbc support
47
+
48
+ Copyright (c) 2010 Sokolov Yura aka funny_falcon, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'rake'
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new('spec')
6
+
7
+ task :default => :spec
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ Gem::Specification.new do |gem|
3
+ gem.name = 'ar_jdbc_pg_array'
4
+ gem.version = '0.1.0'
5
+ gem.platform = 'java'
6
+
7
+ gem.authors = ['Sokolov Yura', 'Dimko']
8
+ gem.email = ['deemox@gmail.com']
9
+ gem.description = "ar_jdbc_pg_array includes support of PostgreSQL's int[], float[], text[], timestamptz[] etc. into ActiveRecord. You could define migrations for array columns, query on array columns."
10
+ gem.summary = 'Use power of PostgreSQL Arrays in ActiveRecord'
11
+ gem.homepage = 'https://github.com/dimko/activerecord-jdbc-postgresql-arrays'
12
+
13
+ gem.files = `git ls-files`.split($/)
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.require_paths = ['lib']
16
+
17
+ gem.add_dependency 'activerecord', '~> 3.1'
18
+ gem.add_dependency 'activerecord-jdbcpostgresql-adapter', '~> 1.2.9'
19
+
20
+ gem.add_development_dependency 'rake'
21
+ gem.add_development_dependency 'cancan'
22
+ gem.add_development_dependency 'bundler', '~> 1.0'
23
+ gem.add_development_dependency 'rspec', '~> 2.5'
24
+ end
@@ -0,0 +1,12 @@
1
+ require 'active_record'
2
+ require 'active_record/base'
3
+ require 'active_record/connection_adapters/abstract_adapter'
4
+ require 'arjdbc/postgresql'
5
+
6
+ require 'ar_jdbc_pg_array/schema'
7
+ require 'ar_jdbc_pg_array/schema_cacheable'
8
+ require 'ar_jdbc_pg_array/querying'
9
+ require 'ar_jdbc_pg_array/allways_save'
10
+ require 'ar_jdbc_pg_array/references_by'
11
+ require 'ar_jdbc_pg_array/schema_arel'
12
+ require 'ar_jdbc_pg_array/querying_arel'
@@ -0,0 +1,38 @@
1
+ module ActiveRecord
2
+ module CheckArrayBeforeUpdate
3
+ def mark_arrays_for_update
4
+ @attributes_cache.each do |name, value|
5
+ attribute_will_change!(name) if Array === value && _read_attribute(name) != value
6
+ end
7
+ end
8
+ end
9
+ if VERSION::MAJOR < 3
10
+ module CheckArrayBeforeUpdate
11
+ def self.included(base)
12
+ base.alias_method_chain :update, :check_array
13
+ base.send(:alias_method, :_read_attribute, :read_attribute)
14
+ end
15
+
16
+ def update_with_check_array
17
+ mark_arrays_for_update
18
+ update_without_check_array
19
+ end
20
+ end
21
+ else
22
+ module CheckArrayBeforeUpdate
23
+ include ActiveSupport::Concern
24
+
25
+ if VERSION::MAJOR == 3 && VERSION::MINOR >= 2
26
+ def _read_attribute(attr_name)
27
+ column_for_attribute(attr_name).type_cast(@attributes[attr_name])
28
+ end
29
+ end
30
+
31
+ def update(*)
32
+ mark_arrays_for_update
33
+ super
34
+ end
35
+ end
36
+ end
37
+ Base.__send__ :include, CheckArrayBeforeUpdate
38
+ end
@@ -0,0 +1,174 @@
1
+ require 'json'
2
+
3
+ module PgArrayParser
4
+ CURLY_BRACKETS = '{}'.freeze
5
+ SQUARE_BRACKETS = '[]'.freeze
6
+ NULL = 'NULL'.freeze
7
+ NIL = 'nil'.freeze
8
+ ESCAPE_HASH={'\\'.freeze=>'\\\\'.freeze, '"'.freeze=>'\\"'.freeze}
9
+
10
+ def parse_numeric_pgarray(text)
11
+ text = text.tr(CURLY_BRACKETS, SQUARE_BRACKETS)
12
+ text.downcase!
13
+ JSON.load(text)
14
+ end
15
+
16
+ def parse_safe_pgarray(text, &block)
17
+ if text =~ /^\{([^\}]*)\}$/
18
+ $1.split(/,\s*/).map!{|v| v == NULL ? nil : yield(v)}
19
+ else
20
+ raise "Mailformed array" unless text =~ /^\{\s*/
21
+ ar, rest = _parse_safe_pgarray($')
22
+ ar
23
+ end
24
+ end
25
+
26
+ def _parse_safe_pgarray(text, &block)
27
+ values = []
28
+ return values if text =~ /^\}\s*/
29
+ if text =~ /^\{\s*/
30
+ text = $'
31
+ while true
32
+ ar, rest = _parse_safe_pgarray(text, &block)
33
+ values << ar
34
+ if rest =~ /^\}\s*/
35
+ return values, $'
36
+ elsif rest =~ /^,\s*/
37
+ rest = $'
38
+ else
39
+ raise "Mailformed postgres array"
40
+ end
41
+ text = rest
42
+ end
43
+ else
44
+ while true
45
+ raise 'Mailformed Array' unless text =~ /^([^,\}]*)([,\}])\s*/
46
+ val, sep, rest = $1, $2, $'
47
+ values << (val == NULL ? nil : yield(val))
48
+ if sep == '}'
49
+ return values, rest
50
+ end
51
+ text = rest
52
+ end
53
+ end
54
+ end
55
+
56
+ def parse_pgarray(text, &block)
57
+ raise "Mailformed postgres array" unless text =~ /^\{\s*/
58
+ ar, rest = _parse_pgarray($', &block)
59
+ ar
60
+ end
61
+
62
+ def _parse_pgarray(text, &block)
63
+ values = []
64
+ return values if text =~ /^\}\s*/
65
+ if text =~ /^\{\s*/
66
+ text = $'
67
+ while true
68
+ ar, rest = _parse_pgarray(text, &block)
69
+ values << ar
70
+ if rest =~ /^\}\s*/
71
+ return values, $'
72
+ elsif rest =~ /^,\s*\{\s*/
73
+ rest = $'
74
+ else
75
+ raise "Mailformed postgres array"
76
+ end
77
+ text = rest
78
+ end
79
+ else
80
+ while true
81
+ if text =~ /^"((?:\\.|[^"\\])*)"([,}])\s*/
82
+ val, sep, rest = $1, $2, $'
83
+ val.gsub!(/\\(.)/, '\1')
84
+ val = yield val
85
+ elsif text =~ /^([^,\}]*)([,}])\s*/
86
+ val, sep, rest = $1, $2, $'
87
+ val = val == NULL ? nil : yield(val)
88
+ else
89
+ raise "Mailformed postgres array"
90
+ end
91
+ values << val
92
+ if sep == '}'
93
+ return values, rest
94
+ end
95
+ text = rest
96
+ end
97
+ end
98
+ end
99
+
100
+ def _remap_array(array, &block)
101
+ array.map{|v|
102
+ case v
103
+ when Array
104
+ _remap_array(v, &block)
105
+ when nil
106
+ nil
107
+ else
108
+ yield v
109
+ end
110
+ }
111
+ end
112
+
113
+ def prepare_pg_integer_array(value)
114
+ val = _remap_array(value){|v| v.to_i}.inspect
115
+ val.gsub!(NIL, NULL)
116
+ val.tr!(SQUARE_BRACKETS, CURLY_BRACKETS)
117
+ val
118
+ end
119
+
120
+ def prepare_pg_float_array(value)
121
+ val = _remap_array(value){|v| v.to_f}.inspect
122
+ val.gsub!(NIL, NULL)
123
+ val.tr!(SQUARE_BRACKETS, CURLY_BRACKETS)
124
+ val
125
+ end
126
+
127
+ def prepare_pg_safe_array(value)
128
+ value = value.map{|val|
129
+ case val
130
+ when Array
131
+ prepare_pg_safe_array(val)
132
+ when nil
133
+ NULL
134
+ else
135
+ val.to_s
136
+ end
137
+ }.join(',')
138
+ "{#{value}}"
139
+ end
140
+
141
+ def prepare_pg_text_array(value)
142
+ value = value.map{|val|
143
+ case val
144
+ when Array
145
+ prepare_pg_text_array(val)
146
+ when nil
147
+ NULL
148
+ else
149
+ "\"#{val.to_s.gsub(/\\|"/){|s| ESCAPE_HASH[s]}}\""
150
+ end
151
+ }.join(',')
152
+ "{#{value}}"
153
+ end
154
+
155
+ def _prepare_pg_string_array(value, &block)
156
+ value = value.map{|val|
157
+ case val
158
+ when Array
159
+ _prepare_pg_string_array(val, &block)
160
+ when nil
161
+ NULL
162
+ else
163
+ val = yield val
164
+ if val =~ /^'(.*)'$/m
165
+ "\"#{ $1.gsub(/\\|"/){|s| ESCAPE_HASH[s]} }\""
166
+ else
167
+ val
168
+ end
169
+ end
170
+ }.join(',')
171
+ "{#{value}}"
172
+ end
173
+ alias prepare_pg_string_array _prepare_pg_string_array
174
+ end
@@ -0,0 +1,72 @@
1
+ module ActiveRecord
2
+ class Base
3
+ class << self
4
+ def quote_bound_value_with_postgresql_arrays(value, c = connection)
5
+ if ::PGArrays::PgArray === value
6
+ c.quote_array_by_base_type(value, value.base_type)
7
+ else
8
+ quote_bound_value_without_postgresql_arrays(value, c)
9
+ end
10
+ end
11
+ alias_method_chain :quote_bound_value, :postgresql_arrays
12
+ end
13
+ end
14
+ end
15
+
16
+ module PGArrays
17
+ class PgArray < Array
18
+ attr_reader :base_type
19
+
20
+ def initialize(array, type=nil)
21
+ super(array)
22
+ @base_type = type if type
23
+ end
24
+
25
+ def base_type
26
+ @base_type || :other
27
+ end
28
+ end
29
+
30
+ class PgAny < PgArray; end
31
+ class PgAll < PgArray; end
32
+ class PgIncludes < PgArray; end
33
+
34
+ if defined? CanCan::Ability
35
+ class PgAny
36
+ def include?(v)
37
+ Array === v && !( v & self ).empty?
38
+ end
39
+ end
40
+
41
+ class PgAll
42
+ def include?(v)
43
+ Array === v && (self - v).empty?
44
+ end
45
+ end
46
+
47
+ class PgIncludes < PgArray
48
+ def include?(v)
49
+ Array === v && (v - self).empty?
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ class Array
56
+
57
+ def pg(type=nil)
58
+ ::PGArrays::PgArray.new(self, type)
59
+ end
60
+
61
+ def search_any(type=nil)
62
+ ::PGArrays::PgAny.new(self, type)
63
+ end
64
+
65
+ def search_all(type=nil)
66
+ ::PGArrays::PgAll.new(self, type)
67
+ end
68
+
69
+ def search_subarray(type=nil)
70
+ ::PGArrays::PgIncludes.new(self, type)
71
+ end
72
+ end
@@ -0,0 +1,172 @@
1
+ module Arel
2
+ module Nodes
3
+ class ArrayAny < Arel::Nodes::Binary
4
+ end
5
+
6
+ class ArrayAll < Arel::Nodes::Binary
7
+ end
8
+
9
+ class ArrayIncluded < Arel::Nodes::Binary
10
+ end
11
+ end
12
+
13
+ module Predications
14
+ def ar_any other
15
+ Nodes::ArrayAny.new self, other
16
+ end
17
+
18
+ def ar_all other
19
+ Nodes::ArrayAll.new self, other
20
+ end
21
+
22
+ def ar_included other
23
+ Nodes::ArrayIncluded.new self, other
24
+ end
25
+ end
26
+
27
+ module Visitors
28
+ class PostgreSQL
29
+ def visit_Arel_Nodes_ArrayAny o
30
+ "#{visit o.left} && #{visit o.right}"
31
+ end
32
+
33
+ def visit_Arel_Nodes_ArrayAll o
34
+ "#{visit o.left} @> #{visit o.right}"
35
+ end
36
+
37
+ def visit_Arel_Nodes_ArrayIncluded o
38
+ "#{visit o.left} <@ #{visit o.right}"
39
+ end
40
+
41
+ def visit_PGArrays_PgArray o
42
+ @connection.quote_array_by_base_type(o, o.base_type)
43
+ end
44
+
45
+ alias :visit_PGArrays_PgAny :visit_PGArrays_PgArray
46
+ alias :visit_PGArrays_PgAll :visit_PGArrays_PgArray
47
+ alias :visit_PGArrays_PgIncluded :visit_PGArrays_PgArray
48
+ end
49
+ end
50
+ end
51
+
52
+ if ActiveRecord::VERSION::STRING < '3.1'
53
+ module ActiveRecord
54
+ class PredicateBuilder
55
+ def build_from_hash(attributes, default_table)
56
+ predicates = attributes.map do |column, value|
57
+ table = default_table
58
+
59
+ if value.is_a?(Hash)
60
+ table = Arel::Table.new(column, :engine => @engine)
61
+ build_from_hash(value, table)
62
+ else
63
+ column = column.to_s
64
+
65
+ if column.include?('.')
66
+ table_name, column = column.split('.', 2)
67
+ table = Arel::Table.new(table_name, :engine => @engine)
68
+ end
69
+
70
+ attribute = table[column] || Arel::Attribute.new(table, column)
71
+
72
+ case value
73
+ when PGArrays::PgAny
74
+ attribute.ar_any(value)
75
+ when PGArrays::PgAll
76
+ attribute.ar_all(value)
77
+ when PGArrays::PgIncludes
78
+ attribute.ar_included(value)
79
+ when PGArrays::PgArray
80
+ attribute.eq(value)
81
+ when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation
82
+ values = value.to_a.map { |x|
83
+ x.is_a?(ActiveRecord::Base) ? x.id : x
84
+ }
85
+ attribute.in(values)
86
+ when Range, Arel::Relation
87
+ attribute.in(value)
88
+ when ActiveRecord::Base
89
+ attribute.eq(value.id)
90
+ when Class
91
+ # FIXME: I think we need to deprecate this behavior
92
+ attribute.eq(value.name)
93
+ else
94
+ attribute.eq(value)
95
+ end
96
+ end
97
+ end
98
+
99
+ predicates.flatten
100
+ end
101
+ end
102
+ end
103
+ else
104
+ module ActiveRecord
105
+ class PredicateBuilder
106
+ def self.build_from_hash(engine, attributes, default_table)
107
+ predicates = attributes.map do |column, value|
108
+ table = default_table
109
+
110
+ if value.is_a?(Hash)
111
+ table = Arel::Table.new(column, engine)
112
+ build_from_hash(engine, value, table)
113
+ else
114
+ column = column.to_s
115
+
116
+ if column.include?('.')
117
+ table_name, column = column.split('.', 2)
118
+ table = Arel::Table.new(table_name, engine)
119
+ end
120
+
121
+ attribute = table[column.to_sym]
122
+
123
+ case value
124
+ when PGArrays::PgAny
125
+ attribute.ar_any(value)
126
+ when PGArrays::PgAll
127
+ attribute.ar_all(value)
128
+ when PGArrays::PgIncludes
129
+ attribute.ar_included(value)
130
+ when PGArrays::PgArray
131
+ attribute.eq(value)
132
+ when ActiveRecord::Relation
133
+ value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
134
+ attribute.in(value.arel.ast)
135
+ when Array, ActiveRecord::Associations::CollectionProxy
136
+ values = value.to_a.map { |x|
137
+ x.is_a?(ActiveRecord::Base) ? x.id : x
138
+ }
139
+
140
+ ranges, values = values.partition{|v| v.is_a?(Range) || v.is_a?(Arel::Relation)}
141
+ predicates = ranges.map{|range| attribute.in(range)}
142
+
143
+ predicates << if values.include?(nil)
144
+ values = values.compact
145
+ if values.empty?
146
+ attribute.eq nil
147
+ else
148
+ attribute.in(values.compact).or attribute.eq(nil)
149
+ end
150
+ else
151
+ attribute.in(values)
152
+ end
153
+
154
+ predicates.inject{|composite, predicate| composite.or(predicate)}
155
+ when Range, Arel::Relation
156
+ attribute.in(value)
157
+ when ActiveRecord::Base
158
+ attribute.eq(value.id)
159
+ when Class
160
+ # FIXME: I think we need to deprecate this behavior
161
+ attribute.eq(value.name)
162
+ else
163
+ attribute.eq(value)
164
+ end
165
+ end
166
+ end
167
+
168
+ predicates.flatten
169
+ end
170
+ end
171
+ end
172
+ end