perpetuity-postgres 0.0.4 → 0.0.5
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/.travis.yml +13 -0
- data/CHANGELOG.md +9 -0
- data/Rakefile +5 -0
- data/lib/perpetuity/postgres/index.rb +5 -1
- data/lib/perpetuity/postgres/serialized_data.rb +5 -0
- data/lib/perpetuity/postgres/serializer.rb +2 -0
- data/lib/perpetuity/postgres/table/attribute.rb +14 -19
- data/lib/perpetuity/postgres/version.rb +1 -1
- data/lib/perpetuity/postgres.rb +34 -8
- data/spec/perpetuity/postgres/index_spec.rb +3 -3
- data/spec/perpetuity/postgres/serialized_data_spec.rb +5 -0
- data/spec/perpetuity/postgres/serializer_spec.rb +9 -4
- data/spec/perpetuity/postgres/table/attribute_spec.rb +8 -2
- data/spec/perpetuity/postgres/table_spec.rb +1 -1
- data/spec/perpetuity/postgres_spec.rb +23 -8
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 558b498d177b994d2d6ff89101aba27d040a4fdf
|
4
|
+
data.tar.gz: e69cfe6e5454170c6565c7018eb002f5d2e3e161
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 39653126b7754933a22d9ba8fd8dac82436c71340c298954245783a84bd1868c6a566937ead0d5379d300fd98becd7c773c9f143d1db43f9bcf9e8879817647d
|
7
|
+
data.tar.gz: 4bb4d6b025ed091a7b975b379d45bd7514215960771fc1b2f3a5ac4fc9cbd03cfa63e264057d36ea7d64f944ec61cc066dc3c10b798ac2d1d81533c4db7fe821
|
data/.travis.yml
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## Version 0.0.5
|
2
|
+
|
3
|
+
- Name indexes explicitly based on table and attributes. Previously we were counting on the PostgreSQL naming convention, but that could break in future versions of Postgres.
|
4
|
+
- Fixed a bug that was causing duplicate indexes to be built
|
5
|
+
- Deserialize booleans. They should be declared in the mapper as `TrueClass` or `FalseClass`.
|
6
|
+
- Fixed a bug that choked when you tried to save an attribute for an object if that column was not yet added.
|
7
|
+
- Allow deleting an array of objects
|
8
|
+
- Removed VARCHAR conversion. VARCHAR and TEXT are stored exactly the same in PostgreSQL. There's no reason to limit yourself to a VARCHAR unless you're using table constraints, which are far more flexible.
|
9
|
+
|
1
10
|
## Version 0.0.4
|
2
11
|
|
3
12
|
- Set minimum logging from the database to warnings. This silences info messages like those from `DROP TABLE IF NOT EXISTS ...` when the table doesn't exist.
|
data/Rakefile
CHANGED
@@ -49,7 +49,7 @@ module Perpetuity
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def table
|
52
|
-
name.gsub("_#{attributes.map(&:to_s).join('_')}
|
52
|
+
name.gsub("_#{attributes.map(&:to_s).join('_')}_index", '')
|
53
53
|
end
|
54
54
|
|
55
55
|
def == other
|
@@ -62,6 +62,10 @@ module Perpetuity
|
|
62
62
|
def eql? other
|
63
63
|
self == other
|
64
64
|
end
|
65
|
+
|
66
|
+
def hash
|
67
|
+
name.to_s.hash
|
68
|
+
end
|
65
69
|
end
|
66
70
|
end
|
67
71
|
end
|
@@ -15,6 +15,11 @@ module Perpetuity
|
|
15
15
|
"(#{column_names.join(',')}) VALUES #{value_strings}"
|
16
16
|
end
|
17
17
|
|
18
|
+
def [] key
|
19
|
+
index = column_names.index(key.to_s)
|
20
|
+
values.first[index]
|
21
|
+
end
|
22
|
+
|
18
23
|
def []= column, value
|
19
24
|
value = TextValue.new(value)
|
20
25
|
if column_names.include? column
|
@@ -9,6 +9,19 @@ module Perpetuity
|
|
9
9
|
NoDefaultValue = Module.new
|
10
10
|
UUID = Module.new
|
11
11
|
|
12
|
+
SQL_TYPE_MAP = {
|
13
|
+
String => 'TEXT',
|
14
|
+
Integer => 'BIGINT',
|
15
|
+
Fixnum => 'BIGINT',
|
16
|
+
Bignum => 'NUMERIC',
|
17
|
+
BigDecimal => 'NUMERIC',
|
18
|
+
Float => 'FLOAT',
|
19
|
+
UUID => 'UUID',
|
20
|
+
Time => 'TIMESTAMPTZ',
|
21
|
+
TrueClass => 'BOOLEAN',
|
22
|
+
FalseClass => 'BOOLEAN'
|
23
|
+
}.tap{|m| m.default = 'JSON' }
|
24
|
+
|
12
25
|
def initialize name, type, options={}
|
13
26
|
@name = name
|
14
27
|
@type = type
|
@@ -18,25 +31,7 @@ module Perpetuity
|
|
18
31
|
end
|
19
32
|
|
20
33
|
def sql_type
|
21
|
-
|
22
|
-
if max_length
|
23
|
-
"VARCHAR(#{max_length})"
|
24
|
-
else
|
25
|
-
'TEXT'
|
26
|
-
end
|
27
|
-
elsif type == Integer or type == Fixnum
|
28
|
-
'BIGINT'
|
29
|
-
elsif type == Bignum or type == BigDecimal
|
30
|
-
'NUMERIC'
|
31
|
-
elsif type == Float
|
32
|
-
'FLOAT'
|
33
|
-
elsif type == UUID
|
34
|
-
'UUID'
|
35
|
-
elsif type == Time
|
36
|
-
'TIMESTAMPTZ'
|
37
|
-
else
|
38
|
-
'JSON'
|
39
|
-
end
|
34
|
+
SQL_TYPE_MAP[type]
|
40
35
|
end
|
41
36
|
|
42
37
|
def sql_declaration
|
data/lib/perpetuity/postgres.rb
CHANGED
@@ -70,11 +70,23 @@ module Perpetuity
|
|
70
70
|
raise
|
71
71
|
end
|
72
72
|
|
73
|
-
def delete
|
73
|
+
def delete ids, klass
|
74
|
+
ids = Array(ids)
|
74
75
|
table = TableName.new(klass)
|
75
|
-
|
76
|
-
|
77
|
-
|
76
|
+
|
77
|
+
if ids.one?
|
78
|
+
id_string = TextValue.new(ids.first)
|
79
|
+
sql = "DELETE FROM #{table} WHERE id = #{id_string}"
|
80
|
+
|
81
|
+
connection.execute(sql).to_a
|
82
|
+
elsif ids.none?
|
83
|
+
# Do nothing, we weren't given anything to delete
|
84
|
+
else
|
85
|
+
id_string = ids.map { |id| TextValue.new(id) }
|
86
|
+
sql = "DELETE FROM #{table} WHERE id IN (#{id_string.join(',')})"
|
87
|
+
|
88
|
+
connection.execute(sql).to_a
|
89
|
+
end
|
78
90
|
end
|
79
91
|
|
80
92
|
def count klass, query='TRUE', options={}, &block
|
@@ -128,10 +140,19 @@ module Perpetuity
|
|
128
140
|
def update klass, id, attributes
|
129
141
|
sql = SQLUpdate.new(klass, id, attributes).to_s
|
130
142
|
connection.execute(sql).to_a
|
143
|
+
rescue PG::UndefinedColumn => e
|
144
|
+
if e.message =~ /column "(.*)" of relation "(.*)" does not exist/
|
145
|
+
column_name = $1
|
146
|
+
table_name = $2
|
147
|
+
add_column table_name, column_name, [Attribute.new(column_name, attributes[column_name].class)]
|
148
|
+
retry
|
149
|
+
else
|
150
|
+
raise e
|
151
|
+
end
|
131
152
|
end
|
132
153
|
|
133
154
|
def index klass, attributes, options={}
|
134
|
-
name = "#{klass}_#{Array(attributes).map(&:name).join('_')}
|
155
|
+
name = "#{klass}_#{Array(attributes).map(&:name).join('_')}_index"
|
135
156
|
index = Index.new(name: name,
|
136
157
|
attributes: Array(attributes),
|
137
158
|
unique: !!options[:unique],
|
@@ -142,13 +163,18 @@ module Perpetuity
|
|
142
163
|
|
143
164
|
def indexes klass
|
144
165
|
@indexes ||= {}
|
145
|
-
@indexes[klass] ||=
|
166
|
+
@indexes[klass] ||= active_indexes(klass)
|
146
167
|
end
|
147
168
|
|
148
169
|
def activate_index! index
|
170
|
+
return if index.active?
|
171
|
+
|
172
|
+
attribute_names = index.attribute_names.join('_')
|
173
|
+
index_name = "#{index.table}_#{attribute_names}_index"
|
149
174
|
sql = "CREATE "
|
150
175
|
sql << "UNIQUE " if index.unique?
|
151
|
-
sql << "INDEX
|
176
|
+
sql << "INDEX \"#{index_name}\" "
|
177
|
+
sql << "ON #{TableName.new(index.table)} (#{attribute_names})"
|
152
178
|
connection.execute(sql)
|
153
179
|
index.activate!
|
154
180
|
rescue PG::UndefinedTable => e
|
@@ -168,7 +194,7 @@ module Perpetuity
|
|
168
194
|
pg_index.indisready AS active
|
169
195
|
FROM pg_class
|
170
196
|
INNER JOIN pg_index ON pg_class.oid = pg_index.indexrelid
|
171
|
-
WHERE pg_class.relname ~ '^#{table}.*
|
197
|
+
WHERE pg_class.relname ~ '^#{table}.*index$'
|
172
198
|
SQL
|
173
199
|
|
174
200
|
indexes = connection.execute(sql).map do |index|
|
@@ -5,7 +5,7 @@ module Perpetuity
|
|
5
5
|
describe Index do
|
6
6
|
it 'can be generated from SQL results' do
|
7
7
|
index_hash = {
|
8
|
-
"name"=>"
|
8
|
+
"name"=>"Object_id_name_index",
|
9
9
|
"attributes"=>"{id,name}",
|
10
10
|
"unique"=>"t",
|
11
11
|
"active"=>"t"
|
@@ -13,7 +13,7 @@ module Perpetuity
|
|
13
13
|
|
14
14
|
index = Index.from_sql(index_hash)
|
15
15
|
index.attribute_names.should == ['id', 'name']
|
16
|
-
index.name.should == '
|
16
|
+
index.name.should == 'Object_id_name_index'
|
17
17
|
index.table.should == 'Object'
|
18
18
|
index.should be_unique
|
19
19
|
index.should be_active
|
@@ -47,7 +47,7 @@ module Perpetuity
|
|
47
47
|
it 'is not equal to an index with different attributes' do
|
48
48
|
new_index = Index.new(attributes: [:lol],
|
49
49
|
name: name,
|
50
|
-
unique:
|
50
|
+
unique: unique)
|
51
51
|
|
52
52
|
new_index.should_not == index
|
53
53
|
end
|
@@ -55,6 +55,11 @@ module Perpetuity
|
|
55
55
|
[['name', "'Jamie'"], ['age', 31]]
|
56
56
|
end
|
57
57
|
|
58
|
+
it 'accesses values like a hash' do
|
59
|
+
serialized['age'].should == 31
|
60
|
+
serialized[:age].should == 31
|
61
|
+
end
|
62
|
+
|
58
63
|
it 'equals another with the same data' do
|
59
64
|
original = SerializedData.new([:a, :b], [1, 2])
|
60
65
|
duplicate = SerializedData.new([:a, :b], [1, 2])
|
@@ -125,12 +125,13 @@ module Perpetuity
|
|
125
125
|
|
126
126
|
let(:article_class) do
|
127
127
|
Class.new do
|
128
|
-
attr_reader :title, :body, :views, :published_at
|
128
|
+
attr_reader :title, :body, :views, :published_at, :published
|
129
129
|
def initialize attributes={}
|
130
130
|
@title = attributes[:title]
|
131
131
|
@body = attributes[:body]
|
132
132
|
@views = attributes.fetch(:views) { 0 }
|
133
133
|
@published_at = attributes.fetch(:published_at) { Time.now }
|
134
|
+
@published = attributes.fetch(:published) { false }
|
134
135
|
end
|
135
136
|
|
136
137
|
def == other
|
@@ -138,7 +139,8 @@ module Perpetuity
|
|
138
139
|
other.title == title &&
|
139
140
|
other.body == body &&
|
140
141
|
other.views == views &&
|
141
|
-
other.published_at == published_at
|
142
|
+
other.published_at == published_at &&
|
143
|
+
other.published == published
|
142
144
|
end
|
143
145
|
end
|
144
146
|
end
|
@@ -151,6 +153,7 @@ module Perpetuity
|
|
151
153
|
attribute :body, type: String
|
152
154
|
attribute :views, type: Integer
|
153
155
|
attribute :published_at, type: Time
|
156
|
+
attribute :published, type: TrueClass
|
154
157
|
end.new(registry)
|
155
158
|
end
|
156
159
|
|
@@ -161,14 +164,16 @@ module Perpetuity
|
|
161
164
|
'title' => 'Title',
|
162
165
|
'body' => 'Body',
|
163
166
|
'views' => '0',
|
164
|
-
'published_at' => '2013-01-02 03:04:05.123456-05'
|
167
|
+
'published_at' => '2013-01-02 03:04:05.123456-05',
|
168
|
+
'published' => 't'
|
165
169
|
}
|
166
170
|
|
167
171
|
article = article_class.new(
|
168
172
|
title: 'Title',
|
169
173
|
body: 'Body',
|
170
174
|
views: 0,
|
171
|
-
published_at: Time.new(2013, 1, 2, 3, 4, 5.123456, '-05:00')
|
175
|
+
published_at: Time.new(2013, 1, 2, 3, 4, 5.123456, '-05:00'),
|
176
|
+
published: true
|
172
177
|
)
|
173
178
|
serializer.unserialize(serialized_article).should == article
|
174
179
|
end
|
@@ -5,7 +5,7 @@ module Perpetuity
|
|
5
5
|
class Postgres
|
6
6
|
class Table
|
7
7
|
describe Attribute do
|
8
|
-
let(:title) { Attribute.new('title', String
|
8
|
+
let(:title) { Attribute.new('title', String) }
|
9
9
|
|
10
10
|
it 'knows its name' do
|
11
11
|
title.name.should == 'title'
|
@@ -44,7 +44,6 @@ module Perpetuity
|
|
44
44
|
let(:body) { Attribute.new('body', String, default: 'foo') }
|
45
45
|
|
46
46
|
it 'converts to the proper SQL type' do
|
47
|
-
title.sql_type.should == 'VARCHAR(40)'
|
48
47
|
body.sql_type.should == 'TEXT'
|
49
48
|
end
|
50
49
|
|
@@ -81,6 +80,13 @@ module Perpetuity
|
|
81
80
|
end
|
82
81
|
end
|
83
82
|
|
83
|
+
describe 'booleans' do
|
84
|
+
it 'is stored in a BOOLEAN column' do
|
85
|
+
Attribute.new(:true, TrueClass).sql_type.should == 'BOOLEAN'
|
86
|
+
Attribute.new(:false, FalseClass).sql_type.should == 'BOOLEAN'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
84
90
|
describe 'non-serializable types' do
|
85
91
|
let(:author) { Attribute.new('author', Object) }
|
86
92
|
|
@@ -26,7 +26,7 @@ module Perpetuity
|
|
26
26
|
|
27
27
|
it 'generates proper SQL to create itself' do
|
28
28
|
table.create_table_sql.should ==
|
29
|
-
'CREATE TABLE IF NOT EXISTS "Article" (id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), title
|
29
|
+
'CREATE TABLE IF NOT EXISTS "Article" (id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), title TEXT, body TEXT, author JSON, published_at TIMESTAMPTZ, views BIGINT)'
|
30
30
|
end
|
31
31
|
|
32
32
|
describe 'id column' do
|
@@ -141,12 +141,6 @@ module Perpetuity
|
|
141
141
|
postgres.retrieve('Article', 'TRUE').should == []
|
142
142
|
end
|
143
143
|
|
144
|
-
it 'deletes all records' do
|
145
|
-
postgres.insert 'User', data, attributes
|
146
|
-
postgres.delete_all 'User'
|
147
|
-
postgres.count('User').should == 0
|
148
|
-
end
|
149
|
-
|
150
144
|
it 'updates a specific record' do
|
151
145
|
id = postgres.insert('User', data, attributes).first
|
152
146
|
postgres.update 'User', id, name: 'foo'
|
@@ -155,9 +149,30 @@ module Perpetuity
|
|
155
149
|
retrieved.first['name'].should == 'foo'
|
156
150
|
end
|
157
151
|
|
158
|
-
it '
|
152
|
+
it 'updates a record when a column does not currently exist' do
|
159
153
|
id = postgres.insert('User', data, attributes).first
|
160
|
-
|
154
|
+
postgres.update 'User', id, Postgres::SerializedData.new(['foo'], ["'bar'"])
|
155
|
+
|
156
|
+
retrieved = postgres.retrieve('User', "id = '#{id}'")
|
157
|
+
retrieved.first['foo'].should == 'bar'
|
158
|
+
end
|
159
|
+
|
160
|
+
describe 'deletion' do
|
161
|
+
it 'deletes all records' do
|
162
|
+
postgres.insert 'User', data, attributes
|
163
|
+
postgres.delete_all 'User'
|
164
|
+
postgres.count('User').should == 0
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'deletes a record with a specific id' do
|
168
|
+
id = postgres.insert('User', data, attributes).first
|
169
|
+
expect { postgres.delete id, 'User' }.to change { postgres.count 'User' }.by -1
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'deletes records with specific ids' do
|
173
|
+
ids = Array.new(3) { postgres.insert('User', data, attributes).first }
|
174
|
+
expect { postgres.delete ids.take(2), 'User' }.to change { postgres.count 'User' }.by -2
|
175
|
+
end
|
161
176
|
end
|
162
177
|
|
163
178
|
describe 'incrementing/decrementing' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: perpetuity-postgres
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jamie Gaskins
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -88,6 +88,7 @@ extensions: []
|
|
88
88
|
extra_rdoc_files: []
|
89
89
|
files:
|
90
90
|
- .gitignore
|
91
|
+
- .travis.yml
|
91
92
|
- CHANGELOG.md
|
92
93
|
- Gemfile
|
93
94
|
- LICENSE.txt
|
@@ -178,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
178
179
|
version: '0'
|
179
180
|
requirements: []
|
180
181
|
rubyforge_project:
|
181
|
-
rubygems_version: 2.
|
182
|
+
rubygems_version: 2.2.2
|
182
183
|
signing_key:
|
183
184
|
specification_version: 4
|
184
185
|
summary: PostgreSQL adapter for Perpetuity
|