staging_table 0.1.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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rbs.yml +30 -0
  3. data/.github/workflows/test.yml +124 -0
  4. data/.gitignore +40 -0
  5. data/.rspec +3 -0
  6. data/Gemfile +14 -0
  7. data/README.md +327 -0
  8. data/Rakefile +19 -0
  9. data/lib/staging_table/adapters/base.rb +36 -0
  10. data/lib/staging_table/adapters/mysql.rb +14 -0
  11. data/lib/staging_table/adapters/postgresql.rb +16 -0
  12. data/lib/staging_table/adapters/sqlite.rb +54 -0
  13. data/lib/staging_table/bulk_inserter.rb +43 -0
  14. data/lib/staging_table/configuration.rb +12 -0
  15. data/lib/staging_table/errors.rb +20 -0
  16. data/lib/staging_table/instrumentation.rb +71 -0
  17. data/lib/staging_table/model_factory.rb +24 -0
  18. data/lib/staging_table/session.rb +186 -0
  19. data/lib/staging_table/transfer_result.rb +36 -0
  20. data/lib/staging_table/transfer_strategies/insert.rb +33 -0
  21. data/lib/staging_table/transfer_strategies/upsert.rb +159 -0
  22. data/lib/staging_table/version.rb +5 -0
  23. data/lib/staging_table.rb +70 -0
  24. data/rbs_collection.yaml +18 -0
  25. data/sig/manifest.yaml +5 -0
  26. data/sig/staging_table/adapters/base.rbs +18 -0
  27. data/sig/staging_table/adapters/mysql.rbs +7 -0
  28. data/sig/staging_table/adapters/postgresql.rbs +7 -0
  29. data/sig/staging_table/adapters/sqlite.rbs +11 -0
  30. data/sig/staging_table/bulk_inserter.rbs +16 -0
  31. data/sig/staging_table/configuration.rbs +8 -0
  32. data/sig/staging_table/errors.rbs +25 -0
  33. data/sig/staging_table/instrumentation.rbs +19 -0
  34. data/sig/staging_table/model_factory.rbs +6 -0
  35. data/sig/staging_table/session.rbs +40 -0
  36. data/sig/staging_table/transfer_result.rbs +22 -0
  37. data/sig/staging_table/transfer_strategies/insert.rbs +15 -0
  38. data/sig/staging_table/transfer_strategies/upsert.rbs +26 -0
  39. data/sig/staging_table/version.rbs +3 -0
  40. data/sig/staging_table.rbs +9 -0
  41. data/staging_table.gemspec +35 -0
  42. metadata +195 -0
@@ -0,0 +1,18 @@
1
+ module StagingTable
2
+ module Adapters
3
+ class Base
4
+ attr_reader connection: ActiveRecord::ConnectionAdapters::AbstractAdapter
5
+
6
+ def initialize: (ActiveRecord::ConnectionAdapters::AbstractAdapter connection) -> void
7
+
8
+ # Create a staging table based on the source table structure
9
+ def create_table: (String temp_table_name, String source_table_name, ?Hash[Symbol, untyped] options) -> void
10
+
11
+ # Drop the staging table
12
+ def drop_table: (String temp_table_name) -> void
13
+
14
+ # Factory method to get the appropriate adapter for a connection
15
+ def self.for: (ActiveRecord::ConnectionAdapters::AbstractAdapter connection) -> Base
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ module StagingTable
2
+ module Adapters
3
+ class Mysql < Base
4
+ def create_table: (String temp_table_name, String source_table_name, ?Hash[Symbol, untyped] options) -> void
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module StagingTable
2
+ module Adapters
3
+ class Postgresql < Base
4
+ def create_table: (String temp_table_name, String source_table_name, ?Hash[Symbol, untyped] options) -> void
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module StagingTable
2
+ module Adapters
3
+ class Sqlite < Base
4
+ def create_table: (String temp_table_name, String source_table_name, ?Hash[Symbol, untyped] options) -> void
5
+
6
+ private
7
+
8
+ def copy_indexes: (String temp_table_name, String source_table_name) -> void
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ module StagingTable
2
+ class BulkInserter
3
+ attr_reader model: singleton(ActiveRecord::Base)
4
+ attr_reader batch_size: Integer
5
+
6
+ def initialize: (singleton(ActiveRecord::Base) model, ?batch_size: Integer) -> void
7
+
8
+ # Insert records in batches
9
+ def insert: (Array[Hash[String | Symbol, untyped]] records) -> void
10
+
11
+ private
12
+
13
+ def connection: () -> ActiveRecord::ConnectionAdapters::AbstractAdapter
14
+ def quote: (untyped value) -> String
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ module StagingTable
2
+ class Configuration
3
+ attr_accessor default_batch_size: Integer
4
+ attr_accessor default_transfer_strategy: Symbol
5
+
6
+ def initialize: () -> void
7
+ end
8
+ end
@@ -0,0 +1,25 @@
1
+ module StagingTable
2
+ # Base error class for all StagingTable errors
3
+ class Error < StandardError
4
+ end
5
+
6
+ # Raised when configuration options are invalid
7
+ class ConfigurationError < Error
8
+ end
9
+
10
+ # Raised when the database adapter is not supported
11
+ class AdapterError < Error
12
+ end
13
+
14
+ # Raised when staging table operations fail
15
+ class TableError < Error
16
+ end
17
+
18
+ # Raised when transfer strategy fails or is misconfigured
19
+ class TransferError < Error
20
+ end
21
+
22
+ # Raised when record data is invalid for insertion
23
+ class RecordError < Error
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ module StagingTable
2
+ # Provides ActiveSupport::Notifications instrumentation for StagingTable operations
3
+ module Instrumentation
4
+ NAMESPACE: String
5
+ EVENTS: Array[Symbol]
6
+
7
+ # Instruments a block with the given event name
8
+ def self.instrument: [T] (Symbol event_name, ?Hash[Symbol, untyped] payload) { (?Hash[Symbol, untyped]) -> T } -> T
9
+
10
+ # Subscribe to a StagingTable event
11
+ def self.subscribe: (Symbol | String event_name) { (ActiveSupport::Notifications::Event) -> void } -> ActiveSupport::Notifications::Fanout::Subscribers::Evented
12
+
13
+ # Unsubscribe from a StagingTable event
14
+ def self.unsubscribe: (untyped subscriber) -> void
15
+
16
+ # Subscribe to all StagingTable events
17
+ def self.subscribe_all: () { (ActiveSupport::Notifications::Event) -> void } -> ActiveSupport::Notifications::Fanout::Subscribers::Evented
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ module StagingTable
2
+ class ModelFactory
3
+ # Build a dynamic staging model class based on the source model
4
+ def self.build: (singleton(ActiveRecord::Base) source_model, String table_name, ?excluded_columns: Array[Symbol | String]) -> singleton(ActiveRecord::Base)
5
+ end
6
+ end
@@ -0,0 +1,40 @@
1
+ module StagingTable
2
+ class Session
3
+ # Supported callback options
4
+ CALLBACK_OPTIONS: Array[Symbol]
5
+
6
+ attr_reader source_model: singleton(ActiveRecord::Base)
7
+ attr_reader staging_model: singleton(ActiveRecord::Base)?
8
+ attr_reader options: Hash[Symbol, untyped]
9
+
10
+ def initialize: (singleton(ActiveRecord::Base) source_model, **untyped options) -> void
11
+
12
+ # Creates the staging table in the database
13
+ def create_table: () -> void
14
+
15
+ # Drops the staging table from the database
16
+ def drop_table: () -> void
17
+
18
+ # Insert records into the staging table
19
+ def insert: (Array[Hash[String | Symbol, untyped]] | ActiveRecord::Relation records) -> void
20
+
21
+ # Insert records from an ActiveRecord relation
22
+ def insert_from_query: (ActiveRecord::Relation relation) -> void
23
+
24
+ # Transfer staged data to the target table
25
+ def transfer: () -> TransferResult
26
+
27
+ # Delegate unknown methods to staging_model
28
+ def method_missing: (Symbol method, *untyped args) ?{ (*untyped) -> untyped } -> untyped
29
+
30
+ def respond_to_missing?: (Symbol method, ?bool include_private) -> bool
31
+
32
+ private
33
+
34
+ def run_callback: (Symbol name, *untyped args) -> void
35
+ def adapter: () -> Adapters::Base
36
+ def staging_table_name: () -> String
37
+ def ensure_table_created!: () -> void
38
+ def normalize_records: (untyped records) -> Array[Hash[String, untyped]]
39
+ end
40
+ end
@@ -0,0 +1,22 @@
1
+ module StagingTable
2
+ # Holds statistics about a transfer operation
3
+ class TransferResult
4
+ attr_reader inserted: Integer
5
+ attr_reader updated: Integer
6
+ attr_reader skipped: Integer
7
+ attr_reader total: Integer
8
+
9
+ def initialize: (?inserted: Integer, ?updated: Integer, ?skipped: Integer) -> void
10
+
11
+ # Returns a hash representation of the result
12
+ def to_h: () -> Hash[Symbol, Integer]
13
+
14
+ # Returns true if any records were inserted or updated
15
+ def success?: () -> bool
16
+
17
+ # Returns true if no records were processed
18
+ def empty?: () -> bool
19
+
20
+ def inspect: () -> String
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ module StagingTable
2
+ module TransferStrategies
3
+ class Insert
4
+ @source_model: singleton(ActiveRecord::Base)
5
+ @staging_model: singleton(ActiveRecord::Base)
6
+ @options: Hash[Symbol, untyped]
7
+ @connection: ActiveRecord::ConnectionAdapters::AbstractAdapter
8
+
9
+ def initialize: (singleton(ActiveRecord::Base) source_model, singleton(ActiveRecord::Base) staging_model, ?Hash[Symbol, untyped] options) -> void
10
+
11
+ # Transfer all staged records to the target table
12
+ def transfer: () -> TransferResult
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,26 @@
1
+ module StagingTable
2
+ module TransferStrategies
3
+ class Upsert
4
+ @source_model: singleton(ActiveRecord::Base)
5
+ @staging_model: singleton(ActiveRecord::Base)
6
+ @options: Hash[Symbol, untyped]
7
+ @connection: ActiveRecord::ConnectionAdapters::AbstractAdapter
8
+ @staged_count: Integer
9
+
10
+ def initialize: (singleton(ActiveRecord::Base) source_model, singleton(ActiveRecord::Base) staging_model, ?Hash[Symbol, untyped] options) -> void
11
+
12
+ # Transfer staged records with upsert semantics (insert or update)
13
+ def transfer: () -> TransferResult
14
+
15
+ private
16
+
17
+ def postgresql_upsert: () -> TransferResult
18
+ def mysql_upsert: () -> TransferResult
19
+ def sqlite_upsert: () -> TransferResult
20
+ def quote: (untyped value) -> String
21
+ def column_names: () -> Array[String]
22
+ def quote_column: (String | Symbol name) -> String
23
+ def quote_table: (String name) -> String
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module StagingTable
2
+ VERSION: String
3
+ end
@@ -0,0 +1,9 @@
1
+ # Type signatures for staging_table gem
2
+ # See https://github.com/ruby/rbs for more information
3
+
4
+ module StagingTable
5
+ def self.configuration: () -> Configuration
6
+ def self.configure: () { (Configuration) -> void } -> void
7
+ def self.stage: (singleton(ActiveRecord::Base) source_model, **untyped options) -> Session
8
+ | [T] (singleton(ActiveRecord::Base) source_model, **untyped options) { (Session) -> T } -> TransferResult
9
+ end
@@ -0,0 +1,35 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "staging_table/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "staging_table"
8
+ spec.version = StagingTable::VERSION
9
+ spec.authors = ["eagerworks"]
10
+ spec.email = ["hello@eagerworks.com"]
11
+
12
+ spec.summary = "Mass data imports via temporary staging tables"
13
+ spec.description = "Handles mass data imports via temporary staging tables, supporting PostgreSQL and MySQL, with a clean DSL for table lifecycle management and built-in bulk insert capabilities."
14
+ spec.homepage = "https://github.com/eagerworks/staging_table"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
18
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ end
20
+
21
+ spec.metadata["rubygems_mfa_required"] = "true"
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_dependency "activerecord", ">= 6.0"
27
+ spec.add_dependency "activesupport", ">= 6.0"
28
+
29
+ spec.add_development_dependency "bundler", "~> 2.0"
30
+ spec.add_development_dependency "rake", "~> 13.0"
31
+ spec.add_development_dependency "rspec", "~> 3.0"
32
+ spec.add_development_dependency "pg"
33
+ spec.add_development_dependency "mysql2"
34
+ spec.add_development_dependency "sqlite3"
35
+ end
metadata ADDED
@@ -0,0 +1,195 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: staging_table
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - eagerworks
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activerecord
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '6.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '6.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: activesupport
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '6.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '6.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: bundler
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '13.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '13.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rspec
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: pg
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: mysql2
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: sqlite3
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ description: Handles mass data imports via temporary staging tables, supporting PostgreSQL
125
+ and MySQL, with a clean DSL for table lifecycle management and built-in bulk insert
126
+ capabilities.
127
+ email:
128
+ - hello@eagerworks.com
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - ".github/workflows/rbs.yml"
134
+ - ".github/workflows/test.yml"
135
+ - ".gitignore"
136
+ - ".rspec"
137
+ - Gemfile
138
+ - README.md
139
+ - Rakefile
140
+ - lib/staging_table.rb
141
+ - lib/staging_table/adapters/base.rb
142
+ - lib/staging_table/adapters/mysql.rb
143
+ - lib/staging_table/adapters/postgresql.rb
144
+ - lib/staging_table/adapters/sqlite.rb
145
+ - lib/staging_table/bulk_inserter.rb
146
+ - lib/staging_table/configuration.rb
147
+ - lib/staging_table/errors.rb
148
+ - lib/staging_table/instrumentation.rb
149
+ - lib/staging_table/model_factory.rb
150
+ - lib/staging_table/session.rb
151
+ - lib/staging_table/transfer_result.rb
152
+ - lib/staging_table/transfer_strategies/insert.rb
153
+ - lib/staging_table/transfer_strategies/upsert.rb
154
+ - lib/staging_table/version.rb
155
+ - rbs_collection.yaml
156
+ - sig/manifest.yaml
157
+ - sig/staging_table.rbs
158
+ - sig/staging_table/adapters/base.rbs
159
+ - sig/staging_table/adapters/mysql.rbs
160
+ - sig/staging_table/adapters/postgresql.rbs
161
+ - sig/staging_table/adapters/sqlite.rbs
162
+ - sig/staging_table/bulk_inserter.rbs
163
+ - sig/staging_table/configuration.rbs
164
+ - sig/staging_table/errors.rbs
165
+ - sig/staging_table/instrumentation.rbs
166
+ - sig/staging_table/model_factory.rbs
167
+ - sig/staging_table/session.rbs
168
+ - sig/staging_table/transfer_result.rbs
169
+ - sig/staging_table/transfer_strategies/insert.rbs
170
+ - sig/staging_table/transfer_strategies/upsert.rbs
171
+ - sig/staging_table/version.rbs
172
+ - staging_table.gemspec
173
+ homepage: https://github.com/eagerworks/staging_table
174
+ licenses:
175
+ - MIT
176
+ metadata:
177
+ rubygems_mfa_required: 'true'
178
+ rdoc_options: []
179
+ require_paths:
180
+ - lib
181
+ required_ruby_version: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ required_rubygems_version: !ruby/object:Gem::Requirement
187
+ requirements:
188
+ - - ">="
189
+ - !ruby/object:Gem::Version
190
+ version: '0'
191
+ requirements: []
192
+ rubygems_version: 3.6.9
193
+ specification_version: 4
194
+ summary: Mass data imports via temporary staging tables
195
+ test_files: []