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
data/arel_toolkit.gemspec CHANGED
@@ -23,6 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.bindir = 'exe'
24
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
25
  spec.require_paths = ['lib']
26
+ spec.extensions = ['ext/pg_result_init/extconf.rb']
26
27
 
27
28
  spec.add_dependency 'arel', '~> 9.0.0'
28
29
  spec.add_dependency 'activerecord', '~> 5.2.0'
@@ -31,8 +32,9 @@ Gem::Specification.new do |spec|
31
32
 
32
33
  spec.add_development_dependency 'bundler', '~> 2.0'
33
34
  spec.add_development_dependency 'dpl', '~> 1.10.11'
34
- spec.add_development_dependency 'github_changelog_generator', '~> 1.14.3'
35
+ spec.add_development_dependency 'github_changelog_generator', '~> 1.15'
35
36
  spec.add_development_dependency 'rake', '~> 10.0'
37
+ spec.add_development_dependency 'rake-compiler', '~> 1.0'
36
38
  spec.add_development_dependency 'rspec', '~> 3.8'
37
39
  spec.add_development_dependency 'approvals', '~> 0.0.24'
38
40
  spec.add_development_dependency 'appraisal', '~> 2.2.0'
@@ -45,6 +47,7 @@ Gem::Specification.new do |spec|
45
47
  spec.add_development_dependency 'guard', '~> 2.15'
46
48
  spec.add_development_dependency 'guard-rspec', '~> 4.7'
47
49
  spec.add_development_dependency 'guard-rubocop', '~> 1.3.0'
50
+ spec.add_development_dependency 'guard-rake', '~> 1.0.0'
48
51
 
49
52
  spec.add_development_dependency 'pry'
50
53
  spec.add_development_dependency 'pry-nav'
@@ -0,0 +1,52 @@
1
+ require 'mkmf'
2
+ require 'pg'
3
+
4
+ CONFIG['debugflags'] = '-ggdb3'
5
+ CONFIG['optflags'] = '-O0'
6
+
7
+ # https://github.com/jeremyevans/sequel_pg/blob/master/ext/sequel_pg/extconf.rb
8
+
9
+ pg_include_dir = ENV['POSTGRES_INCLUDE'] ||
10
+ (begin
11
+ IO.popen('pg_config --includedir').readline.chomp
12
+ rescue StandardError
13
+ nil
14
+ end)
15
+ pg_lib_dir = ENV['POSTGRES_LIB'] ||
16
+ (begin
17
+ IO.popen('pg_config --libdir').readline.chomp
18
+ rescue StandardError
19
+ nil
20
+ end)
21
+
22
+ dir_config(
23
+ 'pg',
24
+ pg_include_dir,
25
+ pg_lib_dir,
26
+ )
27
+
28
+ pg_ext = Gem.loaded_specs.fetch('pg')
29
+ pg_ext_inlude_dir = File.join(pg_ext.full_gem_path, 'ext')
30
+ pg_ext_lib_dir = pg_ext.extension_dir
31
+
32
+ dir_config(
33
+ 'pg_ext',
34
+ pg_ext_inlude_dir,
35
+ pg_ext_lib_dir,
36
+ )
37
+
38
+ if (
39
+ have_library('pq') ||
40
+ have_library('libpq') ||
41
+ have_library('ms/libpq')
42
+ ) &&
43
+ have_header('libpq-fe.h') &&
44
+ have_header('pg.h') &&
45
+ have_func('PQcopyResult') &&
46
+ have_func('PQsetResultAttrs') &&
47
+ have_func('PQsetvalue')
48
+
49
+ create_makefile('arel_toolkit/pg_result_init')
50
+ else
51
+ abort 'Could not find PostgreSQL build environment (libraries & headers): Makefile not created'
52
+ end
@@ -0,0 +1,138 @@
1
+ #include <libpq-fe.h>
2
+ #include <pg.h>
3
+ #include "pg_result_init.h"
4
+
5
+ VALUE rb_mPgResultInit;
6
+
7
+ static VALUE
8
+ pg_result_init_create(VALUE self, VALUE rb_pgconn, VALUE rb_result, VALUE rb_columns, VALUE rb_rows) {
9
+ Check_Type(rb_columns, T_ARRAY);
10
+ Check_Type(rb_rows, T_ARRAY);
11
+
12
+ if (!rb_obj_is_kind_of(rb_result, rb_cPGresult)) {
13
+ rb_raise(
14
+ rb_eTypeError,
15
+ "wrong argument type %s (expected kind of PG::Result)",
16
+ rb_obj_classname(rb_result)
17
+ );
18
+ }
19
+
20
+ if (!rb_obj_is_kind_of(rb_pgconn, rb_cPGconn)) {
21
+ rb_raise(
22
+ rb_eTypeError,
23
+ "wrong argument type %s (expected kind of PG::Connection)",
24
+ rb_obj_classname(rb_pgconn)
25
+ );
26
+ }
27
+
28
+ PGresult *result = pgresult_get(rb_result);
29
+ PGresult *result_copy = PQcopyResult(result, PG_COPYRES_EVENTS | PG_COPYRES_NOTICEHOOKS);
30
+
31
+ int num_columns = RARRAY_LEN(rb_columns);
32
+ PGresAttDesc *attDescs = malloc(num_columns * sizeof(PGresAttDesc));
33
+
34
+ if (attDescs == NULL)
35
+ rb_raise(rb_eRuntimeError, "Cannot allocate memory");
36
+
37
+ int index;
38
+ VALUE rb_column;
39
+ VALUE rb_column_name;
40
+ VALUE rb_table_id;
41
+ VALUE rb_column_id;
42
+ VALUE rb_format;
43
+ VALUE rb_typ_id;
44
+ VALUE rb_typ_len;
45
+ VALUE rb_att_typemod;
46
+
47
+ char *column_name;
48
+
49
+ for(index = 0; index < num_columns; index++) {
50
+ rb_column = rb_ary_entry(rb_columns, index);
51
+ Check_Type(rb_column, T_HASH);
52
+
53
+ rb_column_name = rb_funcall(rb_column, rb_intern("fetch"), 1, ID2SYM(rb_intern("name")));
54
+
55
+ // 0 means unknown tableid
56
+ rb_table_id = rb_funcall(rb_column, rb_intern("fetch"), 2, ID2SYM(rb_intern("tableid")), INT2NUM(0));
57
+
58
+ // 0 means unknown columnid
59
+ rb_column_id = rb_funcall(rb_column, rb_intern("fetch"), 2, ID2SYM(rb_intern("columnid")), INT2NUM(0));
60
+
61
+ // 0 is text, 1 is binary https://www.postgresql.org/docs/10/libpq-exec.html
62
+ rb_format = rb_funcall(rb_column, rb_intern("fetch"), 2, ID2SYM(rb_intern("format")), INT2NUM(0));
63
+
64
+ rb_typ_id = rb_funcall(rb_column, rb_intern("fetch"), 1, ID2SYM(rb_intern("typid")));
65
+ rb_typ_len = rb_funcall(rb_column, rb_intern("fetch"), 1, ID2SYM(rb_intern("typlen")));
66
+
67
+ // -1 means that there is no type modifier
68
+ rb_att_typemod = rb_funcall(rb_column, rb_intern("fetch"), 2, ID2SYM(rb_intern("atttypmod")), INT2NUM(-1));
69
+
70
+ // Using StringValueCStr, if column contains null bytes it should raise Argument error
71
+ // postgres does not handle null bytes.
72
+ column_name = StringValueCStr(rb_column_name);
73
+
74
+ // postgres/src/interfaces/libpq/libpq-fe.h:235
75
+ attDescs[index].name = column_name;
76
+ attDescs[index].tableid = NUM2INT(rb_table_id);
77
+ attDescs[index].columnid = NUM2INT(rb_column_id);
78
+ attDescs[index].format = NUM2INT(rb_format);
79
+ attDescs[index].typid = NUM2INT(rb_typ_id);
80
+ attDescs[index].typlen = NUM2INT(rb_typ_len);
81
+ attDescs[index].atttypmod = NUM2INT(rb_att_typemod);
82
+ }
83
+
84
+ int success;
85
+
86
+ success = PQsetResultAttrs(result_copy, num_columns, attDescs);
87
+
88
+ if (success == 0)
89
+ rb_raise(rb_eRuntimeError, "PQsetResultAttrs failed: %d", success);
90
+
91
+ free(attDescs);
92
+
93
+ int num_rows = RARRAY_LEN(rb_rows);
94
+ int row_index;
95
+ int column_index;
96
+ VALUE rb_row;
97
+ VALUE rb_value;
98
+ char *value;
99
+ int value_len;
100
+
101
+ for(row_index = 0; row_index < num_rows; row_index++) {
102
+ for(column_index = 0; column_index < num_columns; column_index++) {
103
+ rb_row = rb_ary_entry(rb_rows, row_index);
104
+ rb_value = rb_ary_entry(rb_row, column_index);
105
+
106
+ if (NIL_P(rb_value)) {
107
+ success = PQsetvalue(result_copy, row_index, column_index, NULL, -1);
108
+
109
+ if (success == 0)
110
+ rb_raise(rb_eRuntimeError, "PQsetvalue failed: %d", success);
111
+ }
112
+ else {
113
+ rb_value = rb_funcall(rb_value, rb_intern("to_s"), 0);
114
+
115
+ // Using StringValueCStr, if column contains null bytes it should raise Argument error
116
+ // postgres does not handle null bytes.
117
+ value = StringValueCStr(rb_value);
118
+ value_len = RSTRING_LEN(rb_value);
119
+
120
+ success = PQsetvalue(result_copy, row_index, column_index, value, value_len);
121
+
122
+ if (success == 0)
123
+ rb_raise(rb_eRuntimeError, "PQsetvalue failed: %d", success);
124
+ }
125
+ }
126
+ }
127
+
128
+ VALUE rb_pgresult = pg_new_result(result_copy, rb_pgconn);
129
+ return rb_pgresult;
130
+ }
131
+
132
+ void
133
+ Init_pg_result_init(void)
134
+ {
135
+ rb_mPgResultInit = rb_define_module("PgResultInit");
136
+
137
+ rb_define_module_function(rb_mPgResultInit, "create", pg_result_init_create, 4);
138
+ }
@@ -0,0 +1,6 @@
1
+ #ifndef PG_RESULT_INIT_H
2
+ #define PG_RESULT_INIT_H 1
3
+
4
+ #include "ruby.h"
5
+
6
+ #endif /* PG_RESULT_INIT_H */
@@ -112,6 +112,9 @@ GEM
112
112
  shellany (~> 0.0)
113
113
  thor (>= 0.18.1)
114
114
  guard-compat (1.2.1)
115
+ guard-rake (1.0.0)
116
+ guard
117
+ rake
115
118
  guard-rspec (4.7.3)
116
119
  guard (~> 2.1)
117
120
  guard-compat (~> 1.1)
@@ -186,6 +189,8 @@ GEM
186
189
  thor (>= 0.19.0, < 2.0)
187
190
  rainbow (3.0.0)
188
191
  rake (10.5.0)
192
+ rake-compiler (1.0.7)
193
+ rake
189
194
  rb-fsevent (0.10.3)
190
195
  rb-inotify (0.10.0)
191
196
  ffi (~> 1.0)
@@ -253,6 +258,7 @@ DEPENDENCIES
253
258
  dpl (~> 1.10.11)
254
259
  github_changelog_generator (~> 1.14.3)
255
260
  guard (~> 2.15)
261
+ guard-rake (~> 1.0.0)
256
262
  guard-rspec (~> 4.7)
257
263
  guard-rubocop (~> 1.3.0)
258
264
  pg_search!
@@ -264,6 +270,7 @@ DEPENDENCIES
264
270
  pry-rescue
265
271
  pry-stack_explorer
266
272
  rake (~> 10.0)
273
+ rake-compiler (~> 1.0)
267
274
  rspec (~> 3.8)
268
275
  rspec-rails (~> 3.8.0)
269
276
  rubocop (= 0.71.0)
@@ -66,6 +66,9 @@ GEM
66
66
  shellany (~> 0.0)
67
67
  thor (>= 0.18.1)
68
68
  guard-compat (1.2.1)
69
+ guard-rake (1.0.0)
70
+ guard
71
+ rake
69
72
  guard-rspec (4.7.3)
70
73
  guard (~> 2.1)
71
74
  guard-compat (~> 1.1)
@@ -122,6 +125,8 @@ GEM
122
125
  public_suffix (3.1.1)
123
126
  rainbow (3.0.0)
124
127
  rake (10.5.0)
128
+ rake-compiler (1.0.7)
129
+ rake
125
130
  rb-fsevent (0.10.3)
126
131
  rb-inotify (0.10.0)
127
132
  ffi (~> 1.0)
@@ -180,6 +185,7 @@ DEPENDENCIES
180
185
  dpl (~> 1.10.11)
181
186
  github_changelog_generator (~> 1.14.3)
182
187
  guard (~> 2.15)
188
+ guard-rake (~> 1.0.0)
183
189
  guard-rspec (~> 4.7)
184
190
  guard-rubocop (~> 1.3.0)
185
191
  pry
@@ -189,6 +195,7 @@ DEPENDENCIES
189
195
  pry-rescue
190
196
  pry-stack_explorer
191
197
  rake (~> 10.0)
198
+ rake-compiler (~> 1.0)
192
199
  rspec (~> 3.8)
193
200
  rubocop (= 0.71.0)
194
201
  simplecov (~> 0.16.1)
data/lib/arel/enhance.rb CHANGED
@@ -2,6 +2,7 @@ require_relative './enhance/node'
2
2
  require_relative './enhance/path'
3
3
  require_relative './enhance/path_node'
4
4
  require_relative './enhance/query'
5
+ require_relative './enhance/query_methods'
5
6
  require_relative './enhance/visitor'
6
7
 
7
8
  module Arel
@@ -6,18 +6,35 @@ module Arel
6
6
  # rubocop:disable Metrics/CyclomaticComplexity
7
7
  # rubocop:disable Metrics/AbcSize
8
8
  def self.call(node)
9
- context = node.context.merge!(range_variable: false, column_reference: false)
9
+ context = node.context.merge!(
10
+ range_variable: false, column_reference: false, alias: false,
11
+ )
10
12
  parent_object = node.parent.object
11
13
 
12
14
  # Using Arel::Table as SELECT ... FROM <table>
13
15
  if parent_object.is_a?(Arel::Nodes::JoinSource)
14
16
  context[:range_variable] = true
15
17
 
18
+ # NOTE: only applies to ActiveRecord generated Arel
19
+ # which does not use Arel::Table#alias but Arel::TableAlias instead
20
+ # Using Arel::Table as SELECT ... FROM <table> AS alias
21
+ elsif parent_object.is_a?(Arel::Nodes::TableAlias) &&
22
+ node.parent.parent.object.is_a?(Arel::Nodes::JoinSource)
23
+ context[:range_variable] = true
24
+
16
25
  # Using Arel::Table as SELECT ... FROM [<table>]
17
26
  elsif parent_object.is_a?(Array) &&
18
27
  node.parent.parent.object.is_a?(Arel::Nodes::JoinSource)
19
28
  context[:range_variable] = true
20
29
 
30
+ # NOTE: only applies to ActiveRecord generated Arel
31
+ # which does not use Arel::Table#alias but Arel::TableAlias instead
32
+ # Using Arel::Table as SELECT ... FROM [<table> AS alias]
33
+ elsif parent_object.is_a?(Arel::Nodes::TableAlias) &&
34
+ node.parent.parent.object.is_a?(Array) &&
35
+ node.parent.parent.parent.object.is_a?(Arel::Nodes::JoinSource)
36
+ context[:range_variable] = true
37
+
21
38
  # Using Arel::Table as SELECT ... INNER JOIN <table> ON TRUE
22
39
  elsif parent_object.is_a?(Arel::Nodes::Join)
23
40
  context[:range_variable] = true
@@ -59,20 +59,39 @@ module Arel
59
59
  node.path = path.append(path_node)
60
60
  node.parent = self
61
61
  node.root_node = root_node
62
- @children[path_node.value] = node
62
+ @children[path_node.value.to_s] = node
63
63
  end
64
64
 
65
65
  def to_sql(engine = Table.engine)
66
66
  return nil if children.empty?
67
67
 
68
- target_object = object.is_a?(Arel::TreeManager) ? object.ast : object
69
- collector = Arel::Collectors::SQLString.new
70
- collector = engine.connection.visitor.accept target_object, collector
71
- collector.value
68
+ if object.respond_to?(:to_sql)
69
+ object.to_sql(engine)
70
+ else
71
+ collector = Arel::Collectors::SQLString.new
72
+ collector = engine.connection.visitor.accept object, collector
73
+ collector.value
74
+ end
75
+ end
76
+
77
+ def to_sql_and_binds(engine = Table.engine)
78
+ object.to_sql_and_binds(engine)
79
+ end
80
+
81
+ def method_missing(name, *args, &block)
82
+ child = @children[name.to_s]
83
+ return super if child.nil?
84
+
85
+ child
86
+ end
87
+
88
+ def respond_to_missing?(method, include_private = false)
89
+ child = @children[method.to_s]
90
+ child.present? || super
72
91
  end
73
92
 
74
93
  def [](key)
75
- @children.fetch(key)
94
+ @children.fetch(key.to_s)
76
95
  end
77
96
 
78
97
  def child_at_path(path_items)
@@ -27,6 +27,8 @@ module Arel
27
27
  matches? object_attribute_value, test_value
28
28
  end
29
29
  end
30
+ when Arel::Enhance::QueryMethods::QueryMethod
31
+ test.matches?(object)
30
32
  else
31
33
  object == test
32
34
  end
@@ -0,0 +1,23 @@
1
+ module Arel
2
+ module Enhance
3
+ module QueryMethods
4
+ class QueryMethod
5
+ attr_reader :subject
6
+
7
+ def initialize(subject)
8
+ @subject = subject
9
+ end
10
+ end
11
+
12
+ class Ancestors < QueryMethod
13
+ def matches?(other)
14
+ other <= subject
15
+ end
16
+ end
17
+
18
+ def self.in_ancestors?(object)
19
+ Ancestors.new(object)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -40,8 +40,10 @@ module Arel
40
40
  end
41
41
  alias visit_Arel_Nodes_And nary
42
42
 
43
- def visit_Hash(_object)
44
- raise 'Hash is not supported'
43
+ def visit_Hash(object)
44
+ object.each do |key, child|
45
+ process_node(child, Arel::Enhance::PathNode.new([:[], key], key))
46
+ end
45
47
  end
46
48
 
47
49
  def visit_Array(object)
@@ -21,6 +21,7 @@ require 'arel/extensions/lateral'
21
21
  require 'arel/extensions/range_function'
22
22
  require 'arel/extensions/with_ordinality'
23
23
  require 'arel/extensions/table'
24
+ require 'arel/extensions/attributes_attribute'
24
25
  require 'arel/extensions/row'
25
26
  require 'arel/extensions/ordering'
26
27
  require 'arel/extensions/all'
@@ -63,8 +64,6 @@ require 'arel/extensions/current_of_expression'
63
64
  require 'arel/extensions/delete_statement'
64
65
  require 'arel/extensions/least'
65
66
  require 'arel/extensions/greatest'
66
- require 'arel/extensions/generate_series'
67
- require 'arel/extensions/rank'
68
67
  require 'arel/extensions/coalesce'
69
68
  require 'arel/extensions/not_equal'
70
69
  require 'arel/extensions/equality'
@@ -110,6 +109,12 @@ require 'arel/extensions/to_sql'
110
109
  require 'arel/extensions/prepare'
111
110
  require 'arel/extensions/dealocate'
112
111
  require 'arel/extensions/active_record_type_caster_map'
112
+ require 'arel/extensions/active_record_type_caster_connection'
113
+ require 'arel/extensions/active_record_relation_query_attribute'
114
+ require 'arel/extensions/active_model_attribute_with_cast_value'
115
+ require 'arel/extensions/exists'
116
+ require 'arel/extensions/bind_param'
117
+ require 'arel/extensions/node'
113
118
 
114
119
  module Arel
115
120
  module Extensions