sqlreport 0.1.1 → 0.1.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -1
- data/README.md +85 -4
- data/examples/batch_operations.rb +82 -0
- data/lib/sqlreport/active_record_extension.rb +37 -0
- data/lib/sqlreport/base.rb +25 -0
- data/lib/sqlreport/batch_manager.rb +105 -0
- data/lib/sqlreport/result.rb +25 -3
- data/lib/sqlreport/version.rb +1 -1
- data/lib/sqlreport.rb +2 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e9ab7d553ecd33126c38418100399abab18686a28faba11e0e4ac0d46491c01e
|
4
|
+
data.tar.gz: 7b77b6af58c083760be6ce350903ad58cf9ea7742084aa464a6e64173ca0a8e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe9b20a35baad6c7a39fd0d5b598aea299bb6f9e8c661b1972c6ed46cd0bb45676ebdc66ee1e8fff9ad32f0af643d35cd25e0072b896a632b94d0bf51c2483e6
|
7
|
+
data.tar.gz: ff031385aa5b1e84973e668cda73b2ef705d736485c500f7366d07268db8b286891fcd637a3b136162f17782b0fa257256c14cbd664e997e524c0863ebff718f
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
-
## [
|
1
|
+
## [0.1.2]
|
2
|
+
|
3
|
+
- Added batch processing for large datasets
|
4
|
+
- Added streaming CSV export for memory-efficient processing
|
5
|
+
- Added progress tracking for batch operations
|
6
|
+
- Added ActiveRecord integration for direct model-to-CSV conversion
|
2
7
|
|
3
8
|
## [0.1.1] - 2025-03-09
|
4
9
|
|
data/README.md
CHANGED
@@ -10,6 +10,12 @@ This gem provides an easy way to convert SQL database queries to CSV. Below you
|
|
10
10
|
- [Write result to CSV file](#write-result-to-csv-file)
|
11
11
|
- [Get columns / headers](#get-column-headers)
|
12
12
|
- [Get rows](#get-rows)
|
13
|
+
- [Batch Processing](#batch-processing)
|
14
|
+
- [Creating a Batch Manager](#creating-a-batch-manager)
|
15
|
+
- [Processing Batches](#processing-batches)
|
16
|
+
- [Streaming to CSV](#streaming-to-csv)
|
17
|
+
- [Tracking Progress](#tracking-progress)
|
18
|
+
- [ActiveRecord Integration](#activerecord-integration)
|
13
19
|
- [Compatibility](#compatibility)
|
14
20
|
- [Todo](#todo)
|
15
21
|
- [Contributing](#contributing)
|
@@ -94,13 +100,88 @@ This gem is tested with the following Ruby versions on Linux and Mac OS X:
|
|
94
100
|
|
95
101
|
- Ruby > 2.2.2
|
96
102
|
|
103
|
+
## Batch Processing
|
104
|
+
|
105
|
+
For handling large datasets, SQLreport provides batch processing capabilities that allow you to process data in chunks to avoid memory issues.
|
106
|
+
|
107
|
+
### Creating a Batch Manager
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
batch_manager = Sqlreport.batch_query("SELECT * FROM large_table", batch_size: 1000)
|
111
|
+
```
|
112
|
+
|
113
|
+
### Processing Batches
|
114
|
+
|
115
|
+
You can process one batch at a time:
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
# Get the next batch
|
119
|
+
batch = batch_manager.next_batch
|
120
|
+
# Process the batch
|
121
|
+
batch.rows.each do |row|
|
122
|
+
# Process each row
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
126
|
+
Or process all batches at once with a block:
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
batch_manager.process_all do |batch|
|
130
|
+
# Process each batch
|
131
|
+
puts "Processing batch with #{batch.rows.count} rows"
|
132
|
+
end
|
133
|
+
```
|
134
|
+
|
135
|
+
### Streaming to CSV
|
136
|
+
|
137
|
+
For very large datasets, you can stream directly to a CSV file without loading all data into memory:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
batch_manager.stream_to_csv("large_table.csv")
|
141
|
+
```
|
142
|
+
|
143
|
+
### Tracking Progress
|
144
|
+
|
145
|
+
You can track the progress of batch processing:
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
batch_manager.count_total_rows # Get total row count for progress calculation
|
149
|
+
batch_manager.next_batch
|
150
|
+
puts "Processed #{batch_manager.processed_rows} of #{batch_manager.total_rows} rows"
|
151
|
+
puts "Progress: #{batch_manager.progress_percentage}%"
|
152
|
+
```
|
153
|
+
|
154
|
+
## ActiveRecord Integration
|
155
|
+
|
156
|
+
SQLreport can be used directly with ActiveRecord models and relations, allowing for a more fluent interface:
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
# Generate a report from an ActiveRecord relation
|
160
|
+
User.where(active: true).sqlreport.result.write_csv("active_users.csv")
|
161
|
+
|
162
|
+
# Or with more options
|
163
|
+
Post.where(published: true)
|
164
|
+
.order(created_at: :desc)
|
165
|
+
.limit(100)
|
166
|
+
.sqlreport
|
167
|
+
.result
|
168
|
+
.to_csv(include_headers: true, separator: ",")
|
169
|
+
|
170
|
+
# Use batch processing with ActiveRecord
|
171
|
+
User.where(created_at: 1.month.ago..Time.current)
|
172
|
+
.sqlreport_batch(batch_size: 500)
|
173
|
+
.stream_to_csv("new_users.csv")
|
174
|
+
```
|
175
|
+
|
176
|
+
This integration makes it easy to generate reports directly from your models without having to write raw SQL.
|
177
|
+
|
97
178
|
## Todo
|
98
179
|
|
99
|
-
-
|
180
|
+
- ~~Add simple safeguard validations~~
|
181
|
+
- ~~Allow it to use different databases~~
|
182
|
+
- ~~Batch jobs (for bigger tables)~~
|
183
|
+
- ~~Tie into Rails models~~
|
100
184
|
- Add support for multiple export options (PDF, textfile, LaTex)
|
101
|
-
- Add safeguard validations
|
102
|
-
- Tie into Rails models
|
103
|
-
- Allow it to use different databases
|
104
185
|
- ..
|
105
186
|
|
106
187
|
## Contributing
|
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "sqlreport"
|
6
|
+
require "active_record"
|
7
|
+
|
8
|
+
# Set up a test database
|
9
|
+
ActiveRecord::Base.establish_connection(
|
10
|
+
adapter: "sqlite3",
|
11
|
+
database: ":memory:"
|
12
|
+
)
|
13
|
+
|
14
|
+
# Create a test table
|
15
|
+
ActiveRecord::Base.connection.create_table(:test_table, force: true) do |t|
|
16
|
+
t.column :user_id, :integer
|
17
|
+
t.column :name, :string
|
18
|
+
t.column :json_object, :json
|
19
|
+
end
|
20
|
+
|
21
|
+
# Insert test data
|
22
|
+
10.times do |i|
|
23
|
+
ActiveRecord::Base.connection.execute("INSERT INTO test_table (user_id, name) VALUES (#{i}, 'test name #{i}');")
|
24
|
+
end
|
25
|
+
|
26
|
+
# Create a model for the test table
|
27
|
+
class TestTable < ActiveRecord::Base
|
28
|
+
self.table_name = "test_table" # Use the singular table name
|
29
|
+
end
|
30
|
+
|
31
|
+
puts "=== Basic Query ==="
|
32
|
+
result = Sqlreport.query("SELECT * FROM test_table").result
|
33
|
+
puts "Columns: #{result.columns.join(", ")}"
|
34
|
+
puts "Rows: #{result.rows.count}"
|
35
|
+
puts "CSV: #{result.to_csv.lines.first}"
|
36
|
+
|
37
|
+
puts "\n=== Batch Query ==="
|
38
|
+
batch_manager = Sqlreport.batch_query("SELECT * FROM test_table", batch_size: 3)
|
39
|
+
batch_manager.count_total_rows
|
40
|
+
puts "Total rows: #{batch_manager.total_rows}"
|
41
|
+
|
42
|
+
batch = batch_manager.next_batch
|
43
|
+
puts "First batch rows: #{batch.rows.count}"
|
44
|
+
puts "Processed rows: #{batch_manager.processed_rows}"
|
45
|
+
puts "Progress: #{batch_manager.progress_percentage}%"
|
46
|
+
|
47
|
+
batch = batch_manager.next_batch
|
48
|
+
puts "Second batch rows: #{batch.rows.count}"
|
49
|
+
puts "Processed rows: #{batch_manager.processed_rows}"
|
50
|
+
puts "Progress: #{batch_manager.progress_percentage}%"
|
51
|
+
|
52
|
+
puts "\n=== ActiveRecord Integration ==="
|
53
|
+
# The extensions are automatically included in ActiveRecord::Relation
|
54
|
+
# by the ActiveSupport.on_load(:active_record) hook
|
55
|
+
|
56
|
+
# Test the sqlreport method
|
57
|
+
result = TestTable.where(user_id: 1..5).sqlreport.result
|
58
|
+
puts "ActiveRecord query rows: #{result.rows.count}"
|
59
|
+
|
60
|
+
# Test the sqlreport_batch method
|
61
|
+
batch_manager = TestTable.where(user_id: 1..5).sqlreport_batch(batch_size: 2)
|
62
|
+
batch = batch_manager.next_batch
|
63
|
+
puts "ActiveRecord batch rows: #{batch.rows.count}"
|
64
|
+
puts "Processed rows: #{batch_manager.processed_rows}"
|
65
|
+
|
66
|
+
puts "\n=== CSV Export ==="
|
67
|
+
# Write to CSV
|
68
|
+
csv_file = "test_table.csv"
|
69
|
+
result.write_csv(csv_file)
|
70
|
+
puts "CSV file created: #{File.exist?(csv_file)}"
|
71
|
+
puts "CSV content: #{File.read(csv_file).lines.first}"
|
72
|
+
FileUtils.rm_f(csv_file)
|
73
|
+
|
74
|
+
# Stream to CSV
|
75
|
+
batch_csv_file = "batch_test_table.csv"
|
76
|
+
batch_manager = TestTable.where(user_id: 1..5).sqlreport_batch(batch_size: 3)
|
77
|
+
batch_manager.stream_to_csv(batch_csv_file)
|
78
|
+
puts "Batch CSV file created: #{File.exist?(batch_csv_file)}"
|
79
|
+
puts "Batch CSV content: #{File.read(batch_csv_file).lines.first}"
|
80
|
+
FileUtils.rm_f(batch_csv_file)
|
81
|
+
|
82
|
+
puts "\nAll examples completed successfully!"
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sqlreport
|
4
|
+
# ActiveRecordExtension
|
5
|
+
# Extends ActiveRecord::Relation with SQLReport functionality
|
6
|
+
module ActiveRecordExtension
|
7
|
+
# Provides SQLReport functionality to ActiveRecord models
|
8
|
+
module RelationMethods
|
9
|
+
# Convert the relation to a SQLReport result
|
10
|
+
def sqlreport
|
11
|
+
# Get the SQL query from the relation
|
12
|
+
sql = to_sql
|
13
|
+
|
14
|
+
# Create a SQLReport result from the query
|
15
|
+
::Sqlreport::Result.new(sql)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Provides batch processing functionality to ActiveRecord models
|
20
|
+
module BatchMethods
|
21
|
+
# Convert the relation to a SQLReport batch manager
|
22
|
+
def sqlreport_batch(batch_size: 1000)
|
23
|
+
# Get the SQL query from the relation
|
24
|
+
sql = to_sql
|
25
|
+
|
26
|
+
# Create a SQLReport batch manager from the query
|
27
|
+
::Sqlreport::BatchManager.new(sql, batch_size: batch_size)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Extend ActiveRecord::Relation with SQLReport functionality
|
34
|
+
ActiveSupport.on_load(:active_record) do
|
35
|
+
ActiveRecord::Relation.include(Sqlreport::ActiveRecordExtension::RelationMethods)
|
36
|
+
ActiveRecord::Relation.include(Sqlreport::ActiveRecordExtension::BatchMethods)
|
37
|
+
end
|
data/lib/sqlreport/base.rb
CHANGED
@@ -7,4 +7,29 @@ module Sqlreport
|
|
7
7
|
def self.query(sql_query)
|
8
8
|
::Sqlreport::Result.new(sql_query)
|
9
9
|
end
|
10
|
+
|
11
|
+
def self.batch_query(sql_query, batch_size: 1000)
|
12
|
+
::Sqlreport::BatchManager.new(sql_query, batch_size: batch_size)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.database(db_config)
|
16
|
+
# Return a DatabaseConnector instance
|
17
|
+
DatabaseConnector.new(db_config)
|
18
|
+
end
|
19
|
+
|
20
|
+
# DatabaseConnector
|
21
|
+
# Handles database connection switching
|
22
|
+
class DatabaseConnector
|
23
|
+
def initialize(db_config)
|
24
|
+
@db_config = db_config
|
25
|
+
end
|
26
|
+
|
27
|
+
def query(sql_query)
|
28
|
+
::Sqlreport::Result.new(sql_query, db_config: @db_config)
|
29
|
+
end
|
30
|
+
|
31
|
+
def batch_query(sql_query, batch_size: 1000)
|
32
|
+
::Sqlreport::BatchManager.new(sql_query, batch_size: batch_size, db_config: @db_config)
|
33
|
+
end
|
34
|
+
end
|
10
35
|
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sqlreport
|
4
|
+
# BatchManager
|
5
|
+
# Handles batch processing of SQL queries for large datasets
|
6
|
+
class BatchManager
|
7
|
+
attr_reader :total_rows, :processed_rows, :batch_size, :current_offset
|
8
|
+
|
9
|
+
def initialize(query, batch_size: 1000, db_config: false)
|
10
|
+
@query = query
|
11
|
+
@batch_size = batch_size
|
12
|
+
@db_config = db_config
|
13
|
+
@total_rows = nil
|
14
|
+
@processed_rows = 0
|
15
|
+
@current_offset = 0
|
16
|
+
@complete = false
|
17
|
+
end
|
18
|
+
|
19
|
+
def connection
|
20
|
+
@connection ||= if @db_config
|
21
|
+
ActiveRecord::Base.establish_connection(@db_config)
|
22
|
+
else
|
23
|
+
ActiveRecord::Base.connection
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def count_total_rows
|
28
|
+
return @total_rows if @total_rows
|
29
|
+
|
30
|
+
# Extract the FROM clause and any WHERE conditions to count total rows
|
31
|
+
from_clause = @query.match(/FROM\s+([^\s;]+)(\s+WHERE\s+(.+?))?(\s+ORDER BY|\s+GROUP BY|\s+LIMIT|\s*;|\s*$)/i)
|
32
|
+
return nil unless from_clause
|
33
|
+
|
34
|
+
table = from_clause[1]
|
35
|
+
where_clause = from_clause[3]
|
36
|
+
|
37
|
+
count_query = "SELECT COUNT(*) FROM #{table}"
|
38
|
+
count_query += " WHERE #{where_clause}" if where_clause
|
39
|
+
|
40
|
+
result = connection.exec_query(count_query)
|
41
|
+
@total_rows = result.rows.first.first.to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
def next_batch
|
45
|
+
return nil if @complete
|
46
|
+
|
47
|
+
# Add LIMIT and OFFSET to the query
|
48
|
+
paginated_query = if @query.include?("LIMIT")
|
49
|
+
# If query already has LIMIT, we need to handle differently
|
50
|
+
raise "Batch processing not supported for queries with LIMIT clause"
|
51
|
+
else
|
52
|
+
"#{@query.chomp(";")} LIMIT #{@batch_size} OFFSET #{@current_offset}"
|
53
|
+
end
|
54
|
+
|
55
|
+
result = Sqlreport::Result.new(paginated_query, db_config: @db_config).result
|
56
|
+
|
57
|
+
# Update state
|
58
|
+
rows_in_batch = result.rows.count
|
59
|
+
@processed_rows += rows_in_batch
|
60
|
+
@current_offset += @batch_size
|
61
|
+
@complete = rows_in_batch < @batch_size
|
62
|
+
|
63
|
+
result
|
64
|
+
end
|
65
|
+
|
66
|
+
def process_all
|
67
|
+
results = []
|
68
|
+
while (batch = next_batch)
|
69
|
+
results << batch
|
70
|
+
yield batch if block_given?
|
71
|
+
end
|
72
|
+
results
|
73
|
+
end
|
74
|
+
|
75
|
+
def stream_to_csv(path, include_headers: true, separator: ",", quote_char: '"')
|
76
|
+
first_batch = true
|
77
|
+
|
78
|
+
File.open(path, "w") do |file|
|
79
|
+
process_all do |batch|
|
80
|
+
if first_batch && include_headers
|
81
|
+
headers = batch.columns.join(separator)
|
82
|
+
file.puts headers
|
83
|
+
first_batch = false
|
84
|
+
elsif first_batch
|
85
|
+
first_batch = false
|
86
|
+
end
|
87
|
+
|
88
|
+
# Write rows without loading all into memory
|
89
|
+
batch.rows.each do |row|
|
90
|
+
csv_row = CSV.generate_line(row, col_sep: separator, quote_char: quote_char)
|
91
|
+
file.write(csv_row)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
true
|
97
|
+
end
|
98
|
+
|
99
|
+
def progress_percentage
|
100
|
+
return 0 unless @total_rows && @total_rows.positive?
|
101
|
+
|
102
|
+
(@processed_rows.to_f / @total_rows * 100).round(2)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/lib/sqlreport/result.rb
CHANGED
@@ -1,17 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "csv"
|
4
|
+
require "yaml"
|
4
5
|
|
5
6
|
module Sqlreport
|
6
7
|
# Result
|
7
8
|
class Result
|
8
|
-
def initialize(query)
|
9
|
+
def initialize(query, db_config: false)
|
9
10
|
@query = query
|
10
|
-
@
|
11
|
+
@db_config = db_config
|
11
12
|
@response = nil
|
12
13
|
end
|
13
14
|
|
14
|
-
def
|
15
|
+
def connection
|
16
|
+
@connection ||= if @db_config
|
17
|
+
ActiveRecord::Base.establish_connection(@db_config)
|
18
|
+
else
|
19
|
+
ActiveRecord::Base.connection
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def result(validate: true)
|
24
|
+
connection
|
25
|
+
validations = validate_input if validate
|
26
|
+
return validations if validations
|
27
|
+
|
15
28
|
@response = @connection.exec_query(@query)
|
16
29
|
self
|
17
30
|
end
|
@@ -36,5 +49,14 @@ module Sqlreport
|
|
36
49
|
File.write(path, data)
|
37
50
|
true
|
38
51
|
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def validate_input
|
56
|
+
return "DELETE, UPDATE, DROP, RENAME, ALTER cannot be used in queries" \
|
57
|
+
if %w[DELETE UPDATE DROP RENAME ALTER].any? { |needle| @query.include? needle }
|
58
|
+
|
59
|
+
false
|
60
|
+
end
|
39
61
|
end
|
40
62
|
end
|
data/lib/sqlreport/version.rb
CHANGED
data/lib/sqlreport.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sqlreport
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gem shards
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-04-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -79,8 +79,11 @@ files:
|
|
79
79
|
- LICENSE.txt
|
80
80
|
- README.md
|
81
81
|
- Rakefile
|
82
|
+
- examples/batch_operations.rb
|
82
83
|
- lib/sqlreport.rb
|
84
|
+
- lib/sqlreport/active_record_extension.rb
|
83
85
|
- lib/sqlreport/base.rb
|
86
|
+
- lib/sqlreport/batch_manager.rb
|
84
87
|
- lib/sqlreport/result.rb
|
85
88
|
- lib/sqlreport/version.rb
|
86
89
|
- sig/sqlreport.rbs
|