wasmify-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +7 -0
  3. data/LICENSE.txt +23 -0
  4. data/README.md +168 -0
  5. data/lib/active_record/connection_adapters/nulldb_adapter/checkpoint.rb +13 -0
  6. data/lib/active_record/connection_adapters/nulldb_adapter/column.rb +4 -0
  7. data/lib/active_record/connection_adapters/nulldb_adapter/configuration.rb +5 -0
  8. data/lib/active_record/connection_adapters/nulldb_adapter/core.rb +391 -0
  9. data/lib/active_record/connection_adapters/nulldb_adapter/empty_result.rb +41 -0
  10. data/lib/active_record/connection_adapters/nulldb_adapter/index_definition.rb +5 -0
  11. data/lib/active_record/connection_adapters/nulldb_adapter/null_object.rb +13 -0
  12. data/lib/active_record/connection_adapters/nulldb_adapter/quoting.rb +3 -0
  13. data/lib/active_record/connection_adapters/nulldb_adapter/statement.rb +15 -0
  14. data/lib/active_record/connection_adapters/nulldb_adapter/table_definition.rb +23 -0
  15. data/lib/active_record/connection_adapters/nulldb_adapter.rb +67 -0
  16. data/lib/active_record/connection_adapters/sqlite3_wasm_adapter.rb +163 -0
  17. data/lib/active_storage/null_delivery.rb +15 -0
  18. data/lib/generators/wasmify/install/USAGE +5 -0
  19. data/lib/generators/wasmify/install/install_generator.rb +73 -0
  20. data/lib/generators/wasmify/install/templates/config/environments/wasm.rb +30 -0
  21. data/lib/generators/wasmify/install/templates/config/wasmify.yml +17 -0
  22. data/lib/generators/wasmify/pwa/USAGE +5 -0
  23. data/lib/generators/wasmify/pwa/pwa_generator.rb +13 -0
  24. data/lib/generators/wasmify/pwa/templates/pwa/README.md +40 -0
  25. data/lib/generators/wasmify/pwa/templates/pwa/boot.html +102 -0
  26. data/lib/generators/wasmify/pwa/templates/pwa/boot.js +96 -0
  27. data/lib/generators/wasmify/pwa/templates/pwa/database.js +18 -0
  28. data/lib/generators/wasmify/pwa/templates/pwa/index.html +2 -0
  29. data/lib/generators/wasmify/pwa/templates/pwa/package.json.tt +20 -0
  30. data/lib/generators/wasmify/pwa/templates/pwa/rails.sw.js +115 -0
  31. data/lib/generators/wasmify/pwa/templates/pwa/vite.config.js +29 -0
  32. data/lib/image_processing/null.rb +22 -0
  33. data/lib/wasmify/rails/builder.rb +45 -0
  34. data/lib/wasmify/rails/configuration.rb +41 -0
  35. data/lib/wasmify/rails/packer.rb +60 -0
  36. data/lib/wasmify/rails/railtie.rb +11 -0
  37. data/lib/wasmify/rails/shim.rb +65 -0
  38. data/lib/wasmify/rails/shims/io/console/size.rb +0 -0
  39. data/lib/wasmify/rails/shims/io/console.rb +11 -0
  40. data/lib/wasmify/rails/shims/io/wait.rb +0 -0
  41. data/lib/wasmify/rails/shims/ipaddr.rb +7 -0
  42. data/lib/wasmify/rails/shims/nio.rb +0 -0
  43. data/lib/wasmify/rails/shims/nokogiri/nokogiri.rb +0 -0
  44. data/lib/wasmify/rails/shims/nokogiri.rb +0 -0
  45. data/lib/wasmify/rails/shims/rails-html-sanitizer.rb +163 -0
  46. data/lib/wasmify/rails/shims/socket.rb +21 -0
  47. data/lib/wasmify/rails/shims/sqlite3.rb +0 -0
  48. data/lib/wasmify/rails/tasks.rake +120 -0
  49. data/lib/wasmify/rails/version.rb +7 -0
  50. data/lib/wasmify/rails/wasmtimer.rb +31 -0
  51. data/lib/wasmify-rails.rb +25 -0
  52. metadata +200 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 442a1c699d7e906546d63b1d5c6c451652d65ecd651bd16fa72d98841a845acf
4
+ data.tar.gz: d8dac2abf684945cc8c9fbd10d3ed6c6236c9535c3f7c3974501638b7be6f607
5
+ SHA512:
6
+ metadata.gz: 14b9d7c161e577b80d5d9b92d6cb6a7294203f535f938423b194e4adf204541f0d93c4efa466847d2546bf8a919aa96c82037613fb31c7114f3fe1c4debaa5ee
7
+ data.tar.gz: 61c8ee0c9f26376f53e22bff246bba40c9d038094d846c17cc7b022f600344c70142777901d28d60eb41a5c8471c4dacbf6c188166a0aa76e1b88554c52900e9
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Change log
2
+
3
+ ## master
4
+
5
+ ## 0.1.0
6
+
7
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2024 Vladimir Dementyev
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
data/README.md ADDED
@@ -0,0 +1,168 @@
1
+ [![Gem Version](https://badge.fury.io/rb/wasmify-rails.svg)](https://rubygems.org/gems/wasmify-rails)
2
+
3
+ # Wasmify Rails
4
+
5
+ This gem provides tools and extensions to compile Rails applications to WebAssembly.
6
+
7
+ ## Installation
8
+
9
+ Adding to your Gemfile:
10
+
11
+ ```ruby
12
+ # Gemfile
13
+ gem "wasmify-rails", group: [:development, :wasm]
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ This gem comes with a few commands (Rake tasks) to help you get started with packing your Rails app into a Wasm module.
19
+
20
+ ### Step 1: `bin/rails wasmify:install`
21
+
22
+ Run the following command to preconfigure your project for _wasmification_:
23
+
24
+ ```sh
25
+ bin/rails wasmify:install
26
+ ```
27
+
28
+ The script will tweak you configuration files and create a couple of new ones:
29
+
30
+ - `config/environments/wasm.rb` — a dedicated Rails environment based on the `production` environment to be used in Wasm builds.
31
+
32
+ - `config/wasmify.yml` — this is a configuration file for different _wasmify_ commands (see below).
33
+
34
+ ### Step 2: `bin/rails wasmify:build:core`
35
+
36
+ Now, it's time to build a ruby.wasm with all your project dependencies (from the Gemfile). This is gonna be a base Wasm module
37
+ for your project but not including the project files.
38
+
39
+ Prior to running the build command, you MUST update your Gemfile to mark the gems that you need in the Wasm environment
40
+ with the `:wasm` group. For example:
41
+
42
+ ```ruby
43
+ # Rails is required.
44
+ # NOTE: don't forget about the default group — you need it for the regular Rails environments.
45
+ gem "rails", group: [:default, :wasm]
46
+
47
+ # We don't need Puma though
48
+ gem "puma"
49
+
50
+ # You can also use group ... do ... end syntax
51
+ group :default, :wasm do
52
+ gem "propshaft"
53
+ gem "importmap-rails"
54
+ gem "turbo-rails"
55
+ gem "stimulus-rails"
56
+ end
57
+ ...
58
+ ```
59
+
60
+ **NOTE:** If you use `ruby file: ".ruby-version"` in your Gemfile, you should probably configure the Ruby version for Wasm platform
61
+ a bit differently (since patch versions might not match). For example:
62
+
63
+ ```ruby
64
+ if RUBY_PLATFORM =~ /wasm/
65
+ ruby "3.3.3"
66
+ else
67
+ ruby file: ".ruby-version"
68
+ end
69
+ ```
70
+
71
+ Now, try to run the following command:
72
+
73
+ ```sh
74
+ $ bin/rails wasmify:build:core
75
+
76
+ ...
77
+ INFO: Packaging gem: rails-7.2.1
78
+ ...
79
+ INFO: Size: 77.92 MB
80
+ ```
81
+
82
+ If it succeeds then you're good to go further. However, 99% that it will fail with some compilation error.
83
+ You must identify the gem that caused the problem (usually, it happens when we failed to compile a C extension to Wasm—not every C extension is Wasm-compilable).
84
+ Then, you can either update your Gemfile to exclude the problematic gem from the Wasm group or you can update your `config/wasmify.yml` and
85
+ add the gem to the `exclude_gems` list.
86
+
87
+ Repeat until the `bin/rails wasmify:build:core` succeeds.
88
+
89
+ You can also verify that the resulting Wasm module works by running:
90
+
91
+ ```sh
92
+ $ bin/rails wasmify:build:core:verify
93
+
94
+ Your Rails version is: 7.2.1 [wasm32-wasi]
95
+ ```
96
+
97
+ If this command fails, try to iterate on gem exclusions and rebuild the core module.
98
+
99
+ ### Step 3: `bin/rails wasmify:pack:core`
100
+
101
+ Now, we're ready to pack the whole application into a single Wasm module.
102
+ For that, run the following command:
103
+
104
+ ```sh
105
+ $ bin/rails wasmify:pack:core
106
+
107
+ Packed the application to tmp/wasmify/app-core.wasm
108
+ Size: 103 MB
109
+ ```
110
+
111
+ That should succeeds given that the previous step was successful.
112
+
113
+ Now, let's try to boot the application and see if it works:
114
+
115
+ ```sh
116
+ $ bin/rails wasmify:pack:core:verify
117
+
118
+ Initializing Rails application...
119
+ Rails application initialized!
120
+ ```
121
+
122
+ If the verification passes, you can proceed to the final step — building the Wasm module to be used on the web.
123
+ If it fails, check out the error message and try to fix the issues (usually, configuration related).
124
+
125
+ ### Step 4: `bin/rails wasmify:pwa`
126
+
127
+ We're ready to launch our Rails application fully within a browser!
128
+
129
+ For that, you can use our starter Vite PWA application that can be generated via the following command:
130
+
131
+ ```sh
132
+ bin/rails wasmify:pwa
133
+ ```
134
+
135
+ Then, update your `config/wasmify.yml` to specify the path to the PWA app as the output:
136
+
137
+ ```yml
138
+ output_dir: "pwa"
139
+ # ...
140
+ ```
141
+
142
+ Now, create the final Wasm module:
143
+
144
+ ```sh
145
+ bin/rails wasmify:pack
146
+ ```
147
+
148
+ And go to the `pwa` for the instructions on how to launch the application.
149
+
150
+ ## Roadmap
151
+
152
+ - PGLite support (see [this example](https://github.com/kateinoigakukun/mastodon/blob/fff2e4a626a20a616c546ddf4f91766abaf1133a/pwa/dist/pglite.rb#L1))
153
+ - Active Storage OPFS service
154
+ - File uploads support (multipart/form-data)
155
+ - Background jobs support
156
+ - WASI Preview 2 support (also [this](https://github.com/kateinoigakukun/mastodon/tree/katei/wasmify))
157
+
158
+ ## Contributing
159
+
160
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/](https://github.com/).
161
+
162
+ ## Credits
163
+
164
+ The `nulldb` adapter for Active Record (used for tests) is adopted from [this project](https://github.com/nulldb/nulldb).
165
+
166
+ ## License
167
+
168
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,13 @@
1
+ class ActiveRecord::ConnectionAdapters::NullDBAdapter
2
+
3
+ class Checkpoint < Statement
4
+ def initialize
5
+ super(:checkpoint, "")
6
+ end
7
+
8
+ def ==(other)
9
+ self.class == other.class
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,4 @@
1
+ class ActiveRecord::ConnectionAdapters::NullDBAdapter
2
+ class Column < ::ActiveRecord::ConnectionAdapters::Column
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ class ActiveRecord::ConnectionAdapters::NullDBAdapter
2
+
3
+ class Configuration < Struct.new(:project_root); end
4
+
5
+ end
@@ -0,0 +1,391 @@
1
+ class ActiveRecord::ConnectionAdapters::NullDBAdapter < ActiveRecord::ConnectionAdapters::AbstractAdapter
2
+
3
+ # A convenience method for integratinginto RSpec. See README for example of
4
+ # use.
5
+ def self.insinuate_into_spec(config)
6
+ config.before :all do
7
+ ActiveRecord::Base.establish_connection(:adapter => :nulldb)
8
+ end
9
+
10
+ config.after :all do
11
+ ActiveRecord::Base.establish_connection(:test)
12
+ end
13
+ end
14
+
15
+ # Recognized options:
16
+ #
17
+ # [+:schema+] path to the schema file, relative to Rails.root
18
+ # [+:table_definition_class_name+] table definition class
19
+ # (e.g. ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition for Postgres) or nil.
20
+ def initialize(config={})
21
+ @log = StringIO.new
22
+ @logger = Logger.new(@log)
23
+ @last_unique_id = 0
24
+ @tables = {'schema_info' => new_table_definition(nil)}
25
+ @indexes = Hash.new { |hash, key| hash[key] = [] }
26
+ @schema_path = config.fetch(:schema){ "db/schema.rb" }
27
+ @config = config.merge(:adapter => :nulldb)
28
+ super *initialize_args
29
+ @visitor ||= Arel::Visitors::ToSql.new self if defined?(Arel::Visitors::ToSql)
30
+
31
+ if config[:table_definition_class_name]
32
+ ActiveRecord::ConnectionAdapters::NullDBAdapter.send(:remove_const, 'TableDefinition')
33
+ ActiveRecord::ConnectionAdapters::NullDBAdapter.const_set('TableDefinition',
34
+ self.class.const_get(config[:table_definition_class_name]))
35
+ end
36
+
37
+ register_types
38
+ end
39
+
40
+ # A log of every statement that has been "executed" by this connection adapter
41
+ # instance.
42
+ def execution_log
43
+ (@execution_log ||= [])
44
+ end
45
+
46
+ # A log of every statement that has been "executed" since the last time
47
+ # #checkpoint! was called, or since the connection was created.
48
+ def execution_log_since_checkpoint
49
+ checkpoint_index = @execution_log.rindex(Checkpoint.new)
50
+ checkpoint_index = checkpoint_index ? checkpoint_index + 1 : 0
51
+ @execution_log[(checkpoint_index..-1)]
52
+ end
53
+
54
+ # Inserts a checkpoint in the log. See also #execution_log_since_checkpoint.
55
+ def checkpoint!
56
+ self.execution_log << Checkpoint.new
57
+ end
58
+
59
+ def adapter_name
60
+ "NullDB"
61
+ end
62
+
63
+ def supports_migrations?
64
+ true
65
+ end
66
+
67
+ def connect
68
+ end
69
+
70
+ def reconnect
71
+ end
72
+
73
+ def create_table(table_name, options = {})
74
+ table_definition = new_table_definition(self, table_name, options.delete(:temporary), options)
75
+
76
+ unless options[:id] == false
77
+ table_definition.primary_key(options[:primary_key] || "id")
78
+ end
79
+
80
+ yield table_definition if block_given?
81
+
82
+ @tables[table_name.to_s] = table_definition
83
+ end
84
+
85
+ def rename_table(table_name, new_name)
86
+ table_definition = @tables.delete(table_name.to_s)
87
+
88
+ table_definition.name = new_name.to_s
89
+ @tables[new_name.to_s] = table_definition
90
+ end
91
+
92
+ def add_index(table_name, column_names, **options)
93
+ options[:unique] = false unless options.key?(:unique)
94
+ column_names = Array.wrap(column_names).map(&:to_s)
95
+
96
+ index, index_type, ignore = add_index_options(table_name, column_names, **options)
97
+
98
+ if index.is_a?(ActiveRecord::ConnectionAdapters::IndexDefinition)
99
+ @indexes[table_name] << index
100
+ else
101
+ # Rails < 6.1
102
+ @indexes[table_name] << IndexDefinition.new(table_name, index, (index_type == 'UNIQUE'), column_names, [], [])
103
+ end
104
+ end
105
+
106
+ # Rails 6.1+
107
+ if ActiveRecord::VERSION::MAJOR >= 7 || (ActiveRecord::VERSION::MAJOR >= 6 and ActiveRecord::VERSION::MINOR > 0)
108
+ def remove_index(table_name, column_name = nil, **options )
109
+ index_name = index_name_for_remove(table_name, column_name, options)
110
+ index = @indexes[table_name].reject! { |index| index.name == index_name }
111
+ end
112
+ else
113
+ def remove_index(table_name, options = {} )
114
+ index_name = index_name_for_remove(table_name, options)
115
+ index = @indexes[table_name].reject! { |index| index.name == index_name }
116
+ end
117
+ end
118
+
119
+ def add_fk_constraint(*args)
120
+ # NOOP
121
+ end
122
+
123
+ def add_pk_constraint(*args)
124
+ # NOOP
125
+ end
126
+
127
+ def enable_extension(*)
128
+ # NOOP
129
+ end
130
+
131
+ # Retrieve the table names defined by the schema
132
+ def tables
133
+ @tables.keys.map(&:to_s)
134
+ end
135
+
136
+ def views
137
+ [] # TODO: Implement properly if needed - This is new method in rails
138
+ end
139
+
140
+ # Retrieve table columns as defined by the schema
141
+ def columns(table_name, name = nil)
142
+ if @tables.size <= 1
143
+ ActiveRecord::Migration.verbose = false
144
+ schema_path = if Pathname(@schema_path).absolute?
145
+ @schema_path
146
+ else
147
+ File.join(NullDB.configuration.project_root, @schema_path)
148
+ end
149
+ Kernel.load(schema_path)
150
+ end
151
+
152
+ if table = @tables[table_name]
153
+ table.columns.map do |col_def|
154
+ col_args = default_column_arguments(col_def)
155
+ ActiveRecord::ConnectionAdapters::NullDBAdapter::Column.new(*col_args)
156
+ end
157
+ else
158
+ []
159
+ end
160
+ end
161
+
162
+ # Retrieve table indexes as defined by the schema
163
+ def indexes(table_name, name = nil)
164
+ @indexes[table_name]
165
+ end
166
+
167
+ def execute(statement, name = nil)
168
+ self.execution_log << Statement.new(entry_point, statement)
169
+ NullObject.new
170
+ end
171
+
172
+ def exec_query(statement, name = 'SQL', binds = [], options = {})
173
+ internal_exec_query(statement, name, binds, **options)
174
+ end
175
+
176
+ def internal_exec_query(statement, name = 'SQL', binds = [], prepare: false, async: false)
177
+ self.execution_log << Statement.new(entry_point, statement)
178
+ EmptyResult.new
179
+ end
180
+
181
+ def select_rows(statement, name = nil, binds = [], async: false)
182
+ [].tap do
183
+ self.execution_log << Statement.new(entry_point, statement)
184
+ end
185
+ end
186
+
187
+ def insert(statement, name = nil, primary_key = nil, object_id = nil, sequence_name = nil, binds = [], returning: nil)
188
+ with_entry_point(:insert) do
189
+ super(statement, name, primary_key, object_id, sequence_name)
190
+ end
191
+
192
+ result = object_id || next_unique_id
193
+
194
+ returning ? [result] : result
195
+ end
196
+ alias :create :insert
197
+
198
+ def update(statement, name=nil, binds = [])
199
+ with_entry_point(:update) do
200
+ super(statement, name)
201
+ end
202
+ end
203
+
204
+ def delete(statement, name=nil, binds = [])
205
+ with_entry_point(:delete) do
206
+ super(statement, name).size
207
+ end
208
+ end
209
+
210
+ def select_all(statement, name=nil, binds = [], options = {})
211
+ with_entry_point(:select_all) do
212
+ super(statement, name)
213
+ end
214
+ end
215
+
216
+ def select_one(statement, name=nil, binds = [])
217
+ with_entry_point(:select_one) do
218
+ super(statement, name)
219
+ end
220
+ end
221
+
222
+ def select_value(statement, name=nil, binds = [])
223
+ with_entry_point(:select_value) do
224
+ super(statement, name)
225
+ end
226
+ end
227
+
228
+ def select_values(statement, name=nil)
229
+ with_entry_point(:select_values) do
230
+ super(statement, name)
231
+ end
232
+ end
233
+
234
+ def primary_key(table_name)
235
+ columns(table_name).detect { |col| col.type == :primary_key }.try(:name)
236
+ end
237
+
238
+ def add_column(table_name, column_name, type, **options)
239
+ super
240
+
241
+ table_meta = @tables[table_name.to_s]
242
+ return unless table_meta
243
+
244
+ table_meta.column column_name, type, **options
245
+ end
246
+
247
+ def change_column(table_name, column_name, type, options = {})
248
+ table_meta = @tables[table_name.to_s]
249
+ column = table_meta.columns.find { |column| column.name == column_name.to_s }
250
+ return unless column
251
+
252
+ column.type = type
253
+ column.options = options if options
254
+ end
255
+
256
+ def rename_column(table_name, column_name, new_column_name)
257
+ table_meta = @tables[table_name.to_s]
258
+ column = table_meta.columns.find { |column| column.name == column_name.to_s }
259
+ return unless column
260
+
261
+ column.name = new_column_name
262
+ end
263
+
264
+ def change_column_default(table_name, column_name, default_or_changes)
265
+ table_meta = @tables[table_name.to_s]
266
+ column = table_meta.columns.find { |column| column.name == column_name.to_s }
267
+
268
+ return unless column
269
+
270
+ if default_or_changes.kind_of? Hash
271
+ column.default = default_or_changes[:to]
272
+ else
273
+ column.default = default_or_changes
274
+ end
275
+ end
276
+
277
+ protected
278
+
279
+ def select(statement, name = nil, binds = [], prepare: nil, async: nil, **)
280
+ EmptyResult.new.tap do |r|
281
+ r.bind_column_meta(columns_for(name))
282
+ self.execution_log << Statement.new(entry_point, statement)
283
+ end
284
+ end
285
+
286
+ private
287
+
288
+ def columns_for(table_name)
289
+ table_meta = @tables[table_name]
290
+ return [] unless table_meta
291
+ table_meta.columns
292
+ end
293
+
294
+ def next_unique_id
295
+ @last_unique_id += 1
296
+ end
297
+
298
+ def with_entry_point(method)
299
+ if entry_point.nil?
300
+ with_thread_local_variable(:entry_point, method) do
301
+ yield
302
+ end
303
+ else
304
+ yield
305
+ end
306
+ end
307
+
308
+ def entry_point
309
+ Thread.current[:entry_point]
310
+ end
311
+
312
+ def with_thread_local_variable(name, value)
313
+ old_value = Thread.current[name]
314
+ Thread.current[name] = value
315
+ begin
316
+ yield
317
+ ensure
318
+ Thread.current[name] = old_value
319
+ end
320
+ end
321
+
322
+ def includes_column?
323
+ false
324
+ end
325
+
326
+ def new_table_definition(adapter = nil, table_name = nil, is_temporary = nil, options = {})
327
+ case ::ActiveRecord::VERSION::MAJOR
328
+ when 6, 7
329
+ TableDefinition.new(self, table_name, temporary: is_temporary, options: options.except(:id))
330
+ when 5
331
+ TableDefinition.new(table_name, is_temporary, options.except(:id), nil)
332
+ else
333
+ raise "Unsupported ActiveRecord version #{::ActiveRecord::VERSION::STRING}"
334
+ end
335
+ end
336
+
337
+ def default_column_arguments(col_def)
338
+ [
339
+ col_def.name.to_s,
340
+ col_def.default.present? ? col_def.default.to_s : nil,
341
+ sql_type_definition(col_def),
342
+ col_def.null.nil? || col_def.null
343
+ ]
344
+ end
345
+
346
+ def sql_type_definition(col_def)
347
+ ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(
348
+ type: col_def.type,
349
+ sql_type: col_def.type.to_s,
350
+ limit: col_def.limit
351
+ )
352
+ end
353
+
354
+ def initialize_args
355
+ [nil, @logger, @config]
356
+ end
357
+
358
+ # Register types only once to avoid ActiveRecord::TypeConflictError
359
+ # in ActiveRecord::Type::Registration#<=>
360
+ REGISTRATION_MUTEX = Mutex.new
361
+
362
+ def register_types
363
+ REGISTRATION_MUTEX.synchronize do
364
+ return if self.class.types_registered
365
+
366
+ self.class.types_registered = true
367
+ end
368
+
369
+ ActiveRecord::Type.register(
370
+ :primary_key,
371
+ ActiveModel::Type::Integer,
372
+ adapter: adapter_name,
373
+ override: true
374
+ )
375
+
376
+ ActiveRecord::Type.add_modifier({ array: true }, DummyOID, adapter: :nulldb)
377
+ ActiveRecord::Type.add_modifier({ range: true }, DummyOID, adapter: :nulldb)
378
+ end
379
+
380
+ class << self
381
+ attr_accessor :types_registered
382
+ end
383
+
384
+ class DummyOID < ActiveModel::Type::Value
385
+ attr_reader :subtype
386
+
387
+ def initialize(*args)
388
+ @subtype = args.first
389
+ end
390
+ end
391
+ end
@@ -0,0 +1,41 @@
1
+ class ActiveRecord::ConnectionAdapters::NullDBAdapter
2
+
3
+ class EmptyResult < Array
4
+ attr_reader :column_types
5
+
6
+ def bind_column_meta(columns)
7
+ @columns = columns
8
+ return if columns.empty?
9
+
10
+ @column_types = columns.reduce({}) do |ctypes, col|
11
+ ctypes[col.name] = ActiveRecord::Type.lookup(col.type)
12
+ ctypes
13
+ end
14
+ end
15
+
16
+ def columns
17
+ @columns ||= []
18
+ end
19
+
20
+ def column_types
21
+ @column_types ||= {}
22
+ end
23
+
24
+ def cast_values(type_overrides = nil)
25
+ rows
26
+ end
27
+
28
+ def rows
29
+ []
30
+ end
31
+
32
+ def >(num)
33
+ rows.size > num
34
+ end
35
+
36
+ def includes_column?(name)
37
+ false
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,5 @@
1
+ class ActiveRecord::ConnectionAdapters::NullDBAdapter
2
+
3
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders); end
4
+
5
+ end
@@ -0,0 +1,13 @@
1
+ class ActiveRecord::ConnectionAdapters::NullDBAdapter
2
+
3
+ class NullObject
4
+ def method_missing(*args, &block)
5
+ nil
6
+ end
7
+
8
+ def to_a
9
+ []
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,3 @@
1
+ class ActiveRecord::ConnectionAdapters::NullDBAdapter
2
+ def self.quote_column_name(name) = %Q("#{name.to_s.gsub('"', '""')}")
3
+ end