arel_toolkit 0.4.0 → 0.4.1

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/develop.yml +86 -0
  3. data/.github/workflows/master.yml +67 -0
  4. data/.gitignore +4 -0
  5. data/CHANGELOG.md +35 -3
  6. data/Gemfile.lock +22 -15
  7. data/Guardfile +4 -0
  8. data/README.md +11 -8
  9. data/Rakefile +11 -1
  10. data/arel_toolkit.gemspec +4 -1
  11. data/ext/pg_result_init/extconf.rb +52 -0
  12. data/ext/pg_result_init/pg_result_init.c +138 -0
  13. data/ext/pg_result_init/pg_result_init.h +6 -0
  14. data/gemfiles/arel_gems.gemfile.lock +7 -0
  15. data/gemfiles/default.gemfile.lock +7 -0
  16. data/lib/arel/enhance.rb +1 -0
  17. data/lib/arel/enhance/context_enhancer/arel_table.rb +18 -1
  18. data/lib/arel/enhance/node.rb +25 -6
  19. data/lib/arel/enhance/query.rb +2 -0
  20. data/lib/arel/enhance/query_methods.rb +23 -0
  21. data/lib/arel/enhance/visitor.rb +4 -2
  22. data/lib/arel/extensions.rb +7 -2
  23. data/lib/arel/extensions/active_model_attribute_with_cast_value.rb +22 -0
  24. data/lib/arel/extensions/active_record_relation_query_attribute.rb +22 -0
  25. data/lib/arel/extensions/active_record_type_caster_connection.rb +7 -0
  26. data/lib/arel/extensions/attributes_attribute.rb +47 -0
  27. data/lib/arel/extensions/bind_param.rb +15 -0
  28. data/lib/arel/extensions/coalesce.rb +17 -3
  29. data/lib/arel/extensions/exists.rb +59 -0
  30. data/lib/arel/extensions/function.rb +2 -1
  31. data/lib/arel/extensions/greatest.rb +17 -3
  32. data/lib/arel/extensions/insert_statement.rb +2 -2
  33. data/lib/arel/extensions/least.rb +17 -3
  34. data/lib/arel/extensions/node.rb +10 -0
  35. data/lib/arel/extensions/range_function.rb +10 -2
  36. data/lib/arel/extensions/select_core.rb +1 -0
  37. data/lib/arel/extensions/tree_manager.rb +5 -0
  38. data/lib/arel/middleware.rb +5 -1
  39. data/lib/arel/middleware/active_record_extension.rb +13 -0
  40. data/lib/arel/middleware/chain.rb +76 -21
  41. data/lib/arel/middleware/database_executor.rb +68 -0
  42. data/lib/arel/middleware/postgresql_adapter.rb +41 -5
  43. data/lib/arel/middleware/railtie.rb +6 -2
  44. data/lib/arel/middleware/result.rb +170 -0
  45. data/lib/arel/middleware/to_sql_executor.rb +15 -0
  46. data/lib/arel/middleware/to_sql_middleware.rb +33 -0
  47. data/lib/arel/sql_to_arel/pg_query_visitor.rb +34 -33
  48. data/lib/arel/sql_to_arel/result.rb +19 -2
  49. data/lib/arel/transformer.rb +2 -1
  50. data/lib/arel/transformer/prefix_schema_name.rb +183 -0
  51. data/lib/arel/transformer/remove_active_record_info.rb +2 -4
  52. data/lib/arel/transformer/replace_table_with_subquery.rb +31 -0
  53. data/lib/arel_toolkit.rb +6 -1
  54. data/lib/arel_toolkit/version.rb +1 -1
  55. metadata +55 -10
  56. data/.travis.yml +0 -34
  57. data/lib/arel/extensions/generate_series.rb +0 -9
  58. data/lib/arel/extensions/rank.rb +0 -9
  59. data/lib/arel/transformer/add_schema_to_table.rb +0 -26
@@ -1,8 +1,25 @@
1
1
  module Arel
2
2
  module SqlToArel
3
3
  class Result < Array
4
- def to_sql
5
- map(&:to_sql).join('; ')
4
+ def to_sql(engine = Arel::Table.engine)
5
+ sql, _binds = to_sql_and_binds(engine)
6
+ sql
7
+ end
8
+
9
+ def to_sql_and_binds(engine = Arel::Table.engine)
10
+ sql_collection = []
11
+ binds_collection = []
12
+
13
+ each do |item|
14
+ sql, binds = item.to_sql_and_binds(engine)
15
+ sql_collection << sql
16
+ binds_collection.concat(binds)
17
+ end
18
+
19
+ [
20
+ sql_collection.join('; '),
21
+ binds_collection,
22
+ ]
6
23
  end
7
24
 
8
25
  def map(&block)
@@ -1,4 +1,5 @@
1
- require_relative './transformer/add_schema_to_table'
1
+ require_relative './transformer/prefix_schema_name'
2
+ require_relative './transformer/replace_table_with_subquery'
2
3
  require_relative './transformer/remove_active_record_info'
3
4
 
4
5
  module Arel
@@ -0,0 +1,183 @@
1
+ module Arel
2
+ module Transformer
3
+ class PrefixSchemaName
4
+ PG_CATALOG = 'pg_catalog'.freeze
5
+ DEFAULT_SCHEMA_PRIORITY = ['public', PG_CATALOG].freeze
6
+
7
+ attr_reader :object_mapping
8
+ attr_reader :schema_priority
9
+
10
+ def initialize(
11
+ schema_priority = DEFAULT_SCHEMA_PRIORITY,
12
+ override_object_mapping = {}
13
+ )
14
+ @schema_priority = schema_priority
15
+ @object_mapping = database_object_mapping.merge(override_object_mapping)
16
+ end
17
+
18
+ def call(arel, next_middleware)
19
+ tree = Arel.enhance(arel)
20
+ update_arel_tables(tree)
21
+ update_typecasts(tree)
22
+ update_functions(tree)
23
+
24
+ next_middleware.call tree
25
+ end
26
+
27
+ private
28
+
29
+ def update_arel_tables(tree)
30
+ tree.query(
31
+ class: Arel::Table,
32
+ schema_name: nil,
33
+ context: { range_variable: true },
34
+ ).each do |node|
35
+ schema_name = schema_name_from_object_name(node['name'].object.to_s)
36
+ node['schema_name'].replace(schema_name)
37
+ end
38
+ end
39
+
40
+ def update_typecasts(tree)
41
+ tree.query(
42
+ class: Arel::Nodes::TypeCast,
43
+ type_name: 'regclass',
44
+ ).each do |node|
45
+ update_typecast_node(node)
46
+ end
47
+ end
48
+
49
+ def update_typecast_node(node)
50
+ table_name = table_name_from_arel_node(node['arg'].object)
51
+ reference_parts = table_name.split('.')
52
+
53
+ case reference_parts.length
54
+ when 1
55
+ schema_name = schema_name_from_object_name(table_name)
56
+ reference_parts.unshift(schema_name)
57
+ node['arg']['expr'].replace(reference_parts.join('.'))
58
+ when 2
59
+ node # Do nothing
60
+ else
61
+ raise "Don't know how to handle `#{reference_parts.length}` parts in " \
62
+ "`#{reference_parts}` for sql `#{node.to_sql}`"
63
+ end
64
+ end
65
+
66
+ def update_functions(tree)
67
+ tree.query(
68
+ class: Arel::Enhance::QueryMethods.in_ancestors?(Arel::Nodes::Function),
69
+ schema_name: nil,
70
+ ).each do |node|
71
+ update_function_node(node)
72
+ end
73
+ end
74
+
75
+ def update_function_node(node)
76
+ object_name = if node.object.is_a?(Arel::Nodes::NamedFunction)
77
+ node['name'].object.downcase
78
+ else
79
+ node.object.class.to_s.demodulize.underscore
80
+ end
81
+
82
+ schema_name = schema_name_from_object_name(object_name)
83
+ node['schema_name'].replace(schema_name)
84
+ end
85
+
86
+ def table_name_from_arel_node(arel_node)
87
+ case arel_node
88
+ when Arel::Nodes::Quoted
89
+ arel_node.expr
90
+ else
91
+ raise "Unknown node `#{table_name}` for `#{node.inspect}`"
92
+ end
93
+ end
94
+
95
+ def schema_name_from_object_name(table_name)
96
+ table_name = unquote_string(table_name)
97
+ possible_schemas = object_mapping[table_name]
98
+
99
+ if possible_schemas.nil?
100
+ raise "Object `#{table_name}` does not exist in the object_mapping and cannot be prefixed"
101
+ end
102
+
103
+ schema_name = schema_priority.find do |possible_schema_name|
104
+ possible_schemas.include?(possible_schema_name)
105
+ end
106
+
107
+ if schema_name.nil?
108
+ raise "Could not find a schema name for table `#{table_name}`.\n" \
109
+ "Current schema priority is `#{schema_priority}`.\n" \
110
+ "Possible schemas are `#{possible_schemas}`."
111
+ end
112
+
113
+ # We don't need to prefix nodes with `pg_catalog`, because that's the default
114
+ # even if the search_path does not include `pg_catalog`.
115
+ return nil if schema_name == PG_CATALOG
116
+
117
+ schema_name
118
+ end
119
+
120
+ # https://www.rubydoc.info/github/rubyworks/facets/String:unquote
121
+ def unquote_string(string)
122
+ case string[0, 1]
123
+ when "'", '"', '`'
124
+ string[0] = ''
125
+ end
126
+
127
+ case string[-1, 1]
128
+ when "'", '"', '`'
129
+ string[-1] = ''
130
+ end
131
+
132
+ string
133
+ end
134
+
135
+ def database_object_mapping
136
+ mapping = {}
137
+ update_mapping mapping, database_tables
138
+ update_mapping mapping, database_views
139
+ update_mapping mapping, database_materialized_views
140
+ update_mapping mapping, database_functions
141
+
142
+ mapping
143
+ end
144
+
145
+ def update_mapping(mapping, objects)
146
+ objects.each do |object|
147
+ name = object.fetch('object_name').to_s.downcase
148
+ mapping[name] ||= []
149
+ mapping[name] << object.fetch('schema_name').to_s
150
+ end
151
+ end
152
+
153
+ def database_tables
154
+ connection.execute(
155
+ 'SELECT tablename AS object_name, schemaname AS schema_name FROM pg_tables',
156
+ )
157
+ end
158
+
159
+ def database_views
160
+ connection.execute(
161
+ 'SELECT viewname AS object_name, schemaname AS schema_name FROM pg_views',
162
+ )
163
+ end
164
+
165
+ def database_materialized_views
166
+ connection.execute(
167
+ 'SELECT matviewname AS object_name, schemaname AS schema_name FROM pg_matviews',
168
+ )
169
+ end
170
+
171
+ def database_functions
172
+ connection.execute(
173
+ 'SELECT pg_proc.proname AS object_name, pg_namespace.nspname AS schema_name ' \
174
+ 'FROM pg_proc INNER JOIN pg_namespace ON pg_proc.pronamespace = pg_namespace.oid',
175
+ )
176
+ end
177
+
178
+ def connection
179
+ Arel::Table.engine.connection
180
+ end
181
+ end
182
+ end
183
+ end
@@ -2,7 +2,7 @@ module Arel
2
2
  module Transformer
3
3
  class RemoveActiveRecordInfo
4
4
  class << self
5
- def call(arel, _context)
5
+ def call(arel, next_middleware)
6
6
  tree = Arel.enhance(arel)
7
7
 
8
8
  tree.query(class: Arel::Table).each do |node|
@@ -15,7 +15,7 @@ module Arel
15
15
  )
16
16
  end
17
17
 
18
- tree.object
18
+ next_middleware.call tree.object
19
19
  end
20
20
 
21
21
  private
@@ -30,8 +30,6 @@ module Arel
30
30
  Arel::Nodes::TypeCast.new(Arel::Nodes::Quoted.new('t'), 'bool')
31
31
  when FalseClass
32
32
  Arel::Nodes::TypeCast.new(Arel::Nodes::Quoted.new('f'), 'bool')
33
- when Float
34
- value
35
33
  else
36
34
  raise "Unknown value cast `#{value}` with class `#{value.class}`"
37
35
  end
@@ -0,0 +1,31 @@
1
+ module Arel
2
+ module Transformer
3
+ class ReplaceTableWithSubquery
4
+ attr_reader :subquery_for_table
5
+
6
+ def initialize(subquery_for_table)
7
+ @subquery_for_table = subquery_for_table
8
+ end
9
+
10
+ def call(arel, next_middleware)
11
+ tree = Arel.enhance(arel)
12
+ update_arel_tables(tree)
13
+ next_middleware.call tree
14
+ end
15
+
16
+ private
17
+
18
+ def update_arel_tables(tree)
19
+ tree.query(
20
+ class: Arel::Table,
21
+ context: { range_variable: true },
22
+ schema_name: nil,
23
+ ).each do |node|
24
+ if (subquery = subquery_for_table.call(node.name.value))
25
+ node.replace subquery.as(node.name.value)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
data/lib/arel_toolkit.rb CHANGED
@@ -3,9 +3,14 @@ require 'postgres_ext' if Gem.loaded_specs.key?('postgres_ext')
3
3
  require 'active_record_upsert' if Gem.loaded_specs.key?('active_record_upsert')
4
4
  require 'pg_search' if Gem.loaded_specs.key?('pg_search')
5
5
  require 'rails/railtie' if Gem.loaded_specs.key?('railties')
6
+ require 'arel'
7
+ require 'active_record'
6
8
 
7
9
  require 'arel_toolkit/version'
8
- require 'arel'
10
+
11
+ require 'pg'
12
+ require 'arel_toolkit/pg_result_init'
13
+
9
14
  require 'arel/extensions'
10
15
  require 'arel/sql_to_arel'
11
16
  require 'arel/middleware'
@@ -1,3 +1,3 @@
1
1
  module ArelToolkit
2
- VERSION = '0.4.0'.freeze
2
+ VERSION = '0.4.1'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arel_toolkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - maarten
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-07-31 00:00:00.000000000 Z
11
+ date: 2019-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: arel
@@ -100,14 +100,14 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 1.14.3
103
+ version: '1.15'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 1.14.3
110
+ version: '1.15'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rake
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: '10.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rake-compiler
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.0'
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: rspec
127
141
  requirement: !ruby/object:Gem::Requirement
@@ -262,6 +276,20 @@ dependencies:
262
276
  - - "~>"
263
277
  - !ruby/object:Gem::Version
264
278
  version: 1.3.0
279
+ - !ruby/object:Gem::Dependency
280
+ name: guard-rake
281
+ requirement: !ruby/object:Gem::Requirement
282
+ requirements:
283
+ - - "~>"
284
+ - !ruby/object:Gem::Version
285
+ version: 1.0.0
286
+ type: :development
287
+ prerelease: false
288
+ version_requirements: !ruby/object:Gem::Requirement
289
+ requirements:
290
+ - - "~>"
291
+ - !ruby/object:Gem::Version
292
+ version: 1.0.0
265
293
  - !ruby/object:Gem::Dependency
266
294
  name: pry
267
295
  requirement: !ruby/object:Gem::Requirement
@@ -349,19 +377,21 @@ dependencies:
349
377
  description: 'ArelToolkit contains parsing, querying, modifying, optimisations, extensions
350
378
  and more for Arel.
351
379
 
352
- '
380
+ '
353
381
  email:
354
382
  - maarten@vgijssel.nl
355
383
  executables: []
356
- extensions: []
384
+ extensions:
385
+ - ext/pg_result_init/extconf.rb
357
386
  extra_rdoc_files: []
358
387
  files:
359
388
  - ".codeclimate.yml"
389
+ - ".github/workflows/develop.yml"
390
+ - ".github/workflows/master.yml"
360
391
  - ".gitignore"
361
392
  - ".rspec"
362
393
  - ".rubocop.yml"
363
394
  - ".ruby-version"
364
- - ".travis.yml"
365
395
  - Appraisals
366
396
  - CHANGELOG.md
367
397
  - CODE_OF_CONDUCT.md
@@ -374,6 +404,9 @@ files:
374
404
  - arel_toolkit.gemspec
375
405
  - bin/console
376
406
  - bin/setup
407
+ - ext/pg_result_init/extconf.rb
408
+ - ext/pg_result_init/pg_result_init.c
409
+ - ext/pg_result_init/pg_result_init.h
377
410
  - gemfiles/.bundle/config
378
411
  - gemfiles/arel_gems.gemfile
379
412
  - gemfiles/arel_gems.gemfile.lock
@@ -385,9 +418,13 @@ files:
385
418
  - lib/arel/enhance/path.rb
386
419
  - lib/arel/enhance/path_node.rb
387
420
  - lib/arel/enhance/query.rb
421
+ - lib/arel/enhance/query_methods.rb
388
422
  - lib/arel/enhance/visitor.rb
389
423
  - lib/arel/extensions.rb
390
424
  - lib/arel/extensions/absolute.rb
425
+ - lib/arel/extensions/active_model_attribute_with_cast_value.rb
426
+ - lib/arel/extensions/active_record_relation_query_attribute.rb
427
+ - lib/arel/extensions/active_record_type_caster_connection.rb
391
428
  - lib/arel/extensions/active_record_type_caster_map.rb
392
429
  - lib/arel/extensions/all.rb
393
430
  - lib/arel/extensions/any.rb
@@ -395,8 +432,10 @@ files:
395
432
  - lib/arel/extensions/array_subselect.rb
396
433
  - lib/arel/extensions/assignment.rb
397
434
  - lib/arel/extensions/at_time_zone.rb
435
+ - lib/arel/extensions/attributes_attribute.rb
398
436
  - lib/arel/extensions/between_symmetric.rb
399
437
  - lib/arel/extensions/binary.rb
438
+ - lib/arel/extensions/bind_param.rb
400
439
  - lib/arel/extensions/bit_string.rb
401
440
  - lib/arel/extensions/bitwise_xor.rb
402
441
  - lib/arel/extensions/case.rb
@@ -425,12 +464,12 @@ files:
425
464
  - lib/arel/extensions/dot.rb
426
465
  - lib/arel/extensions/equality.rb
427
466
  - lib/arel/extensions/except_all.rb
467
+ - lib/arel/extensions/exists.rb
428
468
  - lib/arel/extensions/exponentiation.rb
429
469
  - lib/arel/extensions/extract_from.rb
430
470
  - lib/arel/extensions/factorial.rb
431
471
  - lib/arel/extensions/false.rb
432
472
  - lib/arel/extensions/function.rb
433
- - lib/arel/extensions/generate_series.rb
434
473
  - lib/arel/extensions/greatest.rb
435
474
  - lib/arel/extensions/indirection.rb
436
475
  - lib/arel/extensions/infer.rb
@@ -454,6 +493,7 @@ files:
454
493
  - lib/arel/extensions/named_argument.rb
455
494
  - lib/arel/extensions/named_function.rb
456
495
  - lib/arel/extensions/natural_join.rb
496
+ - lib/arel/extensions/node.rb
457
497
  - lib/arel/extensions/not_between.rb
458
498
  - lib/arel/extensions/not_between_symmetric.rb
459
499
  - lib/arel/extensions/not_distinct_from.rb
@@ -467,7 +507,6 @@ files:
467
507
  - lib/arel/extensions/position.rb
468
508
  - lib/arel/extensions/prepare.rb
469
509
  - lib/arel/extensions/range_function.rb
470
- - lib/arel/extensions/rank.rb
471
510
  - lib/arel/extensions/row.rb
472
511
  - lib/arel/extensions/select_core.rb
473
512
  - lib/arel/extensions/select_manager.rb
@@ -496,17 +535,23 @@ files:
496
535
  - lib/arel/extensions/variable_show.rb
497
536
  - lib/arel/extensions/with_ordinality.rb
498
537
  - lib/arel/middleware.rb
538
+ - lib/arel/middleware/active_record_extension.rb
499
539
  - lib/arel/middleware/chain.rb
540
+ - lib/arel/middleware/database_executor.rb
500
541
  - lib/arel/middleware/postgresql_adapter.rb
501
542
  - lib/arel/middleware/railtie.rb
543
+ - lib/arel/middleware/result.rb
544
+ - lib/arel/middleware/to_sql_executor.rb
545
+ - lib/arel/middleware/to_sql_middleware.rb
502
546
  - lib/arel/sql_to_arel.rb
503
547
  - lib/arel/sql_to_arel/error.rb
504
548
  - lib/arel/sql_to_arel/pg_query_visitor.rb
505
549
  - lib/arel/sql_to_arel/pg_query_visitor/frame_options.rb
506
550
  - lib/arel/sql_to_arel/result.rb
507
551
  - lib/arel/transformer.rb
508
- - lib/arel/transformer/add_schema_to_table.rb
552
+ - lib/arel/transformer/prefix_schema_name.rb
509
553
  - lib/arel/transformer/remove_active_record_info.rb
554
+ - lib/arel/transformer/replace_table_with_subquery.rb
510
555
  - lib/arel_toolkit.rb
511
556
  - lib/arel_toolkit/version.rb
512
557
  homepage: https://github.com/mvgijssel/arel_toolkit