sqlpostgres 1.2.6 → 1.3.0
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.
- data/Changelog.md +18 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +12 -0
- data/README.rdoc +5 -2
- data/Rakefile +5 -24
- data/VERSION +1 -1
- data/lib/sqlpostgres/Connection.rb +8 -3
- data/lib/sqlpostgres/Cursor.rb +2 -2
- data/lib/sqlpostgres/Insert.rb +1 -1
- data/lib/sqlpostgres/PgBit.rb +1 -1
- data/lib/sqlpostgres/PgBox.rb +1 -1
- data/lib/sqlpostgres/PgCidr.rb +1 -1
- data/lib/sqlpostgres/PgCircle.rb +1 -1
- data/lib/sqlpostgres/PgInet.rb +1 -1
- data/lib/sqlpostgres/PgInterval.rb +1 -1
- data/lib/sqlpostgres/PgLineSegment.rb +1 -1
- data/lib/sqlpostgres/PgMacAddr.rb +1 -1
- data/lib/sqlpostgres/PgPath.rb +1 -1
- data/lib/sqlpostgres/PgPoint.rb +1 -1
- data/lib/sqlpostgres/PgPolygon.rb +1 -1
- data/lib/sqlpostgres/PgTime.rb +1 -1
- data/lib/sqlpostgres/PgTimeWithTimeZone.rb +1 -1
- data/lib/sqlpostgres/PgTimestamp.rb +1 -1
- data/lib/sqlpostgres/PgTwoPoints.rb +1 -1
- data/lib/sqlpostgres/PgWrapper.rb +1 -1
- data/lib/sqlpostgres/Select.rb +25 -25
- data/lib/sqlpostgres/Translate.rb +7 -29
- data/lib/sqlpostgres/Update.rb +1 -1
- data/rake_tasks/db.rake +17 -0
- data/rake_tasks/default.rake +1 -0
- data/rake_tasks/jeweler.rake +18 -0
- data/rake_tasks/test.rake +2 -0
- data/rake_tasks/test_spec.rake +3 -0
- data/rake_tasks/test_unit.rake +4 -0
- data/spec/Translate_spec.rb +533 -0
- data/spec/config/.gitignore +1 -0
- data/spec/config/config.yml +10 -0
- data/spec/config/database.yml.template +6 -0
- data/spec/connection_spec.rb +515 -0
- data/spec/cursor_spec.rb +288 -0
- data/spec/lib/database_config.rb +33 -0
- data/spec/lib/database_server.rb +42 -0
- data/spec/lib/postgres_template.rb +60 -0
- data/spec/lib/target_database_servers.rb +55 -0
- data/spec/lib/temporary_table.rb +45 -0
- data/spec/lib/test_config.rb +24 -0
- data/spec/lib/test_connection.rb +29 -0
- data/spec/lib/test_database.rb +57 -0
- data/spec/roundtrip_spec.rb +582 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/all_characters.rb +18 -0
- data/spec/support/clear_default_connection.rb +5 -0
- data/spec/support/temporary_table.rb +24 -0
- data/spec/support/test_connections.rb +10 -0
- data/sqlpostgres.gemspec +35 -4
- data/test/Connection.test.rb +7 -5
- data/test/Select.test.rb +1 -1
- data/test/TestConfig.rb +9 -0
- data/test/TestUtil.rb +17 -3
- metadata +66 -9
- data/test/Translate.test.rb +0 -354
- data/test/roundtrip.test.rb +0 -565
data/spec/cursor_spec.rb
ADDED
@@ -0,0 +1,288 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module SqlPostgres
|
4
|
+
|
5
|
+
describe Cursor do
|
6
|
+
|
7
|
+
CURSOR_DOES_NOT_EXIST = [PG::Error, /cursor.*does not exist/]
|
8
|
+
|
9
|
+
let(:select_statement) do
|
10
|
+
sql = Select.new
|
11
|
+
sql.select('i')
|
12
|
+
sql.from('table1')
|
13
|
+
sql
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:hold) {nil}
|
17
|
+
let(:scroll) {nil}
|
18
|
+
|
19
|
+
def make_cursor
|
20
|
+
cursor = Cursor.new('cursor1',
|
21
|
+
select_statement,
|
22
|
+
{
|
23
|
+
:hold => hold,
|
24
|
+
:scroll => scroll,
|
25
|
+
},
|
26
|
+
connection)
|
27
|
+
end
|
28
|
+
|
29
|
+
def make_cursor_in_transaction
|
30
|
+
cursor = nil
|
31
|
+
Transaction.new(connection) do
|
32
|
+
cursor = make_cursor
|
33
|
+
end
|
34
|
+
cursor
|
35
|
+
end
|
36
|
+
|
37
|
+
let(:cursor) {make_cursor}
|
38
|
+
|
39
|
+
shared_context 'table for cursor test' do |table_name|
|
40
|
+
|
41
|
+
include_context('temporary table',
|
42
|
+
:table_name => table_name,
|
43
|
+
:columns => ['i int'])
|
44
|
+
|
45
|
+
before(:each) do
|
46
|
+
5.times do |i|
|
47
|
+
sql = Insert.new(table_name, connection)
|
48
|
+
sql.insert('i', i)
|
49
|
+
sql.exec
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
shared_context 'table1 for cursor test' do
|
56
|
+
include_context 'table for cursor test', 'table1'
|
57
|
+
end
|
58
|
+
|
59
|
+
shared_context 'table2 for cursor test' do
|
60
|
+
include_context 'table for cursor test', 'table2'
|
61
|
+
end
|
62
|
+
|
63
|
+
describe 'scroll' do
|
64
|
+
|
65
|
+
include_context 'table1 for cursor test'
|
66
|
+
include_context 'table2 for cursor test'
|
67
|
+
|
68
|
+
let(:select_statement) do
|
69
|
+
sql = Select.new
|
70
|
+
sql.select('i')
|
71
|
+
sql.from('table1')
|
72
|
+
sql.join_using('inner', 'table2', 'i')
|
73
|
+
sql.where('i % 2 = 0')
|
74
|
+
sql
|
75
|
+
end
|
76
|
+
|
77
|
+
shared_examples_for 'is a scroll cursor' do
|
78
|
+
test_connection do |test_connection|
|
79
|
+
let(:connection) {test_connection}
|
80
|
+
specify do
|
81
|
+
Transaction.new(connection) do
|
82
|
+
cursor.fetch.should == [{'i' => 0}]
|
83
|
+
cursor.fetch.should == [{'i' => 2}]
|
84
|
+
cursor.fetch('PRIOR').should == [{'i' => 0}]
|
85
|
+
cursor.fetch('PRIOR').should == []
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
shared_examples_for 'is a no scroll cursor' do
|
92
|
+
test_connection do |test_context|
|
93
|
+
let(:connection) {test_connection}
|
94
|
+
specify do
|
95
|
+
Transaction.new(connection) do
|
96
|
+
expect {
|
97
|
+
cursor.fetch('PRIOR')
|
98
|
+
}.to raise_error PG::Error, /cursor can only scan forward/
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context '(on)' do
|
105
|
+
let(:scroll) {true}
|
106
|
+
it_behaves_like 'is a scroll cursor'
|
107
|
+
end
|
108
|
+
|
109
|
+
context '(off)' do
|
110
|
+
let(:scroll) {false}
|
111
|
+
it_behaves_like 'is a no scroll cursor'
|
112
|
+
end
|
113
|
+
|
114
|
+
context '(default)' do
|
115
|
+
let(:scroll) {nil}
|
116
|
+
it_behaves_like 'is a no scroll cursor'
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
describe 'hold' do
|
122
|
+
|
123
|
+
include_context 'table1 for cursor test'
|
124
|
+
|
125
|
+
let(:cursor) {make_cursor_in_transaction}
|
126
|
+
|
127
|
+
shared_examples 'cursor persists after transaction' do
|
128
|
+
test_connection do |test_context|
|
129
|
+
let(:connection) {test_connection}
|
130
|
+
specify do
|
131
|
+
cursor = make_cursor_in_transaction
|
132
|
+
cursor.fetch.should == [{'i' => 0}]
|
133
|
+
cursor.close
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
shared_examples_for 'cursor closed when transaction ends' do
|
139
|
+
test_connection do |test_context|
|
140
|
+
let(:connection) {test_connection}
|
141
|
+
specify do
|
142
|
+
cursor = make_cursor_in_transaction
|
143
|
+
expect {
|
144
|
+
cursor.fetch
|
145
|
+
}.to raise_error *CURSOR_DOES_NOT_EXIST
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context '(on)' do
|
151
|
+
let(:hold) {true}
|
152
|
+
it_behaves_like 'cursor persists after transaction'
|
153
|
+
end
|
154
|
+
|
155
|
+
context '(off)' do
|
156
|
+
let(:hold) {false}
|
157
|
+
it_behaves_like 'cursor closed when transaction ends'
|
158
|
+
end
|
159
|
+
|
160
|
+
context '(default)' do
|
161
|
+
let(:hold) {nil}
|
162
|
+
it_behaves_like 'cursor closed when transaction ends'
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
describe '#initialize' do
|
168
|
+
|
169
|
+
context '(taking block)' do
|
170
|
+
|
171
|
+
test_connection do |test_context|
|
172
|
+
let(:connection) {test_connection}
|
173
|
+
|
174
|
+
include_context 'table1 for cursor test', test_connection
|
175
|
+
|
176
|
+
specify do
|
177
|
+
Transaction.new(connection) do
|
178
|
+
Cursor.new('cursor1',
|
179
|
+
select_statement,
|
180
|
+
{},
|
181
|
+
connection) do |cursor|
|
182
|
+
cursor.fetch.should == [{'i' => 0}]
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
describe '#close' do
|
195
|
+
|
196
|
+
include_context 'table1 for cursor test'
|
197
|
+
|
198
|
+
test_connections.each do |test_context, test_connection|
|
199
|
+
context test_context do
|
200
|
+
let(:connection) {test_connection}
|
201
|
+
|
202
|
+
it 'cannot be used after being closed' do
|
203
|
+
Transaction.new(connection) do
|
204
|
+
cursor = make_cursor
|
205
|
+
cursor.close
|
206
|
+
expect {
|
207
|
+
cursor.fetch
|
208
|
+
}.to raise_error *CURSOR_DOES_NOT_EXIST
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
describe '#move' do
|
219
|
+
|
220
|
+
test_connections.each do |test_context, test_connection|
|
221
|
+
context test_context do
|
222
|
+
let(:connection) {test_connection}
|
223
|
+
|
224
|
+
include_context 'table1 for cursor test', test_connection
|
225
|
+
|
226
|
+
specify do
|
227
|
+
Transaction.new(connection) do
|
228
|
+
cursor = make_cursor
|
229
|
+
cursor.move('absolute 2')
|
230
|
+
cursor.fetch.should == [{'i' => 2}]
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
|
239
|
+
describe '#fetch' do
|
240
|
+
|
241
|
+
context '(default)' do
|
242
|
+
|
243
|
+
test_connection do |test_context|
|
244
|
+
let(:connection) {test_connection}
|
245
|
+
|
246
|
+
include_context 'table1 for cursor test', test_connection
|
247
|
+
|
248
|
+
specify do
|
249
|
+
Transaction.new(connection) do
|
250
|
+
cursor.fetch.should == [{'i' => 0}]
|
251
|
+
cursor.fetch.should == [{'i' => 1}]
|
252
|
+
cursor.fetch.should == [{'i' => 2}]
|
253
|
+
cursor.fetch.should == [{'i' => 3}]
|
254
|
+
cursor.fetch.should == [{'i' => 4}]
|
255
|
+
cursor.fetch.should == []
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
262
|
+
|
263
|
+
context '(with count)' do
|
264
|
+
|
265
|
+
test_connection do |test_context|
|
266
|
+
let(:connection) {test_connection}
|
267
|
+
|
268
|
+
include_context 'table1 for cursor test', test_connection
|
269
|
+
|
270
|
+
specify do
|
271
|
+
Transaction.new(connection) do
|
272
|
+
cursor = Cursor.new('cursor1', select_statement, {}, connection)
|
273
|
+
cursor.fetch(2).should == [{'i' => 0}, {'i' => 1}]
|
274
|
+
cursor.fetch(2).should == [{'i' => 2}, {'i' => 3}]
|
275
|
+
cursor.fetch(2).should == [{'i' => 4}]
|
276
|
+
cursor.fetch.should == []
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
end
|
281
|
+
|
282
|
+
end
|
283
|
+
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module TestSupport
|
4
|
+
class DatabaseConfig
|
5
|
+
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@config = load_config
|
10
|
+
end
|
11
|
+
|
12
|
+
def_delegator :@config, :map
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
PATH = File.expand_path('../config/database.yml',
|
17
|
+
File.dirname(__FILE__))
|
18
|
+
TEMPLATE_PATH = PATH + '.template'
|
19
|
+
|
20
|
+
def load_config
|
21
|
+
YAML.load_file(PATH)
|
22
|
+
rescue Errno::ENOENT
|
23
|
+
print_config_instructions
|
24
|
+
raise 'Missing database config'
|
25
|
+
end
|
26
|
+
|
27
|
+
def print_config_instructions
|
28
|
+
puts "Missing config at #{PATH}"
|
29
|
+
puts "Please create it by copying and editing #{TEMPLATE_PATH}"
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.expand_path('postgres_template', File.dirname(__FILE__))
|
2
|
+
require File.expand_path('test_database', File.dirname(__FILE__))
|
3
|
+
|
4
|
+
module TestSupport
|
5
|
+
class DatabaseServer
|
6
|
+
|
7
|
+
attr_reader :test_databases
|
8
|
+
|
9
|
+
def initialize(name, connection_args, encodings)
|
10
|
+
@server_name = name
|
11
|
+
@encodings = encodings
|
12
|
+
@connection_args = connection_args
|
13
|
+
@template = PostgresTemplate.new(@server_name, connection_args)
|
14
|
+
@test_databases = test_databases
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_connections
|
18
|
+
@test_databases.map(&:test_connections).flatten
|
19
|
+
end
|
20
|
+
|
21
|
+
def drop_databases
|
22
|
+
test_databases.each do |test_database|
|
23
|
+
test_database.drop(@template)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_databases
|
28
|
+
test_databases.each do |test_database|
|
29
|
+
test_database.create(@template)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def test_databases
|
36
|
+
@encodings.map do |encoding|
|
37
|
+
TestDatabase.new(@server_name, @connection_args, encoding)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require File.expand_path('../../lib/sqlpostgres', File.dirname(__FILE__))
|
2
|
+
require File.expand_path('test_database', File.dirname(__FILE__))
|
3
|
+
|
4
|
+
module TestSupport
|
5
|
+
class PostgresTemplate
|
6
|
+
|
7
|
+
def initialize(server_name, connection_args)
|
8
|
+
@server_name = server_name
|
9
|
+
@connection_args = connection_args
|
10
|
+
@connection = template_connection
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_database(db_name, database_encoding)
|
14
|
+
return if db_exists?(db_name)
|
15
|
+
puts "Creating database #{qualified_db_name(db_name)}"
|
16
|
+
create_db(db_name, database_encoding)
|
17
|
+
end
|
18
|
+
|
19
|
+
def drop_database(db_name)
|
20
|
+
return unless db_exists?(db_name)
|
21
|
+
unless db_name =~ /^#{TestDatabase::NAME_PREFIX}/
|
22
|
+
raise "Refusing to drop database #{qualified_db_name(db_name)}"
|
23
|
+
end
|
24
|
+
puts "Dropping database #{qualified_db_name(db_name)}"
|
25
|
+
@connection.exec("drop database #{db_name}")
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def qualified_db_name(db_name)
|
31
|
+
[@server_name, db_name].join('/')
|
32
|
+
end
|
33
|
+
|
34
|
+
def db_exists?(db_name)
|
35
|
+
sql = SqlPostgres::Select.new(@connection)
|
36
|
+
sql.select_literal(1)
|
37
|
+
sql.from('pg_database')
|
38
|
+
sql.where(['datname = %s', db_name])
|
39
|
+
!sql.exec.empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_db(db_name, database_encoding)
|
43
|
+
statement = [
|
44
|
+
'create database', db_name,
|
45
|
+
"with encoding '#{database_encoding}'",
|
46
|
+
'template template0',
|
47
|
+
].join(' ')
|
48
|
+
@connection.exec(statement)
|
49
|
+
end
|
50
|
+
|
51
|
+
def template_connection
|
52
|
+
SqlPostgres::Connection.new(template_connection_args)
|
53
|
+
end
|
54
|
+
|
55
|
+
def template_connection_args
|
56
|
+
@connection_args.merge(:db_name => 'template1')
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'memoizer'
|
2
|
+
require 'singleton'
|
3
|
+
require File.expand_path('database_config', File.dirname(__FILE__))
|
4
|
+
require File.expand_path('database_server', File.dirname(__FILE__))
|
5
|
+
require File.expand_path('test_config', File.dirname(__FILE__))
|
6
|
+
|
7
|
+
module TestSupport
|
8
|
+
class TargetDatabaseServers
|
9
|
+
|
10
|
+
include Memoizer
|
11
|
+
include Singleton
|
12
|
+
|
13
|
+
def test_connections
|
14
|
+
database_servers.map(&:test_connections).flatten.map do |test_connection|
|
15
|
+
[test_connection.context, test_connection.connection]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
memoize :test_connections
|
19
|
+
|
20
|
+
def test_connection
|
21
|
+
test_connections.last
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_databases
|
25
|
+
database_servers.each(&:create_databases)
|
26
|
+
end
|
27
|
+
|
28
|
+
def drop_databases
|
29
|
+
database_servers.each(&:drop_databases)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def database_servers
|
35
|
+
database_config.map do |config_name, config|
|
36
|
+
DatabaseServer.new(config_name, config, encodings)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def encodings
|
41
|
+
test_config['encodings']
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_config
|
45
|
+
TestConfig.new
|
46
|
+
end
|
47
|
+
memoize :test_config
|
48
|
+
|
49
|
+
def database_config
|
50
|
+
DatabaseConfig.new
|
51
|
+
end
|
52
|
+
memoize :database_config
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|