perpetuity-postgres 0.0.1

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