arel_toolkit 0.4.0 → 0.4.5

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/coverage.yml +48 -0
  3. data/.github/workflows/test.yml +65 -0
  4. data/.gitignore +6 -1
  5. data/Appraisals +4 -0
  6. data/CHANGELOG.md +87 -7
  7. data/Gemfile.lock +50 -39
  8. data/Guardfile +4 -0
  9. data/README.md +25 -11
  10. data/Rakefile +11 -1
  11. data/arel_toolkit.gemspec +10 -5
  12. data/benchmark.rb +54 -0
  13. data/ext/pg_result_init/extconf.rb +52 -0
  14. data/ext/pg_result_init/pg_result_init.c +138 -0
  15. data/ext/pg_result_init/pg_result_init.h +6 -0
  16. data/gemfiles/active_record_6.gemfile +7 -0
  17. data/gemfiles/active_record_6.gemfile.lock +210 -0
  18. data/gemfiles/arel_gems.gemfile.lock +28 -18
  19. data/gemfiles/default.gemfile.lock +30 -20
  20. data/lib/arel/enhance.rb +1 -0
  21. data/lib/arel/enhance/context_enhancer/arel_table.rb +18 -1
  22. data/lib/arel/enhance/node.rb +71 -28
  23. data/lib/arel/enhance/query.rb +2 -0
  24. data/lib/arel/enhance/query_methods.rb +23 -0
  25. data/lib/arel/enhance/visitor.rb +19 -3
  26. data/lib/arel/extensions.rb +8 -2
  27. data/lib/arel/extensions/active_model_attribute_with_cast_value.rb +22 -0
  28. data/lib/arel/extensions/active_record_relation_query_attribute.rb +22 -0
  29. data/lib/arel/extensions/active_record_type_caster_connection.rb +7 -0
  30. data/lib/arel/extensions/attributes_attribute.rb +47 -0
  31. data/lib/arel/extensions/bind_param.rb +15 -0
  32. data/lib/arel/extensions/coalesce.rb +17 -3
  33. data/lib/arel/extensions/delete_statement.rb +20 -15
  34. data/lib/arel/extensions/exists.rb +59 -0
  35. data/lib/arel/extensions/function.rb +3 -2
  36. data/lib/arel/extensions/greatest.rb +17 -3
  37. data/lib/arel/extensions/infer.rb +1 -1
  38. data/lib/arel/extensions/insert_statement.rb +3 -3
  39. data/lib/arel/extensions/least.rb +17 -3
  40. data/lib/arel/extensions/node.rb +10 -0
  41. data/lib/arel/extensions/range_function.rb +10 -2
  42. data/lib/arel/extensions/select_core.rb +22 -7
  43. data/lib/arel/extensions/top.rb +8 -0
  44. data/lib/arel/extensions/tree_manager.rb +5 -0
  45. data/lib/arel/extensions/update_statement.rb +9 -23
  46. data/lib/arel/middleware.rb +5 -1
  47. data/lib/arel/middleware/active_record_extension.rb +13 -0
  48. data/lib/arel/middleware/cache_accessor.rb +35 -0
  49. data/lib/arel/middleware/chain.rb +110 -31
  50. data/lib/arel/middleware/database_executor.rb +77 -0
  51. data/lib/arel/middleware/no_op_cache.rb +9 -0
  52. data/lib/arel/middleware/postgresql_adapter.rb +41 -5
  53. data/lib/arel/middleware/railtie.rb +6 -2
  54. data/lib/arel/middleware/result.rb +170 -0
  55. data/lib/arel/middleware/to_sql_executor.rb +15 -0
  56. data/lib/arel/middleware/to_sql_middleware.rb +33 -0
  57. data/lib/arel/sql_to_arel/pg_query_visitor.rb +34 -33
  58. data/lib/arel/sql_to_arel/result.rb +19 -2
  59. data/lib/arel/transformer.rb +2 -1
  60. data/lib/arel/transformer/prefix_schema_name.rb +183 -0
  61. data/lib/arel/transformer/remove_active_record_info.rb +2 -4
  62. data/lib/arel/transformer/replace_table_with_subquery.rb +31 -0
  63. data/lib/arel_toolkit.rb +7 -1
  64. data/lib/arel_toolkit/version.rb +1 -1
  65. metadata +101 -37
  66. data/.travis.yml +0 -34
  67. data/lib/arel/extensions/generate_series.rb +0 -9
  68. data/lib/arel/extensions/rank.rb +0 -9
  69. data/lib/arel/transformer/add_schema_to_table.rb +0 -26
data/Guardfile CHANGED
@@ -47,3 +47,7 @@ group :red_green_refactor, halt_on_fail: true do
47
47
  watch(%r{(?:.+/)?\.rubocop(?:_todo)?\.yml$}) { |m| File.dirname(m[0]) }
48
48
  end
49
49
  end
50
+
51
+ guard 'rake', task: 'compile' do
52
+ watch(/^ext/)
53
+ end
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- - [![Build Status](https://travis-ci.com/mvgijssel/arel_toolkit.svg?branch=master)](https://travis-ci.com/mvgijssel/arel_toolkit)
5
+ - [![](https://github.com/mvgijssel/arel_toolkit/workflows/CI%20-%20master/badge.svg)](https://github.com/mvgijssel/arel_toolkit/actions)
6
6
  - [![Maintainability](https://api.codeclimate.com/v1/badges/3ef13d1649a00a98562d/maintainability)](https://codeclimate.com/github/mvgijssel/arel_toolkit/maintainability)
7
7
  - [![Test Coverage](https://api.codeclimate.com/v1/badges/3ef13d1649a00a98562d/test_coverage)](https://codeclimate.com/github/mvgijssel/arel_toolkit/test_coverage)
8
8
  - [![Gem Version](https://badge.fury.io/rb/arel_toolkit.svg)](https://badge.fury.io/rb/arel_toolkit)
@@ -65,7 +65,7 @@ enhanced_arel.child_at_path(['ast', 'cores', 0, 'projections', 1]).object
65
65
  ```
66
66
 
67
67
  ##### Replace or remove nodes without modifying the original arel
68
- `remove` and `replace` allow for modifications to the Arel AST. The changes are aplied to a new copy of the AST, making sure the original AST is not touched.
68
+ `remove` and `replace` allow for modifications to the Arel AST. The changes are aplied to a new copy of the AST, making sure the original AST is not touched. The `replace` method accepts any Arel node or a precomputed enhanced node for improved performance.
69
69
 
70
70
  ```ruby
71
71
  enhanced_arel.child_at_path(['ast', 'cores', 0, 'projections', 1]).replace(Post.arel_table[:content])
@@ -84,33 +84,36 @@ Middleware sits between ActiveRecord and the database, it allows you to alter th
84
84
  The middleware works out of the box in combination with Rails. If using ActiveRecord standalone you need to run the following **after** setting up the database connection:
85
85
 
86
86
  ```ruby
87
- Arel::Middleware::Railtie.insert_postgresql
87
+ Arel::Middleware::Railtie.insert
88
88
  ```
89
89
 
90
90
  ### Example
91
91
 
92
- Create some middleware (this can be any Ruby object as long as it responds to `call`). In this example, we're creating a middleware that will reorder any query. Next to reordering, we're adding an additional middleware that prints out the result of the reorder middleware.
92
+ Create middleware which can be any Ruby object as long as it responds to `call`. Middleware accepts 2 or 3 arguments, context is optional. Calling `.call` on `next_middleware` invokes the next middleware in the chain, returning the response from the database.
93
+
94
+ In this example, we're creating a middleware that will reorder any query. Next to reordering, we're adding an additional middleware that prints out the result of the reorder middleware.
93
95
 
94
96
  ```ruby
95
97
  class ReorderMiddleware
96
- def self.call(arel, _context)
98
+ def self.call(arel, next_middleware)
97
99
  enhanced_arel = Arel.enhance(arel)
98
100
  enhanced_arel.query(class: Arel::Nodes::SelectStatement).each do |node|
99
101
  arel_table = node.child_at_path(['cores', 0, 'source', 'left']).object
100
102
  node['orders'].replace([arel_table[:id].asc])
101
103
  end
102
104
 
103
- arel.order(Post.arel_table[:id].asc)
105
+ new_arel = arel.order(Post.arel_table[:id].asc)
106
+ next_middleware.call(new_arel)
104
107
  end
105
108
  end
106
109
 
107
110
  class LoggingMiddleware
108
- def self.call(arel, context)
111
+ def self.call(arel, next_middleware, context)
109
112
  puts "User executing query: `#{context[:current_user_id]}`"
110
113
  puts "Original SQL: `#{context[:original_sql]}`"
111
114
  puts "Modified SQL: `#{arel.to_sql}`"
112
-
113
- arel
115
+
116
+ next_middleware.call(arel)
114
117
  end
115
118
  end
116
119
  ```
@@ -140,9 +143,20 @@ This gem aims to have full support for PostgreSQL's SQL. In order to do so, it n
140
143
 
141
144
  ## Development
142
145
 
143
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
146
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`.
147
+
148
+ ## Releasing
144
149
 
145
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
150
+ 1. Update version in `version.rb`
151
+ 1. Create a new branch `v<<VERSION_HERE>>`
152
+ 1. Run `bundle install`
153
+ 1. Run `bundle exec appraisal install`
154
+ 1. Run `bundle exec rake changelog`
155
+ 1. Commit the changes
156
+ 1. Open a PR with name `Version <<VERSION_HERE>>` ([example](https://github.com/mvgijssel/arel_toolkit/pull/172))
157
+ 1. Merge the PR
158
+ 1. Checkout the master branch and pull latest changes
159
+ 1. Run `bundle exec rake release`
146
160
 
147
161
  ## Contributing
148
162
 
data/Rakefile CHANGED
@@ -10,5 +10,15 @@ GitHubChangelogGenerator::RakeTask.new :changelog do |config|
10
10
  config.user = 'mvgijssel'
11
11
  config.project = 'arel_toolkit'
12
12
  config.future_release = "v#{ArelToolkit::VERSION}"
13
- config.pulls = false
13
+ config.add_pr_wo_labels = false
14
+ config.enhancement_labels = %w[enhancement dependencies]
15
+ config.exclude_tags = ['v0.1.0']
16
+ end
17
+
18
+ require 'rake/extensiontask'
19
+
20
+ task build: :compile
21
+
22
+ Rake::ExtensionTask.new('pg_result_init') do |ext|
23
+ ext.lib_dir = 'lib/arel_toolkit'
14
24
  end
data/arel_toolkit.gemspec CHANGED
@@ -23,16 +23,17 @@ 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
- spec.add_dependency 'arel', '~> 9.0.0'
28
- spec.add_dependency 'activerecord', '~> 5.2.0'
28
+ spec.add_dependency 'activerecord'
29
29
  spec.add_dependency 'pg', '~> 1.1.4'
30
- spec.add_dependency 'pg_query', '~> 1.1.0'
30
+ spec.add_dependency 'pg_query', '~> 1.3'
31
31
 
32
32
  spec.add_development_dependency 'bundler', '~> 2.0'
33
33
  spec.add_development_dependency 'dpl', '~> 1.10.11'
34
- spec.add_development_dependency 'github_changelog_generator', '~> 1.14.3'
35
- spec.add_development_dependency 'rake', '~> 10.0'
34
+ spec.add_development_dependency 'github_changelog_generator', '~> 1.15'
35
+ spec.add_development_dependency 'rake', '~> 13.0'
36
+ spec.add_development_dependency 'rake-compiler', '~> 1.0'
36
37
  spec.add_development_dependency 'rspec', '~> 3.8'
37
38
  spec.add_development_dependency 'approvals', '~> 0.0.24'
38
39
  spec.add_development_dependency 'appraisal', '~> 2.2.0'
@@ -45,6 +46,10 @@ Gem::Specification.new do |spec|
45
46
  spec.add_development_dependency 'guard', '~> 2.15'
46
47
  spec.add_development_dependency 'guard-rspec', '~> 4.7'
47
48
  spec.add_development_dependency 'guard-rubocop', '~> 1.3.0'
49
+ spec.add_development_dependency 'guard-rake', '~> 1.0.0'
50
+
51
+ spec.add_development_dependency 'stackprof', '~> 0.2'
52
+ spec.add_development_dependency 'memory_profiler', '~> 0.9'
48
53
 
49
54
  spec.add_development_dependency 'pry'
50
55
  spec.add_development_dependency 'pry-nav'
data/benchmark.rb ADDED
@@ -0,0 +1,54 @@
1
+ GC.disable
2
+
3
+ require 'arel'
4
+ require 'active_record'
5
+ require 'memory_profiler'
6
+ require 'json'
7
+ require 'stackprof'
8
+
9
+ require './lib/arel/enhance'
10
+ require './lib/arel/extensions'
11
+ require './lib/arel/sql_to_arel'
12
+
13
+ ActiveRecord::Base.establish_connection(
14
+ adapter: 'postgresql',
15
+ host: 'localhost',
16
+ databse: 'arel_toolkit_test',
17
+ username: 'postgres',
18
+ )
19
+
20
+ # rubocop:disable Metrics/LineLength
21
+ SQL = %{
22
+ SELECT COUNT("hacktivity_items"."id") FROM (SELECT "hacktivity_items".* FROM (SELECT "hacktivity_items".* FROM (SELECT "hacktivity_items".*, (NULLIF(current_setting('secure.requester_id'), ''))::integer AS "_requester_id" FROM (SELECT "outer_hacktivity_items".*, EXISTS (SELECT 1 FROM "public"."reports" INNER JOIN "public"."teams" ON "teams"."id" = "reports"."team_id" WHERE "teams"."state" = 5 AND "reports"."disclosed_at" IS NOT NULL AND "reports"."public_view" = 'full' AND "reports"."id" = "outer_hacktivity_items"."report_id") AS "is_someone_visiting_a_public_report", EXISTS (SELECT 1 FROM "public"."reports" INNER JOIN "public"."teams" ON "teams"."id" = "reports"."team_id" INNER JOIN "public"."whitelisted_reporters" ON "whitelisted_reporters"."team_id" = "teams"."id" WHERE "teams" ."state" = 4 AND "teams"."allows_private_disclosure" = 't'::bool AND (("whitelisted_reporters"."user_id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "whitelisted_reporters"."user_id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "reports"."disclosed_at" IS NOT NULL AND "reports"."public_view" = 'full' AND "reports"."id" = "outer_hacktivity_items"."report_id") AS "is_someone_visiting_a_report_privately_disclosed_to_them", EXISTS (SELECT 1 FROM "public"."reports" WHERE "reports"."hacker_published" = 't'::bool AND "reports"."disclosed_at" IS NOT NULL AND "reports"."id" = "outer_hacktivity_items"."report_id") AS "is_hacker_published", EXISTS (SELECT 1 FROM "public"."teams" INNER JOIN "public"."team_members" ON "team_members"."team_id" = "teams"."id" WHERE (("team_members"."user_id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "team_members"."user_id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "team_members"."user_id" IS NOT NULL AND "teams"."id" = "outer_hacktivity_items"."team_id") AS "is_team_team_member", EXISTS (SELECT 1 FROM "public"."teams" WHERE "teams"."state" = 5 AND "teams"."id" = "outer_hacktivity_items"."team_id") AS "is_team_public", EXISTS (SELECT 1 FROM "public"."teams" INNER JOIN "public"."whitelisted_reporters" ON "whitelisted_reporters"."team_id" = "teams"."id" INNER JOIN "public"."users" ON "users"."id" = "whitelisted_reporters"."user_id" WHERE (("users"."id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "users"."id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "users"."id" IS NOT NULL AND "teams"."id" = "outer_hacktivity_items"."team_id") AS "is_team_whitelisted_reporter", EXISTS (SELECT 1 FROM "public"."teams" WHERE "teams"."state" = 4 AND "teams"."id" = "outer_hacktivity_items"."team_id") AS "is_team_private", EXISTS (SELECT 1 FROM "public"."teams" WHERE "teams"."offers_bounties" = 't'::bool AND "teams"."hide_bounty_amounts" = 'f'::bool AND "teams"."id" = "outer_hacktivity_items"."team_id") AS "is_team_shows_bounty_amounts" FROM "public"."hacktivity_items" "outer_hacktivity_items") AS "hacktivity_items" WHERE "hacktivity_items"."is_team_public" = 't'::bool OR ("hacktivity_items"."is_team_private" = 't'::bool AND ("hacktivity_items"."is_team_team_member" = 't'::bool OR "hacktivity_items"."is_team_whitelisted_reporter" = 't'::bool)) OR "hacktivity_items"."is_hacker_published" = 't'::bool) hacktivity_items) AS "hacktivity_items" LEFT OUTER JOIN (SELECT "reports".*, "new_j0"."disclosed_at" AS "__new_filterable_disclosed_at" FROM (SELECT "reports".*, (NULLIF(current_setting('secure.requester_id'), ''))::integer AS "_requester_id" FROM (SELECT "outer_reports".*, EXISTS (SELECT 1 FROM "public"."teams" INNER JOIN "public"."team_members" ON "team_members"."team_id" = "teams"."id" INNER JOIN "public"."users" ON "users"."id" = "team_members"."user_id" WHERE (("users"."id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "users"."id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "users"."id" IS NOT NULL AND "teams"."id" = "outer_reports"."team_id") AS "is_team_member", (("outer_reports"."reporter_id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "outer_reports"."reporter_id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "outer_reports"."reporter_id" IS NOT NULL AS "is_reporter", EXISTS (SELECT 1 FROM "public"."reports_users" INNER JOIN "public"."users" ON "users"."id" = "reports_users"."user_id" WHERE (("reports_users"."user_id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "reports_users"."user_id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "reports_users"."user_id" IS NOT NULL AND "reports_users"."report_id" = "outer_reports"."id") AS "is_external_user", EXISTS (SELECT 1 FROM "public"."report_retests" INNER JOIN "public"."report_retest_users" ON "report_retest_users"."report_retest_id" = "report_retests"."id" WHERE (EXISTS (SELECT "invitations".* FROM "public"."invitations" WHERE ("accepted_at" IS NOT NULL OR "expires_at" >= now() OR "expires_at" IS NULL) AND "invitations"."cancelled_at" IS NULL AND "invitations"."rejected_at" IS NULL AND "invitations"."id" = "report_retest_users"."invitation_id") OR (NOT (EXISTS (SELECT "invitations".* FROM "public"."invitations" WHERE "invitations"."id" = "report_retest_users"."invitation_id")))) AND (("report_retest_users"."user_id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "report_retest_users"."user_id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "report_retest_users"."user_id" IS NOT NULL AND "report_retests"."report_id" = "outer_reports"."id") AS "is_retester", EXISTS (SELECT 1 FROM "public"."teams" LEFT OUTER JOIN "public"."users" ON "users"."anc_triager" WHERE "outer_reports"."pre_submission_review_state" IS NOT NULL AND "teams"."state" IN (4, 5) AND "teams"."anc_enabled" = 't'::bool AND (("users"."id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "users"."id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "users"."id" IS NOT NULL AND "teams"."id" = "outer_reports"."team_id") AS "is_anc_triager", EXISTS (SELECT 1 FROM "public"."teams" INNER JOIN "public"."team_members" ON "team_members"."team_id" = "teams"."id" INNER JOIN "public"."users" ON "users"."id" = "team_members"."user_id" WHERE (("users"."id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "users"."id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "users"."hackerone_triager" = 't'::bool AND "users"."id" IS NOT NULL AND "teams"."id" = "outer_reports"."team_id") AS "is_hackerone_triager", EXISTS (SELECT 1 FROM "public"."report_collaborators" WHERE (("report_collaborators"."user_id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "report_collaborators"."user_id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "report_collaborators"."user_id" IS NOT NULL AND "report_collaborators"."report_id" = "outer_reports"."id") AS "is_report_collaborator", EXISTS (SELECT 1 FROM "public"."teams" WHERE "teams"."state" = 5 AND "outer_reports"."disclosed_at" IS NOT NULL AND "outer_reports"."public_view" = 'full' AND "teams"."id" = "outer_reports"."team_id") AS "is_someone_visiting_a_public_report", EXISTS (SELECT 1 FROM "public"."teams" WHERE "teams"."state" = 5 AND "outer_reports"."disclosed_at" IS NOT NULL AND "outer_reports"."public_view" = 'no-content' AND "teams"."id" = "outer_reports"."team_id") AS "is_someone_visiting_a_public_limited_report", EXISTS (SELECT 1 FROM "public"."teams" INNER JOIN "public"."whitelisted_reporters" ON "whitelisted_reporters"."team_id" = "teams"."id" WHERE "teams"."state" = 4 AND "teams"."allows_private_disclosure" = 't'::bool AND (("whitelisted_reporters"."user_id" IS NULL AND NULLIF(current_setting('secure.requester_id'), '') IS NULL) OR "whitelisted_reporters"."user_id" = (NULLIF(current_setting('secure.requester_id'), ''))::integer) AND "outer_reports"."disclosed_at" IS NOT NULL AND "outer_reports"."public_view" = 'full' AND "teams"."id" = "outer_reports"."team_id") AS "is_someone_visiting_a_report_privately_disclosed_to_them", "outer_reports"."hacker_published" = 't'::bool AND "outer_reports"."disclosed_at" IS NOT NULL AS "is_hacker_published" FROM "public"."reports" "outer_reports") AS "reports" WHERE "reports"."is_reporter" = 't'::bool OR "reports"."is_team_member" = 't'::bool OR "reports"."is_external_user" = 't'::bool OR "reports"."is_anc_triager" = 't'::bool OR "reports"."is_report_collaborator" = 't'::bool OR "reports"."is_someone_visiting_a_public_report" = 't'::bool OR "reports"."is_someone_visiting_a_public_limited_report" = 't'::bool OR "reports"."is_someone_visiting_a_report_privately_disclosed_to_them" = 't'::bool OR "reports"."is_hacker_published" = 't'::bool OR "reports"."is_retester" = 't'::bool) reports LEFT OUTER JOIN "public"."reports" "new_j0" ON "reports"."id" = "new_j0"."id" AND ("reports"."is_reporter" = 't'::bool OR "reports"."is_team_member" = 't'::bool OR "reports"."is_external_user" = 't'::bool OR "reports"."is_anc_triager" = 't'::bool OR "reports"."is_report_collaborator" = 't'::bool OR "reports"."is_someone_visiting_a_public_report" = 't'::bool OR "reports"."is_someone_visiting_a_public_limited_report" = 't'::bool OR "reports"."is_someone_visiting_a_report_privately_disclosed_to_them" = 't'::bool OR "reports"."is_hacker_published" = 't'::bool)) AS "reports" ON "reports"."id" = "hacktivity_items"."report_id" WHERE "reports"."__new_filterable_disclosed_at" IS NOT NULL) AS "hacktivity_items" LEFT OUTER JOIN "hacktivity_scores" ON "hacktivity_scores"."hacktivity_item_id" = "hacktivity_items"."id"
23
+ }.freeze
24
+ # rubocop:enable Metrics/LineLength
25
+
26
+ def do_it
27
+ arel = Arel.sql_to_arel SQL
28
+ enhanced_arel = Arel.enhance(arel)
29
+
30
+ enhanced_arel.query(class: Arel::Attributes::Attribute).each do |node|
31
+ node.replace node
32
+ end
33
+
34
+ enhanced_arel
35
+ end
36
+
37
+ # Warm up
38
+ do_it
39
+
40
+ profile = StackProf.run(mode: :wall, raw: true) { do_it }
41
+ File.write('stackprof.json', JSON.generate(profile))
42
+
43
+ report = MemoryProfiler.report { do_it }
44
+ puts report.pretty_print
45
+
46
+ n = 100
47
+
48
+ Benchmark.bm do |benchmark|
49
+ benchmark.report do
50
+ n.times do
51
+ do_it
52
+ end
53
+ end
54
+ end
@@ -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 */
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'activerecord', '~> 6.0.0'
6
+
7
+ gemspec path: '../'
@@ -0,0 +1,210 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ arel_toolkit (0.4.5)
5
+ activerecord
6
+ pg (~> 1.1.4)
7
+ pg_query (~> 1.3)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activemodel (6.0.0)
13
+ activesupport (= 6.0.0)
14
+ activerecord (6.0.0)
15
+ activemodel (= 6.0.0)
16
+ activesupport (= 6.0.0)
17
+ activesupport (6.0.0)
18
+ concurrent-ruby (~> 1.0, >= 1.0.2)
19
+ i18n (>= 0.7, < 2)
20
+ minitest (~> 5.1)
21
+ tzinfo (~> 1.1)
22
+ zeitwerk (~> 2.1, >= 2.1.8)
23
+ addressable (2.7.0)
24
+ public_suffix (>= 2.0.2, < 5.0)
25
+ ansi (1.5.0)
26
+ appraisal (2.2.0)
27
+ bundler
28
+ rake
29
+ thor (>= 0.14.0)
30
+ approvals (0.0.24)
31
+ nokogiri (~> 1.6)
32
+ thor (~> 0.18)
33
+ ast (2.4.1)
34
+ binding_of_caller (0.8.0)
35
+ debug_inspector (>= 0.0.1)
36
+ coderay (1.1.3)
37
+ concurrent-ruby (1.1.7)
38
+ database_cleaner (1.7.0)
39
+ debug_inspector (0.0.3)
40
+ diff-lcs (1.4.4)
41
+ docile (1.3.2)
42
+ dpl (1.10.15)
43
+ faraday (1.1.0)
44
+ multipart-post (>= 1.2, < 3)
45
+ ruby2_keywords
46
+ faraday-http-cache (2.2.0)
47
+ faraday (>= 0.8)
48
+ ffi (1.13.1)
49
+ formatador (0.2.5)
50
+ github_changelog_generator (1.15.2)
51
+ activesupport
52
+ faraday-http-cache
53
+ multi_json
54
+ octokit (~> 4.6)
55
+ rainbow (>= 2.2.1)
56
+ rake (>= 10.0)
57
+ retriable (~> 3.0)
58
+ guard (2.16.2)
59
+ formatador (>= 0.2.4)
60
+ listen (>= 2.7, < 4.0)
61
+ lumberjack (>= 1.0.12, < 2.0)
62
+ nenv (~> 0.1)
63
+ notiffany (~> 0.0)
64
+ pry (>= 0.9.12)
65
+ shellany (~> 0.0)
66
+ thor (>= 0.18.1)
67
+ guard-compat (1.2.1)
68
+ guard-rake (1.0.0)
69
+ guard
70
+ rake
71
+ guard-rspec (4.7.3)
72
+ guard (~> 2.1)
73
+ guard-compat (~> 1.1)
74
+ rspec (>= 2.99.0, < 4.0)
75
+ guard-rubocop (1.3.0)
76
+ guard (~> 2.0)
77
+ rubocop (~> 0.20)
78
+ hirb (0.7.3)
79
+ i18n (1.8.5)
80
+ concurrent-ruby (~> 1.0)
81
+ interception (0.5)
82
+ jaro_winkler (1.5.4)
83
+ json (2.3.1)
84
+ listen (3.2.1)
85
+ rb-fsevent (~> 0.10, >= 0.10.3)
86
+ rb-inotify (~> 0.9, >= 0.9.10)
87
+ lumberjack (1.2.8)
88
+ memory_profiler (0.9.14)
89
+ method_source (0.9.2)
90
+ mini_portile2 (2.4.0)
91
+ minitest (5.14.2)
92
+ multi_json (1.15.0)
93
+ multipart-post (2.1.1)
94
+ nenv (0.3.0)
95
+ nokogiri (1.10.10)
96
+ mini_portile2 (~> 2.4.0)
97
+ notiffany (0.1.3)
98
+ nenv (~> 0.1)
99
+ shellany (~> 0.0)
100
+ octokit (4.19.0)
101
+ faraday (>= 0.9)
102
+ sawyer (~> 0.8.0, >= 0.5.3)
103
+ parallel (1.19.2)
104
+ parser (2.7.2.0)
105
+ ast (~> 2.4.1)
106
+ pg (1.1.4)
107
+ pg_query (1.3.0)
108
+ pry (0.12.2)
109
+ coderay (~> 1.1.0)
110
+ method_source (~> 0.9.0)
111
+ pry-alias (0.0.1)
112
+ binding_of_caller
113
+ pry
114
+ pry-doc (1.1.0)
115
+ pry (~> 0.11)
116
+ yard (~> 0.9.11)
117
+ pry-nav (0.3.0)
118
+ pry (>= 0.9.10, < 0.13.0)
119
+ pry-rescue (1.5.2)
120
+ interception (>= 0.5)
121
+ pry (>= 0.12.0)
122
+ pry-stack_explorer (0.4.9.3)
123
+ binding_of_caller (>= 0.7)
124
+ pry (>= 0.9.11)
125
+ public_suffix (4.0.6)
126
+ rainbow (3.0.0)
127
+ rake (13.0.1)
128
+ rake-compiler (1.1.1)
129
+ rake
130
+ rb-fsevent (0.10.4)
131
+ rb-inotify (0.10.1)
132
+ ffi (~> 1.0)
133
+ retriable (3.1.2)
134
+ rspec (3.9.0)
135
+ rspec-core (~> 3.9.0)
136
+ rspec-expectations (~> 3.9.0)
137
+ rspec-mocks (~> 3.9.0)
138
+ rspec-core (3.9.3)
139
+ rspec-support (~> 3.9.3)
140
+ rspec-expectations (3.9.3)
141
+ diff-lcs (>= 1.2.0, < 2.0)
142
+ rspec-support (~> 3.9.0)
143
+ rspec-mocks (3.9.1)
144
+ diff-lcs (>= 1.2.0, < 2.0)
145
+ rspec-support (~> 3.9.0)
146
+ rspec-support (3.9.4)
147
+ rubocop (0.71.0)
148
+ jaro_winkler (~> 1.5.1)
149
+ parallel (~> 1.10)
150
+ parser (>= 2.6)
151
+ rainbow (>= 2.2.2, < 4.0)
152
+ ruby-progressbar (~> 1.7)
153
+ unicode-display_width (>= 1.4.0, < 1.7)
154
+ ruby-progressbar (1.10.1)
155
+ ruby2_keywords (0.0.2)
156
+ sawyer (0.8.2)
157
+ addressable (>= 2.3.5)
158
+ faraday (> 0.8, < 2.0)
159
+ shellany (0.0.1)
160
+ simplecov (0.16.1)
161
+ docile (~> 1.1)
162
+ json (>= 1.8, < 3)
163
+ simplecov-html (~> 0.10.0)
164
+ simplecov-console (0.4.2)
165
+ ansi
166
+ hirb
167
+ simplecov
168
+ simplecov-html (0.10.2)
169
+ stackprof (0.2.16)
170
+ thor (0.20.3)
171
+ thread_safe (0.3.6)
172
+ tzinfo (1.2.7)
173
+ thread_safe (~> 0.1)
174
+ unicode-display_width (1.6.1)
175
+ yard (0.9.25)
176
+ zeitwerk (2.4.0)
177
+
178
+ PLATFORMS
179
+ ruby
180
+
181
+ DEPENDENCIES
182
+ activerecord (~> 6.0.0)
183
+ appraisal (~> 2.2.0)
184
+ approvals (~> 0.0.24)
185
+ arel_toolkit!
186
+ bundler (~> 2.0)
187
+ database_cleaner (~> 1.7.0)
188
+ dpl (~> 1.10.11)
189
+ github_changelog_generator (~> 1.15)
190
+ guard (~> 2.15)
191
+ guard-rake (~> 1.0.0)
192
+ guard-rspec (~> 4.7)
193
+ guard-rubocop (~> 1.3.0)
194
+ memory_profiler (~> 0.9)
195
+ pry
196
+ pry-alias
197
+ pry-doc
198
+ pry-nav
199
+ pry-rescue
200
+ pry-stack_explorer
201
+ rake (~> 13.0)
202
+ rake-compiler (~> 1.0)
203
+ rspec (~> 3.8)
204
+ rubocop (= 0.71.0)
205
+ simplecov (~> 0.16.1)
206
+ simplecov-console (~> 0.4.2)
207
+ stackprof (~> 0.2)
208
+
209
+ BUNDLED WITH
210
+ 2.2.19