activerecord-postgres-hstore 0.1.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.3.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{activerecord-postgres-hstore}
8
- s.version = "0.1.2"
8
+ s.version = "0.3.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Juan Maiz", "Diogo Biazus"]
12
- s.date = %q{2011-08-02}
12
+ s.date = %q{2011-12-28}
13
13
  s.description = %q{This gem adds support for the postgres hstore type. It is the _just right_ alternative for storing hashes instead of using seralization or dynamic tables.}
14
14
  s.email = %q{juanmaiz@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -84,6 +84,7 @@ Gem::Specification.new do |s|
84
84
  "lib/activerecord-postgres-hstore/hash.rb",
85
85
  "lib/activerecord-postgres-hstore/string.rb",
86
86
  "lib/templates/setup_hstore.rb",
87
+ "lib/templates/setup_hstore91.rb",
87
88
  "spec/activerecord-postgres-hstore_spec.rb",
88
89
  "spec/spec_helper.rb"
89
90
  ]
data/app/Gemfile CHANGED
@@ -2,7 +2,7 @@ source 'http://rubygems.org'
2
2
 
3
3
  gem 'rails', '3.0.0'
4
4
  gem 'pg'
5
- gem 'activerecord-postgres-hstore', '0.0.2'
5
+ gem 'activerecord-postgres-hstore', :path => '../'
6
6
 
7
7
  # Use unicorn as the web server
8
8
  # gem 'unicorn'
data/app/Gemfile.lock CHANGED
@@ -1,3 +1,10 @@
1
+ PATH
2
+ remote: ../
3
+ specs:
4
+ activerecord-postgres-hstore (0.1.2)
5
+ activerecord
6
+ rake
7
+
1
8
  GEM
2
9
  remote: http://rubygems.org/
3
10
  specs:
@@ -24,7 +31,6 @@ GEM
24
31
  activesupport (= 3.0.0)
25
32
  arel (~> 1.0.0)
26
33
  tzinfo (~> 0.3.23)
27
- activerecord-postgres-hstore (0.0.2)
28
34
  activeresource (3.0.0)
29
35
  activemodel (= 3.0.0)
30
36
  activesupport (= 3.0.0)
@@ -70,6 +76,6 @@ PLATFORMS
70
76
  ruby
71
77
 
72
78
  DEPENDENCIES
73
- activerecord-postgres-hstore (= 0.0.2)
79
+ activerecord-postgres-hstore!
74
80
  pg
75
81
  rails (= 3.0.0)
@@ -38,5 +38,6 @@ module Benchs
38
38
 
39
39
  # Configure sensitive parameters which will be filtered from the log file.
40
40
  config.filter_parameters += [:password]
41
+ config.active_record.schema_format = :sql
41
42
  end
42
43
  end
@@ -267,6 +267,15 @@ CREATE FUNCTION hs_contains(hstore, hstore) RETURNS boolean
267
267
  AS '$libdir/hstore', 'hs_contains';
268
268
 
269
269
 
270
+ --
271
+ -- Name: hstore(text, text); Type: FUNCTION; Schema: public; Owner: -
272
+ --
273
+
274
+ CREATE FUNCTION hstore(text, text) RETURNS hstore
275
+ LANGUAGE c IMMUTABLE
276
+ AS '$libdir/hstore', 'tconvert';
277
+
278
+
270
279
  --
271
280
  -- Name: isdefined(hstore, text); Type: FUNCTION; Schema: public; Owner: -
272
281
  --
@@ -553,13 +562,6 @@ ALTER TABLE ONLY foos
553
562
  ADD CONSTRAINT foos_pkey PRIMARY KEY (id);
554
563
 
555
564
 
556
- --
557
- -- Name: bars_gin_data; Type: INDEX; Schema: public; Owner: -; Tablespace:
558
- --
559
-
560
- CREATE INDEX bars_gin_data ON bars USING gin (data);
561
-
562
-
563
565
  --
564
566
  -- Name: unique_schema_migrations; Type: INDEX; Schema: public; Owner: -; Tablespace:
565
567
  --
@@ -571,8 +573,8 @@ CREATE UNIQUE INDEX unique_schema_migrations ON schema_migrations USING btree (v
571
573
  -- PostgreSQL database dump complete
572
574
  --
573
575
 
574
- INSERT INTO schema_migrations (version) VALUES ('20100906191151');
575
-
576
576
  INSERT INTO schema_migrations (version) VALUES ('20100906191457');
577
577
 
578
- INSERT INTO schema_migrations (version) VALUES ('20100906191506');
578
+ INSERT INTO schema_migrations (version) VALUES ('20100906191506');
579
+
580
+ INSERT INTO schema_migrations (version) VALUES ('20100906191151');
data/app/db/schema.rb CHANGED
@@ -11,5 +11,17 @@
11
11
  # It's strongly recommended to check this file into your version control system.
12
12
 
13
13
  ActiveRecord::Schema.define(:version => 20100906191506) do
14
- execute File.read("#{Rails.root}/db/development_structure.sql")
14
+
15
+ create_table "bars", :force => true do |t|
16
+ t.hstore "data"
17
+ t.datetime "created_at"
18
+ t.datetime "updated_at"
19
+ end
20
+
21
+ create_table "foos", :force => true do |t|
22
+ t.text "data"
23
+ t.datetime "created_at"
24
+ t.datetime "updated_at"
25
+ end
26
+
15
27
  end
@@ -1,6 +1,7 @@
1
1
  require 'rails'
2
2
  require 'rails/generators'
3
3
  require 'rails/generators/migration'
4
+ require 'pg'
4
5
 
5
6
  # = Hstore Railtie
6
7
  #
@@ -39,10 +40,15 @@ class Hstore < Rails::Railtie
39
40
  end
40
41
 
41
42
  def create_migration_file
42
- migration_template 'setup_hstore.rb', 'db/migrate/setup_hstore.rb'
43
+ pgversion = ActiveRecord::Base.connection.send(:postgresql_version)
44
+ if pgversion < 90100
45
+ migration_template 'setup_hstore.rb', 'db/migrate/setup_hstore.rb'
46
+ else
47
+ migration_template 'setup_hstore91.rb', 'db/migrate/setup_hstore.rb'
48
+ end
43
49
  end
44
50
 
45
51
  end
46
52
  end
47
53
  require "activerecord-postgres-hstore/string"
48
- require "activerecord-postgres-hstore/hash"
54
+ require "activerecord-postgres-hstore/hash"
@@ -77,12 +77,10 @@ module ActiveRecord
77
77
  if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
78
78
  if include_readonly_attributes || (!include_readonly_attributes && !self.class.readonly_attributes.include?(name))
79
79
  value = read_attribute(name)
80
- if value && ((self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))) || value.is_a?(Hash) || value.is_a?(Array))
81
- if self.class.columns_hash[name].type == :hstore
82
- value = value.to_hstore # Done!
83
- else
84
- value = value.to_yaml
85
- end
80
+ if self.class.columns_hash[name].type == :hstore && value && value.is_a?(Hash)
81
+ value = value.to_hstore # Done!
82
+ elsif value && self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time) || value.is_a?(Hash) || value.is_a?(Array))
83
+ value = value.to_yaml
86
84
  end
87
85
  attrs[self.class.arel_table[name]] = value
88
86
  end
@@ -118,39 +116,36 @@ module ActiveRecord
118
116
  end
119
117
 
120
118
  class PostgreSQLColumn < Column
121
- alias :old_type_cast_code :type_cast_code
122
- alias :old_simplified_type :simplified_type
123
-
124
119
  # Does the type casting from hstore columns using String#from_hstore or Hash#from_hstore.
125
- def type_cast_code(var_name)
126
- type == :hstore ? "#{var_name}.from_hstore" : old_type_cast_code(var_name)
120
+ def type_cast_code_with_hstore(var_name)
121
+ type == :hstore ? "#{var_name}.from_hstore" : type_cast_code_without_hstore(var_name)
127
122
  end
128
123
 
129
124
  # Adds the hstore type for the column.
130
- def simplified_type(field_type)
131
- field_type =~ /^hstore$/ ? :hstore : old_simplified_type(field_type)
125
+ def simplified_type_with_hstore(field_type)
126
+ field_type == 'hstore' ? :hstore : simplified_type_without_hstore(field_type)
132
127
  end
133
-
128
+
129
+ alias_method_chain :type_cast_code, :hstore
130
+ alias_method_chain :simplified_type, :hstore
134
131
  end
135
132
 
136
133
  class PostgreSQLAdapter < AbstractAdapter
137
-
138
- alias :old_quote :quote
139
- alias :old_columns :columns
140
- alias :old_native_database_types :native_database_types
141
-
142
- def native_database_types
143
- old_native_database_types.merge({:hstore => { :name => "hstore" }})
134
+ def native_database_types_with_hstore
135
+ native_database_types_without_hstore.merge({:hstore => { :name => "hstore" }})
144
136
  end
145
137
 
146
138
  # Quotes correctly a hstore column value.
147
- def quote(value, column = nil)
148
- if value && column && column.sql_type =~ /^hstore$/
139
+ def quote_with_hstore(value, column = nil)
140
+ if value && column && column.sql_type == 'hstore'
149
141
  raise HstoreTypeMismatch, "#{column.name} must have a Hash or a valid hstore value (#{value})" unless value.kind_of?(Hash) || value.valid_hstore?
150
- return value.to_hstore
142
+ return quote_without_hstore(value.to_hstore, column)
151
143
  end
152
- old_quote(value,column)
144
+ quote_without_hstore(value,column)
153
145
  end
146
+
147
+ alias_method_chain :quote, :hstore
148
+ alias_method_chain :native_database_types, :hstore
154
149
  end
155
150
  end
156
151
  end
@@ -1,11 +1,24 @@
1
1
  class Hash
2
2
 
3
- # Generates a single quoted hstore string format. This is the format used
3
+ # Generates an hstore string format. This is the format used
4
4
  # to insert or update stuff in the database.
5
5
  def to_hstore
6
- return "''" if empty?
7
- #@todo DIOGO! Check security issues with this quoting pleaz
8
- map{|idx, val| "('#{idx.to_s.escape_quotes}'=>'#{val.to_s.escape_quotes}')" }.join(' || ')
6
+ return "" if empty?
7
+
8
+ map { |idx, val|
9
+ iv = [idx,val].map { |_|
10
+ e = _.to_s.gsub(/"/, '\"')
11
+ if _.nil?
12
+ 'NULL'
13
+ elsif e =~ /[,\s=>]/ || e.blank?
14
+ '"%s"' % e
15
+ else
16
+ e
17
+ end
18
+ }
19
+
20
+ "%s=>%s" % iv
21
+ } * ","
9
22
  end
10
23
 
11
24
  # If the method from_hstore is called in a Hash, it just returns self.
@@ -9,27 +9,35 @@ class String
9
9
  # Validates the hstore format. Valid formats are:
10
10
  # * An empty string
11
11
  # * A string like %("foo"=>"bar"). I'll call it a "double quoted hstore format".
12
- # * A string like %('foo'=>'bar'). I'll call it a "single quoted hstore format".
12
+ # * A string like %(foo=>bar). Postgres doesn't emit this but it does accept it as input, we should accept any input Postgres does
13
+
13
14
  def valid_hstore?
14
- return true if empty? || self == "''"
15
- # This is what comes from the database
16
- dbl_quotes_re = /"([^"]+)"=>"([^"]+)"/
17
- # TODO
18
- # This is what comes from the plugin
19
- # this is a big problem, 'cause regexes does not know how to count...
20
- # how should i very values quoted with two single quotes? using .+ sux.
21
- sngl_quotes_re = /'(.+)'=>'(.+)'/
22
- self.match(dbl_quotes_re) || self.match(sngl_quotes_re)
15
+ pair = hstore_pair
16
+ !!match(/^\s*(#{pair}\s*(,\s*#{pair})*)?\s*$/)
23
17
  end
24
18
 
25
19
  # Creates a hash from a valid double quoted hstore format, 'cause this is the format
26
20
  # that postgresql spits out.
27
21
  def from_hstore
28
- Hash[ scan(/"([^"]+)"=>"([^"]+)"/) ]
22
+ token_pairs = (scan(hstore_pair)).map { |k,v| [k,v =~ /^NULL$/i ? nil : v] }
23
+ token_pairs = token_pairs.map { |k,v|
24
+ [k,v].map { |t|
25
+ case t
26
+ when nil then t
27
+ when /^"(.*)"$/ then $1.gsub(/\\(.)/, '\1')
28
+ else t.gsub(/\\(.)/, '\1')
29
+ end
30
+ }
31
+ }
32
+ Hash[ token_pairs ]
29
33
  end
30
34
 
31
- def escape_quotes
32
- self.gsub(/'/,"''")
33
- end
35
+ private
34
36
 
37
+ def hstore_pair
38
+ quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
39
+ unquoted_string = /[^\s=,][^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
40
+ string = /(#{quoted_string}|#{unquoted_string})/
41
+ /#{string}\s*=>\s*#{string}/
42
+ end
35
43
  end
@@ -0,0 +1,9 @@
1
+ class SetupHstore < ActiveRecord::Migration
2
+ def self.up
3
+ execute "CREATE EXTENSION hstore"
4
+ end
5
+
6
+ def self.down
7
+ execute "DROP EXTENSION hstore"
8
+ end
9
+ end
@@ -1,9 +1,28 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
3
  describe "ActiverecordPostgresHstore" do
4
+ it "should recognize a valid hstore string" do
5
+ "".valid_hstore?.should be_true
6
+ "a=>b".valid_hstore?.should be_true
7
+ '"a"=>"b"'.valid_hstore?.should be_true
8
+ '"a" => "b"'.valid_hstore?.should be_true
9
+ '"a"=>"b","c"=>"d"'.valid_hstore?.should be_true
10
+ '"a"=>"b", "c"=>"d"'.valid_hstore?.should be_true
11
+ '"a" => "b", "c"=>"d"'.valid_hstore?.should be_true
12
+ '"a"=>"b","c" => "d"'.valid_hstore?.should be_true
13
+ 'k => v'.valid_hstore?.should be_true
14
+ 'foo => bar, baz => whatever'.valid_hstore?.should be_true
15
+ '"1-a" => "anything at all"'.valid_hstore?.should be_true
16
+ 'key => NULL'.valid_hstore?.should be_true
17
+ %q(c=>"}", "\"a\""=>"b \"a b").valid_hstore?.should be_true
18
+ end
19
+
20
+ it "should not recognize an invalid hstore string" do
21
+ '"a"=>"b",Hello?'.valid_hstore?.should be_false
22
+ end
4
23
 
5
- it "should convert hash to hstore string" do
6
- ["('a'=>'1') || ('b'=>'2')", "('b'=>'2') || ('a'=>'1')"].should include({:a => 1, :b => 2}.to_hstore)
24
+ it "should convert hash to hstore string and back (sort of)" do
25
+ {:a => 1, :b => 2}.to_hstore.from_hstore.should eq({"a" => "1", "b" => "2"})
7
26
  end
8
27
 
9
28
  it "should convert hstore string to hash" do
@@ -11,23 +30,53 @@ describe "ActiverecordPostgresHstore" do
11
30
  end
12
31
 
13
32
  it "should quote correctly" do
14
- {:a => "'a'"}.to_hstore.should eq("('a'=>'''a''')")
33
+ {:a => "'a'"}.to_hstore.should eq(%q(a=>'a'))
15
34
  end
16
35
 
17
36
  it "should quote keys correctly" do
18
- {"'a'" => "a"}.to_hstore.should eq("('''a'''=>'a')")
37
+ {"'a'" => "a"}.to_hstore.should eq(%q('a'=>a))
38
+ end
39
+
40
+ it "should preserve null values on store" do
41
+ # NULL=>b will be interpreted as the string pair "NULL"=>"b"
42
+
43
+ {'a' => nil,nil=>'b'}.to_hstore.should eq(%q(a=>NULL,NULL=>b))
44
+ end
45
+
46
+ it "should preserve null values on load" do
47
+ 'a=>null,b=>NuLl,c=>"NuLl",null=>c'.from_hstore.should eq({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'})
48
+ end
49
+
50
+ it "should quote tokens with nothing space comma equals or greaterthan" do
51
+ {' '=>''}.to_hstore.should eq(%q(" "=>""))
52
+ {','=>''}.to_hstore.should eq(%q(","=>""))
53
+ {'='=>''}.to_hstore.should eq(%q("="=>""))
54
+ {'>'=>''}.to_hstore.should eq(%q(">"=>""))
55
+ end
56
+
57
+ it "should unquote keys correctly with single quotes" do
58
+ "\"'a'\"=>\"a\"". from_hstore.should eq({"'a'" => "a"})
59
+ '\=a=>q=w'. from_hstore.should eq({"=a"=>"q=w"})
60
+ '"=a"=>q\=w'. from_hstore.should eq({"=a"=>"q=w"});
61
+ '"\"a"=>q>w'. from_hstore.should eq({"\"a"=>"q>w"});
62
+ '\"a=>q"w'. from_hstore.should eq({"\"a"=>"q\"w"})
63
+ end
64
+
65
+ it "should quote keys and values correctly with combinations of single and double quotes" do
66
+ { %q("a') => %q(b "a' b) }.to_hstore.should eq(%q(\"a'=>"b \"a' b"))
19
67
  end
20
68
 
21
- it "should unquote keys correctly" do
22
- "\"'a'\"=>\"a\"".from_hstore.should eq({"'a'" => "a"})
69
+ it "should unquote keys and values correctly with combinations of single and double quotes" do
70
+ %q("\"a'"=>"b \"a' b").from_hstore.should eq({%q("a') => %q(b "a' b)})
23
71
  end
24
72
 
25
73
  it "should convert empty hash" do
26
- {}.to_hstore.should eq("''")
74
+ {}.to_hstore.should eq("")
27
75
  end
28
76
 
29
77
  it "should convert empty string" do
30
78
  ''.from_hstore.should eq({})
79
+ ' '.from_hstore.should eq({})
31
80
  end
32
81
 
33
82
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-postgres-hstore
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 2
10
- version: 0.1.2
8
+ - 3
9
+ - 0
10
+ version: 0.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Juan Maiz
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2011-08-02 00:00:00 -03:00
19
+ date: 2011-12-28 00:00:00 -02:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -198,6 +198,7 @@ files:
198
198
  - lib/activerecord-postgres-hstore/hash.rb
199
199
  - lib/activerecord-postgres-hstore/string.rb
200
200
  - lib/templates/setup_hstore.rb
201
+ - lib/templates/setup_hstore91.rb
201
202
  - spec/activerecord-postgres-hstore_spec.rb
202
203
  - spec/spec_helper.rb
203
204
  has_rdoc: true