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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +34 -0
  6. data/Rakefile +1 -0
  7. data/lib/perpetuity/postgres/boolean_value.rb +17 -0
  8. data/lib/perpetuity/postgres/connection.rb +78 -0
  9. data/lib/perpetuity/postgres/connection_pool.rb +39 -0
  10. data/lib/perpetuity/postgres/expression.rb +21 -0
  11. data/lib/perpetuity/postgres/json_array.rb +31 -0
  12. data/lib/perpetuity/postgres/json_hash.rb +55 -0
  13. data/lib/perpetuity/postgres/json_string_value.rb +17 -0
  14. data/lib/perpetuity/postgres/negated_query.rb +21 -0
  15. data/lib/perpetuity/postgres/nil_query.rb +9 -0
  16. data/lib/perpetuity/postgres/null_value.rb +9 -0
  17. data/lib/perpetuity/postgres/numeric_value.rb +17 -0
  18. data/lib/perpetuity/postgres/query.rb +34 -0
  19. data/lib/perpetuity/postgres/query_attribute.rb +33 -0
  20. data/lib/perpetuity/postgres/query_expression.rb +90 -0
  21. data/lib/perpetuity/postgres/query_intersection.rb +26 -0
  22. data/lib/perpetuity/postgres/query_union.rb +26 -0
  23. data/lib/perpetuity/postgres/serialized_data.rb +63 -0
  24. data/lib/perpetuity/postgres/serializer.rb +179 -0
  25. data/lib/perpetuity/postgres/sql_select.rb +71 -0
  26. data/lib/perpetuity/postgres/sql_update.rb +28 -0
  27. data/lib/perpetuity/postgres/sql_value.rb +40 -0
  28. data/lib/perpetuity/postgres/table/attribute.rb +60 -0
  29. data/lib/perpetuity/postgres/table.rb +36 -0
  30. data/lib/perpetuity/postgres/table_name.rb +21 -0
  31. data/lib/perpetuity/postgres/text_value.rb +14 -0
  32. data/lib/perpetuity/postgres/timestamp_value.rb +68 -0
  33. data/lib/perpetuity/postgres/value_with_attribute.rb +19 -0
  34. data/lib/perpetuity/postgres/version.rb +5 -0
  35. data/lib/perpetuity/postgres.rb +170 -0
  36. data/perpetuity-postgres.gemspec +26 -0
  37. data/spec/perpetuity/postgres/boolean_value_spec.rb +15 -0
  38. data/spec/perpetuity/postgres/connection_pool_spec.rb +55 -0
  39. data/spec/perpetuity/postgres/connection_spec.rb +30 -0
  40. data/spec/perpetuity/postgres/expression_spec.rb +13 -0
  41. data/spec/perpetuity/postgres/json_array_spec.rb +31 -0
  42. data/spec/perpetuity/postgres/json_hash_spec.rb +39 -0
  43. data/spec/perpetuity/postgres/json_string_value_spec.rb +11 -0
  44. data/spec/perpetuity/postgres/negated_query_spec.rb +39 -0
  45. data/spec/perpetuity/postgres/null_value_spec.rb +11 -0
  46. data/spec/perpetuity/postgres/numeric_value_spec.rb +12 -0
  47. data/spec/perpetuity/postgres/query_attribute_spec.rb +48 -0
  48. data/spec/perpetuity/postgres/query_expression_spec.rb +110 -0
  49. data/spec/perpetuity/postgres/query_intersection_spec.rb +23 -0
  50. data/spec/perpetuity/postgres/query_spec.rb +23 -0
  51. data/spec/perpetuity/postgres/query_union_spec.rb +23 -0
  52. data/spec/perpetuity/postgres/serialized_data_spec.rb +69 -0
  53. data/spec/perpetuity/postgres/serializer_spec.rb +216 -0
  54. data/spec/perpetuity/postgres/sql_select_spec.rb +51 -0
  55. data/spec/perpetuity/postgres/sql_update_spec.rb +22 -0
  56. data/spec/perpetuity/postgres/sql_value_spec.rb +38 -0
  57. data/spec/perpetuity/postgres/table/attribute_spec.rb +82 -0
  58. data/spec/perpetuity/postgres/table_name_spec.rb +15 -0
  59. data/spec/perpetuity/postgres/table_spec.rb +43 -0
  60. data/spec/perpetuity/postgres/text_value_spec.rb +15 -0
  61. data/spec/perpetuity/postgres/timestamp_value_spec.rb +28 -0
  62. data/spec/perpetuity/postgres/value_with_attribute_spec.rb +34 -0
  63. data/spec/perpetuity/postgres_spec.rb +163 -0
  64. data/spec/support/test_classes/book.rb +16 -0
  65. data/spec/support/test_classes/person.rb +11 -0
  66. 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,11 @@
1
+ require 'perpetuity/postgres/json_string_value'
2
+
3
+ module Perpetuity
4
+ class Postgres
5
+ describe JSONStringValue do
6
+ it 'serializes into a JSON string value' do
7
+ JSONStringValue.new('Jamie').to_s.should == '"Jamie"'
8
+ end
9
+ end
10
+ end
11
+ 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,11 @@
1
+ require 'perpetuity/postgres/null_value'
2
+
3
+ module Perpetuity
4
+ class Postgres
5
+ describe NullValue do
6
+ it 'serializes into a Postgres NULL value' do
7
+ NullValue.new.to_s.should == 'NULL'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ require 'perpetuity/postgres/numeric_value'
2
+
3
+ module Perpetuity
4
+ class Postgres
5
+ describe NumericValue do
6
+ it 'serializes into a Postgres-compatible number value' do
7
+ NumericValue.new(1).to_s.should == '1'
8
+ end
9
+ end
10
+ end
11
+ end
12
+
@@ -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