perpetuity-postgres 0.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +34 -0
- data/Rakefile +1 -0
- data/lib/perpetuity/postgres/boolean_value.rb +17 -0
- data/lib/perpetuity/postgres/connection.rb +78 -0
- data/lib/perpetuity/postgres/connection_pool.rb +39 -0
- data/lib/perpetuity/postgres/expression.rb +21 -0
- data/lib/perpetuity/postgres/json_array.rb +31 -0
- data/lib/perpetuity/postgres/json_hash.rb +55 -0
- data/lib/perpetuity/postgres/json_string_value.rb +17 -0
- data/lib/perpetuity/postgres/negated_query.rb +21 -0
- data/lib/perpetuity/postgres/nil_query.rb +9 -0
- data/lib/perpetuity/postgres/null_value.rb +9 -0
- data/lib/perpetuity/postgres/numeric_value.rb +17 -0
- data/lib/perpetuity/postgres/query.rb +34 -0
- data/lib/perpetuity/postgres/query_attribute.rb +33 -0
- data/lib/perpetuity/postgres/query_expression.rb +90 -0
- data/lib/perpetuity/postgres/query_intersection.rb +26 -0
- data/lib/perpetuity/postgres/query_union.rb +26 -0
- data/lib/perpetuity/postgres/serialized_data.rb +63 -0
- data/lib/perpetuity/postgres/serializer.rb +179 -0
- data/lib/perpetuity/postgres/sql_select.rb +71 -0
- data/lib/perpetuity/postgres/sql_update.rb +28 -0
- data/lib/perpetuity/postgres/sql_value.rb +40 -0
- data/lib/perpetuity/postgres/table/attribute.rb +60 -0
- data/lib/perpetuity/postgres/table.rb +36 -0
- data/lib/perpetuity/postgres/table_name.rb +21 -0
- data/lib/perpetuity/postgres/text_value.rb +14 -0
- data/lib/perpetuity/postgres/timestamp_value.rb +68 -0
- data/lib/perpetuity/postgres/value_with_attribute.rb +19 -0
- data/lib/perpetuity/postgres/version.rb +5 -0
- data/lib/perpetuity/postgres.rb +170 -0
- data/perpetuity-postgres.gemspec +26 -0
- data/spec/perpetuity/postgres/boolean_value_spec.rb +15 -0
- data/spec/perpetuity/postgres/connection_pool_spec.rb +55 -0
- data/spec/perpetuity/postgres/connection_spec.rb +30 -0
- data/spec/perpetuity/postgres/expression_spec.rb +13 -0
- data/spec/perpetuity/postgres/json_array_spec.rb +31 -0
- data/spec/perpetuity/postgres/json_hash_spec.rb +39 -0
- data/spec/perpetuity/postgres/json_string_value_spec.rb +11 -0
- data/spec/perpetuity/postgres/negated_query_spec.rb +39 -0
- data/spec/perpetuity/postgres/null_value_spec.rb +11 -0
- data/spec/perpetuity/postgres/numeric_value_spec.rb +12 -0
- data/spec/perpetuity/postgres/query_attribute_spec.rb +48 -0
- data/spec/perpetuity/postgres/query_expression_spec.rb +110 -0
- data/spec/perpetuity/postgres/query_intersection_spec.rb +23 -0
- data/spec/perpetuity/postgres/query_spec.rb +23 -0
- data/spec/perpetuity/postgres/query_union_spec.rb +23 -0
- data/spec/perpetuity/postgres/serialized_data_spec.rb +69 -0
- data/spec/perpetuity/postgres/serializer_spec.rb +216 -0
- data/spec/perpetuity/postgres/sql_select_spec.rb +51 -0
- data/spec/perpetuity/postgres/sql_update_spec.rb +22 -0
- data/spec/perpetuity/postgres/sql_value_spec.rb +38 -0
- data/spec/perpetuity/postgres/table/attribute_spec.rb +82 -0
- data/spec/perpetuity/postgres/table_name_spec.rb +15 -0
- data/spec/perpetuity/postgres/table_spec.rb +43 -0
- data/spec/perpetuity/postgres/text_value_spec.rb +15 -0
- data/spec/perpetuity/postgres/timestamp_value_spec.rb +28 -0
- data/spec/perpetuity/postgres/value_with_attribute_spec.rb +34 -0
- data/spec/perpetuity/postgres_spec.rb +163 -0
- data/spec/support/test_classes/book.rb +16 -0
- data/spec/support/test_classes/person.rb +11 -0
- metadata +207 -0
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'perpetuity'
|
2
|
+
require 'json'
|
3
|
+
require 'perpetuity/postgres/connection_pool'
|
4
|
+
require 'perpetuity/postgres/serializer'
|
5
|
+
require 'perpetuity/postgres/query'
|
6
|
+
require 'perpetuity/postgres/negated_query'
|
7
|
+
require 'perpetuity/postgres/table'
|
8
|
+
require 'perpetuity/postgres/table/attribute'
|
9
|
+
require 'perpetuity/postgres/sql_select'
|
10
|
+
require 'perpetuity/postgres/sql_update'
|
11
|
+
|
12
|
+
module Perpetuity
|
13
|
+
class Postgres
|
14
|
+
attr_reader :host, :port, :db, :pool_size, :username, :password,
|
15
|
+
:connection
|
16
|
+
|
17
|
+
def initialize options
|
18
|
+
@host = options.fetch(:host) { 'localhost' }
|
19
|
+
@port = options.fetch(:port) { 5432 }
|
20
|
+
@db = options.fetch(:db)
|
21
|
+
@pool_size = options.fetch(:pool_size) { 5 }
|
22
|
+
@username = options.fetch(:username) { ENV['USER'] }
|
23
|
+
@password = options.fetch(:password) {}
|
24
|
+
|
25
|
+
@connection ||= ConnectionPool.new(
|
26
|
+
db: db,
|
27
|
+
host: host,
|
28
|
+
port: port,
|
29
|
+
username: username,
|
30
|
+
password: password,
|
31
|
+
pool_size: pool_size
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def insert klass, serialized_objects, attributes
|
36
|
+
table = TableName.new(klass)
|
37
|
+
data = serialized_objects.reduce(:+)
|
38
|
+
sql = "INSERT INTO #{table} #{data} RETURNING id"
|
39
|
+
|
40
|
+
results = connection.execute(sql).to_a
|
41
|
+
ids = results.map { |result| result['id'] }
|
42
|
+
|
43
|
+
ids
|
44
|
+
rescue PG::UndefinedTable => e # Table doesn't exist, so we create it.
|
45
|
+
retries ||= 0
|
46
|
+
retries += 1
|
47
|
+
create_table_with_attributes klass, attributes
|
48
|
+
retry unless retries > 1
|
49
|
+
raise e
|
50
|
+
end
|
51
|
+
|
52
|
+
def delete id, klass
|
53
|
+
table = TableName.new(klass)
|
54
|
+
id_string = TextValue.new(id)
|
55
|
+
sql = "DELETE FROM #{table} WHERE id = #{id_string}"
|
56
|
+
connection.execute(sql).to_a
|
57
|
+
end
|
58
|
+
|
59
|
+
def count klass, query='TRUE', options={}, &block
|
60
|
+
where = if block_given?
|
61
|
+
query(&block)
|
62
|
+
else
|
63
|
+
query
|
64
|
+
end
|
65
|
+
options = translate_options(options).merge(from: klass, where: where)
|
66
|
+
table = table_name(klass)
|
67
|
+
sql = select 'COUNT(*)', options
|
68
|
+
connection.execute(sql).to_a.first['count'].to_i
|
69
|
+
rescue PG::UndefinedTable
|
70
|
+
# Table does not exist, so there are 0 records
|
71
|
+
0
|
72
|
+
end
|
73
|
+
|
74
|
+
def find klass, id
|
75
|
+
retrieve(klass, query { |o| o.id == id }.to_db).first
|
76
|
+
end
|
77
|
+
|
78
|
+
def table_name klass
|
79
|
+
TableName.new(klass)
|
80
|
+
end
|
81
|
+
|
82
|
+
def delete_all klass
|
83
|
+
table = table_name(klass)
|
84
|
+
sql = "DELETE FROM #{table}"
|
85
|
+
connection.execute(sql)
|
86
|
+
rescue PG::UndefinedTable
|
87
|
+
# Do nothing. There is already nothing here.
|
88
|
+
end
|
89
|
+
|
90
|
+
def query &block
|
91
|
+
Query.new(&block)
|
92
|
+
end
|
93
|
+
|
94
|
+
def negate_query &block
|
95
|
+
NegatedQuery.new(&block)
|
96
|
+
end
|
97
|
+
|
98
|
+
def retrieve klass, criteria, options={}
|
99
|
+
options = translate_options(options).merge from: klass, where: criteria
|
100
|
+
|
101
|
+
sql = select options
|
102
|
+
connection.execute(sql).to_a
|
103
|
+
rescue PG::UndefinedTable
|
104
|
+
[]
|
105
|
+
end
|
106
|
+
|
107
|
+
def update klass, id, attributes
|
108
|
+
sql = SQLUpdate.new(klass, id, attributes).to_s
|
109
|
+
connection.execute(sql).to_a
|
110
|
+
end
|
111
|
+
|
112
|
+
def translate_options options
|
113
|
+
options = options.dup
|
114
|
+
if options[:attribute]
|
115
|
+
options[:order] = options.delete(:attribute)
|
116
|
+
if direction = options.delete(:direction)
|
117
|
+
direction = direction.to_s[/\w{1,2}sc/i]
|
118
|
+
options[:order] = { options[:order] => direction }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
if options[:skip]
|
122
|
+
options[:offset] = options.delete(:skip)
|
123
|
+
end
|
124
|
+
|
125
|
+
options
|
126
|
+
end
|
127
|
+
|
128
|
+
def select *args
|
129
|
+
SQLSelect.new(*args).to_s
|
130
|
+
end
|
131
|
+
|
132
|
+
def drop_table name
|
133
|
+
connection.execute "DROP TABLE IF EXISTS #{table_name(name)}"
|
134
|
+
end
|
135
|
+
|
136
|
+
def create_table name, attributes
|
137
|
+
connection.execute Table.new(name, attributes).create_table_sql
|
138
|
+
end
|
139
|
+
|
140
|
+
def has_table? name
|
141
|
+
connection.tables.include? name
|
142
|
+
end
|
143
|
+
|
144
|
+
def postgresify value
|
145
|
+
Serializer.new(nil).serialize_attribute value
|
146
|
+
end
|
147
|
+
|
148
|
+
def serialize object, mapper
|
149
|
+
Serializer.new(mapper).serialize object
|
150
|
+
end
|
151
|
+
|
152
|
+
def serialize_changed_attributes object, original, mapper
|
153
|
+
Serializer.new(mapper).serialize_changes object, original
|
154
|
+
end
|
155
|
+
|
156
|
+
def unserialize data, mapper
|
157
|
+
Serializer.new(mapper).unserialize data
|
158
|
+
end
|
159
|
+
|
160
|
+
def create_table_with_attributes klass, attributes
|
161
|
+
table_attributes = attributes.map do |attr|
|
162
|
+
name = attr.name
|
163
|
+
type = attr.type
|
164
|
+
options = attr.options
|
165
|
+
Table::Attribute.new name, type, options
|
166
|
+
end
|
167
|
+
create_table klass.to_s, table_attributes
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'perpetuity/postgres/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "perpetuity-postgres"
|
8
|
+
spec.version = Perpetuity::Postgres::VERSION
|
9
|
+
spec.authors = ["Jamie Gaskins"]
|
10
|
+
spec.email = ["jgaskins@gmail.com"]
|
11
|
+
spec.description = %q{PostgreSQL adapter for Perpetuity}
|
12
|
+
spec.summary = %q{PostgreSQL adapter for Perpetuity}
|
13
|
+
spec.homepage = "https://github.com/jgaskins/perpetuity-postgres"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rspec"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_runtime_dependency "perpetuity", "~> 1.0.0.beta"
|
25
|
+
spec.add_runtime_dependency "pg"
|
26
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'perpetuity/postgres/boolean_value'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
class Postgres
|
5
|
+
describe BooleanValue do
|
6
|
+
it 'serializes true into a Postgres true value' do
|
7
|
+
BooleanValue.new(true).to_s.should == 'TRUE'
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'serializes false into a Postgres false value' do
|
11
|
+
BooleanValue.new(false).to_s.should == 'FALSE'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'perpetuity/postgres/connection_pool'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
class Postgres
|
5
|
+
describe ConnectionPool do
|
6
|
+
let(:pool) { ConnectionPool.new }
|
7
|
+
|
8
|
+
it 'defaults to 5 connections' do
|
9
|
+
pool.should have(5).connections
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'lending a connection' do
|
13
|
+
it 'executes the given block' do
|
14
|
+
expect { |probe| pool.lend_connection(&probe) }.to yield_control
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'does not yield when there is no block given' do
|
18
|
+
pool.lend_connection
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'lends a connection for the duration of a block' do
|
22
|
+
pool.lend_connection do |connection|
|
23
|
+
pool.should have(4).connections
|
24
|
+
end
|
25
|
+
pool.should have(5).connections
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'returns the value of the block' do
|
29
|
+
pool.lend_connection { 1 }.should == 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'executes a given SQL statement' do
|
34
|
+
sql = "SELECT TRUE"
|
35
|
+
Connection.any_instance.should_receive(:execute)
|
36
|
+
.with(sql)
|
37
|
+
pool.execute sql
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'passes the tables message to a connection' do
|
41
|
+
Connection.any_instance.should_receive(:tables)
|
42
|
+
pool.tables
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'cycles through each connection round-robin style' do
|
46
|
+
connections = []
|
47
|
+
pool.size.times do
|
48
|
+
pool.lend_connection { |c| connections << c }
|
49
|
+
end
|
50
|
+
|
51
|
+
connections.uniq.should have(pool.size).items
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'perpetuity/postgres/connection'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
class Postgres
|
5
|
+
describe Connection do
|
6
|
+
let(:connection) { Connection.new(db: 'perpetuity_gem_test') }
|
7
|
+
|
8
|
+
it 'sanitizes the options for the pg gem' do
|
9
|
+
options = { db: 'db', username: 'user' }
|
10
|
+
connection.sanitize_options(options).should == {
|
11
|
+
dbname: 'db',
|
12
|
+
user: 'user'
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'is only activated when it is used' do
|
17
|
+
connection.should_not be_active
|
18
|
+
connection.connect
|
19
|
+
connection.should be_active
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'executes SQL' do
|
23
|
+
connection.execute 'CREATE TABLE IF NOT EXISTS abcdefg (name text)'
|
24
|
+
connection.tables.should include 'abcdefg'
|
25
|
+
connection.execute 'DROP TABLE IF EXISTS abcdefg'
|
26
|
+
connection.tables.should_not include 'abcdefg'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'perpetuity/postgres/expression'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
class Postgres
|
5
|
+
describe Expression do
|
6
|
+
let(:expression) { Expression.new('uuid_generate_v4()') }
|
7
|
+
|
8
|
+
it 'passes the expression straight into SQL' do
|
9
|
+
expression.to_sql.should == 'uuid_generate_v4()'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'perpetuity/postgres/json_array'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
class Postgres
|
5
|
+
describe JSONArray do
|
6
|
+
it 'serializes empty arrays' do
|
7
|
+
JSONArray.new([]).to_s.should == "'[]'"
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'serializes arrays of numeric values' do
|
11
|
+
JSONArray.new([1,2,3]).to_s.should == "'[1,2,3]'"
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'serializes arrays of strings' do
|
15
|
+
JSONArray.new(%w(foo bar baz)).to_s.should == %q{'["foo","bar","baz"]'}
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'serializes arrays of hashes' do
|
19
|
+
JSONArray.new([{a: 1}, {b: 2}]).to_s.should == %q{'[{"a":1},{"b":2}]'}
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'serializes arrays of JSONHashes' do
|
23
|
+
JSONArray.new([JSONHash.new(a: 1)]).to_s.should == %q{'[{"a":1}]'}
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'serializes elements of arrays' do
|
27
|
+
JSONArray.new([1,'a']).to_s.should == %q{'[1,"a"]'}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'perpetuity/postgres/json_hash'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
class Postgres
|
5
|
+
describe JSONHash do
|
6
|
+
it 'serializes empty hashes' do
|
7
|
+
JSONHash.new({}).to_s.should == "'{}'"
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'serializes hashes with string elements' do
|
11
|
+
JSONHash.new({a: 'b'}).to_s.should == %q{'{"a":"b"}'}
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'serializes hashes with numeric elements' do
|
15
|
+
JSONHash.new({a: 1}).to_s.should == %q{'{"a":1}'}
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'serializes hashes with boolean elements' do
|
19
|
+
JSONHash.new({a: true, b: false}).to_s.should == %q('{"a":true,"b":false}')
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'does not surround the an inner serialized value with quotes' do
|
23
|
+
JSONHash.new({a: 1}, :inner).to_s.should == %q[{"a":1}]
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'serializes hashes with multiple entries' do
|
27
|
+
JSONHash.new({a: 1, b: 'c'}).to_s.should == %q{'{"a":1,"b":"c"}'}
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'converts back to a hash' do
|
31
|
+
JSONHash.new({a: 1}).to_hash.should == { a: 1 }
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'is equal to an identical hash' do
|
35
|
+
JSONHash.new(a: 1).should == JSONHash.new(a: 1)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'perpetuity/postgres/negated_query'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
class Postgres
|
5
|
+
describe NegatedQuery do
|
6
|
+
it 'negates equality' do
|
7
|
+
NegatedQuery.new { |o| o.name == 'foo' }.to_db.should == "NOT (name = 'foo')"
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'negates regex matching' do
|
11
|
+
NegatedQuery.new { |o| o.name =~ /foo/ }.to_db.should == "NOT (name ~ 'foo')"
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'negates case-insensitive regex matching' do
|
15
|
+
NegatedQuery.new { |o| o.name =~ /foo/i }.to_db.should == "NOT (name ~* 'foo')"
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'negates inequality' do
|
19
|
+
NegatedQuery.new { |o| o.name != /foo/i }.to_db.should == "NOT (name != 'foo')"
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'negates greater-than' do
|
23
|
+
NegatedQuery.new { |o| o.age > 1 }.to_db.should == "NOT (age > 1)"
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'negates greater-than-or-equal' do
|
27
|
+
NegatedQuery.new { |o| o.age >= 1 }.to_db.should == "NOT (age >= 1)"
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'negates less-than' do
|
31
|
+
NegatedQuery.new { |o| o.age < 1 }.to_db.should == "NOT (age < 1)"
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'negates less-than-or-equal' do
|
35
|
+
NegatedQuery.new { |o| o.age <= 1 }.to_db.should == "NOT (age <= 1)"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'perpetuity/postgres/query_attribute'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
class Postgres
|
5
|
+
describe QueryAttribute do
|
6
|
+
let(:attribute) { QueryAttribute.new :attribute_name }
|
7
|
+
subject { attribute }
|
8
|
+
|
9
|
+
its(:name) { should == :attribute_name }
|
10
|
+
|
11
|
+
it 'checks for equality' do
|
12
|
+
(attribute == 1).should be_a QueryExpression
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'checks for less than' do
|
16
|
+
(attribute < 1).should be_a QueryExpression
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'checks for <=' do
|
20
|
+
(attribute <= 1).should be_a QueryExpression
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'checks for greater than' do
|
24
|
+
(attribute > 1).should be_a QueryExpression
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'checks for >=' do
|
28
|
+
(attribute >= 1).should be_a QueryExpression
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'checks for inequality' do
|
32
|
+
(attribute != 1).should be_a QueryExpression
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'checks for regexp matches' do
|
36
|
+
(attribute =~ /value/).should be_a QueryExpression
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'checks for inclusion' do
|
40
|
+
(attribute.in [1, 2, 3]).should be_a QueryExpression
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'checks for nil' do
|
44
|
+
attribute.nil?.should be_a QueryExpression
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'perpetuity/postgres/query_expression'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
class Postgres
|
5
|
+
describe QueryExpression do
|
6
|
+
let(:expression) { QueryExpression.new :attribute, :==, :value }
|
7
|
+
subject { expression }
|
8
|
+
|
9
|
+
describe 'translation to SQL expressions' do
|
10
|
+
it 'translates equality to symbol by comparing with a string' do
|
11
|
+
expression.to_db.should == "attribute = 'value'"
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'translates equality to strings' do
|
15
|
+
expression.value = expression.value.to_s
|
16
|
+
expression.to_db.should == "attribute = 'value'"
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'removes SQL injection from strings' do
|
20
|
+
expression.value = "' OR 1; --"
|
21
|
+
expression.to_db.should == "attribute = ''' OR 1; --'"
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'translates equality to numbers' do
|
25
|
+
expression.value = 1
|
26
|
+
expression.to_db.should == 'attribute = 1'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'less-than expression' do
|
30
|
+
expression.comparator = :<
|
31
|
+
expression.to_db.should == "attribute < 'value'"
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'less-than-or-equal-to expression' do
|
35
|
+
expression.comparator = :<=
|
36
|
+
expression.to_db.should == "attribute <= 'value'"
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'greater-than expression' do
|
40
|
+
expression.comparator = :>
|
41
|
+
expression.to_db.should == "attribute > 'value'"
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'greater-than-or-equal-to expression' do
|
45
|
+
expression.comparator = :>=
|
46
|
+
expression.to_db.should == "attribute >= 'value'"
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'not-equal' do
|
50
|
+
expression.comparator = :!=
|
51
|
+
expression.to_db.should == "attribute != 'value'"
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'checks for inclusion' do
|
55
|
+
expression.comparator = :in
|
56
|
+
expression.value = [1, 2, 3]
|
57
|
+
expression.to_db.should == "attribute IN (1,2,3)"
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'checks for inclusion of strings' do
|
61
|
+
expression.comparator = :in
|
62
|
+
expression.value = ['abc', '123']
|
63
|
+
expression.to_db.should == "attribute IN ('abc','123')"
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'checks for regexp matching' do
|
67
|
+
expression.comparator = :=~
|
68
|
+
expression.value = /value/
|
69
|
+
expression.to_db.should == "attribute ~ 'value'"
|
70
|
+
|
71
|
+
expression.value = /value/i
|
72
|
+
expression.to_db.should == "attribute ~* 'value'"
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'checks for nil' do
|
76
|
+
expression.value = nil
|
77
|
+
expression.to_db.should == "attribute IS NULL"
|
78
|
+
|
79
|
+
expression.comparator = :!=
|
80
|
+
expression.to_db.should == "attribute IS NOT NULL"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe 'unions' do
|
85
|
+
let(:lhs) { QueryExpression.new :first, :==, :one }
|
86
|
+
let(:rhs) { QueryExpression.new :second, :==, :two }
|
87
|
+
|
88
|
+
it 'converts | to an $or query' do
|
89
|
+
(lhs | rhs).to_db.should == "(first = 'one' OR second = 'two')"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe 'intersections' do
|
94
|
+
let(:lhs) { QueryExpression.new :first, :==, :one }
|
95
|
+
let(:rhs) { QueryExpression.new :second, :==, :two }
|
96
|
+
|
97
|
+
it 'converts & to an $and query' do
|
98
|
+
(lhs & rhs).to_db.should == "(first = 'one' AND second = 'two')"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe 'values' do
|
103
|
+
it 'compares against times' do
|
104
|
+
expression.value = Time.new(2013, 1, 2, 3, 4, 5.1234567, '-05:00')
|
105
|
+
expression.to_db.should == "attribute = '2013-01-02 03:04:05.123456-0500'::timestamptz"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'perpetuity/postgres/query_intersection'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
class Postgres
|
5
|
+
describe QueryIntersection do
|
6
|
+
let(:lhs) { double('LHS', to_db: 'left = 1') }
|
7
|
+
let(:rhs) { double('RHS', to_db: 'right = 2') }
|
8
|
+
let(:intersection) { QueryIntersection.new(lhs, rhs) }
|
9
|
+
|
10
|
+
it 'converts to a SQL "AND" expression' do
|
11
|
+
intersection.to_db.should == '(left = 1 AND right = 2)'
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'allows intersections to have other intersections' do
|
15
|
+
(intersection&intersection).to_db.should == '((left = 1 AND right = 2) AND (left = 1 AND right = 2))'
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'allows intersections to have unions' do
|
19
|
+
(intersection|intersection).to_db.should == '((left = 1 AND right = 2) OR (left = 1 AND right = 2))'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'perpetuity/postgres/query'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
class Postgres
|
5
|
+
describe Query do
|
6
|
+
let(:query) { Query.new { |o| o.name == 'foo' } }
|
7
|
+
|
8
|
+
it 'generates an equality statement' do
|
9
|
+
query.to_db.should == "name = 'foo'"
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'automatically converts to a string' do
|
13
|
+
q = ''
|
14
|
+
q << query
|
15
|
+
q.should == "name = 'foo'"
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns TRUE with no block passed' do
|
19
|
+
Query.new.to_db.should == 'TRUE'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|