real_data_tests 0.3.14 → 0.3.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/real_data_tests/rspec_helper.rb +179 -49
- data/lib/real_data_tests/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fbf98b8c1c4b72e614a490731c40ff4f56c36eb14b4b13cf19fc13b7c3022fe3
|
4
|
+
data.tar.gz: '029a17e72f0a42f34eae153cbf4a212e185100027e438b8a9e48efb112b7e349'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 854233f093058fe510b0c37409a6a4c3445a2c65a70dc9c862d3a8eaec12a535ba435b354a5348b8e4f41bf15291caacabcb1d9b4274b506f02d1baeec97e2b0
|
7
|
+
data.tar.gz: ce700cb666714010f2622307da5321fac568eb32afbaf6795121d855e6e47409047192a0c38d3b0ed1f3b8e82d109988135c7c8b6a6cfb63c6e8e274c79a686e
|
@@ -1,5 +1,38 @@
|
|
1
1
|
module RealDataTests
|
2
2
|
module RSpecHelper
|
3
|
+
class SqlBlock
|
4
|
+
attr_reader :type, :content, :table_name
|
5
|
+
|
6
|
+
def initialize(content)
|
7
|
+
@content = content.strip
|
8
|
+
@type = determine_block_type
|
9
|
+
@table_name = extract_table_name if @type == :insert
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def determine_block_type
|
15
|
+
case @content
|
16
|
+
when /\AINSERT INTO/i
|
17
|
+
:insert
|
18
|
+
when /\ACOPY.*FROM stdin/i
|
19
|
+
:copy
|
20
|
+
when /\AALTER TABLE/i
|
21
|
+
:alter
|
22
|
+
when /\ASET/i
|
23
|
+
:set
|
24
|
+
else
|
25
|
+
:other
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def extract_table_name
|
30
|
+
if @content =~ /INSERT INTO\s+"?([^\s"(]+)"?\s/i
|
31
|
+
$1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
3
36
|
def load_real_test_data(name)
|
4
37
|
dump_path = File.join(RealDataTests.configuration.dump_path, "#{name}.sql")
|
5
38
|
raise Error, "Test data file not found: #{dump_path}" unless File.exist?(dump_path)
|
@@ -17,11 +50,13 @@ module RealDataTests
|
|
17
50
|
end
|
18
51
|
end
|
19
52
|
|
20
|
-
# Native Ruby implementation
|
21
53
|
def load_real_test_data_native(name)
|
22
54
|
dump_path = File.join(RealDataTests.configuration.dump_path, "#{name}.sql")
|
23
55
|
raise Error, "Test data file not found: #{dump_path}" unless File.exist?(dump_path)
|
24
56
|
|
57
|
+
sql_content = File.read(dump_path)
|
58
|
+
blocks = parse_sql_blocks(sql_content)
|
59
|
+
|
25
60
|
ActiveRecord::Base.transaction do
|
26
61
|
connection = ActiveRecord::Base.connection
|
27
62
|
|
@@ -29,17 +64,8 @@ module RealDataTests
|
|
29
64
|
connection.execute('SET session_replication_role = replica;')
|
30
65
|
|
31
66
|
begin
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
statements.each_with_index do |statement, index|
|
36
|
-
begin
|
37
|
-
cleaned_statement = clean_sql_statement(statement)
|
38
|
-
puts "Executing Statement ##{index + 1}: #{cleaned_statement}" # Debug log
|
39
|
-
connection.execute(cleaned_statement)
|
40
|
-
rescue ActiveRecord::StatementInvalid => e
|
41
|
-
raise Error, "Error executing statement ##{index + 1}: #{e.message}\nSQL: #{cleaned_statement}"
|
42
|
-
end
|
67
|
+
blocks.each_with_index do |block, index|
|
68
|
+
execute_block(block, index + 1, blocks.length)
|
43
69
|
end
|
44
70
|
ensure
|
45
71
|
connection.execute('SET session_replication_role = DEFAULT;')
|
@@ -64,6 +90,147 @@ module RealDataTests
|
|
64
90
|
options.join(" ")
|
65
91
|
end
|
66
92
|
|
93
|
+
class SqlBlock
|
94
|
+
attr_reader :type, :content, :table_name
|
95
|
+
|
96
|
+
def initialize(content)
|
97
|
+
@content = content.strip
|
98
|
+
@type = determine_block_type
|
99
|
+
@table_name = extract_table_name if @type == :insert
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def determine_block_type
|
105
|
+
if @content.match?(/\AINSERT INTO/i)
|
106
|
+
:insert
|
107
|
+
elsif @content.match?(/\ACOPY.*FROM stdin/i)
|
108
|
+
:copy
|
109
|
+
elsif @content.match?(/\AALTER TABLE/i)
|
110
|
+
:alter
|
111
|
+
elsif @content.match?(/\ASET/i)
|
112
|
+
:set
|
113
|
+
else
|
114
|
+
:other
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def extract_table_name
|
119
|
+
if @content =~ /INSERT INTO\s+"?([^\s"(]+)"?\s/i
|
120
|
+
$1
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def parse_sql_blocks(content)
|
126
|
+
blocks = []
|
127
|
+
current_block = []
|
128
|
+
in_copy_block = false
|
129
|
+
|
130
|
+
content.each_line do |line|
|
131
|
+
line = line.chomp
|
132
|
+
|
133
|
+
# Skip empty lines and comments unless in COPY block
|
134
|
+
next if !in_copy_block && (line.empty? || line.start_with?('--'))
|
135
|
+
|
136
|
+
# Handle start of COPY block
|
137
|
+
if !in_copy_block && line.upcase.match?(/\ACOPY.*FROM stdin/i)
|
138
|
+
current_block = [line]
|
139
|
+
in_copy_block = true
|
140
|
+
next
|
141
|
+
end
|
142
|
+
|
143
|
+
# Handle end of COPY block
|
144
|
+
if in_copy_block && line == '\\.'
|
145
|
+
current_block << line
|
146
|
+
blocks << SqlBlock.new(current_block.join("\n"))
|
147
|
+
current_block = []
|
148
|
+
in_copy_block = false
|
149
|
+
next
|
150
|
+
end
|
151
|
+
|
152
|
+
# Accumulate lines in COPY block
|
153
|
+
if in_copy_block
|
154
|
+
current_block << line
|
155
|
+
next
|
156
|
+
end
|
157
|
+
|
158
|
+
# Handle regular SQL statements
|
159
|
+
current_block << line
|
160
|
+
if line.end_with?(';')
|
161
|
+
blocks << SqlBlock.new(current_block.join("\n"))
|
162
|
+
current_block = []
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Handle any remaining block
|
167
|
+
blocks << SqlBlock.new(current_block.join("\n")) unless current_block.empty?
|
168
|
+
blocks
|
169
|
+
end
|
170
|
+
|
171
|
+
def execute_block(block, index, total)
|
172
|
+
case block.type
|
173
|
+
when :insert
|
174
|
+
execute_insert_block(block, index, total)
|
175
|
+
when :copy
|
176
|
+
execute_copy_block(block, index, total)
|
177
|
+
else
|
178
|
+
execute_regular_block(block, index, total)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def execute_insert_block(block, index, total)
|
183
|
+
puts "Executing INSERT block #{index}/#{total} for table: #{block.table_name}"
|
184
|
+
# Don't modify statements that already end with semicolon
|
185
|
+
statement = if block.content.strip.end_with?(';')
|
186
|
+
block.content
|
187
|
+
else
|
188
|
+
"#{block.content};"
|
189
|
+
end
|
190
|
+
|
191
|
+
begin
|
192
|
+
ActiveRecord::Base.connection.execute(statement)
|
193
|
+
rescue ActiveRecord::StatementInvalid => e
|
194
|
+
if e.message.include?('syntax error at or near "ON"')
|
195
|
+
# Try alternative formatting for ON CONFLICT
|
196
|
+
modified_statement = statement.gsub(/\)\s+ON\s+CONFLICT/, ') ON CONFLICT')
|
197
|
+
ActiveRecord::Base.connection.execute(modified_statement)
|
198
|
+
else
|
199
|
+
raise
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def execute_copy_block(block, index, total)
|
205
|
+
puts "Executing COPY block #{index}/#{total}"
|
206
|
+
ActiveRecord::Base.connection.execute(block.content)
|
207
|
+
end
|
208
|
+
|
209
|
+
def execute_regular_block(block, index, total)
|
210
|
+
puts "Executing block #{index}/#{total} of type: #{block.type}"
|
211
|
+
ActiveRecord::Base.connection.execute(block.content)
|
212
|
+
end
|
213
|
+
|
214
|
+
def normalize_insert_statement(statement)
|
215
|
+
# First clean up any excess whitespace around parentheses
|
216
|
+
statement = statement.gsub(/\(\s+/, '(')
|
217
|
+
.gsub(/\s+\)/, ')')
|
218
|
+
.gsub(/\)\s+ON\s+CONFLICT/, ') ON CONFLICT')
|
219
|
+
|
220
|
+
# Ensure proper spacing around ON CONFLICT
|
221
|
+
if statement =~ /(.*?)\s*ON\s+CONFLICT\s+(.*?)\s*(?:DO\s+.*?)?\s*;\s*\z/i
|
222
|
+
base = $1.strip
|
223
|
+
conflict_part = $2.strip
|
224
|
+
action_part = $3&.strip || 'DO NOTHING'
|
225
|
+
|
226
|
+
# Rebuild the statement with consistent formatting
|
227
|
+
"#{base} ON CONFLICT #{conflict_part} #{action_part};"
|
228
|
+
else
|
229
|
+
# If no ON CONFLICT clause, just clean up the spacing
|
230
|
+
statement.strip.sub(/;?\s*$/, ';')
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
67
234
|
def split_sql_statements(sql)
|
68
235
|
statements = []
|
69
236
|
current_statement = ''
|
@@ -206,41 +373,4 @@ module RealDataTests
|
|
206
373
|
end
|
207
374
|
end
|
208
375
|
end
|
209
|
-
|
210
|
-
def import
|
211
|
-
@logger.info "Starting SQL import..."
|
212
|
-
|
213
|
-
ActiveRecord::Base.transaction do
|
214
|
-
begin
|
215
|
-
# Disable foreign key checks and triggers temporarily
|
216
|
-
ActiveRecord::Base.connection.execute('SET session_replication_role = replica;')
|
217
|
-
|
218
|
-
# Split the SQL content into individual statements
|
219
|
-
statements = split_sql_statements(@sql_content)
|
220
|
-
|
221
|
-
statements.each_with_index do |statement, index|
|
222
|
-
next if statement.strip.empty?
|
223
|
-
|
224
|
-
begin
|
225
|
-
@logger.info "Executing statement #{index + 1} of #{statements.length}"
|
226
|
-
cleaned_statement = clean_sql_statement(statement)
|
227
|
-
ActiveRecord::Base.connection.execute(cleaned_statement)
|
228
|
-
rescue ActiveRecord::StatementInvalid => e
|
229
|
-
@logger.error "Error executing statement #{index + 1}: #{e.message}"
|
230
|
-
@logger.error "Statement: #{cleaned_statement[0..100]}..."
|
231
|
-
raise
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
@logger.info "Successfully imported all SQL statements"
|
236
|
-
rescue StandardError => e
|
237
|
-
@logger.error "Error during import: #{e.message}"
|
238
|
-
@logger.error e.backtrace.join("\n")
|
239
|
-
raise
|
240
|
-
ensure
|
241
|
-
# Re-enable foreign key checks and triggers
|
242
|
-
ActiveRecord::Base.connection.execute('SET session_replication_role = DEFAULT;')
|
243
|
-
end
|
244
|
-
end
|
245
|
-
end
|
246
376
|
end
|