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 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