perpetuity-postgres 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|